]> git.argeo.org Git - gpl/argeo-slc.git/blob - runtime/org.argeo.slc.repo/src/main/java/org/argeo/slc/repo/maven/GenerateBinaries.java
git-svn-id: https://svn.argeo.org/slc/trunk@6893 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc
[gpl/argeo-slc.git] / runtime / org.argeo.slc.repo / src / main / java / 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.HashMap;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Set;
23 import java.util.StringTokenizer;
24 import java.util.TreeSet;
25
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;
32
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;
50
51 /**
52 * Make sure that all JCR metadata and Maven metadata are consistent for this
53 * group of OSGi bundles.
54 */
55 public class GenerateBinaries implements Runnable, SlcNames {
56 private final static Log log = LogFactory.getLog(GenerateBinaries.class);
57
58 // Connection info
59 private Repository repository;
60 private Credentials credentials;
61 private String workspace;
62
63 // Business info
64 private String groupId;
65 private String parentPomCoordinates;
66 private Boolean overridePoms = false;
67 private String version = null;
68
69 // Constants
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
74 .getSystemPackages();
75
76 private ArtifactIndexer artifactIndexer = new ArtifactIndexer();
77
78 // Local indexes
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;
86
87 public void run() {
88 Session session = null;
89 try {
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 "
97 + workspace, e);
98 } finally {
99 JcrUtils.logoutQuietly(session);
100 }
101 }
102
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)
109 .getString();
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);
116 }
117
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)
124 .getString();
125 gb.setGroupId(groupId);
126 // gb.setVersion(version);
127 // gb.setOverridePoms(overridePoms);
128 gb.internalPreProcessing(groupNode, monitor);
129 return gb;
130 }
131
132 // exposes indexes. to display results of the pre-processing phase.
133 public Set<Artifact> getBinaries() {
134 return binaries;
135 }
136
137 public Artifact getHighestArtifactVersion() throws RepositoryException {
138 return allArtifactsHighestVersion == null ? null : RepoUtils
139 .asArtifact(allArtifactsHighestVersion);
140 }
141
142 protected void internalPreProcessing(Node groupNode, ArgeoMonitor monitor)
143 throws RepositoryException {
144 if (monitor != null)
145 monitor.subTask("Pre processing group " + groupId);
146
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
152 .hasNext();) {
153 Node aBase = aBases.nextNode();
154 if (aBase.isNodeType(SlcTypes.SLC_ARTIFACT_BASE)) {
155 Node highestAVersion = getArtifactLatestVersion(aBase);
156 if (highestAVersion == null)
157 continue aBases;
158 else {
159 // retrieve relevant child node
160 for (NodeIterator files = highestAVersion.getNodes(); files
161 .hasNext();) {
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);
167 }
168 }
169 }
170 }
171 }
172 if (log.isDebugEnabled()) {
173 int bundleCount = symbolicNamesToNodes.size();
174 log.debug("" + bundleCount + " bundles have been indexed for "
175 + groupId);
176 }
177 }
178
179 /** Does the real job : writes JCR META-DATA and generates binaries */
180 protected void processGroupNode(Node groupNode, ArgeoMonitor monitor)
181 throws RepositoryException {
182 if (monitor != null)
183 monitor.subTask("Processing group " + groupId);
184
185 Session session = groupNode.getSession();
186
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();
195 else
196 throw new SlcException("Group version " + version
197 + " is empty.");
198
199 int bundleCount = symbolicNamesToNodes.size();
200
201 int count = 1;
202 for (Node bundleNode : symbolicNamesToNodes.values()) {
203 if (log.isDebugEnabled())
204 log.debug("Processing " + bundleNode.getName() + " ( " + count
205 + "/" + bundleCount + " )");
206
207 processBundleArtifact(bundleNode);
208 bundleNode.getSession().save();
209 count++;
210 }
211
212 // indexes
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,
219 sources);
220 indexes.add(indexArtifact);
221 // sdk
222 writeIndex(session, RepoConstants.SDK_ARTIFACT_ID, indexes);
223 if (monitor != null)
224 monitor.worked(1);
225 }
226
227 // Helpers
228 private Node getArtifactLatestVersion(Node artifactBase) {
229 try {
230 Node highestAVersion = null;
231 for (NodeIterator aVersions = artifactBase.getNodes(); aVersions
232 .hasNext();) {
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
241 else {
242 Version cachedHighestVersion = extractOsgiVersion(allArtifactsHighestVersion);
243 Version currVersion = extractOsgiVersion(aVersion);
244 if (currVersion.compareTo(cachedHighestVersion) > 0)
245 allArtifactsHighestVersion = aVersion;
246 }
247 } else {
248 Version currVersion = extractOsgiVersion(aVersion);
249 Version currentHighestVersion = extractOsgiVersion(highestAVersion);
250 if (currVersion.compareTo(currentHighestVersion) > 0) {
251 highestAVersion = aVersion;
252 }
253 if (currVersion
254 .compareTo(extractOsgiVersion(allArtifactsHighestVersion)) > 0) {
255 allArtifactsHighestVersion = aVersion;
256 }
257 }
258
259 }
260 }
261 return highestAVersion;
262 } catch (RepositoryException re) {
263 throw new SlcException("Unable to get latest version for node "
264 + artifactBase, re);
265 }
266 }
267
268 private Version extractOsgiVersion(Node artifactVersion)
269 throws RepositoryException {
270 String rawVersion = artifactVersion.getProperty(SLC_ARTIFACT_VERSION)
271 .getString();
272 String cleanVersion = rawVersion.replace("-SNAPSHOT", ".SNAPSHOT");
273 Version osgiVersion = null;
274 // log invalid version value to enable tracking them
275 try {
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");
282 // throw e;
283 }
284 return osgiVersion;
285 }
286
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();
296 // Major
297 builder.append(st.nextToken()).append(st.nextToken());
298 // Minor
299 builder.append(st.nextToken()).append(st.nextToken());
300 // Micro
301 builder.append(st.nextToken()).append(st.nextToken());
302 // Qualifier
303 builder.append(st.nextToken());
304 while (st.hasMoreTokens()) {
305 // consume delimiter
306 st.nextToken();
307 if (st.hasMoreTokens())
308 builder.append("-").append(st.nextToken());
309 }
310 tmpVersion = builder.toString();
311 }
312 return tmpVersion;
313 }
314
315 protected void preProcessBundleArtifact(Node bundleNode)
316 throws RepositoryException {
317
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
326 }
327 sources.add(RepoUtils.asArtifact(bundleNode));
328 return;
329 }
330
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);
337 }
338
339 symbolicNamesToNodes.put(symbolicName, bundleNode);
340 for (String excludedSuffix : excludedSuffixes) {
341 if (symbolicName.endsWith(excludedSuffix))
342 return;// skip adding to binaries
343 }
344 binaries.add(RepoUtils.asArtifact(bundleNode));
345
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();
350 }
351
352 protected void processBundleArtifact(Node bundleNode)
353 throws RepositoryException {
354 Node artifactFolder = bundleNode.getParent();
355 String baseName = FilenameUtils.getBaseName(bundleNode.getName());
356
357 // pom
358 String pomName = baseName + ".pom";
359 if (artifactFolder.hasNode(pomName) && !overridePoms)
360 return;// skip
361
362 String pom = generatePomForBundle(bundleNode);
363 Node pomNode = JcrUtils.copyBytesAsFile(artifactFolder, pomName,
364 pom.getBytes());
365 // checksum
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",
371 pomSha.getBytes());
372 }
373
374 // Writers
375 private Artifact writeIndex(Session session, String artifactId,
376 Set<Artifact> artifacts) throws RepositoryException {
377 Artifact artifact = new DefaultArtifact(groupId, artifactId, "pom",
378 version);
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);
386
387 // TODO factorize
388 String pomSha = JcrUtils.checksumFile(node, "SHA-1");
389 JcrUtils.copyBytesAsFile(node.getParent(), node.getName() + ".sha1",
390 pomSha.getBytes());
391 String pomMd5 = JcrUtils.checksumFile(node, "MD5");
392 JcrUtils.copyBytesAsFile(node.getParent(), node.getName() + ".md5",
393 pomMd5.getBytes());
394 session.save();
395 return artifact;
396 }
397
398 private String generatePomForBundle(Node n) throws RepositoryException {
399 String ownSymbolicName = JcrUtils.get(n, SLC_SYMBOLIC_NAME);
400
401 StringBuffer p = new StringBuffer();
402
403 // XML header
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>");
407
408 // Artifact
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))
417 p.append("<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>")
422 .append(JcrUtils
423 .get(n, SLC_ + Constants.BUNDLE_DESCRIPTION))
424 .append("</description>\n");
425
426 // Dependencies
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
436 .get(pkg);
437 if (JcrUtils.check(importPackage, SLC_OPTIONAL))
438 optionalSymbolicNames.add(dependencySymbolicName);
439 else
440 dependenciesSymbolicNames.add(dependencySymbolicName);
441 } else {
442 if (!JcrUtils.check(importPackage, SLC_OPTIONAL)
443 && !systemPackages.contains(pkg))
444 log.warn("No bundle found for pkg " + pkg);
445 }
446 }
447
448 if (n.hasNode(SLC_ + Constants.FRAGMENT_HOST)) {
449 String fragmentHost = JcrUtils.get(
450 n.getNode(SLC_ + Constants.FRAGMENT_HOST),
451 SLC_SYMBOLIC_NAME);
452 dependenciesSymbolicNames.add(fragmentHost);
453 }
454
455 // TODO require bundles
456
457 List<Node> dependencyNodes = new ArrayList<Node>();
458 for (String depSymbName : dependenciesSymbolicNames) {
459 if (depSymbName.equals(ownSymbolicName))
460 continue;// skip self
461
462 if (symbolicNamesToNodes.containsKey(depSymbName))
463 dependencyNodes.add(symbolicNamesToNodes.get(depSymbName));
464 else
465 log.warn("Could not find node for " + depSymbName);
466 }
467 List<Node> optionalDependencyNodes = new ArrayList<Node>();
468 for (String depSymbName : optionalSymbolicNames) {
469 if (symbolicNamesToNodes.containsKey(depSymbName))
470 optionalDependencyNodes.add(symbolicNamesToNodes
471 .get(depSymbName));
472 else
473 log.warn("Could not find node for " + depSymbName);
474 }
475
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");
486 }
487
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");
500 }
501 p.append("</dependencies>\n");
502
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");
518
519 p.append("</project>\n");
520 return p.toString();
521 }
522
523 /* SETTERS */
524 public void setRepository(Repository repository) {
525 this.repository = repository;
526 }
527
528 public void setWorkspace(String workspace) {
529 this.workspace = workspace;
530 }
531
532 public void setGroupId(String groupId) {
533 this.groupId = groupId;
534 }
535
536 public void setParentPomCoordinates(String parentPomCoordinates) {
537 this.parentPomCoordinates = parentPomCoordinates;
538 }
539
540 public void setArtifactBasePath(String artifactBasePath) {
541 this.artifactBasePath = artifactBasePath;
542 }
543
544 public void setVersion(String version) {
545 this.version = version;
546 }
547
548 public void setExcludedSuffixes(List<String> excludedSuffixes) {
549 this.excludedSuffixes = excludedSuffixes;
550 }
551
552 public void setOverridePoms(Boolean overridePoms) {
553 this.overridePoms = overridePoms;
554 }
555
556 public void setArtifactIndexer(ArtifactIndexer artifactIndexer) {
557 this.artifactIndexer = artifactIndexer;
558 }
559 }