1 package org
.argeo
.slc
.repo
;
3 import java
.io
.ByteArrayOutputStream
;
5 import java
.io
.FileInputStream
;
6 import java
.io
.IOException
;
7 import java
.io
.InputStream
;
8 import java
.io
.OutputStream
;
9 import java
.util
.Enumeration
;
10 import java
.util
.Iterator
;
12 import java
.util
.StringTokenizer
;
13 import java
.util
.TreeSet
;
14 import java
.util
.jar
.Attributes
;
15 import java
.util
.jar
.JarEntry
;
16 import java
.util
.jar
.JarFile
;
17 import java
.util
.jar
.JarInputStream
;
18 import java
.util
.jar
.JarOutputStream
;
19 import java
.util
.jar
.Manifest
;
20 import java
.util
.zip
.ZipInputStream
;
22 import javax
.jcr
.Credentials
;
23 import javax
.jcr
.GuestCredentials
;
24 import javax
.jcr
.Node
;
25 import javax
.jcr
.NodeIterator
;
26 import javax
.jcr
.Property
;
27 import javax
.jcr
.PropertyIterator
;
28 import javax
.jcr
.Repository
;
29 import javax
.jcr
.RepositoryException
;
30 import javax
.jcr
.RepositoryFactory
;
31 import javax
.jcr
.Session
;
32 import javax
.jcr
.SimpleCredentials
;
33 import javax
.jcr
.nodetype
.NodeType
;
35 import org
.apache
.commons
.io
.FilenameUtils
;
36 import org
.apache
.commons
.io
.IOUtils
;
37 import org
.argeo
.api
.cms
.CmsLog
;
38 import org
.argeo
.api
.cms
.keyring
.Keyring
;
39 import org
.argeo
.cms
.ArgeoNames
;
40 import org
.argeo
.cms
.ArgeoTypes
;
41 import org
.argeo
.cms
.jcr
.CmsJcrUtils
;
42 import org
.argeo
.jcr
.JcrMonitor
;
43 import org
.argeo
.jcr
.JcrUtils
;
44 import org
.argeo
.slc
.DefaultNameVersion
;
45 import org
.argeo
.slc
.NameVersion
;
46 import org
.argeo
.slc
.SlcException
;
47 import org
.argeo
.slc
.SlcNames
;
48 import org
.argeo
.slc
.SlcTypes
;
49 import org
.argeo
.slc
.repo
.maven
.ArtifactIdComparator
;
50 import org
.argeo
.slc
.repo
.maven
.MavenConventionsUtils
;
51 import org
.eclipse
.aether
.artifact
.Artifact
;
52 import org
.eclipse
.aether
.artifact
.DefaultArtifact
;
53 import org
.osgi
.framework
.Constants
;
55 /** Utilities around repo */
56 public class RepoUtils
implements ArgeoNames
, SlcNames
{
57 private final static CmsLog log
= CmsLog
.getLog(RepoUtils
.class);
59 /** Packages a regular sources jar as PDE source. */
60 public static void packagesAsPdeSource(File sourceFile
,
61 NameVersion nameVersion
, OutputStream out
) throws IOException
{
62 if (isAlreadyPdeSource(sourceFile
)) {
63 FileInputStream in
= new FileInputStream(sourceFile
);
64 IOUtils
.copy(in
, out
);
65 IOUtils
.closeQuietly(in
);
67 String sourceSymbolicName
= nameVersion
.getName() + ".source";
69 Manifest sourceManifest
= null;
70 sourceManifest
= new Manifest();
71 sourceManifest
.getMainAttributes().put(
72 Attributes
.Name
.MANIFEST_VERSION
, "1.0");
73 sourceManifest
.getMainAttributes().putValue("Bundle-SymbolicName",
75 sourceManifest
.getMainAttributes().putValue("Bundle-Version",
76 nameVersion
.getVersion());
77 sourceManifest
.getMainAttributes().putValue(
78 "Eclipse-SourceBundle",
79 nameVersion
.getName() + ";version="
80 + nameVersion
.getVersion());
81 copyJar(sourceFile
, out
, sourceManifest
);
85 public static byte[] packageAsPdeSource(InputStream sourceJar
,
86 NameVersion nameVersion
) {
87 String sourceSymbolicName
= nameVersion
.getName() + ".source";
89 Manifest sourceManifest
= null;
90 sourceManifest
= new Manifest();
91 sourceManifest
.getMainAttributes().put(
92 Attributes
.Name
.MANIFEST_VERSION
, "1.0");
93 sourceManifest
.getMainAttributes().putValue("Bundle-SymbolicName",
95 sourceManifest
.getMainAttributes().putValue("Bundle-Version",
96 nameVersion
.getVersion());
97 sourceManifest
.getMainAttributes().putValue("Eclipse-SourceBundle",
98 nameVersion
.getName() + ";version=" + nameVersion
.getVersion());
100 return modifyManifest(sourceJar
, sourceManifest
);
104 * Check whether the file as already been packaged as PDE source, in order
105 * not to mess with Jar signing
107 private static boolean isAlreadyPdeSource(File sourceFile
) {
108 JarInputStream jarInputStream
= null;
111 jarInputStream
= new JarInputStream(new FileInputStream(sourceFile
));
113 Manifest manifest
= jarInputStream
.getManifest();
114 Iterator
<?
> it
= manifest
.getMainAttributes().keySet().iterator();
116 // containsKey() does not work, iterating...
118 if (it
.next().toString().equals("Eclipse-SourceBundle")) {
122 // boolean res = manifest.getMainAttributes().get(
123 // "Eclipse-SourceBundle") != null;
125 log
.info(sourceFile
+ " is already a PDE source");
127 } catch (Exception e
) {
128 // probably not a jar, skipping
129 if (log
.isDebugEnabled())
130 log
.debug("Skipping " + sourceFile
+ " because of "
134 IOUtils
.closeQuietly(jarInputStream
);
139 * Copy a jar, replacing its manifest with the provided one
144 private static void copyJar(File source
, OutputStream out
, Manifest manifest
)
146 JarFile sourceJar
= null;
147 JarOutputStream output
= null;
149 output
= manifest
!= null ?
new JarOutputStream(out
, manifest
)
150 : new JarOutputStream(out
);
151 sourceJar
= new JarFile(source
);
153 entries
: for (Enumeration
<?
> entries
= sourceJar
.entries(); entries
154 .hasMoreElements();) {
155 JarEntry entry
= (JarEntry
) entries
.nextElement();
157 && entry
.getName().equals("META-INF/MANIFEST.MF"))
160 InputStream entryStream
= sourceJar
.getInputStream(entry
);
161 JarEntry newEntry
= new JarEntry(entry
.getName());
162 // newEntry.setMethod(JarEntry.DEFLATED);
163 output
.putNextEntry(newEntry
);
164 IOUtils
.copy(entryStream
, output
);
167 IOUtils
.closeQuietly(output
);
169 if (sourceJar
!= null)
171 } catch (IOException e
) {
177 /** Copy a jar changing onlythe manifest */
178 public static void copyJar(InputStream in
, OutputStream out
,
180 JarInputStream jarIn
= null;
181 JarOutputStream jarOut
= null;
183 jarIn
= new JarInputStream(in
);
184 jarOut
= new JarOutputStream(out
, manifest
);
185 JarEntry jarEntry
= null;
186 while ((jarEntry
= jarIn
.getNextJarEntry()) != null) {
187 if (!jarEntry
.getName().equals("META-INF/MANIFEST.MF")) {
188 JarEntry newJarEntry
= new JarEntry(jarEntry
.getName());
189 jarOut
.putNextEntry(newJarEntry
);
190 IOUtils
.copy(jarIn
, jarOut
);
195 } catch (IOException e
) {
196 throw new SlcException("Could not copy jar with MANIFEST "
197 + manifest
.getMainAttributes(), e
);
199 if (!(in
instanceof ZipInputStream
))
200 IOUtils
.closeQuietly(jarIn
);
201 IOUtils
.closeQuietly(jarOut
);
205 /** Reads a jar file, modify its manifest */
206 public static byte[] modifyManifest(InputStream in
, Manifest manifest
) {
207 ByteArrayOutputStream out
= new ByteArrayOutputStream(200 * 1024);
209 copyJar(in
, out
, manifest
);
210 return out
.toByteArray();
212 IOUtils
.closeQuietly(out
);
216 /** Read the OSGi {@link NameVersion} */
217 public static NameVersion
readNameVersion(Artifact artifact
) {
218 File artifactFile
= artifact
.getFile();
219 if (artifact
.getExtension().equals("pom")) {
220 // hack to process jars which weirdly appear as POMs
221 File jarFile
= new File(artifactFile
.getParentFile(),
222 FilenameUtils
.getBaseName(artifactFile
.getPath()) + ".jar");
223 if (jarFile
.exists()) {
224 log
.warn("Use " + jarFile
+ " instead of " + artifactFile
225 + " for " + artifact
);
226 artifactFile
= jarFile
;
229 return readNameVersion(artifactFile
);
232 /** Read the OSGi {@link NameVersion} */
233 public static NameVersion
readNameVersion(File artifactFile
) {
235 return readNameVersion(new FileInputStream(artifactFile
));
236 } catch (Exception e
) {
237 // probably not a jar, skipping
238 if (log
.isDebugEnabled()) {
239 log
.debug("Skipping " + artifactFile
+ " because of " + e
);
240 // e.printStackTrace();
246 /** Read the OSGi {@link NameVersion} */
247 public static NameVersion
readNameVersion(InputStream in
) {
248 JarInputStream jarInputStream
= null;
250 jarInputStream
= new JarInputStream(in
);
251 return readNameVersion(jarInputStream
.getManifest());
252 } catch (Exception e
) {
253 // probably not a jar, skipping
254 if (log
.isDebugEnabled()) {
255 log
.debug("Skipping because of " + e
);
258 IOUtils
.closeQuietly(jarInputStream
);
263 /** Read the OSGi {@link NameVersion} */
264 public static NameVersion
readNameVersion(Manifest manifest
) {
265 DefaultNameVersion nameVersion
= new DefaultNameVersion();
266 nameVersion
.setName(manifest
.getMainAttributes().getValue(
267 Constants
.BUNDLE_SYMBOLICNAME
));
269 // Skip additional specs such as
271 if (nameVersion
.getName().indexOf(';') > -1) {
273 .setName(new StringTokenizer(nameVersion
.getName(), " ;")
277 nameVersion
.setVersion(manifest
.getMainAttributes().getValue(
278 Constants
.BUNDLE_VERSION
));
286 /** The artifact described by this node */
287 public static Artifact
asArtifact(Node node
) throws RepositoryException
{
288 if (node
.isNodeType(SlcTypes
.SLC_ARTIFACT_VERSION_BASE
)) {
289 // FIXME update data model to store packaging at this level
290 String extension
= "jar";
291 return new DefaultArtifact(node
.getProperty(SLC_GROUP_ID
)
293 node
.getProperty(SLC_ARTIFACT_ID
).getString(), extension
,
294 node
.getProperty(SLC_ARTIFACT_VERSION
).getString());
295 } else if (node
.isNodeType(SlcTypes
.SLC_ARTIFACT
)) {
296 return new DefaultArtifact(node
.getProperty(SLC_GROUP_ID
)
298 node
.getProperty(SLC_ARTIFACT_ID
).getString(), node
299 .getProperty(SLC_ARTIFACT_CLASSIFIER
).getString(),
300 node
.getProperty(SLC_ARTIFACT_EXTENSION
).getString(), node
301 .getProperty(SLC_ARTIFACT_VERSION
).getString());
302 } else if (node
.isNodeType(SlcTypes
.SLC_MODULE_COORDINATES
)) {
303 return new DefaultArtifact(node
.getProperty(SLC_CATEGORY
)
304 .getString(), node
.getProperty(SLC_NAME
).getString(),
305 "jar", node
.getProperty(SLC_VERSION
).getString());
307 throw new SlcException("Unsupported node type for " + node
);
312 * The path to the PDE source related to this artifact (or artifact version
313 * base). There may or there may not be a node at this location (the
314 * returned path will typically be used to test whether PDE sources are
315 * attached to this artifact).
317 public static String
relatedPdeSourcePath(String artifactBasePath
,
318 Node artifactNode
) throws RepositoryException
{
319 Artifact artifact
= asArtifact(artifactNode
);
320 Artifact pdeSourceArtifact
= new DefaultArtifact(artifact
.getGroupId(),
321 artifact
.getArtifactId() + ".source", artifact
.getExtension(),
322 artifact
.getVersion());
323 return MavenConventionsUtils
.artifactPath(artifactBasePath
,
328 * Copy this bytes array as an artifact, relative to the root of the
329 * repository (typically the workspace root node)
331 public static Node
copyBytesAsArtifact(Node artifactsBase
,
332 Artifact artifact
, byte[] bytes
) throws RepositoryException
{
333 String parentPath
= MavenConventionsUtils
.artifactParentPath(
334 artifactsBase
.getPath(), artifact
);
335 Node folderNode
= JcrUtils
.mkfolders(artifactsBase
.getSession(),
337 return JcrUtils
.copyBytesAsFile(folderNode
,
338 MavenConventionsUtils
.artifactFileName(artifact
), bytes
);
341 private RepoUtils() {
344 /** If a source return the base bundle name, does not change otherwise */
345 public static String
extractBundleNameFromSourceName(String sourceBundleName
) {
346 if (sourceBundleName
.endsWith(".source"))
347 return sourceBundleName
.substring(0, sourceBundleName
.length()
348 - ".source".length());
350 return sourceBundleName
;
354 * SOFTWARE REPOSITORIES
357 /** Retrieve repository based on information in the repo node */
358 public static Repository
getRepository(RepositoryFactory repositoryFactory
,
359 Keyring keyring
, Node repoNode
) {
361 Repository repository
;
362 if (repoNode
.isNodeType(ArgeoTypes
.ARGEO_REMOTE_REPOSITORY
)) {
363 String uri
= repoNode
.getProperty(ARGEO_URI
).getString();
364 if (uri
.startsWith("http")) {// http, https
365 repository
= CmsJcrUtils
.getRepositoryByUri(
366 repositoryFactory
, uri
);
367 } else if (uri
.startsWith("vm:")) {// alias
368 repository
= CmsJcrUtils
.getRepositoryByUri(
369 repositoryFactory
, uri
);
371 throw new SlcException("Unsupported repository uri " + uri
);
375 throw new SlcException("Unsupported node type " + repoNode
);
377 } catch (RepositoryException e
) {
378 throw new SlcException("Cannot connect to repository " + repoNode
,
384 * Reads credentials from node, using keyring if there is a password. Can
385 * return null if no credentials needed (local repo) at all, but returns
386 * {@link GuestCredentials} if user id is 'anonymous' .
388 public static Credentials
getRepositoryCredentials(Keyring keyring
,
391 if (repoNode
.isNodeType(ArgeoTypes
.ARGEO_REMOTE_REPOSITORY
)) {
392 if (!repoNode
.hasProperty(ARGEO_USER_ID
))
395 String userId
= repoNode
.getProperty(ARGEO_USER_ID
).getString();
396 if (userId
.equals("anonymous"))// FIXME hardcoded userId
397 return new GuestCredentials();
398 char[] password
= keyring
.getAsChars(repoNode
.getPath() + '/'
400 Credentials credentials
= new SimpleCredentials(userId
,
404 throw new SlcException("Unsupported node type " + repoNode
);
406 } catch (RepositoryException e
) {
407 throw new SlcException("Cannot connect to repository " + repoNode
,
413 * Shortcut to retrieve a session given variable information: Handle the
414 * case where we only have an URI of the repository, that we want to connect
415 * as anonymous or the case of a identified connection to a local or remote
418 * Callers must close the session once it has been used
420 public static Session
getRemoteSession(RepositoryFactory repositoryFactory
,
421 Keyring keyring
, Node repoNode
, String uri
, String workspaceName
) {
423 if (repoNode
== null && uri
== null)
424 throw new SlcException(
425 "At least one of repoNode and uri must be defined");
426 Repository currRepo
= null;
427 Credentials credentials
= null;
428 // Anonymous URI only workspace
429 if (repoNode
== null)
431 currRepo
= CmsJcrUtils
.getRepositoryByUri(repositoryFactory
, uri
);
433 currRepo
= RepoUtils
.getRepository(repositoryFactory
, keyring
,
435 credentials
= RepoUtils
.getRepositoryCredentials(keyring
,
438 return currRepo
.login(credentials
, workspaceName
);
439 } catch (RepositoryException e
) {
440 throw new SlcException("Cannot connect to workspace "
441 + workspaceName
+ " of repository " + repoNode
442 + " with URI " + uri
, e
);
447 * Shortcut to retrieve a session on a remote Jrc Repository from
448 * information stored in a local argeo node or from an URI: Handle the case
449 * where we only have an URI of the repository, that we want to connect as
450 * anonymous or the case of a identified connection to a local or remote
453 * Callers must close the session once it has been used
455 public static Session
getRemoteSession(RepositoryFactory repositoryFactory
,
456 Keyring keyring
, Repository localRepository
, String repoNodePath
,
457 String uri
, String workspaceName
) {
458 Session localSession
= null;
459 Node repoNode
= null;
461 localSession
= localRepository
.login();
462 if (repoNodePath
!= null && localSession
.nodeExists(repoNodePath
))
463 repoNode
= localSession
.getNode(repoNodePath
);
465 return RepoUtils
.getRemoteSession(repositoryFactory
, keyring
,
466 repoNode
, uri
, workspaceName
);
467 } catch (RepositoryException e
) {
468 throw new SlcException("Cannot log to workspace " + workspaceName
469 + " for repo defined in " + repoNodePath
, e
);
471 JcrUtils
.logoutQuietly(localSession
);
476 * Write group indexes: 'binaries' lists all bundles and their versions,
477 * 'sources' list their sources, and 'sdk' aggregates both.
479 public static void writeGroupIndexes(Session session
,
480 String artifactBasePath
, String groupId
, String version
,
481 Set
<Artifact
> binaries
, Set
<Artifact
> sources
) {
483 Set
<Artifact
> indexes
= new TreeSet
<Artifact
>(
484 new ArtifactIdComparator());
485 Artifact binariesArtifact
= writeIndex(session
, artifactBasePath
,
486 groupId
, RepoConstants
.BINARIES_ARTIFACT_ID
, version
,
488 indexes
.add(binariesArtifact
);
489 if (sources
!= null) {
490 Artifact sourcesArtifact
= writeIndex(session
,
491 artifactBasePath
, groupId
,
492 RepoConstants
.SOURCES_ARTIFACT_ID
, version
, sources
);
493 indexes
.add(sourcesArtifact
);
496 writeIndex(session
, artifactBasePath
, groupId
,
497 RepoConstants
.SDK_ARTIFACT_ID
, version
, indexes
);
499 } catch (RepositoryException e
) {
500 throw new SlcException("Cannot write indexes for group " + groupId
,
505 /** Write a group index. */
506 private static Artifact
writeIndex(Session session
,
507 String artifactBasePath
, String groupId
, String artifactId
,
508 String version
, Set
<Artifact
> artifacts
) throws RepositoryException
{
509 Artifact artifact
= new DefaultArtifact(groupId
, artifactId
, "pom",
511 String pom
= MavenConventionsUtils
.artifactsAsDependencyPom(artifact
,
513 Node node
= RepoUtils
.copyBytesAsArtifact(
514 session
.getNode(artifactBasePath
), artifact
, pom
.getBytes());
515 addMavenChecksums(node
);
519 /** Add files containing the SHA-1 and MD5 checksums. */
520 public static void addMavenChecksums(Node node
) throws RepositoryException
{
522 String sha
= JcrUtils
.checksumFile(node
, "SHA-1");
523 JcrUtils
.copyBytesAsFile(node
.getParent(), node
.getName() + ".sha1",
525 String md5
= JcrUtils
.checksumFile(node
, "MD5");
526 JcrUtils
.copyBytesAsFile(node
.getParent(), node
.getName() + ".md5",
531 * Custom copy since the one in commons does not fit the needs when copying
532 * a workspace completely.
534 public static void copy(Node fromNode
, Node toNode
) {
535 copy(fromNode
, toNode
, null);
538 public static void copy(Node fromNode
, Node toNode
, JcrMonitor monitor
) {
540 String fromPath
= fromNode
.getPath();
542 monitor
.subTask("copying node :" + fromPath
);
543 if (log
.isDebugEnabled())
544 log
.debug("copy node :" + fromPath
);
546 // FIXME : small hack to enable specific workspace copy
547 if (fromNode
.isNodeType("rep:ACL")
548 || fromNode
.isNodeType("rep:system")) {
549 if (log
.isTraceEnabled())
550 log
.trace("node " + fromNode
+ " skipped");
555 for (NodeType mixinType
: fromNode
.getMixinNodeTypes()) {
556 toNode
.addMixin(mixinType
.getName());
560 for (NodeType mixinType
: toNode
.getMixinNodeTypes()) {
561 if (log
.isDebugEnabled())
562 log
.debug(mixinType
.getName());
565 // process properties
566 PropertyIterator pit
= fromNode
.getProperties();
567 properties
: while (pit
.hasNext()) {
568 Property fromProperty
= pit
.nextProperty();
569 String propName
= fromProperty
.getName();
571 String propertyName
= fromProperty
.getName();
572 if (toNode
.hasProperty(propertyName
)
573 && toNode
.getProperty(propertyName
).getDefinition()
577 if (fromProperty
.getDefinition().isProtected())
580 if (propertyName
.equals("jcr:created")
581 || propertyName
.equals("jcr:createdBy")
582 || propertyName
.equals("jcr:lastModified")
583 || propertyName
.equals("jcr:lastModifiedBy"))
586 if (fromProperty
.isMultiple()) {
587 toNode
.setProperty(propertyName
,
588 fromProperty
.getValues());
590 toNode
.setProperty(propertyName
,
591 fromProperty
.getValue());
593 } catch (RepositoryException e
) {
594 throw new SlcException("Cannot property " + propName
, e
);
598 // recursively process children nodes
599 NodeIterator nit
= fromNode
.getNodes();
600 while (nit
.hasNext()) {
601 Node fromChild
= nit
.nextNode();
602 Integer index
= fromChild
.getIndex();
603 String nodeRelPath
= fromChild
.getName() + "[" + index
+ "]";
605 if (toNode
.hasNode(nodeRelPath
))
606 toChild
= toNode
.getNode(nodeRelPath
);
608 toChild
= toNode
.addNode(fromChild
.getName(), fromChild
609 .getPrimaryNodeType().getName());
610 copy(fromChild
, toChild
);
613 // update jcr:lastModified and jcr:lastModifiedBy in toNode in
616 if (!toNode
.getDefinition().isProtected()
617 && toNode
.isNodeType(NodeType
.MIX_LAST_MODIFIED
))
618 JcrUtils
.updateLastModified(toNode
);
620 // Workaround to reduce session size: artifact is a saveable
622 if (toNode
.isNodeType(SlcTypes
.SLC_ARTIFACT
))
623 toNode
.getSession().save();
628 } catch (RepositoryException e
) {
629 throw new SlcException("Cannot copy " + fromNode
+ " to " + toNode
, e
);