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