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
.maven
;
18 import java
.util
.ArrayList
;
19 import java
.util
.HashMap
;
20 import java
.util
.List
;
23 import java
.util
.StringTokenizer
;
24 import java
.util
.TreeSet
;
26 import javax
.jcr
.Credentials
;
27 import javax
.jcr
.Node
;
28 import javax
.jcr
.NodeIterator
;
29 import javax
.jcr
.Repository
;
30 import javax
.jcr
.RepositoryException
;
31 import javax
.jcr
.Session
;
33 import org
.apache
.commons
.io
.FilenameUtils
;
34 import org
.apache
.commons
.logging
.Log
;
35 import org
.apache
.commons
.logging
.LogFactory
;
36 import org
.argeo
.ArgeoMonitor
;
37 import org
.argeo
.jcr
.JcrUtils
;
38 import org
.argeo
.slc
.SlcException
;
39 import org
.argeo
.slc
.aether
.ArtifactIdComparator
;
40 import org
.argeo
.slc
.jcr
.SlcNames
;
41 import org
.argeo
.slc
.jcr
.SlcTypes
;
42 import org
.argeo
.slc
.repo
.ArtifactIndexer
;
43 import org
.argeo
.slc
.repo
.RepoConstants
;
44 import org
.argeo
.slc
.repo
.RepoUtils
;
45 import org
.argeo
.slc
.repo
.osgi
.OsgiProfile
;
46 import org
.osgi
.framework
.Constants
;
47 import org
.osgi
.framework
.Version
;
48 import org
.sonatype
.aether
.artifact
.Artifact
;
49 import org
.sonatype
.aether
.util
.artifact
.DefaultArtifact
;
52 * Make sure that all JCR metadata and Maven metadata are consistent for this
53 * group of OSGi bundles.
55 public class GenerateBinaries
implements Runnable
, SlcNames
{
56 private final static Log log
= LogFactory
.getLog(GenerateBinaries
.class);
59 private Repository repository
;
60 private Credentials credentials
;
61 private String workspace
;
64 private String groupId
;
65 private String parentPomCoordinates
;
66 private Boolean overridePoms
= false;
67 private String version
= null;
70 private String artifactBasePath
= RepoConstants
.DEFAULT_ARTIFACTS_BASE_PATH
;
71 private List
<String
> excludedSuffixes
= new ArrayList
<String
>();
72 /** TODO make it more generic */
73 private List
<String
> systemPackages
= OsgiProfile
.PROFILE_JAVA_SE_1_6
76 private ArtifactIndexer artifactIndexer
= new ArtifactIndexer();
79 private Map
<String
, String
> packagesToSymbolicNames
= new HashMap
<String
, String
>();
80 private Map
<String
, Node
> symbolicNamesToNodes
= new HashMap
<String
, Node
>();
81 private Set
<Artifact
> binaries
= new TreeSet
<Artifact
>(
82 new ArtifactIdComparator());
83 private Set
<Artifact
> sources
= new TreeSet
<Artifact
>(
84 new ArtifactIdComparator());
85 private Node allArtifactsHighestVersion
;
88 Session session
= null;
90 session
= repository
.login(workspace
);
91 Node groupNode
= session
.getNode(MavenConventionsUtils
.groupPath(
92 artifactBasePath
, groupId
));
93 internalPreProcessing(groupNode
, null);
94 processGroupNode(groupNode
, null);
95 } catch (Exception e
) {
96 throw new SlcException("Cannot normalize group " + groupId
+ " in "
99 JcrUtils
.logoutQuietly(session
);
103 public static void processGroupNode(Node groupNode
, String version
,
104 Boolean overridePoms
, ArgeoMonitor monitor
)
105 throws RepositoryException
{
106 // TODO set artifactsBase based on group node
107 GenerateBinaries gb
= new GenerateBinaries();
108 String groupId
= groupNode
.getProperty(SlcNames
.SLC_GROUP_BASE_ID
)
110 gb
.setGroupId(groupId
);
111 gb
.setVersion(version
);
112 gb
.setOverridePoms(overridePoms
);
113 // TODO use already done pre-processing
114 gb
.internalPreProcessing(groupNode
, monitor
);
115 gb
.processGroupNode(groupNode
, monitor
);
118 /** Only builds local indexes. Does not change anything in the local Session */
119 public static GenerateBinaries
preProcessGroupNode(Node groupNode
,
120 ArgeoMonitor monitor
) throws RepositoryException
{
121 // TODO set artifactsBase based on group node
122 GenerateBinaries gb
= new GenerateBinaries();
123 String groupId
= groupNode
.getProperty(SlcNames
.SLC_GROUP_BASE_ID
)
125 gb
.setGroupId(groupId
);
126 // gb.setVersion(version);
127 // gb.setOverridePoms(overridePoms);
128 gb
.internalPreProcessing(groupNode
, monitor
);
132 // exposes indexes. to display results of the pre-processing phase.
133 public Set
<Artifact
> getBinaries() {
137 public Artifact
getHighestArtifactVersion() throws RepositoryException
{
138 return allArtifactsHighestVersion
== null ?
null : RepoUtils
139 .asArtifact(allArtifactsHighestVersion
);
142 protected void internalPreProcessing(Node groupNode
, ArgeoMonitor monitor
)
143 throws RepositoryException
{
145 monitor
.subTask("Pre processing group " + groupId
);
147 // Process all direct children nodes,
148 // gathering latest versions of all artifact base
149 allArtifactsHighestVersion
= null;
150 // Session session = groupNode.getSession();
151 aBases
: for (NodeIterator aBases
= groupNode
.getNodes(); aBases
153 Node aBase
= aBases
.nextNode();
154 if (aBase
.isNodeType(SlcTypes
.SLC_ARTIFACT_BASE
)) {
155 Node highestAVersion
= getArtifactLatestVersion(aBase
);
156 if (highestAVersion
== null)
159 // retrieve relevant child node
160 for (NodeIterator files
= highestAVersion
.getNodes(); files
162 Node file
= files
.nextNode();
163 if (file
.isNodeType(SlcTypes
.SLC_BUNDLE_ARTIFACT
)) {
164 if (log
.isDebugEnabled())
165 log
.debug("Pre-Processing " + file
.getName());
166 preProcessBundleArtifact(file
);
172 if (log
.isDebugEnabled()) {
173 int bundleCount
= symbolicNamesToNodes
.size();
174 log
.debug("" + bundleCount
+ " bundles have been indexed for "
179 /** Does the real job : writes JCR META-DATA and generates binaries */
180 protected void processGroupNode(Node groupNode
, ArgeoMonitor monitor
)
181 throws RepositoryException
{
183 monitor
.subTask("Processing group " + groupId
);
185 Session session
= groupNode
.getSession();
187 // if version not set or empty, use the highest version
188 // useful when indexing a product maven repository where
189 // all artifacts have the same version for a given release
190 // => the version can then be left empty
191 if (version
== null || version
.trim().equals(""))
192 if (allArtifactsHighestVersion
!= null)
193 version
= allArtifactsHighestVersion
.getProperty(
194 SLC_ARTIFACT_VERSION
).getString();
196 throw new SlcException("Group version " + version
199 int bundleCount
= symbolicNamesToNodes
.size();
202 for (Node bundleNode
: symbolicNamesToNodes
.values()) {
203 if (log
.isDebugEnabled())
204 log
.debug("Processing " + bundleNode
.getName() + " ( " + count
205 + "/" + bundleCount
+ " )");
207 processBundleArtifact(bundleNode
);
208 bundleNode
.getSession().save();
213 Set
<Artifact
> indexes
= new TreeSet
<Artifact
>(
214 new ArtifactIdComparator());
215 Artifact indexArtifact
= writeIndex(session
,
216 RepoConstants
.BINARIES_ARTIFACT_ID
, binaries
);
217 indexes
.add(indexArtifact
);
218 indexArtifact
= writeIndex(session
, RepoConstants
.SOURCES_ARTIFACT_ID
,
220 indexes
.add(indexArtifact
);
222 writeIndex(session
, RepoConstants
.SDK_ARTIFACT_ID
, indexes
);
228 private Node
getArtifactLatestVersion(Node artifactBase
) {
230 Node highestAVersion
= null;
231 for (NodeIterator aVersions
= artifactBase
.getNodes(); aVersions
233 Node aVersion
= aVersions
.nextNode();
234 if (aVersion
.isNodeType(SlcTypes
.SLC_ARTIFACT_VERSION_BASE
)) {
235 if (highestAVersion
== null) {
236 highestAVersion
= aVersion
;
237 if (allArtifactsHighestVersion
== null)
238 allArtifactsHighestVersion
= aVersion
;
239 // Correctly handle following arrival order:
240 // Name1 - V1, name2 - V3
242 Version cachedHighestVersion
= extractOsgiVersion(allArtifactsHighestVersion
);
243 Version currVersion
= extractOsgiVersion(aVersion
);
244 if (currVersion
.compareTo(cachedHighestVersion
) > 0)
245 allArtifactsHighestVersion
= aVersion
;
248 Version currVersion
= extractOsgiVersion(aVersion
);
249 Version currentHighestVersion
= extractOsgiVersion(highestAVersion
);
250 if (currVersion
.compareTo(currentHighestVersion
) > 0) {
251 highestAVersion
= aVersion
;
254 .compareTo(extractOsgiVersion(allArtifactsHighestVersion
)) > 0) {
255 allArtifactsHighestVersion
= aVersion
;
261 return highestAVersion
;
262 } catch (RepositoryException re
) {
263 throw new SlcException("Unable to get latest version for node "
268 private Version
extractOsgiVersion(Node artifactVersion
)
269 throws RepositoryException
{
270 String rawVersion
= artifactVersion
.getProperty(SLC_ARTIFACT_VERSION
)
272 String cleanVersion
= rawVersion
.replace("-SNAPSHOT", ".SNAPSHOT");
273 Version osgiVersion
= null;
274 // log invalid version value to enable tracking them
276 osgiVersion
= new Version(cleanVersion
);
277 } catch (IllegalArgumentException e
) {
278 log
.error("Version string " + cleanVersion
+ " is invalid ");
279 String twickedVersion
= twickInvalidVersion(cleanVersion
);
280 osgiVersion
= new Version(twickedVersion
);
281 log
.error("Using " + twickedVersion
+ " instead");
287 private String
twickInvalidVersion(String tmpVersion
) {
288 String
[] tokens
= tmpVersion
.split("\\.");
289 if (tokens
.length
== 3 && tokens
[2].lastIndexOf("-") > 0) {
290 String newSuffix
= tokens
[2].replaceFirst("-", ".");
291 tmpVersion
= tmpVersion
.replaceFirst(tokens
[2], newSuffix
);
292 } else if (tokens
.length
> 4) {
293 // FIXME manually remove other "."
294 StringTokenizer st
= new StringTokenizer(tmpVersion
, ".", true);
295 StringBuilder builder
= new StringBuilder();
297 builder
.append(st
.nextToken()).append(st
.nextToken());
299 builder
.append(st
.nextToken()).append(st
.nextToken());
301 builder
.append(st
.nextToken()).append(st
.nextToken());
303 builder
.append(st
.nextToken());
304 while (st
.hasMoreTokens()) {
307 if (st
.hasMoreTokens())
308 builder
.append("-").append(st
.nextToken());
310 tmpVersion
= builder
.toString();
315 protected void preProcessBundleArtifact(Node bundleNode
)
316 throws RepositoryException
{
318 String symbolicName
= JcrUtils
.get(bundleNode
, SLC_SYMBOLIC_NAME
);
319 if (symbolicName
.endsWith(".source")) {
320 // TODO make a shared node with classifier 'sources'?
321 String bundleName
= RepoUtils
322 .extractBundleNameFromSourceName(symbolicName
);
323 for (String excludedSuffix
: excludedSuffixes
) {
324 if (bundleName
.endsWith(excludedSuffix
))
325 return;// skip adding to sources
327 sources
.add(RepoUtils
.asArtifact(bundleNode
));
331 NodeIterator exportPackages
= bundleNode
.getNodes(SLC_
332 + Constants
.EXPORT_PACKAGE
);
333 while (exportPackages
.hasNext()) {
334 Node exportPackage
= exportPackages
.nextNode();
335 String pkg
= JcrUtils
.get(exportPackage
, SLC_NAME
);
336 packagesToSymbolicNames
.put(pkg
, symbolicName
);
339 symbolicNamesToNodes
.put(symbolicName
, bundleNode
);
340 for (String excludedSuffix
: excludedSuffixes
) {
341 if (symbolicName
.endsWith(excludedSuffix
))
342 return;// skip adding to binaries
344 binaries
.add(RepoUtils
.asArtifact(bundleNode
));
346 if (bundleNode
.getSession().hasPendingChanges())
347 throw new SlcException("Pending changes in the session, "
348 + "this should not be true here.");
349 // bundleNode.getSession().save();
352 protected void processBundleArtifact(Node bundleNode
)
353 throws RepositoryException
{
354 Node artifactFolder
= bundleNode
.getParent();
355 String baseName
= FilenameUtils
.getBaseName(bundleNode
.getName());
358 String pomName
= baseName
+ ".pom";
359 if (artifactFolder
.hasNode(pomName
) && !overridePoms
)
362 String pom
= generatePomForBundle(bundleNode
);
363 Node pomNode
= JcrUtils
.copyBytesAsFile(artifactFolder
, pomName
,
366 String bundleSha
= JcrUtils
.checksumFile(bundleNode
, "SHA-1");
367 JcrUtils
.copyBytesAsFile(artifactFolder
,
368 bundleNode
.getName() + ".sha1", bundleSha
.getBytes());
369 String pomSha
= JcrUtils
.checksumFile(pomNode
, "SHA-1");
370 JcrUtils
.copyBytesAsFile(artifactFolder
, pomNode
.getName() + ".sha1",
375 private Artifact
writeIndex(Session session
, String artifactId
,
376 Set
<Artifact
> artifacts
) throws RepositoryException
{
377 Artifact artifact
= new DefaultArtifact(groupId
, artifactId
, "pom",
379 Artifact parentArtifact
= parentPomCoordinates
!= null ?
new DefaultArtifact(
380 parentPomCoordinates
) : null;
381 String pom
= MavenConventionsUtils
.artifactsAsDependencyPom(artifact
,
382 artifacts
, parentArtifact
);
383 Node node
= RepoUtils
.copyBytesAsArtifact(
384 session
.getNode(artifactBasePath
), artifact
, pom
.getBytes());
385 artifactIndexer
.index(node
);
388 String pomSha
= JcrUtils
.checksumFile(node
, "SHA-1");
389 JcrUtils
.copyBytesAsFile(node
.getParent(), node
.getName() + ".sha1",
391 String pomMd5
= JcrUtils
.checksumFile(node
, "MD5");
392 JcrUtils
.copyBytesAsFile(node
.getParent(), node
.getName() + ".md5",
398 private String
generatePomForBundle(Node n
) throws RepositoryException
{
399 String ownSymbolicName
= JcrUtils
.get(n
, SLC_SYMBOLIC_NAME
);
401 StringBuffer p
= new StringBuffer();
404 p
.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
405 p
.append("<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\n");
406 p
.append("<modelVersion>4.0.0</modelVersion>");
409 p
.append("<groupId>").append(JcrUtils
.get(n
, SLC_GROUP_ID
))
410 .append("</groupId>\n");
411 p
.append("<artifactId>").append(JcrUtils
.get(n
, SLC_ARTIFACT_ID
))
412 .append("</artifactId>\n");
413 p
.append("<version>").append(JcrUtils
.get(n
, SLC_ARTIFACT_VERSION
))
414 .append("</version>\n");
415 p
.append("<packaging>pom</packaging>\n");
416 if (n
.hasProperty(SLC_
+ Constants
.BUNDLE_NAME
))
418 .append(JcrUtils
.get(n
, SLC_
+ Constants
.BUNDLE_NAME
))
419 .append("</name>\n");
420 if (n
.hasProperty(SLC_
+ Constants
.BUNDLE_DESCRIPTION
))
421 p
.append("<description>")
423 .get(n
, SLC_
+ Constants
.BUNDLE_DESCRIPTION
))
424 .append("</description>\n");
427 Set
<String
> dependenciesSymbolicNames
= new TreeSet
<String
>();
428 Set
<String
> optionalSymbolicNames
= new TreeSet
<String
>();
429 NodeIterator importPackages
= n
.getNodes(SLC_
430 + Constants
.IMPORT_PACKAGE
);
431 while (importPackages
.hasNext()) {
432 Node importPackage
= importPackages
.nextNode();
433 String pkg
= JcrUtils
.get(importPackage
, SLC_NAME
);
434 if (packagesToSymbolicNames
.containsKey(pkg
)) {
435 String dependencySymbolicName
= packagesToSymbolicNames
437 if (JcrUtils
.check(importPackage
, SLC_OPTIONAL
))
438 optionalSymbolicNames
.add(dependencySymbolicName
);
440 dependenciesSymbolicNames
.add(dependencySymbolicName
);
442 if (!JcrUtils
.check(importPackage
, SLC_OPTIONAL
)
443 && !systemPackages
.contains(pkg
))
444 log
.warn("No bundle found for pkg " + pkg
);
448 if (n
.hasNode(SLC_
+ Constants
.FRAGMENT_HOST
)) {
449 String fragmentHost
= JcrUtils
.get(
450 n
.getNode(SLC_
+ Constants
.FRAGMENT_HOST
),
452 dependenciesSymbolicNames
.add(fragmentHost
);
455 // TODO require bundles
457 List
<Node
> dependencyNodes
= new ArrayList
<Node
>();
458 for (String depSymbName
: dependenciesSymbolicNames
) {
459 if (depSymbName
.equals(ownSymbolicName
))
460 continue;// skip self
462 if (symbolicNamesToNodes
.containsKey(depSymbName
))
463 dependencyNodes
.add(symbolicNamesToNodes
.get(depSymbName
));
465 log
.warn("Could not find node for " + depSymbName
);
467 List
<Node
> optionalDependencyNodes
= new ArrayList
<Node
>();
468 for (String depSymbName
: optionalSymbolicNames
) {
469 if (symbolicNamesToNodes
.containsKey(depSymbName
))
470 optionalDependencyNodes
.add(symbolicNamesToNodes
473 log
.warn("Could not find node for " + depSymbName
);
476 p
.append("<dependencies>\n");
477 for (Node dependencyNode
: dependencyNodes
) {
478 p
.append("<dependency>\n");
479 p
.append("\t<groupId>")
480 .append(JcrUtils
.get(dependencyNode
, SLC_GROUP_ID
))
481 .append("</groupId>\n");
482 p
.append("\t<artifactId>")
483 .append(JcrUtils
.get(dependencyNode
, SLC_ARTIFACT_ID
))
484 .append("</artifactId>\n");
485 p
.append("</dependency>\n");
488 if (optionalDependencyNodes
.size() > 0)
489 p
.append("<!-- OPTIONAL -->\n");
490 for (Node dependencyNode
: optionalDependencyNodes
) {
491 p
.append("<dependency>\n");
492 p
.append("\t<groupId>")
493 .append(JcrUtils
.get(dependencyNode
, SLC_GROUP_ID
))
494 .append("</groupId>\n");
495 p
.append("\t<artifactId>")
496 .append(JcrUtils
.get(dependencyNode
, SLC_ARTIFACT_ID
))
497 .append("</artifactId>\n");
498 p
.append("\t<optional>true</optional>\n");
499 p
.append("</dependency>\n");
501 p
.append("</dependencies>\n");
503 // Dependency management
504 p
.append("<dependencyManagement>\n");
505 p
.append("<dependencies>\n");
506 p
.append("<dependency>\n");
507 p
.append("\t<groupId>").append(groupId
).append("</groupId>\n");
508 p
.append("\t<artifactId>")
509 .append(ownSymbolicName
.endsWith(".source") ? RepoConstants
.SOURCES_ARTIFACT_ID
510 : RepoConstants
.BINARIES_ARTIFACT_ID
)
511 .append("</artifactId>\n");
512 p
.append("\t<version>").append(version
).append("</version>\n");
513 p
.append("\t<type>pom</type>\n");
514 p
.append("\t<scope>import</scope>\n");
515 p
.append("</dependency>\n");
516 p
.append("</dependencies>\n");
517 p
.append("</dependencyManagement>\n");
519 p
.append("</project>\n");
524 public void setRepository(Repository repository
) {
525 this.repository
= repository
;
528 public void setWorkspace(String workspace
) {
529 this.workspace
= workspace
;
532 public void setGroupId(String groupId
) {
533 this.groupId
= groupId
;
536 public void setParentPomCoordinates(String parentPomCoordinates
) {
537 this.parentPomCoordinates
= parentPomCoordinates
;
540 public void setArtifactBasePath(String artifactBasePath
) {
541 this.artifactBasePath
= artifactBasePath
;
544 public void setVersion(String version
) {
545 this.version
= version
;
548 public void setExcludedSuffixes(List
<String
> excludedSuffixes
) {
549 this.excludedSuffixes
= excludedSuffixes
;
552 public void setOverridePoms(Boolean overridePoms
) {
553 this.overridePoms
= overridePoms
;
556 public void setArtifactIndexer(ArtifactIndexer artifactIndexer
) {
557 this.artifactIndexer
= artifactIndexer
;