]> git.argeo.org Git - gpl/argeo-slc.git/blob - org.argeo.slc.factory/src/org/argeo/slc/factory/A2Factory.java
Improve merge
[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 if (entry.getName().equals("module-info.class"))
280 continue entries;
281 Path target = targetBundleDir.resolve(entry.getName());
282 Files.createDirectories(target.getParent());
283 if (!Files.exists(target)) {
284 Files.copy(jarIn, target);
285 } else {
286 if (entry.getName().startsWith("META-INF/services/")) {
287 try (OutputStream out = Files.newOutputStream(target, StandardOpenOption.APPEND)) {
288 out.write("\n".getBytes());
289 jarIn.transferTo(out);
290 System.out.println("Appended "+entry.getName());
291 }
292 } else {
293 throw new IllegalStateException("File " + target + " already exists");
294 }
295 }
296 logger.log(Level.TRACE, () -> "Copied " + target);
297 }
298
299 }
300 downloadAndProcessM2Sources(repoStr, artifact, targetBundleDir);
301 }
302
303 Map<String, String> entries = new TreeMap<>();
304 try (Analyzer bndAnalyzer = new Analyzer()) {
305 bndAnalyzer.setProperties(mergeProps);
306 Jar jar = new Jar(targetBundleDir.toFile());
307 bndAnalyzer.setJar(jar);
308 Manifest manifest = bndAnalyzer.calcManifest();
309
310 keys: for (Object key : manifest.getMainAttributes().keySet()) {
311 Object value = manifest.getMainAttributes().get(key);
312
313 switch (key.toString()) {
314 case "Tool":
315 case "Bnd-LastModified":
316 case "Created-By":
317 continue keys;
318 }
319 if ("Require-Capability".equals(key.toString())
320 && value.toString().equals("osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.1))\""))
321 continue keys;// hack for very old classes
322 entries.put(key.toString(), value.toString());
323 logger.log(DEBUG, () -> key + "=" + value);
324
325 }
326 } catch (Exception e) {
327 throw new RuntimeException("Cannot process " + mergeBnd, e);
328 }
329
330 Manifest manifest = new Manifest();
331 Path manifestPath = targetBundleDir.resolve("META-INF/MANIFEST.MF");
332 Files.createDirectories(manifestPath.getParent());
333 for (String key : entries.keySet()) {
334 String value = entries.get(key);
335 manifest.getMainAttributes().putValue(key, value);
336 }
337 try (OutputStream out = Files.newOutputStream(manifestPath)) {
338 manifest.write(out);
339 }
340
341 createJar(targetBundleDir);
342
343 }
344
345 protected void downloadAndProcessM2Sources(String repoStr, DefaultArtifact artifact, Path targetBundleDir)
346 throws IOException {
347 DefaultArtifact sourcesArtifact = new DefaultArtifact(artifact.toM2Coordinates(), "sources");
348 URL sourcesUrl = MavenConventionsUtils.mavenRepoUrl(repoStr, sourcesArtifact);
349 Path sourcesDownloaded = download(sourcesUrl, originBase, artifact.toM2Coordinates() + ".sources.jar");
350 processM2SourceJar(sourcesDownloaded, targetBundleDir);
351 logger.log(Level.DEBUG, () -> "Processed source " + sourcesDownloaded);
352
353 }
354
355 protected Path processBndJar(Path downloaded, Path targetCategoryBase, Properties fileProps,
356 DefaultArtifact artifact) {
357
358 try {
359 Map<String, String> additionalEntries = new TreeMap<>();
360 boolean doNotModify = Boolean.parseBoolean(fileProps
361 .getOrDefault(ManifestConstants.SLC_ORIGIN_MANIFEST_NOT_MODIFIED.toString(), "false").toString());
362
363 // we always force the symbolic name
364
365 if (doNotModify) {
366 fileEntries: for (Object key : fileProps.keySet()) {
367 if (ManifestConstants.SLC_ORIGIN_M2.toString().equals(key))
368 continue fileEntries;
369 String value = fileProps.getProperty(key.toString());
370 additionalEntries.put(key.toString(), value);
371 }
372 } else {
373 if (artifact != null) {
374 if (!fileProps.containsKey(BUNDLE_SYMBOLICNAME.toString())) {
375 fileProps.put(BUNDLE_SYMBOLICNAME.toString(), artifact.getName());
376 }
377 if (!fileProps.containsKey(BUNDLE_VERSION.toString())) {
378 fileProps.put(BUNDLE_VERSION.toString(), artifact.getVersion());
379 }
380 }
381
382 if (!fileProps.containsKey(EXPORT_PACKAGE.toString())) {
383 fileProps.put(EXPORT_PACKAGE.toString(),
384 "*;version=\"" + fileProps.getProperty(BUNDLE_VERSION.toString()) + "\"");
385 }
386 // if (!fileProps.contains(IMPORT_PACKAGE.toString())) {
387 // fileProps.put(IMPORT_PACKAGE.toString(), "*");
388 // }
389
390 try (Analyzer bndAnalyzer = new Analyzer()) {
391 bndAnalyzer.setProperties(fileProps);
392 Jar jar = new Jar(downloaded.toFile());
393 bndAnalyzer.setJar(jar);
394 Manifest manifest = bndAnalyzer.calcManifest();
395
396 keys: for (Object key : manifest.getMainAttributes().keySet()) {
397 Object value = manifest.getMainAttributes().get(key);
398
399 switch (key.toString()) {
400 case "Tool":
401 case "Bnd-LastModified":
402 case "Created-By":
403 continue keys;
404 }
405 if ("Require-Capability".equals(key.toString())
406 && value.toString().equals("osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.1))\""))
407 continue keys;// hack for very old classes
408 additionalEntries.put(key.toString(), value.toString());
409 logger.log(DEBUG, () -> key + "=" + value);
410
411 }
412 }
413
414 // try (Builder bndBuilder = new Builder()) {
415 // Jar jar = new Jar(downloaded.toFile());
416 // bndBuilder.addClasspath(jar);
417 // Path targetBundleDir = targetCategoryBase.resolve(artifact.getName() + "." + artifact.getBranch());
418 //
419 // Jar target = new Jar(targetBundleDir.toFile());
420 // bndBuilder.setJar(target);
421 // return targetBundleDir;
422 // }
423 }
424 Path targetBundleDir = processBundleJar(downloaded, targetCategoryBase, additionalEntries);
425 logger.log(Level.DEBUG, () -> "Processed " + downloaded);
426 return targetBundleDir;
427 } catch (Exception e) {
428 throw new RuntimeException("Cannot BND process " + downloaded, e);
429 }
430
431 }
432
433 protected void processM2SourceJar(Path file, Path targetBundleDir) throws IOException {
434 try (JarInputStream jarIn = new JarInputStream(Files.newInputStream(file), false)) {
435 Path targetSourceDir = targetBundleDir.resolve("OSGI-OPT/src");
436
437 // TODO make it less dangerous?
438 if (Files.exists(targetSourceDir)) {
439 // deleteDirectory(targetSourceDir);
440 } else {
441 Files.createDirectories(targetSourceDir);
442 }
443
444 // copy entries
445 JarEntry entry;
446 entries: while ((entry = jarIn.getNextJarEntry()) != null) {
447 if (entry.isDirectory())
448 continue entries;
449 if (entry.getName().startsWith("META-INF"))// skip META-INF entries
450 continue entries;
451 if (entry.getName().startsWith("module-info.java"))// skip META-INF entries
452 continue entries;
453 Path target = targetSourceDir.resolve(entry.getName());
454 Files.createDirectories(target.getParent());
455 Files.copy(jarIn, target);
456 logger.log(Level.TRACE, () -> "Copied source " + target);
457 }
458 }
459
460 }
461
462 public void processEclipseArchive(Path duDir) {
463 try {
464 String category = duDir.getParent().getFileName().toString();
465 Path targetCategoryBase = a2Base.resolve(category);
466 Files.createDirectories(targetCategoryBase);
467 // first delete all directories from previous builds
468 for (Path dir : Files.newDirectoryStream(targetCategoryBase, (p) -> Files.isDirectory(p))) {
469 deleteDirectory(dir);
470 }
471
472 Files.createDirectories(originBase);
473
474 Path commonBnd = duDir.resolve(COMMON_BND);
475 Properties commonProps = new Properties();
476 try (InputStream in = Files.newInputStream(commonBnd)) {
477 commonProps.load(in);
478 }
479 Properties includes = new Properties();
480 try (InputStream in = Files.newInputStream(duDir.resolve("includes.properties"))) {
481 includes.load(in);
482 }
483 String url = commonProps.getProperty(ManifestConstants.SLC_ORIGIN_URI.toString());
484 Path downloaded = tryDownload(url, originBase);
485
486 FileSystem zipFs = FileSystems.newFileSystem(downloaded, (ClassLoader) null);
487
488 List<PathMatcher> pathMatchers = new ArrayList<>();
489 for (Object pattern : includes.keySet()) {
490 PathMatcher pathMatcher = zipFs.getPathMatcher("glob:/" + pattern);
491 pathMatchers.add(pathMatcher);
492 }
493
494 Files.walkFileTree(zipFs.getRootDirectories().iterator().next(), new SimpleFileVisitor<Path>() {
495
496 @Override
497 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
498 pathMatchers: for (PathMatcher pathMatcher : pathMatchers) {
499 if (pathMatcher.matches(file)) {
500 if (file.getFileName().toString().contains(".source_")) {
501 processEclipseSourceJar(file, targetCategoryBase);
502 logger.log(Level.DEBUG, () -> "Processed source " + file);
503
504 } else {
505 processBundleJar(file, targetCategoryBase, new HashMap<>());
506 logger.log(Level.DEBUG, () -> "Processed " + file);
507 }
508 break pathMatchers;
509 }
510 }
511 return FileVisitResult.CONTINUE;
512 }
513 });
514
515 DirectoryStream<Path> dirs = Files.newDirectoryStream(targetCategoryBase, (p) -> Files.isDirectory(p));
516 for (Path dir : dirs) {
517 createJar(dir);
518 }
519 } catch (IOException e) {
520 throw new RuntimeException("Cannot process " + duDir, e);
521 }
522
523 }
524
525 protected Path processBundleJar(Path file, Path targetBase, Map<String, String> entries) throws IOException {
526 DefaultNameVersion nameVersion;
527 Path targetBundleDir;
528 try (JarInputStream jarIn = new JarInputStream(Files.newInputStream(file), false)) {
529 Manifest manifest = new Manifest(jarIn.getManifest());
530
531 // remove problematic entries in MANIFEST
532 manifest.getEntries().clear();
533 // Set<String> entriesToDelete = new HashSet<>();
534 // for (String key : manifest.getEntries().keySet()) {
535 //// logger.log(DEBUG, "## " + key);
536 // Attributes attrs = manifest.getAttributes(key);
537 // for (Object attrName : attrs.keySet()) {
538 //// logger.log(DEBUG, attrName + "=" + attrs.get(attrName));
539 // if ("Specification-Version".equals(attrName.toString())
540 // || "Implementation-Version".equals(attrName.toString())) {
541 // entriesToDelete.add(key);
542 //
543 // }
544 // }
545 // }
546 // for (String key : entriesToDelete) {
547 // manifest.getEntries().remove(key);
548 // }
549
550 String symbolicNameFromEntries = entries.get(BUNDLE_SYMBOLICNAME.toString());
551 String versionFromEntries = entries.get(BUNDLE_VERSION.toString());
552
553 if (symbolicNameFromEntries != null && versionFromEntries != null) {
554 nameVersion = new DefaultNameVersion(symbolicNameFromEntries, versionFromEntries);
555 } else {
556 nameVersion = nameVersionFromManifest(manifest);
557 if (versionFromEntries != null && !nameVersion.getVersion().equals(versionFromEntries)) {
558 logger.log(Level.WARNING, "Original version is " + nameVersion.getVersion()
559 + " while new version is " + versionFromEntries);
560 }
561 if (symbolicNameFromEntries != null) {
562 // we always force our symbolic name
563 nameVersion.setName(symbolicNameFromEntries);
564 }
565 }
566 targetBundleDir = targetBase.resolve(nameVersion.getName() + "." + nameVersion.getBranch());
567
568 // TODO make it less dangerous?
569 // if (Files.exists(targetBundleDir)) {
570 // deleteDirectory(targetBundleDir);
571 // }
572
573 // copy entries
574 JarEntry entry;
575 entries: while ((entry = jarIn.getNextJarEntry()) != null) {
576 if (entry.isDirectory())
577 continue entries;
578 if (entry.getName().endsWith(".RSA") || entry.getName().endsWith(".SF"))
579 continue entries;
580 Path target = targetBundleDir.resolve(entry.getName());
581 Files.createDirectories(target.getParent());
582 Files.copy(jarIn, target);
583 logger.log(Level.TRACE, () -> "Copied " + target);
584 }
585
586 // copy MANIFEST
587 Path manifestPath = targetBundleDir.resolve("META-INF/MANIFEST.MF");
588 Files.createDirectories(manifestPath.getParent());
589 for (String key : entries.keySet()) {
590 String value = entries.get(key);
591 Object previousValue = manifest.getMainAttributes().putValue(key, value);
592 if (previousValue != null && !previousValue.equals(value)) {
593 if (ManifestConstants.IMPORT_PACKAGE.toString().equals(key)
594 || ManifestConstants.EXPORT_PACKAGE.toString().equals(key))
595 logger.log(Level.WARNING, file.getFileName() + ": " + key + " was modified");
596
597 else
598 logger.log(Level.WARNING, file.getFileName() + ": " + key + " was " + previousValue
599 + ", overridden with " + value);
600 }
601 }
602 try (OutputStream out = Files.newOutputStream(manifestPath)) {
603 manifest.write(out);
604 }
605 }
606 return targetBundleDir;
607 }
608
609 protected void processEclipseSourceJar(Path file, Path targetBase) throws IOException {
610 try {
611 Path targetBundleDir;
612 try (JarInputStream jarIn = new JarInputStream(Files.newInputStream(file), false)) {
613 Manifest manifest = jarIn.getManifest();
614
615 String[] relatedBundle = manifest.getMainAttributes().getValue("Eclipse-SourceBundle").split(";");
616 String version = relatedBundle[1].substring("version=\"".length());
617 version = version.substring(0, version.length() - 1);
618 NameVersion nameVersion = new DefaultNameVersion(relatedBundle[0], version);
619 targetBundleDir = targetBase.resolve(nameVersion.getName() + "." + nameVersion.getBranch());
620
621 Path targetSourceDir = targetBundleDir.resolve("OSGI-OPT/src");
622
623 // TODO make it less dangerous?
624 if (Files.exists(targetSourceDir)) {
625 // deleteDirectory(targetSourceDir);
626 } else {
627 Files.createDirectories(targetSourceDir);
628 }
629
630 // copy entries
631 JarEntry entry;
632 entries: while ((entry = jarIn.getNextJarEntry()) != null) {
633 if (entry.isDirectory())
634 continue entries;
635 if (entry.getName().startsWith("META-INF"))// skip META-INF entries
636 continue entries;
637 Path target = targetSourceDir.resolve(entry.getName());
638 Files.createDirectories(target.getParent());
639 Files.copy(jarIn, target);
640 logger.log(Level.TRACE, () -> "Copied source " + target);
641 }
642
643 // copy MANIFEST
644 // Path manifestPath = targetBundleDir.resolve("META-INF/MANIFEST.MF");
645 // Files.createDirectories(manifestPath.getParent());
646 // try (OutputStream out = Files.newOutputStream(manifestPath)) {
647 // manifest.write(out);
648 // }
649 }
650 } catch (IOException e) {
651 throw new IllegalStateException("Cannot process " + file, e);
652 }
653
654 }
655
656 static void deleteDirectory(Path path) throws IOException {
657 if (!Files.exists(path))
658 return;
659 Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
660 @Override
661 public FileVisitResult postVisitDirectory(Path directory, IOException e) throws IOException {
662 if (e != null)
663 throw e;
664 Files.delete(directory);
665 return FileVisitResult.CONTINUE;
666 }
667
668 @Override
669 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
670 Files.delete(file);
671 return FileVisitResult.CONTINUE;
672 }
673 });
674 }
675
676 protected DefaultNameVersion nameVersionFromManifest(Manifest manifest) {
677 Attributes attrs = manifest.getMainAttributes();
678 // symbolic name
679 String symbolicName = attrs.getValue(ManifestConstants.BUNDLE_SYMBOLICNAME.toString());
680 if (symbolicName == null)
681 return null;
682 // make sure there is no directive
683 symbolicName = symbolicName.split(";")[0];
684
685 String version = attrs.getValue(ManifestConstants.BUNDLE_VERSION.toString());
686 return new DefaultNameVersion(symbolicName, version);
687 }
688
689 protected Path tryDownload(String uri, Path dir) throws IOException {
690 // find mirror
691 List<String> urlBases = null;
692 String uriPrefix = null;
693 uriPrefixes: for (String uriPref : mirrors.keySet()) {
694 if (uri.startsWith(uriPref)) {
695 if (mirrors.get(uriPref).size() > 0) {
696 urlBases = mirrors.get(uriPref);
697 uriPrefix = uriPref;
698 break uriPrefixes;
699 }
700 }
701 }
702 if (urlBases == null)
703 try {
704 return download(new URL(uri), dir, null);
705 } catch (FileNotFoundException e) {
706 throw new FileNotFoundException("Cannot find " + uri);
707 }
708
709 // try to download
710 for (String urlBase : urlBases) {
711 String relativePath = uri.substring(uriPrefix.length());
712 URL url = new URL(urlBase + relativePath);
713 try {
714 return download(url, dir, null);
715 } catch (FileNotFoundException e) {
716 logger.log(Level.WARNING, "Cannot download " + url + ", trying another mirror");
717 }
718 }
719 throw new FileNotFoundException("Cannot find " + uri);
720 }
721
722 // protected String simplifyName(URL u) {
723 // String name = u.getPath().substring(u.getPath().lastIndexOf('/') + 1);
724 //
725 // }
726
727 protected Path download(URL url, Path dir, String name) throws IOException {
728
729 Path dest;
730 if (name == null) {
731 name = url.getPath().substring(url.getPath().lastIndexOf('/') + 1);
732 }
733
734 dest = dir.resolve(name);
735 if (Files.exists(dest)) {
736 logger.log(Level.TRACE, () -> "File " + dest + " already exists for " + url + ", not downloading again");
737 return dest;
738 }
739
740 try (InputStream in = url.openStream()) {
741 Files.copy(in, dest);
742 logger.log(Level.DEBUG, () -> "Downloaded " + dest + " from " + url);
743 }
744 return dest;
745 }
746
747 protected Path createJar(Path bundleDir) throws IOException {
748 Path jarPath = bundleDir.getParent().resolve(bundleDir.getFileName() + ".jar");
749 Path manifestPath = bundleDir.resolve("META-INF/MANIFEST.MF");
750 Manifest manifest;
751 try (InputStream in = Files.newInputStream(manifestPath)) {
752 manifest = new Manifest(in);
753 }
754 try (JarOutputStream jarOut = new JarOutputStream(Files.newOutputStream(jarPath), manifest)) {
755 Files.walkFileTree(bundleDir, new SimpleFileVisitor<Path>() {
756
757 @Override
758 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
759 if (file.getFileName().toString().equals("MANIFEST.MF"))
760 return super.visitFile(file, attrs);
761 JarEntry entry = new JarEntry(bundleDir.relativize(file).toString());
762 jarOut.putNextEntry(entry);
763 Files.copy(file, jarOut);
764 return super.visitFile(file, attrs);
765 }
766
767 });
768 }
769 deleteDirectory(bundleDir);
770 return jarPath;
771 }
772
773 public static void main(String[] args) {
774 Path factoryBase = Paths.get("../../output/a2").toAbsolutePath().normalize();
775 A2Factory factory = new A2Factory(factoryBase);
776
777 Path descriptorsBase = Paths.get("../tp").toAbsolutePath().normalize();
778
779 // factory.processSingleM2ArtifactDistributionUnit(descriptorsBase.resolve("org.argeo.tp.apache").resolve("org.apache.xml.resolver.bnd"));
780 // factory.processM2BasedDistributionUnit(descriptorsBase.resolve("org.argeo.tp.apache/apache-sshd"));
781 // factory.processM2BasedDistributionUnit(descriptorsBase.resolve("org.argeo.tp.jetty/jetty"));
782 // factory.processM2BasedDistributionUnit(descriptorsBase.resolve("org.argeo.tp.jetty/jetty-websocket"));
783 // factory.processCategory(descriptorsBase.resolve("org.argeo.tp.eclipse.rcp"));
784 // factory.processCategory(descriptorsBase.resolve("org.argeo.tp"));
785 // factory.processCategory(descriptorsBase.resolve("org.argeo.tp.apache"));
786 // factory.processCategory(descriptorsBase.resolve("org.argeo.tp.formats"));
787 factory.processCategory(descriptorsBase.resolve("org.argeo.tp.gis"));
788 System.exit(0);
789
790 // Eclipse
791 factory.processEclipseArchive(
792 descriptorsBase.resolve("org.argeo.tp.eclipse.equinox").resolve("eclipse-equinox"));
793 factory.processEclipseArchive(descriptorsBase.resolve("org.argeo.tp.eclipse.rap").resolve("eclipse-rap"));
794 factory.processEclipseArchive(descriptorsBase.resolve("org.argeo.tp.eclipse.rcp").resolve("eclipse-rcp"));
795
796 System.exit(0);
797
798 // Maven
799 factory.processCategory(descriptorsBase.resolve("org.argeo.tp.sdk"));
800 factory.processCategory(descriptorsBase.resolve("org.argeo.tp"));
801 factory.processCategory(descriptorsBase.resolve("org.argeo.tp.apache"));
802 factory.processCategory(descriptorsBase.resolve("org.argeo.tp.jetty"));
803 factory.processCategory(descriptorsBase.resolve("org.argeo.tp.jcr"));
804 }
805 }