]> git.argeo.org Git - gpl/argeo-slc.git/blob - org.argeo.slc.factory/src/org/argeo/slc/factory/A2Factory.java
Can merge artifact with another version
[gpl/argeo-slc.git] / org.argeo.slc.factory / src / org / argeo / slc / factory / A2Factory.java
1 package org.argeo.slc.factory;
2
3 import static java.lang.System.Logger.Level.DEBUG;
4 import static org.argeo.slc.ManifestConstants.BUNDLE_SYMBOLICNAME;
5 import static org.argeo.slc.ManifestConstants.BUNDLE_VERSION;
6 import static org.argeo.slc.ManifestConstants.EXPORT_PACKAGE;
7 import static org.argeo.slc.ManifestConstants.SLC_ORIGIN_M2;
8 import static org.argeo.slc.ManifestConstants.SLC_ORIGIN_M2_REPO;
9
10 import java.io.FileNotFoundException;
11 import java.io.IOException;
12 import java.io.InputStream;
13 import java.io.OutputStream;
14 import java.lang.System.Logger;
15 import java.lang.System.Logger.Level;
16 import java.net.URL;
17 import java.nio.file.DirectoryStream;
18 import java.nio.file.FileSystem;
19 import java.nio.file.FileSystems;
20 import java.nio.file.FileVisitResult;
21 import java.nio.file.Files;
22 import java.nio.file.Path;
23 import java.nio.file.PathMatcher;
24 import java.nio.file.Paths;
25 import java.nio.file.SimpleFileVisitor;
26 import java.nio.file.StandardOpenOption;
27 import java.nio.file.attribute.BasicFileAttributes;
28 import java.util.ArrayList;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Properties;
33 import java.util.TreeMap;
34 import java.util.jar.Attributes;
35 import java.util.jar.JarEntry;
36 import java.util.jar.JarInputStream;
37 import java.util.jar.JarOutputStream;
38 import java.util.jar.Manifest;
39
40 import org.argeo.slc.DefaultCategoryNameVersion;
41 import org.argeo.slc.DefaultNameVersion;
42 import org.argeo.slc.ManifestConstants;
43 import org.argeo.slc.NameVersion;
44 import org.argeo.slc.factory.m2.DefaultArtifact;
45 import org.argeo.slc.factory.m2.MavenConventionsUtils;
46
47 import aQute.bnd.osgi.Analyzer;
48 import aQute.bnd.osgi.Jar;
49
50 /** The central class for A2 packaging. */
51 public class A2Factory {
52 private final static Logger logger = System.getLogger(A2Factory.class.getName());
53
54 private final static String COMMON_BND = "common.bnd";
55 private final static String MERGE_BND = "merge.bnd";
56
57 private Path originBase;
58 private Path a2Base;
59
60 /** key is URI prefix, value list of base URLs */
61 private Map<String, List<String>> mirrors = new HashMap<String, List<String>>();
62
63 public A2Factory(Path a2Base) {
64 this.originBase = Paths.get(System.getProperty("user.home"), ".cache", "argeo/slc/origin");
65 this.a2Base = a2Base;
66
67 // TODO make it configurable
68 List<String> eclipseMirrors = new ArrayList<>();
69 eclipseMirrors.add("https://archive.eclipse.org/");
70 eclipseMirrors.add("http://ftp-stud.hs-esslingen.de/Mirrors/eclipse/");
71 eclipseMirrors.add("http://ftp.fau.de/eclipse/");
72
73 mirrors.put("http://www.eclipse.org/downloads", eclipseMirrors);
74 }
75
76 public void processCategory(Path targetCategoryBase) {
77 try {
78 DirectoryStream<Path> bnds = Files.newDirectoryStream(targetCategoryBase,
79 (p) -> p.getFileName().toString().endsWith(".bnd") && !p.getFileName().toString().equals(COMMON_BND)
80 && !p.getFileName().toString().equals(MERGE_BND));
81 for (Path p : bnds) {
82 processSingleM2ArtifactDistributionUnit(p);
83 }
84
85 DirectoryStream<Path> dus = Files.newDirectoryStream(targetCategoryBase, (p) -> Files.isDirectory(p));
86 for (Path duDir : dus) {
87 processM2BasedDistributionUnit(duDir);
88 }
89 } catch (IOException e) {
90 throw new RuntimeException("Cannot process category " + targetCategoryBase, e);
91 }
92 }
93
94 public void processSingleM2ArtifactDistributionUnit(Path bndFile) {
95 try {
96 String category = bndFile.getParent().getFileName().toString();
97 Path targetCategoryBase = a2Base.resolve(category);
98 Properties fileProps = new Properties();
99 try (InputStream in = Files.newInputStream(bndFile)) {
100 fileProps.load(in);
101 }
102 String repoStr = fileProps.containsKey(SLC_ORIGIN_M2_REPO.toString())
103 ? fileProps.getProperty(SLC_ORIGIN_M2_REPO.toString())
104 : null;
105
106 if (!fileProps.containsKey(BUNDLE_SYMBOLICNAME.toString())
107 && !fileProps.containsKey(ManifestConstants.SLC_ORIGIN_MANIFEST_NOT_MODIFIED.toString())) {
108 // use file name as symbolic name
109 String symbolicName = bndFile.getFileName().toString();
110 symbolicName = symbolicName.substring(0, symbolicName.length() - ".bnd".length());
111 fileProps.put(BUNDLE_SYMBOLICNAME.toString(), symbolicName);
112 }
113
114 String m2Coordinates = fileProps.getProperty(SLC_ORIGIN_M2.toString());
115 if (m2Coordinates == null)
116 throw new IllegalArgumentException("No M2 coordinates available for " + bndFile);
117 DefaultArtifact artifact = new DefaultArtifact(m2Coordinates);
118 URL url = MavenConventionsUtils.mavenRepoUrl(repoStr, artifact);
119 Path downloaded = download(url, originBase, artifact.toM2Coordinates() + ".jar");
120
121 Path targetBundleDir = processBndJar(downloaded, targetCategoryBase, fileProps, artifact);
122
123 downloadAndProcessM2Sources(repoStr, artifact, targetBundleDir);
124
125 createJar(targetBundleDir);
126 } catch (Exception e) {
127 throw new RuntimeException("Cannot process " + bndFile, e);
128 }
129 }
130
131 public void processM2BasedDistributionUnit(Path duDir) {
132 try {
133 String category = duDir.getParent().getFileName().toString();
134 Path targetCategoryBase = a2Base.resolve(category);
135
136 // merge
137 Path mergeBnd = duDir.resolve(MERGE_BND);
138 if (Files.exists(mergeBnd)) {
139 mergeM2Artifacts(mergeBnd);
140 return;
141 }
142
143 Path commonBnd = duDir.resolve(COMMON_BND);
144 Properties commonProps = new Properties();
145 try (InputStream in = Files.newInputStream(commonBnd)) {
146 commonProps.load(in);
147 }
148
149 String m2Version = commonProps.getProperty(SLC_ORIGIN_M2.toString());
150 if (m2Version == null) {
151 logger.log(Level.WARNING, "Ignoring " + duDir + " as it is not an M2-based distribution unit");
152 return;// ignore, this is probably an Eclipse archive
153 }
154 if (!m2Version.startsWith(":")) {
155 throw new IllegalStateException("Only the M2 version can be specified: " + m2Version);
156 }
157 m2Version = m2Version.substring(1);
158
159 DirectoryStream<Path> ds = Files.newDirectoryStream(duDir,
160 (p) -> p.getFileName().toString().endsWith(".bnd")
161 && !p.getFileName().toString().equals(COMMON_BND));
162 for (Path p : ds) {
163 Properties fileProps = new Properties();
164 try (InputStream in = Files.newInputStream(p)) {
165 fileProps.load(in);
166 }
167 String m2Coordinates = fileProps.getProperty(SLC_ORIGIN_M2.toString());
168 DefaultArtifact artifact = new DefaultArtifact(m2Coordinates);
169
170 // temporary rewrite, for migration
171 // String localLicense = fileProps.getProperty(BUNDLE_LICENSE.toString());
172 // if (localLicense != null || artifact.getVersion() != null) {
173 // fileProps.remove(BUNDLE_LICENSE.toString());
174 // fileProps.put(SLC_ORIGIN_M2.toString(), artifact.getGroupId() + ":" + artifact.getArtifactId());
175 // try (Writer writer = Files.newBufferedWriter(p)) {
176 // for (Object key : fileProps.keySet()) {
177 // String value = fileProps.getProperty(key.toString());
178 // writer.write(key + ": " + value + '\n');
179 // }
180 // logger.log(DEBUG, () -> "Migrated " + p);
181 // }
182 // }
183
184 artifact.setVersion(m2Version);
185
186 // prepare manifest entries
187 Properties mergeProps = new Properties();
188 mergeProps.putAll(commonProps);
189
190 fileEntries: for (Object key : fileProps.keySet()) {
191 if (ManifestConstants.SLC_ORIGIN_M2.toString().equals(key))
192 continue fileEntries;
193 String value = fileProps.getProperty(key.toString());
194 Object previousValue = mergeProps.put(key.toString(), value);
195 if (previousValue != null) {
196 logger.log(Level.WARNING,
197 commonBnd + ": " + key + " was " + previousValue + ", overridden with " + value);
198 }
199 }
200 mergeProps.put(ManifestConstants.SLC_ORIGIN_M2.toString(), artifact.toM2Coordinates());
201 if (!mergeProps.containsKey(BUNDLE_SYMBOLICNAME.toString())
202 && !mergeProps.containsKey(ManifestConstants.SLC_ORIGIN_MANIFEST_NOT_MODIFIED.toString())) {
203 // use file name as symbolic name
204 String symbolicName = p.getFileName().toString();
205 symbolicName = symbolicName.substring(0, symbolicName.length() - ".bnd".length());
206 mergeProps.put(BUNDLE_SYMBOLICNAME.toString(), symbolicName);
207 }
208
209 String repoStr = mergeProps.containsKey(SLC_ORIGIN_M2_REPO.toString())
210 ? mergeProps.getProperty(SLC_ORIGIN_M2_REPO.toString())
211 : null;
212
213 // download
214 URL url = MavenConventionsUtils.mavenRepoUrl(repoStr, artifact);
215 Path downloaded = download(url, originBase, artifact.toM2Coordinates() + ".jar");
216
217 Path targetBundleDir = processBndJar(downloaded, targetCategoryBase, mergeProps, artifact);
218 // logger.log(Level.DEBUG, () -> "Processed " + downloaded);
219
220 // sources
221 downloadAndProcessM2Sources(repoStr, artifact, targetBundleDir);
222
223 createJar(targetBundleDir);
224 }
225 } catch (IOException e) {
226 throw new RuntimeException("Cannot process " + duDir, e);
227 }
228
229 }
230
231 protected void mergeM2Artifacts(Path mergeBnd) throws IOException {
232 Path duDir = mergeBnd.getParent();
233 String category = duDir.getParent().getFileName().toString();
234 Path targetCategoryBase = a2Base.resolve(category);
235
236 Properties mergeProps = new Properties();
237 try (InputStream in = Files.newInputStream(mergeBnd)) {
238 mergeProps.load(in);
239 }
240 String m2Version = mergeProps.getProperty(SLC_ORIGIN_M2.toString());
241 if (m2Version == null) {
242 logger.log(Level.WARNING, "Ignoring " + duDir + " as it is not an M2-based distribution unit");
243 return;// ignore, this is probably an Eclipse archive
244 }
245 if (!m2Version.startsWith(":")) {
246 throw new IllegalStateException("Only the M2 version can be specified: " + m2Version);
247 }
248 m2Version = m2Version.substring(1);
249
250 String artifactsStr = mergeProps.getProperty(ManifestConstants.SLC_ORIGIN_M2_MERGE.toString());
251 String repoStr = mergeProps.containsKey(SLC_ORIGIN_M2_REPO.toString())
252 ? mergeProps.getProperty(SLC_ORIGIN_M2_REPO.toString())
253 : null;
254
255 String bundleSymbolicName = mergeProps.getProperty(ManifestConstants.BUNDLE_SYMBOLICNAME.toString());
256 DefaultCategoryNameVersion nameVersion = new DefaultArtifact(
257 category + ":" + bundleSymbolicName + ":" + m2Version);
258 Path targetBundleDir = targetCategoryBase.resolve(bundleSymbolicName + "." + nameVersion.getBranch());
259
260 String[] artifacts = artifactsStr.split(",");
261 artifacts: for (String str : artifacts) {
262 String m2Coordinates = str.trim();
263 if ("".equals(m2Coordinates))
264 continue artifacts;
265 DefaultArtifact artifact = new DefaultArtifact(m2Coordinates.trim());
266 if (artifact.getVersion() == null)
267 artifact.setVersion(m2Version);
268 URL url = MavenConventionsUtils.mavenRepoUrl(repoStr, artifact);
269 Path downloaded = download(url, originBase, artifact.toM2Coordinates() + ".jar");
270 JarEntry entry;
271 try (JarInputStream jarIn = new JarInputStream(Files.newInputStream(downloaded), false)) {
272 entries: while ((entry = jarIn.getNextJarEntry()) != null) {
273 if (entry.isDirectory())
274 continue entries;
275 if (entry.getName().endsWith(".RSA") || entry.getName().endsWith(".SF"))
276 continue entries;
277 if (entry.getName().startsWith("META-INF/versions/"))
278 continue entries;
279 Path target = targetBundleDir.resolve(entry.getName());
280 Files.createDirectories(target.getParent());
281 if (!Files.exists(target)) {
282 Files.copy(jarIn, target);
283 } else {
284 if (entry.getName().startsWith("META-INF/services/")) {
285 try (OutputStream out = Files.newOutputStream(target, StandardOpenOption.APPEND)) {
286 jarIn.transferTo(out);
287 }
288 } else {
289 throw new IllegalStateException("File " + target + " already exists");
290 }
291 }
292 logger.log(Level.TRACE, () -> "Copied " + target);
293 }
294
295 }
296 downloadAndProcessM2Sources(repoStr, artifact, targetBundleDir);
297 }
298
299 Map<String, String> entries = new TreeMap<>();
300 try (Analyzer bndAnalyzer = new Analyzer()) {
301 bndAnalyzer.setProperties(mergeProps);
302 Jar jar = new Jar(targetBundleDir.toFile());
303 bndAnalyzer.setJar(jar);
304 Manifest manifest = bndAnalyzer.calcManifest();
305
306 keys: for (Object key : manifest.getMainAttributes().keySet()) {
307 Object value = manifest.getMainAttributes().get(key);
308
309 switch (key.toString()) {
310 case "Tool":
311 case "Bnd-LastModified":
312 case "Created-By":
313 continue keys;
314 }
315 if ("Require-Capability".equals(key.toString())
316 && value.toString().equals("osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.1))\""))
317 continue keys;// hack for very old classes
318 entries.put(key.toString(), value.toString());
319 logger.log(DEBUG, () -> key + "=" + value);
320
321 }
322 } catch (Exception e) {
323 throw new RuntimeException("Cannot process " + mergeBnd, e);
324 }
325
326 Manifest manifest = new Manifest();
327 Path manifestPath = targetBundleDir.resolve("META-INF/MANIFEST.MF");
328 Files.createDirectories(manifestPath.getParent());
329 for (String key : entries.keySet()) {
330 String value = entries.get(key);
331 manifest.getMainAttributes().putValue(key, value);
332 }
333 try (OutputStream out = Files.newOutputStream(manifestPath)) {
334 manifest.write(out);
335 }
336
337 createJar(targetBundleDir);
338
339 }
340
341 protected void downloadAndProcessM2Sources(String repoStr, DefaultArtifact artifact, Path targetBundleDir)
342 throws IOException {
343 DefaultArtifact sourcesArtifact = new DefaultArtifact(artifact.toM2Coordinates(), "sources");
344 URL sourcesUrl = MavenConventionsUtils.mavenRepoUrl(repoStr, sourcesArtifact);
345 Path sourcesDownloaded = download(sourcesUrl, originBase, artifact.toM2Coordinates() + ".sources.jar");
346 processM2SourceJar(sourcesDownloaded, targetBundleDir);
347 logger.log(Level.DEBUG, () -> "Processed source " + sourcesDownloaded);
348
349 }
350
351 protected Path processBndJar(Path downloaded, Path targetCategoryBase, Properties fileProps,
352 DefaultArtifact artifact) {
353
354 try {
355 Map<String, String> additionalEntries = new TreeMap<>();
356 boolean doNotModify = Boolean.parseBoolean(fileProps
357 .getOrDefault(ManifestConstants.SLC_ORIGIN_MANIFEST_NOT_MODIFIED.toString(), "false").toString());
358
359 // we always force the symbolic name
360
361 if (doNotModify) {
362 fileEntries: for (Object key : fileProps.keySet()) {
363 if (ManifestConstants.SLC_ORIGIN_M2.toString().equals(key))
364 continue fileEntries;
365 String value = fileProps.getProperty(key.toString());
366 additionalEntries.put(key.toString(), value);
367 }
368 } else {
369 if (artifact != null) {
370 if (!fileProps.containsKey(BUNDLE_SYMBOLICNAME.toString())) {
371 fileProps.put(BUNDLE_SYMBOLICNAME.toString(), artifact.getName());
372 }
373 if (!fileProps.containsKey(BUNDLE_VERSION.toString())) {
374 fileProps.put(BUNDLE_VERSION.toString(), artifact.getVersion());
375 }
376 }
377
378 if (!fileProps.containsKey(EXPORT_PACKAGE.toString())) {
379 fileProps.put(EXPORT_PACKAGE.toString(),
380 "*;version=\"" + fileProps.getProperty(BUNDLE_VERSION.toString()) + "\"");
381 }
382 // if (!fileProps.contains(IMPORT_PACKAGE.toString())) {
383 // fileProps.put(IMPORT_PACKAGE.toString(), "*");
384 // }
385
386 try (Analyzer bndAnalyzer = new Analyzer()) {
387 bndAnalyzer.setProperties(fileProps);
388 Jar jar = new Jar(downloaded.toFile());
389 bndAnalyzer.setJar(jar);
390 Manifest manifest = bndAnalyzer.calcManifest();
391
392 keys: for (Object key : manifest.getMainAttributes().keySet()) {
393 Object value = manifest.getMainAttributes().get(key);
394
395 switch (key.toString()) {
396 case "Tool":
397 case "Bnd-LastModified":
398 case "Created-By":
399 continue keys;
400 }
401 if ("Require-Capability".equals(key.toString())
402 && value.toString().equals("osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.1))\""))
403 continue keys;// hack for very old classes
404 additionalEntries.put(key.toString(), value.toString());
405 logger.log(DEBUG, () -> key + "=" + value);
406
407 }
408 }
409
410 // try (Builder bndBuilder = new Builder()) {
411 // Jar jar = new Jar(downloaded.toFile());
412 // bndBuilder.addClasspath(jar);
413 // Path targetBundleDir = targetCategoryBase.resolve(artifact.getName() + "." + artifact.getBranch());
414 //
415 // Jar target = new Jar(targetBundleDir.toFile());
416 // bndBuilder.setJar(target);
417 // return targetBundleDir;
418 // }
419 }
420 Path targetBundleDir = processBundleJar(downloaded, targetCategoryBase, additionalEntries);
421 logger.log(Level.DEBUG, () -> "Processed " + downloaded);
422 return targetBundleDir;
423 } catch (Exception e) {
424 throw new RuntimeException("Cannot BND process " + downloaded, e);
425 }
426
427 }
428
429 protected void processM2SourceJar(Path file, Path targetBundleDir) throws IOException {
430 try (JarInputStream jarIn = new JarInputStream(Files.newInputStream(file), false)) {
431 Path targetSourceDir = targetBundleDir.resolve("OSGI-OPT/src");
432
433 // TODO make it less dangerous?
434 if (Files.exists(targetSourceDir)) {
435 deleteDirectory(targetSourceDir);
436 } else {
437 Files.createDirectories(targetSourceDir);
438 }
439
440 // copy entries
441 JarEntry entry;
442 entries: while ((entry = jarIn.getNextJarEntry()) != null) {
443 if (entry.isDirectory())
444 continue entries;
445 if (entry.getName().startsWith("META-INF"))// skip META-INF entries
446 continue entries;
447 Path target = targetSourceDir.resolve(entry.getName());
448 Files.createDirectories(target.getParent());
449 Files.copy(jarIn, target);
450 logger.log(Level.TRACE, () -> "Copied source " + target);
451 }
452 }
453
454 }
455
456 public void processEclipseArchive(Path duDir) {
457 try {
458 String category = duDir.getParent().getFileName().toString();
459 Path targetCategoryBase = a2Base.resolve(category);
460 Files.createDirectories(targetCategoryBase);
461 // first delete all directories from previous builds
462 for (Path dir : Files.newDirectoryStream(targetCategoryBase, (p) -> Files.isDirectory(p))) {
463 deleteDirectory(dir);
464 }
465
466 Files.createDirectories(originBase);
467
468 Path commonBnd = duDir.resolve(COMMON_BND);
469 Properties commonProps = new Properties();
470 try (InputStream in = Files.newInputStream(commonBnd)) {
471 commonProps.load(in);
472 }
473 Properties includes = new Properties();
474 try (InputStream in = Files.newInputStream(duDir.resolve("includes.properties"))) {
475 includes.load(in);
476 }
477 String url = commonProps.getProperty(ManifestConstants.SLC_ORIGIN_URI.toString());
478 Path downloaded = tryDownload(url, originBase);
479
480 FileSystem zipFs = FileSystems.newFileSystem(downloaded, (ClassLoader) null);
481
482 List<PathMatcher> pathMatchers = new ArrayList<>();
483 for (Object pattern : includes.keySet()) {
484 PathMatcher pathMatcher = zipFs.getPathMatcher("glob:/" + pattern);
485 pathMatchers.add(pathMatcher);
486 }
487
488 Files.walkFileTree(zipFs.getRootDirectories().iterator().next(), new SimpleFileVisitor<Path>() {
489
490 @Override
491 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
492 pathMatchers: for (PathMatcher pathMatcher : pathMatchers) {
493 if (pathMatcher.matches(file)) {
494 if (file.getFileName().toString().contains(".source_")) {
495 processEclipseSourceJar(file, targetCategoryBase);
496 logger.log(Level.DEBUG, () -> "Processed source " + file);
497
498 } else {
499 processBundleJar(file, targetCategoryBase, new HashMap<>());
500 logger.log(Level.DEBUG, () -> "Processed " + file);
501 }
502 break pathMatchers;
503 }
504 }
505 return FileVisitResult.CONTINUE;
506 }
507 });
508
509 DirectoryStream<Path> dirs = Files.newDirectoryStream(targetCategoryBase, (p) -> Files.isDirectory(p));
510 for (Path dir : dirs) {
511 createJar(dir);
512 }
513 } catch (IOException e) {
514 throw new RuntimeException("Cannot process " + duDir, e);
515 }
516
517 }
518
519 protected Path processBundleJar(Path file, Path targetBase, Map<String, String> entries) throws IOException {
520 DefaultNameVersion nameVersion;
521 Path targetBundleDir;
522 try (JarInputStream jarIn = new JarInputStream(Files.newInputStream(file), false)) {
523 Manifest manifest = new Manifest(jarIn.getManifest());
524
525 // remove problematic entries in MANIFEST
526 manifest.getEntries().clear();
527 // Set<String> entriesToDelete = new HashSet<>();
528 // for (String key : manifest.getEntries().keySet()) {
529 //// logger.log(DEBUG, "## " + key);
530 // Attributes attrs = manifest.getAttributes(key);
531 // for (Object attrName : attrs.keySet()) {
532 //// logger.log(DEBUG, attrName + "=" + attrs.get(attrName));
533 // if ("Specification-Version".equals(attrName.toString())
534 // || "Implementation-Version".equals(attrName.toString())) {
535 // entriesToDelete.add(key);
536 //
537 // }
538 // }
539 // }
540 // for (String key : entriesToDelete) {
541 // manifest.getEntries().remove(key);
542 // }
543
544 String symbolicNameFromEntries = entries.get(BUNDLE_SYMBOLICNAME.toString());
545 String versionFromEntries = entries.get(BUNDLE_VERSION.toString());
546
547 if (symbolicNameFromEntries != null && versionFromEntries != null) {
548 nameVersion = new DefaultNameVersion(symbolicNameFromEntries, versionFromEntries);
549 } else {
550 nameVersion = nameVersionFromManifest(manifest);
551 if (versionFromEntries != null && !nameVersion.getVersion().equals(versionFromEntries)) {
552 logger.log(Level.WARNING, "Original version is " + nameVersion.getVersion()
553 + " while new version is " + versionFromEntries);
554 }
555 if (symbolicNameFromEntries != null) {
556 // we always force our symbolic name
557 nameVersion.setName(symbolicNameFromEntries);
558 }
559 }
560 targetBundleDir = targetBase.resolve(nameVersion.getName() + "." + nameVersion.getBranch());
561
562 // TODO make it less dangerous?
563 // if (Files.exists(targetBundleDir)) {
564 // deleteDirectory(targetBundleDir);
565 // }
566
567 // copy entries
568 JarEntry entry;
569 entries: while ((entry = jarIn.getNextJarEntry()) != null) {
570 if (entry.isDirectory())
571 continue entries;
572 if (entry.getName().endsWith(".RSA") || entry.getName().endsWith(".SF"))
573 continue entries;
574 Path target = targetBundleDir.resolve(entry.getName());
575 Files.createDirectories(target.getParent());
576 Files.copy(jarIn, target);
577 logger.log(Level.TRACE, () -> "Copied " + target);
578 }
579
580 // copy MANIFEST
581 Path manifestPath = targetBundleDir.resolve("META-INF/MANIFEST.MF");
582 Files.createDirectories(manifestPath.getParent());
583 for (String key : entries.keySet()) {
584 String value = entries.get(key);
585 Object previousValue = manifest.getMainAttributes().putValue(key, value);
586 if (previousValue != null && !previousValue.equals(value)) {
587 if (ManifestConstants.IMPORT_PACKAGE.toString().equals(key)
588 || ManifestConstants.EXPORT_PACKAGE.toString().equals(key))
589 logger.log(Level.WARNING, file.getFileName() + ": " + key + " was modified");
590
591 else
592 logger.log(Level.WARNING, file.getFileName() + ": " + key + " was " + previousValue
593 + ", overridden with " + value);
594 }
595 }
596 try (OutputStream out = Files.newOutputStream(manifestPath)) {
597 manifest.write(out);
598 }
599 }
600 return targetBundleDir;
601 }
602
603 protected void processEclipseSourceJar(Path file, Path targetBase) throws IOException {
604 try {
605 Path targetBundleDir;
606 try (JarInputStream jarIn = new JarInputStream(Files.newInputStream(file), false)) {
607 Manifest manifest = jarIn.getManifest();
608
609 String[] relatedBundle = manifest.getMainAttributes().getValue("Eclipse-SourceBundle").split(";");
610 String version = relatedBundle[1].substring("version=\"".length());
611 version = version.substring(0, version.length() - 1);
612 NameVersion nameVersion = new DefaultNameVersion(relatedBundle[0], version);
613 targetBundleDir = targetBase.resolve(nameVersion.getName() + "." + nameVersion.getBranch());
614
615 Path targetSourceDir = targetBundleDir.resolve("OSGI-OPT/src");
616
617 // TODO make it less dangerous?
618 if (Files.exists(targetSourceDir)) {
619 // deleteDirectory(targetSourceDir);
620 } else {
621 Files.createDirectories(targetSourceDir);
622 }
623
624 // copy entries
625 JarEntry entry;
626 entries: while ((entry = jarIn.getNextJarEntry()) != null) {
627 if (entry.isDirectory())
628 continue entries;
629 if (entry.getName().startsWith("META-INF"))// skip META-INF entries
630 continue entries;
631 Path target = targetSourceDir.resolve(entry.getName());
632 Files.createDirectories(target.getParent());
633 Files.copy(jarIn, target);
634 logger.log(Level.TRACE, () -> "Copied source " + target);
635 }
636
637 // copy MANIFEST
638 // Path manifestPath = targetBundleDir.resolve("META-INF/MANIFEST.MF");
639 // Files.createDirectories(manifestPath.getParent());
640 // try (OutputStream out = Files.newOutputStream(manifestPath)) {
641 // manifest.write(out);
642 // }
643 }
644 } catch (IOException e) {
645 throw new IllegalStateException("Cannot process " + file, e);
646 }
647
648 }
649
650 static void deleteDirectory(Path path) throws IOException {
651 if (!Files.exists(path))
652 return;
653 Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
654 @Override
655 public FileVisitResult postVisitDirectory(Path directory, IOException e) throws IOException {
656 if (e != null)
657 throw e;
658 Files.delete(directory);
659 return FileVisitResult.CONTINUE;
660 }
661
662 @Override
663 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
664 Files.delete(file);
665 return FileVisitResult.CONTINUE;
666 }
667 });
668 }
669
670 protected DefaultNameVersion nameVersionFromManifest(Manifest manifest) {
671 Attributes attrs = manifest.getMainAttributes();
672 // symbolic name
673 String symbolicName = attrs.getValue(ManifestConstants.BUNDLE_SYMBOLICNAME.toString());
674 if (symbolicName == null)
675 return null;
676 // make sure there is no directive
677 symbolicName = symbolicName.split(";")[0];
678
679 String version = attrs.getValue(ManifestConstants.BUNDLE_VERSION.toString());
680 return new DefaultNameVersion(symbolicName, version);
681 }
682
683 protected Path tryDownload(String uri, Path dir) throws IOException {
684 // find mirror
685 List<String> urlBases = null;
686 String uriPrefix = null;
687 uriPrefixes: for (String uriPref : mirrors.keySet()) {
688 if (uri.startsWith(uriPref)) {
689 if (mirrors.get(uriPref).size() > 0) {
690 urlBases = mirrors.get(uriPref);
691 uriPrefix = uriPref;
692 break uriPrefixes;
693 }
694 }
695 }
696 if (urlBases == null)
697 try {
698 return download(new URL(uri), dir, null);
699 } catch (FileNotFoundException e) {
700 throw new FileNotFoundException("Cannot find " + uri);
701 }
702
703 // try to download
704 for (String urlBase : urlBases) {
705 String relativePath = uri.substring(uriPrefix.length());
706 URL url = new URL(urlBase + relativePath);
707 try {
708 return download(url, dir, null);
709 } catch (FileNotFoundException e) {
710 logger.log(Level.WARNING, "Cannot download " + url + ", trying another mirror");
711 }
712 }
713 throw new FileNotFoundException("Cannot find " + uri);
714 }
715
716 // protected String simplifyName(URL u) {
717 // String name = u.getPath().substring(u.getPath().lastIndexOf('/') + 1);
718 //
719 // }
720
721 protected Path download(URL url, Path dir, String name) throws IOException {
722
723 Path dest;
724 if (name == null) {
725 name = url.getPath().substring(url.getPath().lastIndexOf('/') + 1);
726 }
727
728 dest = dir.resolve(name);
729 if (Files.exists(dest)) {
730 logger.log(Level.TRACE, () -> "File " + dest + " already exists for " + url + ", not downloading again");
731 return dest;
732 }
733
734 try (InputStream in = url.openStream()) {
735 Files.copy(in, dest);
736 logger.log(Level.DEBUG, () -> "Downloaded " + dest + " from " + url);
737 }
738 return dest;
739 }
740
741 protected Path createJar(Path bundleDir) throws IOException {
742 Path jarPath = bundleDir.getParent().resolve(bundleDir.getFileName() + ".jar");
743 Path manifestPath = bundleDir.resolve("META-INF/MANIFEST.MF");
744 Manifest manifest;
745 try (InputStream in = Files.newInputStream(manifestPath)) {
746 manifest = new Manifest(in);
747 }
748 try (JarOutputStream jarOut = new JarOutputStream(Files.newOutputStream(jarPath), manifest)) {
749 Files.walkFileTree(bundleDir, new SimpleFileVisitor<Path>() {
750
751 @Override
752 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
753 if (file.getFileName().toString().equals("MANIFEST.MF"))
754 return super.visitFile(file, attrs);
755 JarEntry entry = new JarEntry(bundleDir.relativize(file).toString());
756 jarOut.putNextEntry(entry);
757 Files.copy(file, jarOut);
758 return super.visitFile(file, attrs);
759 }
760
761 });
762 }
763 deleteDirectory(bundleDir);
764 return jarPath;
765 }
766
767 public static void main(String[] args) {
768 Path factoryBase = Paths.get("../../output/a2").toAbsolutePath().normalize();
769 A2Factory factory = new A2Factory(factoryBase);
770
771 Path descriptorsBase = Paths.get("../tp").toAbsolutePath().normalize();
772
773 // factory.processSingleM2ArtifactDistributionUnit(descriptorsBase.resolve("org.argeo.tp.apache").resolve("org.apache.xml.resolver.bnd"));
774 // factory.processM2BasedDistributionUnit(descriptorsBase.resolve("org.argeo.tp.apache/apache-sshd"));
775 // factory.processM2BasedDistributionUnit(descriptorsBase.resolve("org.argeo.tp.jetty/jetty"));
776 // factory.processM2BasedDistributionUnit(descriptorsBase.resolve("org.argeo.tp.jetty/jetty-websocket"));
777 // factory.processCategory(descriptorsBase.resolve("org.argeo.tp.eclipse.rcp"));
778 // factory.processCategory(descriptorsBase.resolve("org.argeo.tp"));
779 // factory.processCategory(descriptorsBase.resolve("org.argeo.tp.apache"));
780 // factory.processCategory(descriptorsBase.resolve("org.argeo.tp.formats"));
781 factory.processCategory(descriptorsBase.resolve("org.argeo.tp.gis"));
782 System.exit(0);
783
784 // Eclipse
785 factory.processEclipseArchive(
786 descriptorsBase.resolve("org.argeo.tp.eclipse.equinox").resolve("eclipse-equinox"));
787 factory.processEclipseArchive(descriptorsBase.resolve("org.argeo.tp.eclipse.rap").resolve("eclipse-rap"));
788 factory.processEclipseArchive(descriptorsBase.resolve("org.argeo.tp.eclipse.rcp").resolve("eclipse-rcp"));
789
790 System.exit(0);
791
792 // Maven
793 factory.processCategory(descriptorsBase.resolve("org.argeo.tp.sdk"));
794 factory.processCategory(descriptorsBase.resolve("org.argeo.tp"));
795 factory.processCategory(descriptorsBase.resolve("org.argeo.tp.apache"));
796 factory.processCategory(descriptorsBase.resolve("org.argeo.tp.jetty"));
797 factory.processCategory(descriptorsBase.resolve("org.argeo.tp.jcr"));
798 }
799 }