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