]> git.argeo.org Git - gpl/argeo-slc.git/blob - runtime/org.argeo.slc.repo/src/main/java/org/argeo/slc/repo/RepoUtils.java
Some more UI functionalities :
[gpl/argeo-slc.git] / runtime / org.argeo.slc.repo / src / main / java / org / argeo / slc / repo / RepoUtils.java
1 /*
2 * Copyright (C) 2007-2012 Argeo GmbH
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.argeo.slc.repo;
17
18 import java.io.ByteArrayOutputStream;
19 import java.io.File;
20 import java.io.FileInputStream;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.OutputStream;
24 import java.util.Enumeration;
25 import java.util.Iterator;
26 import java.util.StringTokenizer;
27 import java.util.jar.Attributes;
28 import java.util.jar.JarEntry;
29 import java.util.jar.JarFile;
30 import java.util.jar.JarInputStream;
31 import java.util.jar.JarOutputStream;
32 import java.util.jar.Manifest;
33
34 import javax.jcr.Credentials;
35 import javax.jcr.GuestCredentials;
36 import javax.jcr.Node;
37 import javax.jcr.NodeIterator;
38 import javax.jcr.Property;
39 import javax.jcr.PropertyIterator;
40 import javax.jcr.Repository;
41 import javax.jcr.RepositoryException;
42 import javax.jcr.RepositoryFactory;
43 import javax.jcr.SimpleCredentials;
44 import javax.jcr.nodetype.NodeType;
45
46 import org.apache.commons.io.FilenameUtils;
47 import org.apache.commons.io.IOUtils;
48 import org.apache.commons.logging.Log;
49 import org.apache.commons.logging.LogFactory;
50 import org.argeo.jcr.ArgeoJcrUtils;
51 import org.argeo.jcr.ArgeoNames;
52 import org.argeo.jcr.ArgeoTypes;
53 import org.argeo.jcr.JcrUtils;
54 import org.argeo.slc.BasicNameVersion;
55 import org.argeo.slc.NameVersion;
56 import org.argeo.slc.SlcException;
57 import org.argeo.slc.jcr.SlcNames;
58 import org.argeo.slc.jcr.SlcTypes;
59 import org.argeo.slc.repo.maven.MavenConventionsUtils;
60 import org.argeo.util.security.Keyring;
61 import org.osgi.framework.Constants;
62 import org.sonatype.aether.artifact.Artifact;
63 import org.sonatype.aether.util.artifact.DefaultArtifact;
64
65 /** Utilities around repo */
66 public class RepoUtils implements ArgeoNames, SlcNames {
67 private final static Log log = LogFactory.getLog(RepoUtils.class);
68
69 /** Packages a regular sources jar as PDE source. */
70 public static void packagesAsPdeSource(File sourceFile,
71 NameVersion nameVersion, OutputStream out) throws IOException {
72 if (isAlreadyPdeSource(sourceFile)) {
73 FileInputStream in = new FileInputStream(sourceFile);
74 IOUtils.copy(in, out);
75 IOUtils.closeQuietly(in);
76 } else {
77 String sourceSymbolicName = nameVersion.getName() + ".source";
78
79 Manifest sourceManifest = null;
80 sourceManifest = new Manifest();
81 sourceManifest.getMainAttributes().put(
82 Attributes.Name.MANIFEST_VERSION, "1.0");
83 sourceManifest.getMainAttributes().putValue("Bundle-SymbolicName",
84 sourceSymbolicName);
85 sourceManifest.getMainAttributes().putValue("Bundle-Version",
86 nameVersion.getVersion());
87 sourceManifest.getMainAttributes().putValue(
88 "Eclipse-SourceBundle",
89 nameVersion.getName() + ";version="
90 + nameVersion.getVersion());
91 copyJar(sourceFile, out, sourceManifest);
92 }
93 }
94
95 public static byte[] packageAsPdeSource(InputStream sourceJar,
96 NameVersion nameVersion) {
97 String sourceSymbolicName = nameVersion.getName() + ".source";
98
99 Manifest sourceManifest = null;
100 sourceManifest = new Manifest();
101 sourceManifest.getMainAttributes().put(
102 Attributes.Name.MANIFEST_VERSION, "1.0");
103 sourceManifest.getMainAttributes().putValue("Bundle-SymbolicName",
104 sourceSymbolicName);
105 sourceManifest.getMainAttributes().putValue("Bundle-Version",
106 nameVersion.getVersion());
107 sourceManifest.getMainAttributes().putValue("Eclipse-SourceBundle",
108 nameVersion.getName() + ";version=" + nameVersion.getVersion());
109
110 return modifyManifest(sourceJar, sourceManifest);
111 }
112
113 /**
114 * Check whether the file as already been packaged as PDE source, in order
115 * not to mess with Jar signing
116 */
117 private static boolean isAlreadyPdeSource(File sourceFile) {
118 JarInputStream jarInputStream = null;
119
120 try {
121 jarInputStream = new JarInputStream(new FileInputStream(sourceFile));
122
123 Manifest manifest = jarInputStream.getManifest();
124 Iterator<?> it = manifest.getMainAttributes().keySet().iterator();
125 boolean res = false;
126 // containsKey() does not work, iterating...
127 while (it.hasNext())
128 if (it.next().toString().equals("Eclipse-SourceBundle")) {
129 res = true;
130 break;
131 }
132 // boolean res = manifest.getMainAttributes().get(
133 // "Eclipse-SourceBundle") != null;
134 if (res)
135 log.info(sourceFile + " is already a PDE source");
136 return res;
137 } catch (Exception e) {
138 // probably not a jar, skipping
139 if (log.isDebugEnabled())
140 log.debug("Skipping " + sourceFile + " because of "
141 + e.getMessage());
142 return false;
143 } finally {
144 IOUtils.closeQuietly(jarInputStream);
145 }
146 }
147
148 /**
149 * Copy a jar, replacing its manifest with the provided one
150 *
151 * @param manifest
152 * can be null
153 */
154 private static void copyJar(File source, OutputStream out, Manifest manifest)
155 throws IOException {
156 JarFile sourceJar = null;
157 JarOutputStream output = null;
158 try {
159 output = manifest != null ? new JarOutputStream(out, manifest)
160 : new JarOutputStream(out);
161 sourceJar = new JarFile(source);
162
163 entries: for (Enumeration<?> entries = sourceJar.entries(); entries
164 .hasMoreElements();) {
165 JarEntry entry = (JarEntry) entries.nextElement();
166 if (manifest != null
167 && entry.getName().equals("META-INF/MANIFEST.MF"))
168 continue entries;
169
170 InputStream entryStream = sourceJar.getInputStream(entry);
171 JarEntry newEntry = new JarEntry(entry.getName());
172 // newEntry.setMethod(JarEntry.DEFLATED);
173 output.putNextEntry(newEntry);
174 IOUtils.copy(entryStream, output);
175 }
176 } finally {
177 IOUtils.closeQuietly(output);
178 try {
179 if (sourceJar != null)
180 sourceJar.close();
181 } catch (IOException e) {
182 // silent
183 }
184 }
185 }
186
187 /** Copy a jar changing onlythe manifest */
188 public static void copyJar(InputStream in, OutputStream out,
189 Manifest manifest) {
190 JarInputStream jarIn = null;
191 JarOutputStream jarOut = null;
192 try {
193 jarIn = new JarInputStream(in);
194 jarOut = new JarOutputStream(out, manifest);
195 JarEntry jarEntry = null;
196 while ((jarEntry = jarIn.getNextJarEntry()) != null) {
197 jarOut.putNextEntry(jarEntry);
198 IOUtils.copy(jarIn, jarOut);
199 jarIn.closeEntry();
200 jarOut.closeEntry();
201 }
202 } catch (IOException e) {
203 throw new SlcException("Could not copy jar with MANIFEST "
204 + manifest.getMainAttributes(), e);
205 } finally {
206 IOUtils.closeQuietly(jarIn);
207 IOUtils.closeQuietly(jarOut);
208 }
209 }
210
211 /** Reads a jar file, modify its manifest */
212 public static byte[] modifyManifest(InputStream in, Manifest manifest) {
213 ByteArrayOutputStream out = new ByteArrayOutputStream(200 * 1024);
214 try {
215 copyJar(in, out, manifest);
216 return out.toByteArray();
217 } finally {
218 IOUtils.closeQuietly(out);
219 }
220 }
221
222 /** Read the OSGi {@link NameVersion} */
223 public static NameVersion readNameVersion(Artifact artifact) {
224 File artifactFile = artifact.getFile();
225 if (artifact.getExtension().equals("pom")) {
226 // hack to process jars which weirdly appear as POMs
227 File jarFile = new File(artifactFile.getParentFile(),
228 FilenameUtils.getBaseName(artifactFile.getPath()) + ".jar");
229 if (jarFile.exists()) {
230 log.warn("Use " + jarFile + " instead of " + artifactFile
231 + " for " + artifact);
232 artifactFile = jarFile;
233 }
234 }
235 return readNameVersion(artifactFile);
236 }
237
238 /** Read the OSGi {@link NameVersion} */
239 public static NameVersion readNameVersion(File artifactFile) {
240 try {
241 return readNameVersion(new FileInputStream(artifactFile));
242 } catch (Exception e) {
243 // probably not a jar, skipping
244 if (log.isDebugEnabled()) {
245 log.debug("Skipping " + artifactFile + " because of " + e);
246 // e.printStackTrace();
247 }
248 }
249 return null;
250 }
251
252 /** Read the OSGi {@link NameVersion} */
253 public static NameVersion readNameVersion(InputStream in) {
254 JarInputStream jarInputStream = null;
255 try {
256 jarInputStream = new JarInputStream(in);
257 return readNameVersion(jarInputStream.getManifest());
258 } catch (Exception e) {
259 // probably not a jar, skipping
260 if (log.isDebugEnabled()) {
261 log.debug("Skipping because of " + e);
262 // e.printStackTrace();
263 }
264 } finally {
265 IOUtils.closeQuietly(jarInputStream);
266 }
267 return null;
268 }
269
270 /** Read the OSGi {@link NameVersion} */
271 public static NameVersion readNameVersion(Manifest manifest) {
272 BasicNameVersion nameVersion = new BasicNameVersion();
273 nameVersion.setName(manifest.getMainAttributes().getValue(
274 Constants.BUNDLE_SYMBOLICNAME));
275
276 // Skip additional specs such as
277 // ; singleton:=true
278 if (nameVersion.getName().indexOf(';') > -1) {
279 nameVersion
280 .setName(new StringTokenizer(nameVersion.getName(), " ;")
281 .nextToken());
282 }
283
284 nameVersion.setVersion(manifest.getMainAttributes().getValue(
285 Constants.BUNDLE_VERSION));
286
287 return nameVersion;
288 }
289
290 /*
291 * DATA MODEL
292 */
293 /** The artifact described by this node */
294 public static Artifact asArtifact(Node node) throws RepositoryException {
295 if (node.isNodeType(SlcTypes.SLC_ARTIFACT_VERSION_BASE)) {
296 // FIXME update data model to store packaging at this level
297 String extension = "jar";
298 return new DefaultArtifact(node.getProperty(SLC_GROUP_ID)
299 .getString(),
300 node.getProperty(SLC_ARTIFACT_ID).getString(), extension,
301 node.getProperty(SLC_ARTIFACT_VERSION).getString());
302 } else if (node.isNodeType(SlcTypes.SLC_ARTIFACT)) {
303 return new DefaultArtifact(node.getProperty(SLC_GROUP_ID)
304 .getString(),
305 node.getProperty(SLC_ARTIFACT_ID).getString(), node
306 .getProperty(SLC_ARTIFACT_CLASSIFIER).getString(),
307 node.getProperty(SLC_ARTIFACT_EXTENSION).getString(), node
308 .getProperty(SLC_ARTIFACT_VERSION).getString());
309 } else {
310 throw new SlcException("Unsupported node type for " + node);
311 }
312 }
313
314 /**
315 * Copy this bytes array as an artifact, relative to the root of the
316 * repository (typically the workspace root node)
317 */
318 public static Node copyBytesAsArtifact(Node artifactsBase,
319 Artifact artifact, byte[] bytes) throws RepositoryException {
320 String parentPath = MavenConventionsUtils.artifactParentPath(
321 artifactsBase.getPath(), artifact);
322 Node folderNode = JcrUtils.mkfolders(artifactsBase.getSession(),
323 parentPath);
324 return JcrUtils.copyBytesAsFile(folderNode,
325 MavenConventionsUtils.artifactFileName(artifact), bytes);
326 }
327
328 private RepoUtils() {
329 }
330
331 /** If a source return the base bundle name, does not change otherwise */
332 public static String extractBundleNameFromSourceName(String sourceBundleName) {
333 if (sourceBundleName.endsWith(".source"))
334 return sourceBundleName.substring(0, sourceBundleName.length()
335 - ".source".length());
336 else
337 return sourceBundleName;
338 }
339
340 /*
341 * SOFTWARE REPOSITORIES
342 */
343
344 /** Retrieve repository based on information in the repo node */
345 public static Repository getRepository(RepositoryFactory repositoryFactory,
346 Keyring keyring, Node repoNode) {
347 try {
348 Repository repository;
349 if (repoNode.isNodeType(ArgeoTypes.ARGEO_REMOTE_REPOSITORY)) {
350 String uri = repoNode.getProperty(ARGEO_URI).getString();
351 if (uri.startsWith("http")) {// http, https
352 repository = ArgeoJcrUtils.getRepositoryByUri(
353 repositoryFactory, uri);
354 } else if (uri.startsWith("vm:")) {// alias
355 repository = ArgeoJcrUtils.getRepositoryByUri(
356 repositoryFactory, uri);
357 } else {
358 throw new SlcException("Unsupported repository uri " + uri);
359 }
360 return repository;
361 } else {
362 throw new SlcException("Unsupported node type " + repoNode);
363 }
364 } catch (RepositoryException e) {
365 throw new SlcException("Cannot connect to repository " + repoNode,
366 e);
367 }
368
369 }
370
371 /**
372 * Reads credentials from node, using keyring if there is a password. Cann
373 * return null if no credentials needed (local repo) at all, but returns
374 * {@link GuestCredentials} if user id is 'anonymous' .
375 */
376 public static Credentials getRepositoryCredentials(Keyring keyring,
377 Node repoNode) {
378 try {
379 if (repoNode.isNodeType(ArgeoTypes.ARGEO_REMOTE_REPOSITORY)) {
380 if (!repoNode.hasProperty(ARGEO_USER_ID))
381 return null;
382
383 String userId = repoNode.getProperty(ARGEO_USER_ID).getString();
384 if (userId.equals("anonymous"))// FIXME hardcoded userId
385 return new GuestCredentials();
386 char[] password = keyring.getAsChars(repoNode.getPath() + '/'
387 + ARGEO_PASSWORD);
388 Credentials credentials = new SimpleCredentials(userId,
389 password);
390 return credentials;
391 } else {
392 throw new SlcException("Unsupported node type " + repoNode);
393 }
394 } catch (RepositoryException e) {
395 throw new SlcException("Cannot connect to repository " + repoNode,
396 e);
397 }
398 }
399
400 /**
401 * Custom copy since the one in commons does not fit the needs when copying
402 * a workspace completely.
403 */
404 public static void copy(Node fromNode, Node toNode) {
405 try {
406 if (log.isDebugEnabled())
407 log.debug("copy node :" + fromNode.getPath());
408
409 // FIXME : small hack to enable specific workspace copy
410 if (fromNode.isNodeType("rep:ACL")
411 || fromNode.isNodeType("rep:system")) {
412 if (log.isTraceEnabled())
413 log.trace("node " + fromNode + " skipped");
414 return;
415 }
416
417 // add mixins
418 for (NodeType mixinType : fromNode.getMixinNodeTypes()) {
419 toNode.addMixin(mixinType.getName());
420 }
421
422 // Double check
423 for (NodeType mixinType : toNode.getMixinNodeTypes()) {
424 if (log.isDebugEnabled())
425 log.debug(mixinType.getName());
426 }
427
428 // process properties
429 PropertyIterator pit = fromNode.getProperties();
430 properties: while (pit.hasNext()) {
431 Property fromProperty = pit.nextProperty();
432 String propName = fromProperty.getName();
433 try {
434 String propertyName = fromProperty.getName();
435 if (toNode.hasProperty(propertyName)
436 && toNode.getProperty(propertyName).getDefinition()
437 .isProtected())
438 continue properties;
439
440 if (fromProperty.getDefinition().isProtected())
441 continue properties;
442
443 if (propertyName.equals("jcr:created")
444 || propertyName.equals("jcr:createdBy")
445 || propertyName.equals("jcr:lastModified")
446 || propertyName.equals("jcr:lastModifiedBy"))
447 continue properties;
448
449 if (fromProperty.isMultiple()) {
450 toNode.setProperty(propertyName,
451 fromProperty.getValues());
452 } else {
453 toNode.setProperty(propertyName,
454 fromProperty.getValue());
455 }
456 } catch (RepositoryException e) {
457 throw new SlcException("Cannot property " + propName, e);
458 }
459 }
460
461 // recursively process children nodes
462 NodeIterator nit = fromNode.getNodes();
463 while (nit.hasNext()) {
464 Node fromChild = nit.nextNode();
465 Integer index = fromChild.getIndex();
466 String nodeRelPath = fromChild.getName() + "[" + index + "]";
467 Node toChild;
468 if (toNode.hasNode(nodeRelPath))
469 toChild = toNode.getNode(nodeRelPath);
470 else
471 toChild = toNode.addNode(fromChild.getName(), fromChild
472 .getPrimaryNodeType().getName());
473 copy(fromChild, toChild);
474 }
475
476 // update jcr:lastModified and jcr:lastModifiedBy in toNode in
477 // case
478 // they existed
479 if (!toNode.getDefinition().isProtected()
480 && toNode.isNodeType(NodeType.MIX_LAST_MODIFIED))
481 JcrUtils.updateLastModified(toNode);
482
483 // Workaround to reduce session size: artifact is a saveable
484 // unity
485 if (toNode.isNodeType(SlcTypes.SLC_ARTIFACT))
486 toNode.getSession().save();
487
488 } catch (RepositoryException e) {
489 throw new SlcException("Cannot copy " + fromNode + " to " + toNode,
490 e);
491 }
492 }
493
494 }