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