]> git.argeo.org Git - gpl/argeo-slc.git/blob - runtime/org.argeo.slc.repo/src/main/java/org/argeo/slc/repo/osgi/NormalizeGroup.java
Fix a bug in determination of the highest version of all bundles contained in a group.
[gpl/argeo-slc.git] / runtime / org.argeo.slc.repo / src / main / java / org / argeo / slc / repo / osgi / NormalizeGroup.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.osgi;
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.TreeSet;
24
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.io.FilenameUtils;
32 import org.apache.commons.logging.Log;
33 import org.apache.commons.logging.LogFactory;
34 import org.argeo.ArgeoMonitor;
35 import org.argeo.jcr.JcrUtils;
36 import org.argeo.slc.SlcException;
37 import org.argeo.slc.aether.ArtifactIdComparator;
38 import org.argeo.slc.jcr.SlcNames;
39 import org.argeo.slc.jcr.SlcTypes;
40 import org.argeo.slc.repo.ArtifactIndexer;
41 import org.argeo.slc.repo.RepoConstants;
42 import org.argeo.slc.repo.RepoUtils;
43 import org.argeo.slc.repo.maven.MavenConventionsUtils;
44 import org.osgi.framework.Constants;
45 import org.osgi.framework.Version;
46 import org.sonatype.aether.artifact.Artifact;
47 import org.sonatype.aether.util.artifact.DefaultArtifact;
48
49 /**
50 * Make sure that all JCR metadata and Maven metadata are consistent for this
51 * group of OSGi bundles.
52 */
53 public class NormalizeGroup implements Runnable, SlcNames {
54 private final static Log log = LogFactory.getLog(NormalizeGroup.class);
55
56 private Repository repository;
57 private String workspace;
58 private String groupId;
59 private Boolean overridePoms = false;
60 private String artifactBasePath = "/";
61 private String version = null;
62 private String parentPomCoordinates;
63
64 private List<String> excludedSuffixes = new ArrayList<String>();
65
66 private ArtifactIndexer artifactIndexer = new ArtifactIndexer();
67 // private JarFileIndexer jarFileIndexer = new JarFileIndexer();
68
69 /** TODO make it more generic */
70 private List<String> systemPackages = OsgiProfile.PROFILE_JAVA_SE_1_6
71 .getSystemPackages();
72
73 // indexes
74 private Map<String, String> packagesToSymbolicNames = new HashMap<String, String>();
75 private Map<String, Node> symbolicNamesToNodes = new HashMap<String, Node>();
76
77 private Set<Artifact> binaries = new TreeSet<Artifact>(
78 new ArtifactIdComparator());
79 private Set<Artifact> sources = new TreeSet<Artifact>(
80 new ArtifactIdComparator());
81
82 public void run() {
83 Session session = null;
84 try {
85 session = repository.login(workspace);
86 Node groupNode = session.getNode(MavenConventionsUtils.groupPath(
87 artifactBasePath, groupId));
88 processGroupNode(groupNode, null);
89 } catch (Exception e) {
90 throw new SlcException("Cannot normalize group " + groupId + " in "
91 + workspace, e);
92 } finally {
93 JcrUtils.logoutQuietly(session);
94 }
95 }
96
97 public static void processGroupNode(Node groupNode, String version,
98 Boolean overridePoms, ArgeoMonitor monitor)
99 throws RepositoryException {
100 // TODO set artifactsBase based on group node
101 NormalizeGroup ng = new NormalizeGroup();
102 String groupId = groupNode.getProperty(SlcNames.SLC_GROUP_BASE_ID)
103 .getString();
104 ng.setGroupId(groupId);
105 ng.setVersion(version);
106 ng.setOverridePoms(overridePoms);
107 ng.processGroupNode(groupNode, monitor);
108 }
109
110 protected void processGroupNode(Node groupNode, ArgeoMonitor monitor)
111 throws RepositoryException {
112 if (monitor != null)
113 monitor.subTask("Group " + groupId);
114 Node allArtifactsHighestVersion = null;
115 Session session = groupNode.getSession();
116 aBases: for (NodeIterator aBases = groupNode.getNodes(); aBases
117 .hasNext();) {
118 Node aBase = aBases.nextNode();
119 if (aBase.isNodeType(SlcTypes.SLC_ARTIFACT_BASE)) {
120 Node highestAVersion = null;
121 for (NodeIterator aVersions = aBase.getNodes(); aVersions
122 .hasNext();) {
123 Node aVersion = aVersions.nextNode();
124 if (aVersion.isNodeType(SlcTypes.SLC_ARTIFACT_VERSION_BASE)) {
125 if (highestAVersion == null) {
126 highestAVersion = aVersion;
127 if (allArtifactsHighestVersion == null)
128 allArtifactsHighestVersion = aVersion;
129
130 // BS will fail if artifacts arrive in this order
131 // Name1 - V1, name2 - V3, V1 will remain the
132 // allArtifactsHighestVersion
133 // Fixed below
134 else {
135 Version currVersion = extractOsgiVersion(aVersion);
136 Version highestVersion = extractOsgiVersion(allArtifactsHighestVersion);
137 if (currVersion.compareTo(highestVersion) > 0)
138 allArtifactsHighestVersion = aVersion;
139 }
140
141 } else {
142 Version currVersion = extractOsgiVersion(aVersion);
143 Version currentHighestVersion = extractOsgiVersion(highestAVersion);
144 if (currVersion.compareTo(currentHighestVersion) > 0) {
145 highestAVersion = aVersion;
146 }
147 if (currVersion
148 .compareTo(extractOsgiVersion(allArtifactsHighestVersion)) > 0) {
149 allArtifactsHighestVersion = aVersion;
150 }
151 }
152
153 }
154
155 }
156 if (highestAVersion == null)
157 continue aBases;
158 for (NodeIterator files = highestAVersion.getNodes(); files
159 .hasNext();) {
160 Node file = files.nextNode();
161 if (file.isNodeType(SlcTypes.SLC_BUNDLE_ARTIFACT)) {
162 preProcessBundleArtifact(file);
163 file.getSession().save();
164 if (log.isDebugEnabled())
165 log.debug("Pre-processed " + file.getName());
166 }
167
168 }
169 }
170 }
171
172 // if version not set or empty, use the highest version
173 // useful when indexing a product maven repository where
174 // all artifacts have the same version for a given release
175 // => the version can then be left empty
176 if (version == null || version.trim().equals(""))
177 if (allArtifactsHighestVersion != null)
178 version = allArtifactsHighestVersion.getProperty(
179 SLC_ARTIFACT_VERSION).getString();
180 else
181 throw new SlcException("Group version " + version
182 + " is empty.");
183
184 int bundleCount = symbolicNamesToNodes.size();
185 if (log.isDebugEnabled())
186 log.debug("Indexed " + bundleCount + " bundles");
187
188 int count = 1;
189 for (Node bundleNode : symbolicNamesToNodes.values()) {
190 processBundleArtifact(bundleNode);
191 bundleNode.getSession().save();
192 if (log.isDebugEnabled())
193 log.debug(count + "/" + bundleCount + " Processed "
194 + bundleNode.getName());
195 count++;
196 }
197
198 // indexes
199 Set<Artifact> indexes = new TreeSet<Artifact>(
200 new ArtifactIdComparator());
201 Artifact indexArtifact = writeIndex(session,
202 RepoConstants.BINARIES_ARTIFACT_ID, binaries);
203 indexes.add(indexArtifact);
204 indexArtifact = writeIndex(session, RepoConstants.SOURCES_ARTIFACT_ID,
205 sources);
206 indexes.add(indexArtifact);
207 // sdk
208 writeIndex(session, RepoConstants.SDK_ARTIFACT_ID, indexes);
209 if (monitor != null)
210 monitor.worked(1);
211 }
212
213 private Version extractOsgiVersion(Node artifactVersion)
214 throws RepositoryException {
215 String rawVersion = artifactVersion.getProperty(SLC_ARTIFACT_VERSION)
216 .getString();
217 String cleanVersion = rawVersion.replace("-SNAPSHOT", ".SNAPSHOT");
218 return new Version(cleanVersion);
219 }
220
221 private Artifact writeIndex(Session session, String artifactId,
222 Set<Artifact> artifacts) throws RepositoryException {
223 Artifact artifact = new DefaultArtifact(groupId, artifactId, "pom",
224 version);
225 Artifact parentArtifact = parentPomCoordinates != null ? new DefaultArtifact(
226 parentPomCoordinates) : null;
227 String pom = MavenConventionsUtils.artifactsAsDependencyPom(artifact,
228 artifacts, parentArtifact);
229 Node node = RepoUtils.copyBytesAsArtifact(
230 session.getNode(artifactBasePath), artifact, pom.getBytes());
231 artifactIndexer.index(node);
232
233 // TODO factorize
234 String pomSha = JcrUtils.checksumFile(node, "SHA-1");
235 JcrUtils.copyBytesAsFile(node.getParent(), node.getName() + ".sha1",
236 pomSha.getBytes());
237 String pomMd5 = JcrUtils.checksumFile(node, "MD5");
238 JcrUtils.copyBytesAsFile(node.getParent(), node.getName() + ".md5",
239 pomMd5.getBytes());
240 session.save();
241 return artifact;
242 }
243
244 protected void preProcessBundleArtifact(Node bundleNode)
245 throws RepositoryException {
246 // we assume nodes are already indexed
247 // artifactIndexer.index(bundleNode);
248 // jarFileIndexer.index(bundleNode);
249
250 String symbolicName = JcrUtils.get(bundleNode, SLC_SYMBOLIC_NAME);
251
252 if (symbolicName.endsWith(".source")) {
253 // TODO make a shared node with classifier 'sources'?
254 String bundleName = RepoUtils
255 .extractBundleNameFromSourceName(symbolicName);
256 for (String excludedSuffix : excludedSuffixes) {
257 if (bundleName.endsWith(excludedSuffix))
258 return;// skip adding to sources
259 }
260 sources.add(RepoUtils.asArtifact(bundleNode));
261 return;
262 }
263
264 NodeIterator exportPackages = bundleNode.getNodes(SLC_
265 + Constants.EXPORT_PACKAGE);
266 while (exportPackages.hasNext()) {
267 Node exportPackage = exportPackages.nextNode();
268 String pkg = JcrUtils.get(exportPackage, SLC_NAME);
269 packagesToSymbolicNames.put(pkg, symbolicName);
270 }
271
272 symbolicNamesToNodes.put(symbolicName, bundleNode);
273 for (String excludedSuffix : excludedSuffixes) {
274 if (symbolicName.endsWith(excludedSuffix))
275 return;// skip adding to binaries
276 }
277 binaries.add(RepoUtils.asArtifact(bundleNode));
278
279 if (bundleNode.getSession().hasPendingChanges())
280 bundleNode.getSession().save();
281 }
282
283 protected void processBundleArtifact(Node bundleNode)
284 throws RepositoryException {
285 Node artifactFolder = bundleNode.getParent();
286 String baseName = FilenameUtils.getBaseName(bundleNode.getName());
287
288 // pom
289 String pomName = baseName + ".pom";
290 if (artifactFolder.hasNode(pomName) && !overridePoms)
291 return;// skip
292
293 String pom = generatePomForBundle(bundleNode);
294 Node pomNode = JcrUtils.copyBytesAsFile(artifactFolder, pomName,
295 pom.getBytes());
296 // checksum
297 String bundleSha = JcrUtils.checksumFile(bundleNode, "SHA-1");
298 JcrUtils.copyBytesAsFile(artifactFolder,
299 bundleNode.getName() + ".sha1", bundleSha.getBytes());
300 String pomSha = JcrUtils.checksumFile(pomNode, "SHA-1");
301 JcrUtils.copyBytesAsFile(artifactFolder, pomNode.getName() + ".sha1",
302 pomSha.getBytes());
303 }
304
305 private String generatePomForBundle(Node n) throws RepositoryException {
306 String ownSymbolicName = JcrUtils.get(n, SLC_SYMBOLIC_NAME);
307
308 StringBuffer p = new StringBuffer();
309
310 // XML header
311 p.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
312 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");
313 p.append("<modelVersion>4.0.0</modelVersion>");
314
315 // Artifact
316 // p.append("<parent><groupId>org.argeo</groupId><artifactId>parent</artifactId><version>1.2.0</version></parent>\n");
317 p.append("<groupId>").append(JcrUtils.get(n, SLC_GROUP_ID))
318 .append("</groupId>\n");
319 p.append("<artifactId>").append(JcrUtils.get(n, SLC_ARTIFACT_ID))
320 .append("</artifactId>\n");
321 p.append("<version>").append(JcrUtils.get(n, SLC_ARTIFACT_VERSION))
322 .append("</version>\n");
323 p.append("<packaging>pom</packaging>\n");
324 if (n.hasProperty(SLC_ + Constants.BUNDLE_NAME))
325 p.append("<name>")
326 .append(JcrUtils.get(n, SLC_ + Constants.BUNDLE_NAME))
327 .append("</name>\n");
328 if (n.hasProperty(SLC_ + Constants.BUNDLE_DESCRIPTION))
329 p.append("<description>")
330 .append(JcrUtils
331 .get(n, SLC_ + Constants.BUNDLE_DESCRIPTION))
332 .append("</description>\n");
333
334 // Dependencies
335 Set<String> dependenciesSymbolicNames = new TreeSet<String>();
336 Set<String> optionalSymbolicNames = new TreeSet<String>();
337 NodeIterator importPackages = n.getNodes(SLC_
338 + Constants.IMPORT_PACKAGE);
339 while (importPackages.hasNext()) {
340 Node importPackage = importPackages.nextNode();
341 String pkg = JcrUtils.get(importPackage, SLC_NAME);
342 if (packagesToSymbolicNames.containsKey(pkg)) {
343 String dependencySymbolicName = packagesToSymbolicNames
344 .get(pkg);
345 if (JcrUtils.check(importPackage, SLC_OPTIONAL))
346 optionalSymbolicNames.add(dependencySymbolicName);
347 else
348 dependenciesSymbolicNames.add(dependencySymbolicName);
349 } else {
350 if (!JcrUtils.check(importPackage, SLC_OPTIONAL)
351 && !systemPackages.contains(pkg))
352 log.warn("No bundle found for pkg " + pkg);
353 }
354 }
355
356 if (n.hasNode(SLC_ + Constants.FRAGMENT_HOST)) {
357 String fragmentHost = JcrUtils.get(
358 n.getNode(SLC_ + Constants.FRAGMENT_HOST),
359 SLC_SYMBOLIC_NAME);
360 dependenciesSymbolicNames.add(fragmentHost);
361 }
362
363 // TODO require bundles
364
365 List<Node> dependencyNodes = new ArrayList<Node>();
366 for (String depSymbName : dependenciesSymbolicNames) {
367 if (depSymbName.equals(ownSymbolicName))
368 continue;// skip self
369
370 if (symbolicNamesToNodes.containsKey(depSymbName))
371 dependencyNodes.add(symbolicNamesToNodes.get(depSymbName));
372 else
373 log.warn("Could not find node for " + depSymbName);
374 }
375 List<Node> optionalDependencyNodes = new ArrayList<Node>();
376 for (String depSymbName : optionalSymbolicNames) {
377 if (symbolicNamesToNodes.containsKey(depSymbName))
378 optionalDependencyNodes.add(symbolicNamesToNodes
379 .get(depSymbName));
380 else
381 log.warn("Could not find node for " + depSymbName);
382 }
383
384 p.append("<dependencies>\n");
385 for (Node dependencyNode : dependencyNodes) {
386 p.append("<dependency>\n");
387 p.append("\t<groupId>")
388 .append(JcrUtils.get(dependencyNode, SLC_GROUP_ID))
389 .append("</groupId>\n");
390 p.append("\t<artifactId>")
391 .append(JcrUtils.get(dependencyNode, SLC_ARTIFACT_ID))
392 .append("</artifactId>\n");
393 p.append("</dependency>\n");
394 }
395
396 if (optionalDependencyNodes.size() > 0)
397 p.append("<!-- OPTIONAL -->\n");
398 for (Node dependencyNode : optionalDependencyNodes) {
399 p.append("<dependency>\n");
400 p.append("\t<groupId>")
401 .append(JcrUtils.get(dependencyNode, SLC_GROUP_ID))
402 .append("</groupId>\n");
403 p.append("\t<artifactId>")
404 .append(JcrUtils.get(dependencyNode, SLC_ARTIFACT_ID))
405 .append("</artifactId>\n");
406 p.append("\t<optional>true</optional>\n");
407 p.append("</dependency>\n");
408 }
409 p.append("</dependencies>\n");
410
411 // Dependency management
412 p.append("<dependencyManagement>\n");
413 p.append("<dependencies>\n");
414 p.append("<dependency>\n");
415 p.append("\t<groupId>").append(groupId).append("</groupId>\n");
416 p.append("\t<artifactId>")
417 .append(ownSymbolicName.endsWith(".source") ? RepoConstants.SOURCES_ARTIFACT_ID
418 : RepoConstants.BINARIES_ARTIFACT_ID)
419 .append("</artifactId>\n");
420 p.append("\t<version>").append(version).append("</version>\n");
421 p.append("\t<type>pom</type>\n");
422 p.append("\t<scope>import</scope>\n");
423 p.append("</dependency>\n");
424 p.append("</dependencies>\n");
425 p.append("</dependencyManagement>\n");
426
427 p.append("</project>\n");
428 return p.toString();
429 }
430
431 public void setRepository(Repository repository) {
432 this.repository = repository;
433 }
434
435 public void setWorkspace(String workspace) {
436 this.workspace = workspace;
437 }
438
439 public void setGroupId(String groupId) {
440 this.groupId = groupId;
441 }
442
443 public void setParentPomCoordinates(String parentPomCoordinates) {
444 this.parentPomCoordinates = parentPomCoordinates;
445 }
446
447 public void setArtifactBasePath(String artifactBasePath) {
448 this.artifactBasePath = artifactBasePath;
449 }
450
451 public void setVersion(String version) {
452 this.version = version;
453 }
454
455 public void setExcludedSuffixes(List<String> excludedSuffixes) {
456 this.excludedSuffixes = excludedSuffixes;
457 }
458
459 public void setOverridePoms(Boolean overridePoms) {
460 this.overridePoms = overridePoms;
461 }
462
463 public void setArtifactIndexer(ArtifactIndexer artifactIndexer) {
464 this.artifactIndexer = artifactIndexer;
465 }
466 }