1 package com.atlassian.plugin.osgi.util;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.util.ArrayList;
7 import java.util.Collections;
8 import java.util.HashMap;
9 import java.util.HashSet;
10 import java.util.Iterator;
11 import java.util.LinkedHashMap;
12 import java.util.List;
13 import java.util.Map;
14 import java.util.Set;
15 import java.util.jar.JarFile;
16 import java.util.jar.Manifest;
17
18 import com.atlassian.annotations.Internal;
19 import com.atlassian.plugin.PluginArtifact;
20 import com.atlassian.plugin.PluginInformation;
21 import com.atlassian.plugin.PluginParseException;
22 import com.atlassian.plugin.PluginPermission;
23 import com.atlassian.plugin.osgi.factory.OsgiPlugin;
24 import com.atlassian.plugin.osgi.hostcomponents.HostComponentRegistration;
25 import com.atlassian.plugin.osgi.util.ClassBinaryScanner.InputStreamResource;
26 import com.atlassian.plugin.osgi.util.ClassBinaryScanner.ScanResult;
27 import com.atlassian.plugin.util.ClassLoaderUtils;
28 import com.atlassian.plugin.util.ClassUtils;
29
30 import com.google.common.base.Predicate;
31 import com.google.common.collect.ImmutableMap;
32 import com.google.common.collect.ImmutableSet;
33 import com.google.common.collect.Sets;
34
35 import org.apache.commons.io.IOUtils;
36 import org.osgi.framework.Bundle;
37 import org.osgi.framework.Constants;
38 import org.osgi.framework.Version;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 import aQute.lib.osgi.Clazz;
43 import aQute.libg.header.OSGiHeader;
44
45 import static com.atlassian.plugin.osgi.factory.transform.JarUtils.closeQuietly;
46 import static com.google.common.base.Preconditions.checkArgument;
47 import static com.google.common.base.Preconditions.checkNotNull;
48
49
50
51
52 public class OsgiHeaderUtil
53 {
54 static Logger log = LoggerFactory.getLogger(OsgiHeaderUtil.class);
55 private static final String EMPTY_OSGI_VERSION = Version.emptyVersion.toString();
56 private static final String STAR_PACKAGE = "*";
57
58
59
60
61
62
63
64
65
66
67 public static Set<String> findReferredPackageNames(List<HostComponentRegistration> registrations) throws IOException
68 {
69 return findReferredPackagesInternal(registrations);
70 }
71
72
73
74
75
76
77
78
79
80 public static Map<String, String> findReferredPackageVersions(List<HostComponentRegistration> registrations, Map<String, String> packageVersions) throws IOException
81 {
82 Set<String> referredPackages = findReferredPackagesInternal(registrations);
83 return matchPackageVersions(referredPackages, packageVersions);
84 }
85
86
87
88
89
90
91
92
93
94
95 @Deprecated
96 public static String findReferredPackages(List<HostComponentRegistration> registrations) throws IOException
97 {
98 return findReferredPackages(registrations, Collections.<String, String>emptyMap());
99 }
100
101
102
103
104
105
106
107
108
109 @Deprecated
110 public static String findReferredPackages(List<HostComponentRegistration> registrations, Map<String, String> packageVersions) throws IOException
111 {
112 StringBuffer sb = new StringBuffer();
113 Set<String> referredPackages = new HashSet<String>();
114 Set<String> referredClasses = new HashSet<String>();
115 if (registrations == null)
116 {
117 sb.append(",");
118 }
119 else
120 {
121 for (HostComponentRegistration reg : registrations)
122 {
123 Set<Class> classesToScan = new HashSet<Class>();
124
125
126 for (Class inf : reg.getMainInterfaceClasses())
127 ClassUtils.findAllTypes(inf, classesToScan);
128
129 for (Class inf : classesToScan)
130 {
131 String clsName = inf.getName().replace('.','/')+".class";
132 crawlReferenceTree(clsName, referredClasses, referredPackages, 1);
133 }
134 }
135 for (String pkg : referredPackages)
136 {
137 String version = packageVersions.get(pkg);
138 sb.append(pkg);
139 if (version != null) {
140 try {
141 Version.parseVersion(version);
142 sb.append(";version=").append(version);
143 } catch (IllegalArgumentException ex) {
144 log.info("Unable to parse version: "+version);
145 }
146 }
147 sb.append(",");
148 }
149 }
150 return sb.toString();
151 }
152
153 static Map<String, String> matchPackageVersions(Set<String> packageNames, Map<String, String> packageVersions)
154 {
155 Map<String, String> output = new HashMap<String, String>();
156
157 for (String pkg : packageNames)
158 {
159 String version = packageVersions.get(pkg);
160
161 String effectiveKey = pkg;
162 String effectiveValue = EMPTY_OSGI_VERSION;
163
164 if (version != null)
165 {
166 try
167 {
168 Version.parseVersion(version);
169 effectiveValue = version;
170 }
171 catch (IllegalArgumentException ex)
172 {
173 log.info("Unable to parse version: "+version);
174 }
175 }
176 output.put(effectiveKey, effectiveValue);
177 }
178
179 return ImmutableMap.copyOf(output);
180 }
181
182 static Set<String> findReferredPackagesInternal(List<HostComponentRegistration> registrations) throws IOException
183 {
184 final Set<String> referredPackages = new HashSet<String>();
185 final Set<String> referredClasses = new HashSet<String>();
186
187 if (registrations != null)
188 {
189 for (HostComponentRegistration reg : registrations)
190 {
191 Set<Class> classesToScan = new HashSet<Class>();
192
193
194 for (Class inf : reg.getMainInterfaceClasses())
195 {
196 ClassUtils.findAllTypes(inf, classesToScan);
197 }
198
199 for (Class inf : classesToScan)
200 {
201 String clsName = inf.getName().replace('.','/')+".class";
202 crawlReferenceTree(clsName, referredClasses, referredPackages, 1);
203 }
204 }
205 }
206
207 return ImmutableSet.copyOf(referredPackages);
208 }
209
210
211
212
213 private static final Predicate<String> JAVA_PACKAGE_FILTER = new Predicate<String>()
214 {
215 public boolean apply(String pkg)
216 {
217 return !pkg.startsWith("java.");
218 }
219 };
220
221
222
223
224 private static final Predicate<String> JAVA_CLASS_FILTER = new Predicate<String>()
225 {
226 public boolean apply(String classEntry)
227 {
228 return !classEntry.startsWith("java/");
229 }
230 };
231
232
233
234
235
236
237
238
239
240
241 static void crawlReferenceTree(String className, Set<String> scannedClasses, Set<String> packageImports, int level) throws IOException
242 {
243 if (level <= 0)
244 {
245 return;
246 }
247
248 if (className.startsWith("java/"))
249 return;
250
251 if (scannedClasses.contains(className))
252 return;
253 else
254 scannedClasses.add(className);
255
256 if (log.isDebugEnabled())
257 log.debug("Crawling "+className);
258
259 InputStreamResource classBinaryResource = null;
260 try
261 {
262
263 InputStream in = ClassLoaderUtils.getResourceAsStream(className, OsgiHeaderUtil.class);
264 if (in == null)
265 {
266 log.error("Cannot find class: [" + className + "]");
267 return;
268 }
269
270
271 classBinaryResource = new InputStreamResource(in);
272 final ScanResult scanResult = ClassBinaryScanner.scanClassBinary(new Clazz(className, classBinaryResource));
273
274
275 packageImports.addAll(Sets.filter(scanResult.getReferredPackages(), JAVA_PACKAGE_FILTER));
276
277
278 Set<String> referredClasses = Sets.filter(scanResult.getReferredClasses(), JAVA_CLASS_FILTER);
279 for (String ref : referredClasses)
280 {
281 crawlReferenceTree(ref + ".class", scannedClasses, packageImports, level-1);
282 }
283 }
284 finally
285 {
286 if (classBinaryResource != null)
287 {
288 classBinaryResource.close();
289 }
290 }
291 }
292
293
294
295
296
297
298
299
300
301 public static Map<String,Map<String,String>> parseHeader(String header)
302 {
303 return OSGiHeader.parseHeader(header);
304 }
305
306
307
308
309
310
311
312 public static String buildHeader(Map<String,Map<String,String>> values)
313 {
314 StringBuilder header = new StringBuilder();
315 for (Iterator<Map.Entry<String,Map<String,String>>> i = values.entrySet().iterator(); i.hasNext(); )
316 {
317 Map.Entry<String,Map<String,String>> entry = i.next();
318 buildHeader(entry.getKey(), entry.getValue(), header);
319 if (i.hasNext())
320 {
321 header.append(",");
322 }
323 }
324 return header.toString();
325 }
326
327
328
329
330
331
332
333
334 public static String buildHeader(String key, Map<String,String> attrs)
335 {
336 StringBuilder fullPkg = new StringBuilder();
337 buildHeader(key, attrs, fullPkg);
338 return fullPkg.toString();
339 }
340
341
342
343
344
345 private static void buildHeader(String key, Map<String,String> attrs, StringBuilder builder)
346 {
347 builder.append(key);
348 if (attrs != null && !attrs.isEmpty())
349 {
350 for (Map.Entry<String,String> entry : attrs.entrySet())
351 {
352 builder.append(";");
353 builder.append(entry.getKey());
354 builder.append("=\"");
355 builder.append(entry.getValue());
356 builder.append("\"");
357 }
358 }
359 }
360
361
362
363
364
365
366
367
368
369
370
371
372
373 public static String getPluginKey(Bundle bundle)
374 {
375 return getPluginKey(
376 bundle.getSymbolicName(),
377 bundle.getHeaders().get(OsgiPlugin.ATLASSIAN_PLUGIN_KEY),
378 bundle.getHeaders().get(Constants.BUNDLE_VERSION)
379 );
380 }
381
382
383
384
385
386
387
388 public static String getPluginKey(final File file)
389 {
390 JarFile jar = null;
391 try
392 {
393 jar = new JarFile(file);
394 final Manifest manifest = jar.getManifest();
395 if (manifest != null)
396 {
397 return getPluginKey(manifest);
398 }
399 }
400 catch(final IOException eio)
401 {
402 log.warn("Cannot read jar file '" + file + "': " + eio.getMessage());
403 }
404 finally
405 {
406 closeQuietly(jar);
407 }
408 return null;
409 }
410
411
412
413
414
415
416
417
418 public static String getPluginKey(Manifest mf)
419 {
420 return getPluginKey(
421 getAttributeWithoutValidation(mf, Constants.BUNDLE_SYMBOLICNAME),
422 getAttributeWithoutValidation(mf, OsgiPlugin.ATLASSIAN_PLUGIN_KEY),
423 getAttributeWithoutValidation(mf, Constants.BUNDLE_VERSION)
424 );
425 }
426
427 private static String getPluginKey(Object bundleName, Object atlKey, Object version)
428 {
429 Object key = atlKey;
430 if (key == null)
431 {
432 key = bundleName + "-" + version;
433 }
434
435 return key.toString();
436 }
437
438
439
440
441
442
443
444 public static String generatePackageVersionString(Map<String, String> packages)
445 {
446 if (packages == null || packages.size()==0)
447 {
448 return "";
449 }
450
451 final StringBuilder sb = new StringBuilder();
452
453
454 List<String> packageNames = new ArrayList<String>(packages.keySet());
455 Collections.sort(packageNames);
456
457 for(String packageName:packageNames)
458 {
459 sb.append(",");
460 sb.append(packageName);
461
462 String version = packages.get(packageName);
463
464
465 if (version != null && !version.equals(EMPTY_OSGI_VERSION))
466 {
467 sb.append(";version=").append(version);
468 }
469 }
470
471
472 sb.delete(0, 1);
473
474 return sb.toString();
475 }
476
477
478
479
480
481
482
483
484
485 public static String getValidatedAttribute(final Manifest manifest, String key)
486 {
487 String value = getAttributeWithoutValidation(manifest, key);
488 checkNotNull(value);
489 checkArgument(!value.isEmpty());
490 return value;
491 }
492
493
494
495
496
497
498
499
500 public static String getNonEmptyAttribute(final Manifest manifest, String key)
501 {
502 String attributeWithoutValidation = getAttributeWithoutValidation(manifest, key);
503 checkArgument(!attributeWithoutValidation.isEmpty());
504 return attributeWithoutValidation;
505 }
506
507
508
509
510
511
512
513 public static String getAttributeWithoutValidation(final Manifest manifest, String key)
514 {
515 return manifest.getMainAttributes().getValue(key);
516 }
517
518
519
520
521
522
523
524 @Internal
525 public static PluginInformation extractOsgiPluginInformation(final Manifest manifest, final boolean requireVersion)
526 {
527 final String bundleVersion = requireVersion
528 ? getValidatedAttribute(manifest, Constants.BUNDLE_VERSION)
529 : getAttributeWithoutValidation(manifest, Constants.BUNDLE_VERSION);
530 final String bundleVendor = getAttributeWithoutValidation(manifest, Constants.BUNDLE_VENDOR);
531 final String bundleDescription = getAttributeWithoutValidation(manifest, Constants.BUNDLE_DESCRIPTION);
532 final PluginInformation pluginInformation = new PluginInformation();
533 pluginInformation.setVersion(bundleVersion);
534 pluginInformation.setDescription(bundleDescription);
535 pluginInformation.setVendorName(bundleVendor);
536
537 pluginInformation.setPermissions(ImmutableSet.of(PluginPermission.EXECUTE_JAVA));
538 return pluginInformation;
539 }
540
541
542
543
544
545
546
547 public static Manifest getManifest(final PluginArtifact pluginArtifact)
548 {
549 try
550 {
551 final InputStream manifestStream = pluginArtifact.getResourceAsStream(JarFile.MANIFEST_NAME);
552 if (manifestStream != null)
553 {
554 try
555 {
556 return new Manifest(manifestStream);
557 }
558 catch (final IOException eio)
559 {
560 log.error("Cannot read manifest from plugin artifact '" + pluginArtifact.getName() + "': " + eio.getMessage());
561 }
562 finally
563 {
564 IOUtils.closeQuietly(manifestStream);
565 }
566 }
567 }
568 catch (final PluginParseException epp)
569 {
570 log.error("Cannot get manifest resource from plugin artifact '" + pluginArtifact.getName() + "': " + epp.getMessage());
571 }
572 return null;
573 }
574
575
576
577
578
579
580
581
582 public static boolean containsStarPackage(final String packageString)
583 {
584 final Map<String, Map<String, String>> pkgs = OsgiHeaderUtil.parseHeader(packageString);
585 return pkgs.containsKey(STAR_PACKAGE);
586 }
587
588
589
590
591
592
593
594
595
596
597
598 public static String moveStarPackageToEnd(final String packageString, final String pluginKey)
599 {
600
601 final Map<String, Map<String, String>> currentPkgs = OsgiHeaderUtil.parseHeader(packageString);
602
603 final LinkedHashMap<String, Map<String, String>> starPkgs = new LinkedHashMap<String, Map<String, String>>();
604 final LinkedHashMap<String, Map<String, String>> orderedPkgs = new LinkedHashMap<String, Map<String, String>>();
605
606
607 for (Map.Entry<String, Map<String, String>> pkg : currentPkgs.entrySet())
608 {
609 if (pkg.getKey().contains(STAR_PACKAGE))
610 {
611 starPkgs.put(pkg.getKey(), pkg.getValue());
612 }
613 else
614 {
615 orderedPkgs.put(pkg.getKey(), pkg.getValue());
616 }
617 }
618
619
620 for (Map.Entry<String, Map<String, String>> starPkg : starPkgs.entrySet())
621 {
622 log.debug("moving {} package to end for plugin {}", starPkg.getKey(), pluginKey);
623 orderedPkgs.put(starPkg.getKey(), starPkg.getValue());
624 }
625
626
627 return buildHeader(orderedPkgs);
628 }
629 }