2 * Copyright (C) 2007-2012 Argeo GmbH
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org
.argeo
.slc
.repo
;
18 import java
.io
.ByteArrayOutputStream
;
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
;
27 import java
.util
.StringTokenizer
;
28 import java
.util
.TreeSet
;
29 import java
.util
.jar
.Attributes
;
30 import java
.util
.jar
.JarEntry
;
31 import java
.util
.jar
.JarFile
;
32 import java
.util
.jar
.JarInputStream
;
33 import java
.util
.jar
.JarOutputStream
;
34 import java
.util
.jar
.Manifest
;
35 import java
.util
.zip
.ZipInputStream
;
37 import javax
.jcr
.Credentials
;
38 import javax
.jcr
.GuestCredentials
;
39 import javax
.jcr
.Node
;
40 import javax
.jcr
.NodeIterator
;
41 import javax
.jcr
.Property
;
42 import javax
.jcr
.PropertyIterator
;
43 import javax
.jcr
.Repository
;
44 import javax
.jcr
.RepositoryException
;
45 import javax
.jcr
.RepositoryFactory
;
46 import javax
.jcr
.Session
;
47 import javax
.jcr
.SimpleCredentials
;
48 import javax
.jcr
.nodetype
.NodeType
;
50 import org
.apache
.commons
.io
.FilenameUtils
;
51 import org
.apache
.commons
.io
.IOUtils
;
52 import org
.apache
.commons
.logging
.Log
;
53 import org
.apache
.commons
.logging
.LogFactory
;
54 import org
.argeo
.ArgeoMonitor
;
55 import org
.argeo
.jcr
.ArgeoJcrUtils
;
56 import org
.argeo
.jcr
.ArgeoNames
;
57 import org
.argeo
.jcr
.ArgeoTypes
;
58 import org
.argeo
.jcr
.JcrUtils
;
59 import org
.argeo
.slc
.DefaultNameVersion
;
60 import org
.argeo
.slc
.NameVersion
;
61 import org
.argeo
.slc
.SlcException
;
62 import org
.argeo
.slc
.aether
.ArtifactIdComparator
;
63 import org
.argeo
.slc
.jcr
.SlcNames
;
64 import org
.argeo
.slc
.jcr
.SlcTypes
;
65 import org
.argeo
.slc
.repo
.maven
.MavenConventionsUtils
;
66 import org
.argeo
.util
.security
.Keyring
;
67 import org
.osgi
.framework
.Constants
;
68 import org
.sonatype
.aether
.artifact
.Artifact
;
69 import org
.sonatype
.aether
.util
.artifact
.DefaultArtifact
;
71 /** Utilities around repo */
72 public class RepoUtils
implements ArgeoNames
, SlcNames
{
73 private final static Log log
= LogFactory
.getLog(RepoUtils
.class);
75 /** Packages a regular sources jar as PDE source. */
76 public static void packagesAsPdeSource(File sourceFile
,
77 NameVersion nameVersion
, OutputStream out
) throws IOException
{
78 if (isAlreadyPdeSource(sourceFile
)) {
79 FileInputStream in
= new FileInputStream(sourceFile
);
80 IOUtils
.copy(in
, out
);
81 IOUtils
.closeQuietly(in
);
83 String sourceSymbolicName
= nameVersion
.getName() + ".source";
85 Manifest sourceManifest
= null;
86 sourceManifest
= new Manifest();
87 sourceManifest
.getMainAttributes().put(
88 Attributes
.Name
.MANIFEST_VERSION
, "1.0");
89 sourceManifest
.getMainAttributes().putValue("Bundle-SymbolicName",
91 sourceManifest
.getMainAttributes().putValue("Bundle-Version",
92 nameVersion
.getVersion());
93 sourceManifest
.getMainAttributes().putValue(
94 "Eclipse-SourceBundle",
95 nameVersion
.getName() + ";version="
96 + nameVersion
.getVersion());
97 copyJar(sourceFile
, out
, sourceManifest
);
101 public static byte[] packageAsPdeSource(InputStream sourceJar
,
102 NameVersion nameVersion
) {
103 String sourceSymbolicName
= nameVersion
.getName() + ".source";
105 Manifest sourceManifest
= null;
106 sourceManifest
= new Manifest();
107 sourceManifest
.getMainAttributes().put(
108 Attributes
.Name
.MANIFEST_VERSION
, "1.0");
109 sourceManifest
.getMainAttributes().putValue("Bundle-SymbolicName",
111 sourceManifest
.getMainAttributes().putValue("Bundle-Version",
112 nameVersion
.getVersion());
113 sourceManifest
.getMainAttributes().putValue("Eclipse-SourceBundle",
114 nameVersion
.getName() + ";version=" + nameVersion
.getVersion());
116 return modifyManifest(sourceJar
, sourceManifest
);
120 * Check whether the file as already been packaged as PDE source, in order
121 * not to mess with Jar signing
123 private static boolean isAlreadyPdeSource(File sourceFile
) {
124 JarInputStream jarInputStream
= null;
127 jarInputStream
= new JarInputStream(new FileInputStream(sourceFile
));
129 Manifest manifest
= jarInputStream
.getManifest();
130 Iterator
<?
> it
= manifest
.getMainAttributes().keySet().iterator();
132 // containsKey() does not work, iterating...
134 if (it
.next().toString().equals("Eclipse-SourceBundle")) {
138 // boolean res = manifest.getMainAttributes().get(
139 // "Eclipse-SourceBundle") != null;
141 log
.info(sourceFile
+ " is already a PDE source");
143 } catch (Exception e
) {
144 // probably not a jar, skipping
145 if (log
.isDebugEnabled())
146 log
.debug("Skipping " + sourceFile
+ " because of "
150 IOUtils
.closeQuietly(jarInputStream
);
155 * Copy a jar, replacing its manifest with the provided one
160 private static void copyJar(File source
, OutputStream out
, Manifest manifest
)
162 JarFile sourceJar
= null;
163 JarOutputStream output
= null;
165 output
= manifest
!= null ?
new JarOutputStream(out
, manifest
)
166 : new JarOutputStream(out
);
167 sourceJar
= new JarFile(source
);
169 entries
: for (Enumeration
<?
> entries
= sourceJar
.entries(); entries
170 .hasMoreElements();) {
171 JarEntry entry
= (JarEntry
) entries
.nextElement();
173 && entry
.getName().equals("META-INF/MANIFEST.MF"))
176 InputStream entryStream
= sourceJar
.getInputStream(entry
);
177 JarEntry newEntry
= new JarEntry(entry
.getName());
178 // newEntry.setMethod(JarEntry.DEFLATED);
179 output
.putNextEntry(newEntry
);
180 IOUtils
.copy(entryStream
, output
);
183 IOUtils
.closeQuietly(output
);
185 if (sourceJar
!= null)
187 } catch (IOException e
) {
193 /** Copy a jar changing onlythe manifest */
194 public static void copyJar(InputStream in
, OutputStream out
,
196 JarInputStream jarIn
= null;
197 JarOutputStream jarOut
= null;
199 jarIn
= new JarInputStream(in
);
200 jarOut
= new JarOutputStream(out
, manifest
);
201 JarEntry jarEntry
= null;
202 while ((jarEntry
= jarIn
.getNextJarEntry()) != null) {
203 if (!jarEntry
.getName().equals("META-INF/MANIFEST.MF")) {
204 JarEntry newJarEntry
= new JarEntry(jarEntry
.getName());
205 jarOut
.putNextEntry(newJarEntry
);
206 IOUtils
.copy(jarIn
, jarOut
);
211 } catch (IOException e
) {
212 throw new SlcException("Could not copy jar with MANIFEST "
213 + manifest
.getMainAttributes(), e
);
215 if (!(in
instanceof ZipInputStream
))
216 IOUtils
.closeQuietly(jarIn
);
217 IOUtils
.closeQuietly(jarOut
);
221 /** Reads a jar file, modify its manifest */
222 public static byte[] modifyManifest(InputStream in
, Manifest manifest
) {
223 ByteArrayOutputStream out
= new ByteArrayOutputStream(200 * 1024);
225 copyJar(in
, out
, manifest
);
226 return out
.toByteArray();
228 IOUtils
.closeQuietly(out
);
232 /** Read the OSGi {@link NameVersion} */
233 public static NameVersion
readNameVersion(Artifact artifact
) {
234 File artifactFile
= artifact
.getFile();
235 if (artifact
.getExtension().equals("pom")) {
236 // hack to process jars which weirdly appear as POMs
237 File jarFile
= new File(artifactFile
.getParentFile(),
238 FilenameUtils
.getBaseName(artifactFile
.getPath()) + ".jar");
239 if (jarFile
.exists()) {
240 log
.warn("Use " + jarFile
+ " instead of " + artifactFile
241 + " for " + artifact
);
242 artifactFile
= jarFile
;
245 return readNameVersion(artifactFile
);
248 /** Read the OSGi {@link NameVersion} */
249 public static NameVersion
readNameVersion(File artifactFile
) {
251 return readNameVersion(new FileInputStream(artifactFile
));
252 } catch (Exception e
) {
253 // probably not a jar, skipping
254 if (log
.isDebugEnabled()) {
255 log
.debug("Skipping " + artifactFile
+ " because of " + e
);
256 // e.printStackTrace();
262 /** Read the OSGi {@link NameVersion} */
263 public static NameVersion
readNameVersion(InputStream in
) {
264 JarInputStream jarInputStream
= null;
266 jarInputStream
= new JarInputStream(in
);
267 return readNameVersion(jarInputStream
.getManifest());
268 } catch (Exception e
) {
269 // probably not a jar, skipping
270 if (log
.isDebugEnabled()) {
271 log
.debug("Skipping because of " + e
);
275 IOUtils
.closeQuietly(jarInputStream
);
280 /** Read the OSGi {@link NameVersion} */
281 public static NameVersion
readNameVersion(Manifest manifest
) {
282 DefaultNameVersion nameVersion
= new DefaultNameVersion();
283 nameVersion
.setName(manifest
.getMainAttributes().getValue(
284 Constants
.BUNDLE_SYMBOLICNAME
));
286 // Skip additional specs such as
288 if (nameVersion
.getName().indexOf(';') > -1) {
290 .setName(new StringTokenizer(nameVersion
.getName(), " ;")
294 nameVersion
.setVersion(manifest
.getMainAttributes().getValue(
295 Constants
.BUNDLE_VERSION
));
303 /** The artifact described by this node */
304 public static Artifact
asArtifact(Node node
) throws RepositoryException
{
305 if (node
.isNodeType(SlcTypes
.SLC_ARTIFACT_VERSION_BASE
)) {
306 // FIXME update data model to store packaging at this level
307 String extension
= "jar";
308 return new DefaultArtifact(node
.getProperty(SLC_GROUP_ID
)
310 node
.getProperty(SLC_ARTIFACT_ID
).getString(), extension
,
311 node
.getProperty(SLC_ARTIFACT_VERSION
).getString());
312 } else if (node
.isNodeType(SlcTypes
.SLC_ARTIFACT
)) {
313 return new DefaultArtifact(node
.getProperty(SLC_GROUP_ID
)
315 node
.getProperty(SLC_ARTIFACT_ID
).getString(), node
316 .getProperty(SLC_ARTIFACT_CLASSIFIER
).getString(),
317 node
.getProperty(SLC_ARTIFACT_EXTENSION
).getString(), node
318 .getProperty(SLC_ARTIFACT_VERSION
).getString());
319 } else if (node
.isNodeType(SlcTypes
.SLC_MODULE_COORDINATES
)) {
320 return new DefaultArtifact(node
.getProperty(SLC_CATEGORY
)
321 .getString(), node
.getProperty(SLC_NAME
).getString(),
322 "jar", node
.getProperty(SLC_VERSION
).getString());
324 throw new SlcException("Unsupported node type for " + node
);
329 * The path to the PDE source related to this artifact (or artifact version
330 * base). There may or there may not be a node at this location (the
331 * returned path will typically be used to test whether PDE sources are
332 * attached to this artifact).
334 public static String
relatedPdeSourcePath(String artifactBasePath
,
335 Node artifactNode
) throws RepositoryException
{
336 Artifact artifact
= asArtifact(artifactNode
);
337 Artifact pdeSourceArtifact
= new DefaultArtifact(artifact
.getGroupId(),
338 artifact
.getArtifactId() + ".source", artifact
.getExtension(),
339 artifact
.getVersion());
340 return MavenConventionsUtils
.artifactPath(artifactBasePath
,
345 * Copy this bytes array as an artifact, relative to the root of the
346 * repository (typically the workspace root node)
348 public static Node
copyBytesAsArtifact(Node artifactsBase
,
349 Artifact artifact
, byte[] bytes
) throws RepositoryException
{
350 String parentPath
= MavenConventionsUtils
.artifactParentPath(
351 artifactsBase
.getPath(), artifact
);
352 Node folderNode
= JcrUtils
.mkfolders(artifactsBase
.getSession(),
354 return JcrUtils
.copyBytesAsFile(folderNode
,
355 MavenConventionsUtils
.artifactFileName(artifact
), bytes
);
358 private RepoUtils() {
361 /** If a source return the base bundle name, does not change otherwise */
362 public static String
extractBundleNameFromSourceName(String sourceBundleName
) {
363 if (sourceBundleName
.endsWith(".source"))
364 return sourceBundleName
.substring(0, sourceBundleName
.length()
365 - ".source".length());
367 return sourceBundleName
;
371 * SOFTWARE REPOSITORIES
374 /** Retrieve repository based on information in the repo node */
375 public static Repository
getRepository(RepositoryFactory repositoryFactory
,
376 Keyring keyring
, Node repoNode
) {
378 Repository repository
;
379 if (repoNode
.isNodeType(ArgeoTypes
.ARGEO_REMOTE_REPOSITORY
)) {
380 String uri
= repoNode
.getProperty(ARGEO_URI
).getString();
381 if (uri
.startsWith("http")) {// http, https
382 repository
= ArgeoJcrUtils
.getRepositoryByUri(
383 repositoryFactory
, uri
);
384 } else if (uri
.startsWith("vm:")) {// alias
385 repository
= ArgeoJcrUtils
.getRepositoryByUri(
386 repositoryFactory
, uri
);
388 throw new SlcException("Unsupported repository uri " + uri
);
392 throw new SlcException("Unsupported node type " + repoNode
);
394 } catch (RepositoryException e
) {
395 throw new SlcException("Cannot connect to repository " + repoNode
,
401 * Reads credentials from node, using keyring if there is a password. Can
402 * return null if no credentials needed (local repo) at all, but returns
403 * {@link GuestCredentials} if user id is 'anonymous' .
405 public static Credentials
getRepositoryCredentials(Keyring keyring
,
408 if (repoNode
.isNodeType(ArgeoTypes
.ARGEO_REMOTE_REPOSITORY
)) {
409 if (!repoNode
.hasProperty(ARGEO_USER_ID
))
412 String userId
= repoNode
.getProperty(ARGEO_USER_ID
).getString();
413 if (userId
.equals("anonymous"))// FIXME hardcoded userId
414 return new GuestCredentials();
415 char[] password
= keyring
.getAsChars(repoNode
.getPath() + '/'
417 Credentials credentials
= new SimpleCredentials(userId
,
421 throw new SlcException("Unsupported node type " + repoNode
);
423 } catch (RepositoryException e
) {
424 throw new SlcException("Cannot connect to repository " + repoNode
,
430 * Shortcut to retrieve a session given variable information: Handle the
431 * case where we only have an URI of the repository, that we want to connect
432 * as anonymous or the case of a identified connection to a local or remote
435 * Callers must close the session once it has been used
437 public static Session
getRemoteSession(RepositoryFactory repositoryFactory
,
438 Keyring keyring
, Node repoNode
, String uri
, String workspaceName
) {
440 if (repoNode
== null && uri
== null)
441 throw new SlcException(
442 "At least one of repoNode and uri must be defined");
443 Repository currRepo
= null;
444 Credentials credentials
= null;
445 // Anonymous URI only workspace
446 if (repoNode
== null)
448 currRepo
= ArgeoJcrUtils
.getRepositoryByUri(repositoryFactory
,
451 currRepo
= RepoUtils
.getRepository(repositoryFactory
, keyring
,
453 credentials
= RepoUtils
.getRepositoryCredentials(keyring
,
456 return currRepo
.login(credentials
, workspaceName
);
457 } catch (RepositoryException e
) {
458 throw new SlcException("Cannot connect to workspace "
459 + workspaceName
+ " of repository " + repoNode
460 + " with URI " + uri
, e
);
465 * Shortcut to retrieve a session on a remote Jrc Repository from
466 * information stored in a local argeo node or from an URI: Handle the case
467 * where we only have an URI of the repository, that we want to connect as
468 * anonymous or the case of a identified connection to a local or remote
471 * Callers must close the session once it has been used
473 public static Session
getRemoteSession(RepositoryFactory repositoryFactory
,
474 Keyring keyring
, Repository localRepository
, String repoNodePath
,
475 String uri
, String workspaceName
) {
476 Session localSession
= null;
477 Node repoNode
= null;
479 localSession
= localRepository
.login();
480 if (repoNodePath
!= null && localSession
.nodeExists(repoNodePath
))
481 repoNode
= localSession
.getNode(repoNodePath
);
483 return RepoUtils
.getRemoteSession(repositoryFactory
, keyring
,
484 repoNode
, uri
, workspaceName
);
485 } catch (RepositoryException e
) {
486 throw new SlcException("Cannot log to workspace " + workspaceName
487 + " for repo defined in " + repoNodePath
, e
);
489 JcrUtils
.logoutQuietly(localSession
);
494 * Write group indexes: 'binaries' lists all bundles and their versions,
495 * 'sources' list their sources, and 'sdk' aggregates both.
497 public static void writeGroupIndexes(Session session
,
498 String artifactBasePath
, String groupId
, String version
,
499 Set
<Artifact
> binaries
, Set
<Artifact
> sources
) {
501 Set
<Artifact
> indexes
= new TreeSet
<Artifact
>(
502 new ArtifactIdComparator());
503 Artifact binariesArtifact
= writeIndex(session
, artifactBasePath
,
504 groupId
, RepoConstants
.BINARIES_ARTIFACT_ID
, version
,
506 indexes
.add(binariesArtifact
);
507 if (sources
!= null) {
508 Artifact sourcesArtifact
= writeIndex(session
,
509 artifactBasePath
, groupId
,
510 RepoConstants
.SOURCES_ARTIFACT_ID
, version
, sources
);
511 indexes
.add(sourcesArtifact
);
514 writeIndex(session
, artifactBasePath
, groupId
,
515 RepoConstants
.SDK_ARTIFACT_ID
, version
, indexes
);
517 } catch (RepositoryException e
) {
518 throw new SlcException("Cannot write indexes for group " + groupId
,
523 /** Write a group index. */
524 private static Artifact
writeIndex(Session session
,
525 String artifactBasePath
, String groupId
, String artifactId
,
526 String version
, Set
<Artifact
> artifacts
) throws RepositoryException
{
527 Artifact artifact
= new DefaultArtifact(groupId
, artifactId
, "pom",
529 String pom
= MavenConventionsUtils
.artifactsAsDependencyPom(artifact
,
531 Node node
= RepoUtils
.copyBytesAsArtifact(
532 session
.getNode(artifactBasePath
), artifact
, pom
.getBytes());
533 addMavenChecksums(node
);
537 /** Add files containing the SHA-1 and MD5 checksums. */
538 public static void addMavenChecksums(Node node
) throws RepositoryException
{
540 String sha
= JcrUtils
.checksumFile(node
, "SHA-1");
541 JcrUtils
.copyBytesAsFile(node
.getParent(), node
.getName() + ".sha1",
543 String md5
= JcrUtils
.checksumFile(node
, "MD5");
544 JcrUtils
.copyBytesAsFile(node
.getParent(), node
.getName() + ".md5",
549 * Custom copy since the one in commons does not fit the needs when copying
550 * a workspace completely.
552 public static void copy(Node fromNode
, Node toNode
) {
553 copy(fromNode
, toNode
, null);
556 public static void copy(Node fromNode
, Node toNode
, ArgeoMonitor monitor
) {
558 String fromPath
= fromNode
.getPath();
560 monitor
.subTask("copying node :" + fromPath
);
561 if (log
.isDebugEnabled())
562 log
.debug("copy node :" + fromPath
);
564 // FIXME : small hack to enable specific workspace copy
565 if (fromNode
.isNodeType("rep:ACL")
566 || fromNode
.isNodeType("rep:system")) {
567 if (log
.isTraceEnabled())
568 log
.trace("node " + fromNode
+ " skipped");
573 for (NodeType mixinType
: fromNode
.getMixinNodeTypes()) {
574 toNode
.addMixin(mixinType
.getName());
578 for (NodeType mixinType
: toNode
.getMixinNodeTypes()) {
579 if (log
.isDebugEnabled())
580 log
.debug(mixinType
.getName());
583 // process properties
584 PropertyIterator pit
= fromNode
.getProperties();
585 properties
: while (pit
.hasNext()) {
586 Property fromProperty
= pit
.nextProperty();
587 String propName
= fromProperty
.getName();
589 String propertyName
= fromProperty
.getName();
590 if (toNode
.hasProperty(propertyName
)
591 && toNode
.getProperty(propertyName
).getDefinition()
595 if (fromProperty
.getDefinition().isProtected())
598 if (propertyName
.equals("jcr:created")
599 || propertyName
.equals("jcr:createdBy")
600 || propertyName
.equals("jcr:lastModified")
601 || propertyName
.equals("jcr:lastModifiedBy"))
604 if (fromProperty
.isMultiple()) {
605 toNode
.setProperty(propertyName
,
606 fromProperty
.getValues());
608 toNode
.setProperty(propertyName
,
609 fromProperty
.getValue());
611 } catch (RepositoryException e
) {
612 throw new SlcException("Cannot property " + propName
, e
);
616 // recursively process children nodes
617 NodeIterator nit
= fromNode
.getNodes();
618 while (nit
.hasNext()) {
619 Node fromChild
= nit
.nextNode();
620 Integer index
= fromChild
.getIndex();
621 String nodeRelPath
= fromChild
.getName() + "[" + index
+ "]";
623 if (toNode
.hasNode(nodeRelPath
))
624 toChild
= toNode
.getNode(nodeRelPath
);
626 toChild
= toNode
.addNode(fromChild
.getName(), fromChild
627 .getPrimaryNodeType().getName());
628 copy(fromChild
, toChild
);
631 // update jcr:lastModified and jcr:lastModifiedBy in toNode in
634 if (!toNode
.getDefinition().isProtected()
635 && toNode
.isNodeType(NodeType
.MIX_LAST_MODIFIED
))
636 JcrUtils
.updateLastModified(toNode
);
638 // Workaround to reduce session size: artifact is a saveable
640 if (toNode
.isNodeType(SlcTypes
.SLC_ARTIFACT
))
641 toNode
.getSession().save();
646 } catch (RepositoryException e
) {
647 throw new SlcException("Cannot copy " + fromNode
+ " to " + toNode
,