]> git.argeo.org Git - gpl/argeo-slc.git/blob - org.argeo.slc.repo/src/org/argeo/slc/repo/maven/GenerateBinaries.java
Adapt to changes in BND.
[gpl/argeo-slc.git] / org.argeo.slc.repo / src / org / argeo / slc / repo / maven / GenerateBinaries.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.maven;
17
18 import java.util.ArrayList;
19 import java.util.List;
20 import java.util.Set;
21 import java.util.StringTokenizer;
22 import java.util.TreeSet;
23
24 import javax.jcr.Credentials;
25 import javax.jcr.Node;
26 import javax.jcr.NodeIterator;
27 import javax.jcr.Repository;
28 import javax.jcr.RepositoryException;
29 import javax.jcr.Session;
30
31 import org.apache.commons.logging.Log;
32 import org.apache.commons.logging.LogFactory;
33 import org.argeo.jcr.JcrMonitor;
34 import org.argeo.jcr.JcrUtils;
35 import org.argeo.slc.SlcException;
36 import org.argeo.slc.SlcNames;
37 import org.argeo.slc.SlcTypes;
38 import org.argeo.slc.repo.ArtifactIndexer;
39 import org.argeo.slc.repo.RepoConstants;
40 import org.argeo.slc.repo.RepoUtils;
41 import org.eclipse.aether.artifact.Artifact;
42 import org.eclipse.aether.artifact.DefaultArtifact;
43 import org.osgi.framework.Version;
44
45 /**
46 * Generates binaries-, sources- and sdk-version.pom artifacts for a given
47 * group.
48 */
49 public class GenerateBinaries implements Runnable, SlcNames {
50 private final static Log log = LogFactory.getLog(GenerateBinaries.class);
51
52 // Connection info
53 private Repository repository;
54 private Credentials credentials;
55 private String workspace;
56
57 // Business info
58 private String groupId;
59 private String parentPomCoordinates;
60 private String version = null;
61
62 // Constants
63 private String artifactBasePath = RepoConstants.DEFAULT_ARTIFACTS_BASE_PATH;
64 private List<String> excludedSuffixes = new ArrayList<String>();
65
66 // Indexes
67 private Set<Artifact> binaries = new TreeSet<Artifact>(
68 new ArtifactIdComparator());
69 private Set<Artifact> sources = new TreeSet<Artifact>(
70 new ArtifactIdComparator());
71
72 // local cache
73 private ArtifactIndexer artifactIndexer = new ArtifactIndexer();
74 private Node allArtifactsHighestVersion;
75
76 public void run() {
77 Session session = null;
78 try {
79 session = repository.login(credentials, workspace);
80 Node groupNode = session.getNode(MavenConventionsUtils.groupPath(
81 artifactBasePath, groupId));
82 internalPreProcessing(groupNode, null);
83 internalProcessing(groupNode, null);
84 } catch (Exception e) {
85 throw new SlcException("Cannot normalize group " + groupId + " in "
86 + workspace, e);
87 } finally {
88 JcrUtils.logoutQuietly(session);
89 }
90 }
91
92 /**
93 * Generates binaries-, sources- and sdk-version.pom artifacts for the given
94 * version (or the highest of all children version if none is precised).
95 *
96 * By default, it includes each latest version of all artifact of this
97 * group.
98 *
99 * The 3 generated artifacts are then marked as modular distributions and
100 * indexed.
101 */
102 public static void processGroupNode(Node groupNode, String version,
103 JcrMonitor monitor) throws RepositoryException {
104 // TODO set artifactsBase based on group node
105 GenerateBinaries gb = new GenerateBinaries();
106 String groupId = groupNode.getProperty(SlcNames.SLC_GROUP_BASE_ID)
107 .getString();
108 gb.setGroupId(groupId);
109 gb.setVersion(version);
110 // TODO use already done pre-processing
111 gb.internalPreProcessing(groupNode, monitor);
112 gb.internalProcessing(groupNode, monitor);
113 }
114
115 /** Only builds local indexes. Does not change anything in the local Session */
116 public static GenerateBinaries preProcessGroupNode(Node groupNode,
117 JcrMonitor monitor) throws RepositoryException {
118 // TODO set artifactsBase based on group node
119 GenerateBinaries gb = new GenerateBinaries();
120 String groupId = groupNode.getProperty(SlcNames.SLC_GROUP_BASE_ID)
121 .getString();
122 gb.setGroupId(groupId);
123 // gb.setVersion(version);
124 // gb.setOverridePoms(overridePoms);
125 gb.internalPreProcessing(groupNode, monitor);
126 return gb;
127 }
128
129 // exposes indexes. to display results of the pre-processing phase.
130 public Set<Artifact> getBinaries() {
131 return binaries;
132 }
133
134 public Artifact getHighestArtifactVersion() throws RepositoryException {
135 return allArtifactsHighestVersion == null ? null : RepoUtils
136 .asArtifact(allArtifactsHighestVersion);
137 }
138
139 // //////////////////////////////////////
140 // INTERNAL METHODS
141
142 /**
143 * Browse all children of a Node considered as a folder that follows Aether
144 * conventions i.e that has Aether's artifact base as children.
145 *
146 * Each of such child contains a set of Aether artifact versions. This
147 * methods build the binaries {@code Set<Artifact>} and other indexes. It
148 * does not impact the
149 */
150 protected void internalPreProcessing(Node groupNode, JcrMonitor monitor)
151 throws RepositoryException {
152 if (monitor != null)
153 monitor.subTask("Pre processing group " + groupId);
154
155 // Process all direct children nodes,
156 // gathering latest versions of each artifact
157 allArtifactsHighestVersion = null;
158
159 aBases: for (NodeIterator aBases = groupNode.getNodes(); aBases
160 .hasNext();) {
161 Node aBase = aBases.nextNode();
162 if (aBase.isNodeType(SlcTypes.SLC_ARTIFACT_BASE)) {
163 Node highestAVersion = getArtifactLatestVersion(aBase);
164 if (highestAVersion == null)
165 continue aBases;
166 else {
167 // retrieve relevant child node
168 // Information is stored on the NT_FILE child node.
169 for (NodeIterator files = highestAVersion.getNodes(); files
170 .hasNext();) {
171 Node file = files.nextNode();
172 if (file.isNodeType(SlcTypes.SLC_BUNDLE_ARTIFACT)) {
173 if (log.isDebugEnabled())
174 log.debug("Pre-Processing " + file.getName());
175 preProcessBundleArtifact(file);
176 }
177 }
178 }
179 }
180 }
181 // if (log.isDebugEnabled()) {
182 // int bundleCount = symbolicNamesToNodes.size();
183 // log.debug("" + bundleCount + " bundles have been indexed for "
184 // + groupId);
185 // }
186 }
187
188 /** Does the real job : writes JCR META-DATA and generates binaries */
189 protected void internalProcessing(Node groupNode, JcrMonitor monitor)
190 throws RepositoryException {
191 if (monitor != null)
192 monitor.subTask("Processing group " + groupId);
193
194 Session session = groupNode.getSession();
195
196 // if version not set or empty, use the highest version
197 // useful when indexing a product maven repository where
198 // all artifacts have the same version for a given release
199 // => the version can then be left empty
200 if (version == null || version.trim().equals(""))
201 if (allArtifactsHighestVersion != null)
202 version = allArtifactsHighestVersion.getProperty(
203 SLC_ARTIFACT_VERSION).getString();
204 else
205 throw new SlcException("Group version " + version
206 + " is empty.");
207
208 // int bundleCount = symbolicNamesToNodes.size();
209 // int count = 1;
210 // for (Node bundleNode : symbolicNamesToNodes.values()) {
211 // if (log.isDebugEnabled())
212 // log.debug("Processing " + bundleNode.getName() + " ( " + count
213 // + "/" + bundleCount + " )");
214 //
215 // // processBundleArtifact(bundleNode);
216 // // bundleNode.getSession().save();
217 // count++;
218 // }
219
220 // indexes
221 Set<Artifact> indexes = new TreeSet<Artifact>(
222 new ArtifactIdComparator());
223
224 Artifact indexArtifact;
225 indexArtifact = writeIndex(session, RepoConstants.BINARIES_ARTIFACT_ID,
226 binaries);
227 indexes.add(indexArtifact);
228
229 indexArtifact = writeIndex(session, RepoConstants.SOURCES_ARTIFACT_ID,
230 sources);
231 indexes.add(indexArtifact);
232
233 // sdk
234 writeIndex(session, RepoConstants.SDK_ARTIFACT_ID, indexes);
235
236 if (monitor != null)
237 monitor.worked(1);
238 }
239
240 protected void preProcessBundleArtifact(Node bundleNode)
241 throws RepositoryException {
242
243 String symbolicName = JcrUtils.get(bundleNode, SLC_SYMBOLIC_NAME);
244 // Sanity check.
245 if (symbolicName == null)
246 log.warn("Symbolic name is null for bundle " + bundleNode);
247
248 // Manage source bundles
249 if (symbolicName.endsWith(".source")) {
250 // TODO make a shared node with classifier 'sources'?
251 String bundleName = RepoUtils
252 .extractBundleNameFromSourceName(symbolicName);
253 for (String excludedSuffix : excludedSuffixes) {
254 if (bundleName.endsWith(excludedSuffix))
255 return;// skip adding to sources
256 }
257 sources.add(RepoUtils.asArtifact(bundleNode));
258 return;
259 }
260
261 // // Build indexes
262 // NodeIterator exportPackages = bundleNode.getNodes(SLC_
263 // + Constants.EXPORT_PACKAGE);
264 // while (exportPackages.hasNext()) {
265 // Node exportPackage = exportPackages.nextNode();
266 // String pkg = JcrUtils.get(exportPackage, SLC_NAME);
267 // packagesToSymbolicNames.put(pkg, symbolicName);
268 // }
269 //
270 // symbolicNamesToNodes.put(symbolicName, bundleNode);
271 // for (String excludedSuffix : excludedSuffixes) {
272 // if (symbolicName.endsWith(excludedSuffix))
273 // return;// skip adding to binaries
274 // }
275
276 binaries.add(RepoUtils.asArtifact(bundleNode));
277
278 // Extra check. to remove
279 if (bundleNode.getSession().hasPendingChanges())
280 throw new SlcException("Pending changes in the session, "
281 + "this should not be true here.");
282 }
283
284 // protected void processBundleArtifact(Node bundleNode)
285 // throws RepositoryException {
286 // Node artifactFolder = bundleNode.getParent();
287 // String baseName = FilenameUtils.getBaseName(bundleNode.getName());
288 //
289 // // pom
290 // String pomName = baseName + ".pom";
291 // if (artifactFolder.hasNode(pomName) && !overridePoms)
292 // return;// skip
293 //
294 // String pom = generatePomForBundle(bundleNode);
295 // Node pomNode = JcrUtils.copyBytesAsFile(artifactFolder, pomName,
296 // pom.getBytes());
297 // // checksum
298 // String bundleSha = JcrUtils.checksumFile(bundleNode, "SHA-1");
299 // JcrUtils.copyBytesAsFile(artifactFolder,
300 // bundleNode.getName() + ".sha1", bundleSha.getBytes());
301 // String pomSha = JcrUtils.checksumFile(pomNode, "SHA-1");
302 // JcrUtils.copyBytesAsFile(artifactFolder, pomNode.getName() + ".sha1",
303 // pomSha.getBytes());
304 // }
305
306 // ////////////////////
307 // LOCAL WRITERS
308 //
309
310 private Artifact writeIndex(Session session, String artifactId,
311 Set<Artifact> artifacts) throws RepositoryException {
312 Artifact artifact = new DefaultArtifact(groupId, artifactId, "pom",
313 version);
314 Artifact parentArtifact = parentPomCoordinates != null ? new DefaultArtifact(
315 parentPomCoordinates) : null;
316 String pom = MavenConventionsUtils.artifactsAsDependencyPom(artifact,
317 artifacts, parentArtifact);
318 Node node = RepoUtils.copyBytesAsArtifact(
319 session.getNode(artifactBasePath), artifact, pom.getBytes());
320 artifactIndexer.index(node);
321
322 // TODO factorize
323 String pomSha = JcrUtils.checksumFile(node, "SHA-1");
324 JcrUtils.copyBytesAsFile(node.getParent(), node.getName() + ".sha1",
325 pomSha.getBytes());
326 String pomMd5 = JcrUtils.checksumFile(node, "MD5");
327 JcrUtils.copyBytesAsFile(node.getParent(), node.getName() + ".md5",
328 pomMd5.getBytes());
329 session.save();
330 return artifact;
331 }
332
333 // Helpers
334 private Node getArtifactLatestVersion(Node artifactBase) {
335 try {
336 Node highestAVersion = null;
337 for (NodeIterator aVersions = artifactBase.getNodes(); aVersions
338 .hasNext();) {
339 Node aVersion = aVersions.nextNode();
340 if (aVersion.isNodeType(SlcTypes.SLC_ARTIFACT_VERSION_BASE)) {
341 if (highestAVersion == null) {
342 highestAVersion = aVersion;
343 if (allArtifactsHighestVersion == null)
344 allArtifactsHighestVersion = aVersion;
345 // Correctly handle following arrival order:
346 // Name1 - V1, name2 - V3
347 else {
348 Version cachedHighestVersion = extractOsgiVersion(allArtifactsHighestVersion);
349 Version currVersion = extractOsgiVersion(aVersion);
350 if (currVersion.compareTo(cachedHighestVersion) > 0)
351 allArtifactsHighestVersion = aVersion;
352 }
353 } else {
354 Version currVersion = extractOsgiVersion(aVersion);
355 Version currentHighestVersion = extractOsgiVersion(highestAVersion);
356 if (currVersion.compareTo(currentHighestVersion) > 0) {
357 highestAVersion = aVersion;
358 }
359 if (currVersion
360 .compareTo(extractOsgiVersion(allArtifactsHighestVersion)) > 0) {
361 allArtifactsHighestVersion = aVersion;
362 }
363 }
364
365 }
366 }
367 return highestAVersion;
368 } catch (RepositoryException re) {
369 throw new SlcException("Unable to get latest version for node "
370 + artifactBase, re);
371 }
372 }
373
374 private Version extractOsgiVersion(Node artifactVersion)
375 throws RepositoryException {
376 String rawVersion = artifactVersion.getProperty(SLC_ARTIFACT_VERSION)
377 .getString();
378 String cleanVersion = rawVersion.replace("-SNAPSHOT", ".SNAPSHOT");
379 Version osgiVersion = null;
380 // log invalid version value to enable tracking them
381 try {
382 osgiVersion = new Version(cleanVersion);
383 } catch (IllegalArgumentException e) {
384 log.error("Version string " + cleanVersion + " is invalid ");
385 String twickedVersion = twickInvalidVersion(cleanVersion);
386 osgiVersion = new Version(twickedVersion);
387 log.error("Using " + twickedVersion + " instead");
388 // throw e;
389 }
390 return osgiVersion;
391 }
392
393 private String twickInvalidVersion(String tmpVersion) {
394 String[] tokens = tmpVersion.split("\\.");
395 if (tokens.length == 3 && tokens[2].lastIndexOf("-") > 0) {
396 String newSuffix = tokens[2].replaceFirst("-", ".");
397 tmpVersion = tmpVersion.replaceFirst(tokens[2], newSuffix);
398 } else if (tokens.length > 4) {
399 // FIXME manually remove other "."
400 StringTokenizer st = new StringTokenizer(tmpVersion, ".", true);
401 StringBuilder builder = new StringBuilder();
402 // Major
403 builder.append(st.nextToken()).append(st.nextToken());
404 // Minor
405 builder.append(st.nextToken()).append(st.nextToken());
406 // Micro
407 builder.append(st.nextToken()).append(st.nextToken());
408 // Qualifier
409 builder.append(st.nextToken());
410 while (st.hasMoreTokens()) {
411 // consume delimiter
412 st.nextToken();
413 if (st.hasMoreTokens())
414 builder.append("-").append(st.nextToken());
415 }
416 tmpVersion = builder.toString();
417 }
418 return tmpVersion;
419 }
420
421 // private String generatePomForBundle(Node n) throws RepositoryException {
422 // String ownSymbolicName = JcrUtils.get(n, SLC_SYMBOLIC_NAME);
423 //
424 // StringBuffer p = new StringBuffer();
425 //
426 // // XML header
427 // p.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
428 // 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");
429 // p.append("<modelVersion>4.0.0</modelVersion>");
430 //
431 // // Artifact
432 // p.append("<groupId>").append(JcrUtils.get(n, SLC_GROUP_ID))
433 // .append("</groupId>\n");
434 // p.append("<artifactId>").append(JcrUtils.get(n, SLC_ARTIFACT_ID))
435 // .append("</artifactId>\n");
436 // p.append("<version>").append(JcrUtils.get(n, SLC_ARTIFACT_VERSION))
437 // .append("</version>\n");
438 // p.append("<packaging>pom</packaging>\n");
439 // if (n.hasProperty(SLC_ + Constants.BUNDLE_NAME))
440 // p.append("<name>")
441 // .append(JcrUtils.get(n, SLC_ + Constants.BUNDLE_NAME))
442 // .append("</name>\n");
443 // if (n.hasProperty(SLC_ + Constants.BUNDLE_DESCRIPTION))
444 // p.append("<description>")
445 // .append(JcrUtils
446 // .get(n, SLC_ + Constants.BUNDLE_DESCRIPTION))
447 // .append("</description>\n");
448 //
449 // // Dependencies
450 // Set<String> dependenciesSymbolicNames = new TreeSet<String>();
451 // Set<String> optionalSymbolicNames = new TreeSet<String>();
452 // NodeIterator importPackages = n.getNodes(SLC_
453 // + Constants.IMPORT_PACKAGE);
454 // while (importPackages.hasNext()) {
455 // Node importPackage = importPackages.nextNode();
456 // String pkg = JcrUtils.get(importPackage, SLC_NAME);
457 // if (packagesToSymbolicNames.containsKey(pkg)) {
458 // String dependencySymbolicName = packagesToSymbolicNames
459 // .get(pkg);
460 // if (JcrUtils.check(importPackage, SLC_OPTIONAL))
461 // optionalSymbolicNames.add(dependencySymbolicName);
462 // else
463 // dependenciesSymbolicNames.add(dependencySymbolicName);
464 // } else {
465 // if (!JcrUtils.check(importPackage, SLC_OPTIONAL)
466 // && !systemPackages.contains(pkg))
467 // log.warn("No bundle found for pkg " + pkg);
468 // }
469 // }
470 //
471 // if (n.hasNode(SLC_ + Constants.FRAGMENT_HOST)) {
472 // String fragmentHost = JcrUtils.get(
473 // n.getNode(SLC_ + Constants.FRAGMENT_HOST),
474 // SLC_SYMBOLIC_NAME);
475 // dependenciesSymbolicNames.add(fragmentHost);
476 // }
477 //
478 // // TODO require bundles
479 //
480 // List<Node> dependencyNodes = new ArrayList<Node>();
481 // for (String depSymbName : dependenciesSymbolicNames) {
482 // if (depSymbName.equals(ownSymbolicName))
483 // continue;// skip self
484 //
485 // if (symbolicNamesToNodes.containsKey(depSymbName))
486 // dependencyNodes.add(symbolicNamesToNodes.get(depSymbName));
487 // else
488 // log.warn("Could not find node for " + depSymbName);
489 // }
490 // List<Node> optionalDependencyNodes = new ArrayList<Node>();
491 // for (String depSymbName : optionalSymbolicNames) {
492 // if (symbolicNamesToNodes.containsKey(depSymbName))
493 // optionalDependencyNodes.add(symbolicNamesToNodes
494 // .get(depSymbName));
495 // else
496 // log.warn("Could not find node for " + depSymbName);
497 // }
498 //
499 // p.append("<dependencies>\n");
500 // for (Node dependencyNode : dependencyNodes) {
501 // p.append("<dependency>\n");
502 // p.append("\t<groupId>")
503 // .append(JcrUtils.get(dependencyNode, SLC_GROUP_ID))
504 // .append("</groupId>\n");
505 // p.append("\t<artifactId>")
506 // .append(JcrUtils.get(dependencyNode, SLC_ARTIFACT_ID))
507 // .append("</artifactId>\n");
508 // p.append("</dependency>\n");
509 // }
510 //
511 // if (optionalDependencyNodes.size() > 0)
512 // p.append("<!-- OPTIONAL -->\n");
513 // for (Node dependencyNode : optionalDependencyNodes) {
514 // p.append("<dependency>\n");
515 // p.append("\t<groupId>")
516 // .append(JcrUtils.get(dependencyNode, SLC_GROUP_ID))
517 // .append("</groupId>\n");
518 // p.append("\t<artifactId>")
519 // .append(JcrUtils.get(dependencyNode, SLC_ARTIFACT_ID))
520 // .append("</artifactId>\n");
521 // p.append("\t<optional>true</optional>\n");
522 // p.append("</dependency>\n");
523 // }
524 // p.append("</dependencies>\n");
525 //
526 // // Dependency management
527 // p.append("<dependencyManagement>\n");
528 // p.append("<dependencies>\n");
529 // p.append("<dependency>\n");
530 // p.append("\t<groupId>").append(groupId).append("</groupId>\n");
531 // p.append("\t<artifactId>")
532 // .append(ownSymbolicName.endsWith(".source") ?
533 // RepoConstants.SOURCES_ARTIFACT_ID
534 // : RepoConstants.BINARIES_ARTIFACT_ID)
535 // .append("</artifactId>\n");
536 // p.append("\t<version>").append(version).append("</version>\n");
537 // p.append("\t<type>pom</type>\n");
538 // p.append("\t<scope>import</scope>\n");
539 // p.append("</dependency>\n");
540 // p.append("</dependencies>\n");
541 // p.append("</dependencyManagement>\n");
542 //
543 // p.append("</project>\n");
544 // return p.toString();
545 // }
546
547 /* SETTERS */
548 public void setRepository(Repository repository) {
549 this.repository = repository;
550 }
551
552 public void setCredentials(Credentials credentials) {
553 this.credentials = credentials;
554 }
555
556 public void setWorkspace(String workspace) {
557 this.workspace = workspace;
558 }
559
560 public void setGroupId(String groupId) {
561 this.groupId = groupId;
562 }
563
564 public void setParentPomCoordinates(String parentPomCoordinates) {
565 this.parentPomCoordinates = parentPomCoordinates;
566 }
567
568 public void setArtifactBasePath(String artifactBasePath) {
569 this.artifactBasePath = artifactBasePath;
570 }
571
572 public void setVersion(String version) {
573 this.version = version;
574 }
575
576 public void setExcludedSuffixes(List<String> excludedSuffixes) {
577 this.excludedSuffixes = excludedSuffixes;
578 }
579
580 public void setArtifactIndexer(ArtifactIndexer artifactIndexer) {
581 this.artifactIndexer = artifactIndexer;
582 }
583 }