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