PageRenderTime 54ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/atlassian-plugins-osgi/src/main/java/com/atlassian/plugin/osgi/util/OsgiHeaderUtil.java

https://bitbucket.org/abusch/atlassian-plugins
Java | 431 lines | 274 code | 47 blank | 110 comment | 40 complexity | 45cd41678b8a782dceb840bc9b49c61d MD5 | raw file
Possible License(s): BSD-3-Clause
  1. package com.atlassian.plugin.osgi.util;
  2. import aQute.lib.osgi.Clazz;
  3. import aQute.libg.header.OSGiHeader;
  4. import com.atlassian.plugin.osgi.factory.OsgiPlugin;
  5. import com.atlassian.plugin.osgi.hostcomponents.HostComponentRegistration;
  6. import com.atlassian.plugin.util.ClassLoaderUtils;
  7. import com.atlassian.plugin.util.ClassUtils;
  8. import com.atlassian.plugin.osgi.util.ClassBinaryScanner.InputStreamResource;
  9. import com.atlassian.plugin.osgi.util.ClassBinaryScanner.ScanResult;
  10. import com.google.common.base.Predicate;
  11. import com.google.common.collect.ImmutableMap;
  12. import com.google.common.collect.ImmutableSet;
  13. import com.google.common.collect.Sets;
  14. import org.osgi.framework.Bundle;
  15. import org.osgi.framework.Constants;
  16. import org.osgi.framework.Version;
  17. import org.slf4j.Logger;
  18. import org.slf4j.LoggerFactory;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.util.ArrayList;
  22. import java.util.Collections;
  23. import java.util.HashMap;
  24. import java.util.HashSet;
  25. import java.util.Iterator;
  26. import java.util.List;
  27. import java.util.Map;
  28. import java.util.Set;
  29. import java.util.jar.Manifest;
  30. /**
  31. * Utilities to help create OSGi headers
  32. */
  33. public class OsgiHeaderUtil
  34. {
  35. static Logger log = LoggerFactory.getLogger(OsgiHeaderUtil.class);
  36. private static final String EMPTY_OSGI_VERSION = Version.emptyVersion.toString();
  37. /**
  38. * Finds all referred packages for host component registrations by scanning their declared interfaces' bytecode.
  39. * Packages starting with "java." are ignored.
  40. *
  41. * @param registrations A list of host component registrations
  42. * @return The set of referred packages.
  43. * @throws IOException If there are any problems scanning bytecode
  44. * @since 2.7.0
  45. */
  46. public static Set<String> findReferredPackageNames(List<HostComponentRegistration> registrations) throws IOException
  47. {
  48. return findReferredPackagesInternal(registrations);
  49. }
  50. /**
  51. * Finds all referred packages for host component registrations by scanning their declared interfaces' bytecode.
  52. *
  53. * @param registrations A list of host component registrations
  54. * @return The referred package map ( package-> version ).
  55. * @throws IOException If there are any problems scanning bytecode
  56. * @since 2.7.0
  57. */
  58. public static Map<String, String> findReferredPackageVersions(List<HostComponentRegistration> registrations, Map<String, String> packageVersions) throws IOException
  59. {
  60. Set<String> referredPackages = findReferredPackagesInternal(registrations);
  61. return matchPackageVersions(referredPackages, packageVersions);
  62. }
  63. /**
  64. * Finds all referred packages for host component registrations by scanning their declared interfaces' bytecode.
  65. *
  66. * @param registrations A list of host component registrations
  67. * @return The referred packages in a format compatible with an OSGi header
  68. * @throws IOException If there are any problems scanning bytecode
  69. * @since 2.4.0
  70. * @deprecated Since 2.7.0, use {@link #findReferredPackageNames(java.util.List)} instead.
  71. */
  72. @Deprecated
  73. public static String findReferredPackages(List<HostComponentRegistration> registrations) throws IOException
  74. {
  75. return findReferredPackages(registrations, Collections.<String, String>emptyMap());
  76. }
  77. /**
  78. * Finds all referred packages for host component registrations by scanning their declared interfaces' bytecode.
  79. *
  80. * @param registrations A list of host component registrations
  81. * @return The referred packages in a format compatible with an OSGi header
  82. * @throws IOException If there are any problems scanning bytecode
  83. * @deprecated Since 2.7.0, use {@link #findReferredPackageVersions(java.util.List, java.util.Map)} instead.
  84. */
  85. @Deprecated
  86. public static String findReferredPackages(List<HostComponentRegistration> registrations, Map<String, String> packageVersions) throws IOException
  87. {
  88. StringBuffer sb = new StringBuffer();
  89. Set<String> referredPackages = new HashSet<String>();
  90. Set<String> referredClasses = new HashSet<String>();
  91. if (registrations == null)
  92. {
  93. sb.append(",");
  94. }
  95. else
  96. {
  97. for (HostComponentRegistration reg : registrations)
  98. {
  99. Set<Class> classesToScan = new HashSet<Class>();
  100. // Make sure we scan all extended interfaces as well
  101. for (Class inf : reg.getMainInterfaceClasses())
  102. ClassUtils.findAllTypes(inf, classesToScan);
  103. for (Class inf : classesToScan)
  104. {
  105. String clsName = inf.getName().replace('.','/')+".class";
  106. crawlReferenceTree(clsName, referredClasses, referredPackages, 1);
  107. }
  108. }
  109. for (String pkg : referredPackages)
  110. {
  111. String version = packageVersions.get(pkg);
  112. sb.append(pkg);
  113. if (version != null) {
  114. try {
  115. Version.parseVersion(version);
  116. sb.append(";version=").append(version);
  117. } catch (IllegalArgumentException ex) {
  118. log.info("Unable to parse version: "+version);
  119. }
  120. }
  121. sb.append(",");
  122. }
  123. }
  124. return sb.toString();
  125. }
  126. static Map<String, String> matchPackageVersions(Set<String> packageNames, Map<String, String> packageVersions)
  127. {
  128. Map<String, String> output = new HashMap<String, String>();
  129. for (String pkg : packageNames)
  130. {
  131. String version = packageVersions.get(pkg);
  132. String effectiveKey = pkg;
  133. String effectiveValue = EMPTY_OSGI_VERSION;
  134. if (version != null)
  135. {
  136. try
  137. {
  138. Version.parseVersion(version);
  139. effectiveValue = version;
  140. }
  141. catch (IllegalArgumentException ex)
  142. {
  143. log.info("Unable to parse version: "+version);
  144. }
  145. }
  146. output.put(effectiveKey, effectiveValue);
  147. }
  148. return ImmutableMap.copyOf(output);
  149. }
  150. static Set<String> findReferredPackagesInternal(List<HostComponentRegistration> registrations) throws IOException
  151. {
  152. final Set<String> referredPackages = new HashSet<String>();
  153. final Set<String> referredClasses = new HashSet<String>();
  154. if (registrations != null)
  155. {
  156. for (HostComponentRegistration reg : registrations)
  157. {
  158. Set<Class> classesToScan = new HashSet<Class>();
  159. // Make sure we scan all extended interfaces as well
  160. for (Class inf : reg.getMainInterfaceClasses())
  161. {
  162. ClassUtils.findAllTypes(inf, classesToScan);
  163. }
  164. for (Class inf : classesToScan)
  165. {
  166. String clsName = inf.getName().replace('.','/')+".class";
  167. crawlReferenceTree(clsName, referredClasses, referredPackages, 1);
  168. }
  169. }
  170. }
  171. return ImmutableSet.copyOf(referredPackages);
  172. }
  173. /**
  174. * Helps filter "java." packages.
  175. */
  176. private static final Predicate<String> JAVA_PACKAGE_FILTER = new Predicate<String>()
  177. {
  178. public boolean apply(String pkg)
  179. {
  180. return !pkg.startsWith("java.");
  181. }
  182. };
  183. /**
  184. * Helps filter class entries under "java." packages.
  185. */
  186. private static final Predicate<String> JAVA_CLASS_FILTER = new Predicate<String>()
  187. {
  188. public boolean apply(String classEntry)
  189. {
  190. return !classEntry.startsWith("java/");
  191. }
  192. };
  193. /**
  194. * This will crawl the class interfaces to the desired level.
  195. *
  196. * @param className name of the class.
  197. * @param scannedClasses set of classes that have been scanned.
  198. * @param packageImports set of imports that have been found.
  199. * @param level depth of scan (recursion).
  200. * @throws IOException error loading a class.
  201. */
  202. static void crawlReferenceTree(String className, Set<String> scannedClasses, Set<String> packageImports, int level) throws IOException
  203. {
  204. if (level <= 0)
  205. {
  206. return;
  207. }
  208. if (className.startsWith("java/"))
  209. return;
  210. if (scannedClasses.contains(className))
  211. return;
  212. else
  213. scannedClasses.add(className);
  214. if (log.isDebugEnabled())
  215. log.debug("Crawling "+className);
  216. InputStreamResource classBinaryResource = null;
  217. try
  218. {
  219. // look for the class binary by asking class loader.
  220. InputStream in = ClassLoaderUtils.getResourceAsStream(className, OsgiHeaderUtil.class);
  221. if (in == null)
  222. {
  223. log.error("Cannot find class: [" + className + "]");
  224. return;
  225. }
  226. // read the class binary and scan it.
  227. classBinaryResource = new InputStreamResource(in);
  228. final ScanResult scanResult = ClassBinaryScanner.scanClassBinary(new Clazz(className, classBinaryResource));
  229. // remember all the imported packages. ignore java packages.
  230. packageImports.addAll(Sets.filter(scanResult.getReferredPackages(), JAVA_PACKAGE_FILTER));
  231. // crawl
  232. Set<String> referredClasses = Sets.filter(scanResult.getReferredClasses(), JAVA_CLASS_FILTER);
  233. for (String ref : referredClasses)
  234. {
  235. crawlReferenceTree(ref + ".class", scannedClasses, packageImports, level-1);
  236. }
  237. }
  238. finally
  239. {
  240. if (classBinaryResource != null)
  241. {
  242. classBinaryResource.close();
  243. }
  244. }
  245. }
  246. /**
  247. * Parses an OSGi header line into a map structure
  248. *
  249. * @param header The header line
  250. * @return A map with the key the entry value and the value a map of attributes
  251. * @since 2.2.0
  252. */
  253. public static Map<String,Map<String,String>> parseHeader(String header)
  254. {
  255. return OSGiHeader.parseHeader(header);
  256. }
  257. /**
  258. * Builds the header string from a map
  259. * @param values The header values
  260. * @return A string, suitable for inclusion into an OSGI header string
  261. * @since 2.6
  262. */
  263. public static String buildHeader(Map<String,Map<String,String>> values)
  264. {
  265. StringBuilder header = new StringBuilder();
  266. for (Iterator<Map.Entry<String,Map<String,String>>> i = values.entrySet().iterator(); i.hasNext(); )
  267. {
  268. Map.Entry<String,Map<String,String>> entry = i.next();
  269. buildHeader(entry.getKey(), entry.getValue(), header);
  270. if (i.hasNext())
  271. {
  272. header.append(",");
  273. }
  274. }
  275. return header.toString();
  276. }
  277. /**
  278. * Builds the header string from a map
  279. * @param key The header value
  280. * @param attrs The map of attributes
  281. * @return A string, suitable for inclusion into an OSGI header string
  282. * @since 2.2.0
  283. */
  284. public static String buildHeader(String key, Map<String,String> attrs)
  285. {
  286. StringBuilder fullPkg = new StringBuilder();
  287. buildHeader(key, attrs, fullPkg);
  288. return fullPkg.toString();
  289. }
  290. /**
  291. * Builds the header string from a map
  292. * @since 2.6
  293. */
  294. private static void buildHeader(String key, Map<String,String> attrs, StringBuilder builder)
  295. {
  296. builder.append(key);
  297. if (attrs != null && !attrs.isEmpty())
  298. {
  299. for (Map.Entry<String,String> entry : attrs.entrySet())
  300. {
  301. builder.append(";");
  302. builder.append(entry.getKey());
  303. builder.append("=\"");
  304. builder.append(entry.getValue());
  305. builder.append("\"");
  306. }
  307. }
  308. }
  309. /**
  310. * Gets the plugin key from the bundle
  311. *
  312. * WARNING: shamelessly copied at {@link com.atlassian.plugin.osgi.bridge.PluginBundleUtils}, which can't use
  313. * this class due to creating a cyclic build dependency. Ensure these two implementations are in sync.
  314. *
  315. * This method shouldn't be used directly. Instead consider consuming the {@link com.atlassian.plugin.osgi.bridge.external.PluginRetrievalService}.
  316. *
  317. * @param bundle The plugin bundle
  318. * @return The plugin key, cannot be null
  319. * @since 2.2.0
  320. */
  321. public static String getPluginKey(Bundle bundle)
  322. {
  323. return getPluginKey(
  324. bundle.getSymbolicName(),
  325. bundle.getHeaders().get(OsgiPlugin.ATLASSIAN_PLUGIN_KEY),
  326. bundle.getHeaders().get(Constants.BUNDLE_VERSION)
  327. );
  328. }
  329. /**
  330. * Gets the plugin key from the jar manifest
  331. *
  332. * @param mf The plugin jar manifest
  333. * @return The plugin key, cannot be null
  334. * @since 2.2.0
  335. */
  336. public static String getPluginKey(Manifest mf)
  337. {
  338. return getPluginKey(
  339. mf.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME),
  340. mf.getMainAttributes().getValue(OsgiPlugin.ATLASSIAN_PLUGIN_KEY),
  341. mf.getMainAttributes().getValue(Constants.BUNDLE_VERSION)
  342. );
  343. }
  344. private static String getPluginKey(Object bundleName, Object atlKey, Object version)
  345. {
  346. Object key = atlKey;
  347. if (key == null)
  348. {
  349. key = bundleName + "-" + version;
  350. }
  351. return key.toString();
  352. }
  353. /**
  354. * Generate package version string such as "com.abc;version=1.2,com.atlassian".
  355. * The output can be used for import or export.
  356. *
  357. * @param packages map of packagename->version.
  358. */
  359. public static String generatePackageVersionString(Map<String, String> packages)
  360. {
  361. if (packages == null || packages.size()==0)
  362. {
  363. return "";
  364. }
  365. final StringBuilder sb = new StringBuilder();
  366. // add deterministism to string generation.
  367. List<String> packageNames = new ArrayList<String>(packages.keySet());
  368. Collections.sort(packageNames);
  369. for(String packageName:packageNames)
  370. {
  371. sb.append(",");
  372. sb.append(packageName);
  373. String version = packages.get(packageName);
  374. // we can drop the version component if it's empty for a slight performance gain.
  375. if (version != null && !version.equals(EMPTY_OSGI_VERSION))
  376. {
  377. sb.append(";version=").append(version);
  378. }
  379. }
  380. // delete the initial ",".
  381. sb.delete(0, 1);
  382. return sb.toString();
  383. }
  384. }