PageRenderTime 49ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/atlassian-plugins-osgi/src/main/java/com/atlassian/plugin/osgi/factory/transform/stage/HostComponentSpringStage.java

https://bitbucket.org/purewind/atlassian-plugins
Java | 305 lines | 238 code | 39 blank | 28 comment | 46 complexity | ae87bf7e2203321fec23ab68adc78e13 MD5 | raw file
  1. package com.atlassian.plugin.osgi.factory.transform.stage;
  2. import aQute.bnd.osgi.Analyzer;
  3. import aQute.bnd.osgi.Clazz;
  4. import com.atlassian.plugin.PluginParseException;
  5. import com.atlassian.plugin.osgi.factory.transform.PluginTransformationException;
  6. import com.atlassian.plugin.osgi.factory.transform.TransformContext;
  7. import com.atlassian.plugin.osgi.factory.transform.TransformStage;
  8. import com.atlassian.plugin.osgi.factory.transform.model.ComponentImport;
  9. import com.atlassian.plugin.osgi.factory.transform.model.SystemExports;
  10. import com.atlassian.plugin.osgi.hostcomponents.ComponentRegistrar;
  11. import com.atlassian.plugin.osgi.hostcomponents.HostComponentRegistration;
  12. import com.atlassian.plugin.osgi.hostcomponents.PropertyBuilder;
  13. import com.atlassian.plugin.osgi.util.ClassBinaryScanner;
  14. import com.atlassian.plugin.osgi.util.ClassBinaryScanner.InputStreamResource;
  15. import com.atlassian.plugin.osgi.util.ClassBinaryScanner.ScanResult;
  16. import com.atlassian.plugin.osgi.util.OsgiHeaderUtil;
  17. import com.atlassian.plugin.util.ClassLoaderUtils;
  18. import com.atlassian.plugin.util.PluginUtils;
  19. import org.apache.commons.io.IOUtils;
  20. import org.dom4j.Document;
  21. import org.dom4j.Element;
  22. import org.osgi.framework.Constants;
  23. import org.slf4j.Logger;
  24. import org.slf4j.LoggerFactory;
  25. import java.io.BufferedInputStream;
  26. import java.io.FileInputStream;
  27. import java.io.FilterInputStream;
  28. import java.io.IOException;
  29. import java.io.InputStream;
  30. import java.lang.reflect.Method;
  31. import java.util.ArrayList;
  32. import java.util.Arrays;
  33. import java.util.Collections;
  34. import java.util.LinkedHashSet;
  35. import java.util.List;
  36. import java.util.Set;
  37. import java.util.SortedMap;
  38. import java.util.TreeMap;
  39. import java.util.TreeSet;
  40. import java.util.jar.Manifest;
  41. import java.util.zip.ZipEntry;
  42. import java.util.zip.ZipInputStream;
  43. public class HostComponentSpringStage implements TransformStage {
  44. private static final Logger log = LoggerFactory.getLogger(HostComponentSpringStage.class);
  45. /**
  46. * Path of generated Spring XML file
  47. */
  48. private static final String SPRING_XML = "META-INF/spring/atlassian-plugins-host-components.xml";
  49. public static final String BEAN_SOURCE = "Host Component";
  50. public void execute(TransformContext context) throws PluginTransformationException {
  51. if (SpringHelper.shouldGenerateFile(context, SPRING_XML)) {
  52. Document doc = SpringHelper.createSpringDocument();
  53. Set<String> hostComponentInterfaceNames = convertRegistrationsToSet(context.getHostComponentRegistrations());
  54. Set<String> matchedInterfaceNames = new LinkedHashSet<String>();
  55. List<String> innerJarPaths = findJarPaths(context.getManifest());
  56. InputStream pluginStream = null;
  57. try {
  58. pluginStream = new FileInputStream(context.getPluginFile());
  59. findUsedHostComponents(hostComponentInterfaceNames, matchedInterfaceNames, innerJarPaths, pluginStream);
  60. } catch (IOException e) {
  61. throw new PluginParseException("Unable to scan for host components in plugin classes", e);
  62. } finally {
  63. IOUtils.closeQuietly(pluginStream);
  64. }
  65. List<HostComponentRegistration> matchedRegistrations = new ArrayList<HostComponentRegistration>();
  66. Element root = doc.getRootElement();
  67. final SortedMap<String, Element> generatedBeans = new TreeMap<String, Element>();
  68. if (context.getHostComponentRegistrations() != null) {
  69. int index = -1;
  70. for (HostComponentRegistration reg : context.getHostComponentRegistrations()) {
  71. index++;
  72. boolean found = false;
  73. for (String name : reg.getMainInterfaces()) {
  74. if (matchedInterfaceNames.contains(name) || isRequiredHostComponent(context, name)) {
  75. found = true;
  76. }
  77. }
  78. Set<String> regInterfaces = new LinkedHashSet<String>(Arrays.asList(reg.getMainInterfaces()));
  79. for (ComponentImport compImport : context.getComponentImports().values()) {
  80. if (PluginUtils.doesModuleElementApplyToApplication(compImport.getSource(), context.getApplications(), context.getInstallationMode()) && regInterfaces.containsAll(compImport.getInterfaces())) {
  81. found = false;
  82. break;
  83. }
  84. }
  85. if (!found) {
  86. continue;
  87. }
  88. matchedRegistrations.add(reg);
  89. String beanName = reg.getProperties().get(PropertyBuilder.BEAN_NAME);
  90. // We don't use Spring DM service references here, because when the plugin is disabled, the proxies
  91. // will be marked destroyed, causing undesirable ServiceProxyDestroyedException fireworks. Since we
  92. // know host components won't change over the runtime of the plugin, we can use a simple factory
  93. // bean that returns the actual component instance
  94. Element osgiService = root.addElement("beans:bean");
  95. String beanId = determineId(context.getComponentImports().keySet(), beanName, index);
  96. // make sure the new bean id is not already in use.
  97. context.trackBean(beanId, BEAN_SOURCE);
  98. osgiService.addAttribute("id", beanId);
  99. osgiService.addAttribute("lazy-init", "true");
  100. // These are strings since we aren't compiling against the osgi-bridge jar
  101. osgiService.addAttribute("class", "com.atlassian.plugin.osgi.bridge.external.HostComponentFactoryBean");
  102. context.getExtraImports().add("com.atlassian.plugin.osgi.bridge.external");
  103. Element e = osgiService.addElement("beans:property");
  104. e.addAttribute("name", "filter");
  105. e.addAttribute("value", "(&(bean-name=" + beanName + ")(" + ComponentRegistrar.HOST_COMPONENT_FLAG + "=true))");
  106. Element listProp = osgiService.addElement("beans:property");
  107. listProp.addAttribute("name", "interfaces");
  108. Element list = listProp.addElement("beans:list");
  109. for (String inf : reg.getMainInterfaces()) {
  110. Element tmp = list.addElement("beans:value");
  111. tmp.setText(inf);
  112. }
  113. Element bundleContextProp = osgiService.addElement("beans:property");
  114. bundleContextProp.addAttribute("name", "bundleContext");
  115. bundleContextProp.addAttribute("ref", "bundleContext");
  116. // detach for later sorting by id
  117. osgiService.detach();
  118. generatedBeans.put(beanId, osgiService);
  119. }
  120. }
  121. // reattach sorting by id
  122. for (Element generatedBean : generatedBeans.values()) {
  123. root.add(generatedBean);
  124. }
  125. addImportsForMatchedHostComponents(matchedRegistrations, context.getSystemExports(), context.getExtraImports());
  126. if (root.elements().size() > 0) {
  127. context.setShouldRequireSpring(true);
  128. context.getFileOverrides().put(SPRING_XML, SpringHelper.documentToBytes(doc));
  129. }
  130. }
  131. }
  132. private void addImportsForMatchedHostComponents(List<HostComponentRegistration> matchedRegistrations,
  133. SystemExports systemExports, List<String> extraImports) {
  134. try {
  135. Set<String> referredPackages = OsgiHeaderUtil.findReferredPackageNames(matchedRegistrations);
  136. for (String pkg : referredPackages) {
  137. extraImports.add(systemExports.getFullExport(pkg));
  138. }
  139. } catch (IOException e) {
  140. throw new PluginTransformationException("Unable to scan for host component referred packages", e);
  141. }
  142. }
  143. private Set<String> convertRegistrationsToSet(List<HostComponentRegistration> regs) {
  144. Set<String> interfaceNames = new TreeSet<String>();
  145. if (regs != null) {
  146. for (HostComponentRegistration reg : regs) {
  147. interfaceNames.addAll(Arrays.asList(reg.getMainInterfaces()));
  148. }
  149. }
  150. return interfaceNames;
  151. }
  152. private void findUsedHostComponents(Set<String> allHostComponents, Set<String> matchedHostComponents, List<String> innerJarPaths, InputStream
  153. jarStream) throws IOException {
  154. Set<String> entries = new LinkedHashSet<String>();
  155. Set<String> superClassNames = new LinkedHashSet<String>();
  156. Analyzer analyzer = new Analyzer();
  157. ZipInputStream zin = null;
  158. try {
  159. zin = new ZipInputStream(new BufferedInputStream(jarStream));
  160. ZipEntry zipEntry;
  161. while ((zipEntry = zin.getNextEntry()) != null) {
  162. String path = zipEntry.getName();
  163. if (path.endsWith(".class")) {
  164. entries.add(path.substring(0, path.length() - ".class".length()));
  165. final Clazz cls = new Clazz(analyzer, path, new InputStreamResource(new BufferedInputStream(new UnclosableFilterInputStream(zin))));
  166. final ScanResult scanResult = ClassBinaryScanner.scanClassBinary(cls);
  167. superClassNames.add(scanResult.getSuperClass());
  168. for (String ref : scanResult.getReferredClasses()) {
  169. String name = TransformStageUtils.jarPathToClassName(ref + ".class");
  170. if (allHostComponents.contains(name)) {
  171. matchedHostComponents.add(name);
  172. }
  173. }
  174. } else if (path.endsWith(".jar") && innerJarPaths.contains(path)) {
  175. findUsedHostComponents(allHostComponents, matchedHostComponents, Collections.<String>emptyList(), new UnclosableFilterInputStream(zin));
  176. }
  177. }
  178. } finally {
  179. IOUtils.closeQuietly(zin);
  180. }
  181. addHostComponentsUsedInSuperClasses(allHostComponents, matchedHostComponents, entries, superClassNames);
  182. }
  183. /**
  184. * Searches super classes not in the plugin jar, which have methods that use host components
  185. *
  186. * @param allHostComponents The set of all host component classes
  187. * @param matchedHostComponents The set of host component classes already found
  188. * @param entries The paths of all files in the jar
  189. * @param superClassNames All super classes find by classes in the jar
  190. */
  191. private void addHostComponentsUsedInSuperClasses(Set<String> allHostComponents, Set<String> matchedHostComponents, Set<String> entries, Set<String> superClassNames) {
  192. for (String superClassName : superClassNames) {
  193. // Only search super classes not in the jar
  194. if (!entries.contains(superClassName)) {
  195. String cls = superClassName.replace('/', '.');
  196. // Ignore java classes including Object
  197. if (!cls.startsWith("java.") && !cls.startsWith("javax.")) {
  198. Class spr;
  199. try {
  200. spr = ClassLoaderUtils.loadClass(cls, this.getClass());
  201. } catch (NoClassDefFoundError e) {
  202. // ignore class not found as it could be from another plugin
  203. continue;
  204. } catch (ClassNotFoundException e) {
  205. // ignore class not found as it could be from another plugin
  206. continue;
  207. }
  208. // Search methods for parameters that use host components
  209. for (Method m : spr.getMethods()) {
  210. for (Class param : m.getParameterTypes()) {
  211. if (allHostComponents.contains(param.getName())) {
  212. matchedHostComponents.add(param.getName());
  213. }
  214. }
  215. }
  216. }
  217. }
  218. }
  219. }
  220. private List<String> findJarPaths(Manifest mf) {
  221. List<String> paths = new ArrayList<String>();
  222. String cp = mf.getMainAttributes().getValue(Constants.BUNDLE_CLASSPATH);
  223. if (cp != null) {
  224. for (String entry : cp.split(",")) {
  225. entry = entry.trim();
  226. if (entry.length() != 1 && entry.endsWith(".jar")) {
  227. paths.add(entry);
  228. } else if (!".".equals(entry)) {
  229. log.warn("Non-jar classpath elements not supported: " + entry);
  230. }
  231. }
  232. }
  233. return paths;
  234. }
  235. /**
  236. * Wrapper for the zip input stream to prevent clients from closing it when reading entries
  237. */
  238. private static class UnclosableFilterInputStream extends FilterInputStream {
  239. public UnclosableFilterInputStream(InputStream delegate) {
  240. super(delegate);
  241. }
  242. @Override
  243. public void close() throws IOException {
  244. // do nothing
  245. }
  246. }
  247. private String determineId(Set<String> hostComponentNames, String beanName, int iteration) {
  248. String id = beanName;
  249. if (id == null) {
  250. id = "bean" + iteration;
  251. }
  252. id = id.replaceAll("#", "LB");
  253. if (hostComponentNames.contains(id)) {
  254. id += iteration;
  255. }
  256. return id;
  257. }
  258. private boolean isRequiredHostComponent(TransformContext context, String name) {
  259. for (HostComponentRegistration registration : context.getRequiredHostComponents()) {
  260. if (Arrays.asList(registration.getMainInterfaces()).contains(name)) {
  261. return true;
  262. }
  263. }
  264. return false;
  265. }
  266. }