1 package com.atlassian.plugin.osgi.factory.transform.stage;
2
3 import aQute.lib.osgi.Clazz;
4 import com.atlassian.plugin.osgi.factory.transform.TransformStage;
5 import com.atlassian.plugin.osgi.factory.transform.TransformContext;
6 import com.atlassian.plugin.osgi.factory.transform.PluginTransformationException;
7 import com.atlassian.plugin.osgi.factory.transform.model.ComponentImport;
8 import com.atlassian.plugin.osgi.factory.transform.model.SystemExports;
9 import com.atlassian.plugin.osgi.hostcomponents.HostComponentRegistration;
10 import com.atlassian.plugin.osgi.hostcomponents.PropertyBuilder;
11 import com.atlassian.plugin.osgi.hostcomponents.ComponentRegistrar;
12 import com.atlassian.plugin.osgi.util.ClassBinaryScanner;
13 import com.atlassian.plugin.osgi.util.OsgiHeaderUtil;
14 import com.atlassian.plugin.PluginParseException;
15 import com.atlassian.plugin.util.ClassLoaderUtils;
16 import com.atlassian.plugin.util.PluginUtils;
17 import com.atlassian.plugin.osgi.util.ClassBinaryScanner.InputStreamResource;
18 import com.atlassian.plugin.osgi.util.ClassBinaryScanner.ScanResult;
19 import org.dom4j.Document;
20 import org.dom4j.Element;
21 import org.apache.commons.io.IOUtils;
22 import org.osgi.framework.Constants;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
25
26 import java.io.BufferedInputStream;
27 import java.io.FilterInputStream;
28 import java.util.*;
29 import java.util.jar.Manifest;
30 import java.util.zip.ZipInputStream;
31 import java.util.zip.ZipEntry;
32 import java.io.FileInputStream;
33 import java.io.IOException;
34 import java.io.InputStream;
35 import java.lang.reflect.Method;
36
37 public class HostComponentSpringStage implements TransformStage
38 {
39 private static final Logger log = LoggerFactory.getLogger(HostComponentSpringStage.class);
40
41
42 private static final String SPRING_XML = "META-INF/spring/atlassian-plugins-host-components.xml";
43
44 public static final String BEAN_SOURCE = "Host Component";
45
46 public void execute(TransformContext context) throws PluginTransformationException
47 {
48 if (SpringHelper.shouldGenerateFile(context, SPRING_XML))
49 {
50 Document doc = SpringHelper.createSpringDocument();
51 Set<String> hostComponentInterfaceNames = convertRegistrationsToSet(context.getHostComponentRegistrations());
52 Set<String> matchedInterfaceNames = new HashSet<String>();
53 List<String> innerJarPaths = findJarPaths(context.getManifest());
54 InputStream pluginStream = null;
55 try
56 {
57 pluginStream = new FileInputStream(context.getPluginFile());
58 findUsedHostComponents(hostComponentInterfaceNames, matchedInterfaceNames, innerJarPaths, pluginStream);
59 }
60 catch (IOException e)
61 {
62 throw new PluginParseException("Unable to scan for host components in plugin classes", e);
63 }
64 finally
65 {
66 IOUtils.closeQuietly(pluginStream);
67 }
68
69 List<HostComponentRegistration> matchedRegistrations = new ArrayList<HostComponentRegistration>();
70 Element root = doc.getRootElement();
71 if (context.getHostComponentRegistrations() != null)
72 {
73 int index = -1;
74 for (HostComponentRegistration reg : context.getHostComponentRegistrations())
75 {
76 index++;
77 boolean found = false;
78 for (String name : reg.getMainInterfaces())
79 {
80 if (matchedInterfaceNames.contains(name) || isRequiredHostComponent(context, name))
81 {
82 found = true;
83 }
84 }
85 Set<String> regInterfaces = new HashSet<String>(Arrays.asList(reg.getMainInterfaces()));
86 for (ComponentImport compImport : context.getComponentImports().values())
87 {
88 if (PluginUtils.doesModuleElementApplyToApplication(compImport.getSource(), context.getApplications(), context.getInstallationMode()) && regInterfaces.containsAll(compImport.getInterfaces()))
89 {
90 found = false;
91 break;
92 }
93 }
94
95 if (!found)
96 {
97 continue;
98 }
99 matchedRegistrations.add(reg);
100
101 String beanName = reg.getProperties().get(PropertyBuilder.BEAN_NAME);
102
103
104
105
106
107
108 Element osgiService = root.addElement("beans:bean");
109 String beanId = determineId(context.getComponentImports().keySet(), beanName, index);
110
111
112 context.trackBean(beanId, BEAN_SOURCE);
113
114 osgiService.addAttribute("id", beanId);
115 osgiService.addAttribute("lazy-init", "true");
116
117
118 osgiService.addAttribute("class", "com.atlassian.plugin.osgi.bridge.external.HostComponentFactoryBean");
119 context.getExtraImports().add("com.atlassian.plugin.osgi.bridge.external");
120
121 Element e = osgiService.addElement("beans:property");
122 e.addAttribute("name", "filter");
123
124 e.addAttribute("value", "(&(bean-name=" + beanName + ")(" + ComponentRegistrar.HOST_COMPONENT_FLAG + "=true))");
125
126 Element listProp = osgiService.addElement("beans:property");
127 listProp.addAttribute("name", "interfaces");
128 Element list = listProp.addElement("beans:list");
129 for (String inf : reg.getMainInterfaces())
130 {
131 Element tmp = list.addElement("beans:value");
132 tmp.setText(inf);
133 }
134 }
135 }
136 addImportsForMatchedHostComponents(matchedRegistrations, context.getSystemExports(), context.getExtraImports());
137 if (root.elements().size() > 0)
138 {
139 context.setShouldRequireSpring(true);
140 context.getFileOverrides().put(SPRING_XML, SpringHelper.documentToBytes(doc));
141 }
142 }
143 }
144
145 private void addImportsForMatchedHostComponents(List<HostComponentRegistration> matchedRegistrations,
146 SystemExports systemExports, List<String> extraImports)
147 {
148 try
149 {
150 Set<String> referredPackages = OsgiHeaderUtil.findReferredPackageNames(matchedRegistrations);
151
152 for (String pkg:referredPackages)
153 {
154 extraImports.add(systemExports.getFullExport(pkg));
155 }
156 }
157 catch (IOException e)
158 {
159 throw new PluginTransformationException("Unable to scan for host component referred packages", e);
160 }
161 }
162
163
164 private Set<String> convertRegistrationsToSet(List<HostComponentRegistration> regs)
165 {
166 Set<String> interfaceNames = new HashSet<String>();
167 if (regs != null)
168 {
169 for (HostComponentRegistration reg : regs)
170 {
171 interfaceNames.addAll(Arrays.asList(reg.getMainInterfaces()));
172 }
173 }
174 return interfaceNames;
175 }
176
177 private void findUsedHostComponents(Set<String> allHostComponents, Set<String> matchedHostComponents, List<String> innerJarPaths, InputStream
178 jarStream) throws IOException
179 {
180 Set<String> entries = new HashSet<String>();
181 Set<String> superClassNames = new HashSet<String>();
182 ZipInputStream zin = null;
183 try
184 {
185 zin = new ZipInputStream(new BufferedInputStream(jarStream));
186 ZipEntry zipEntry;
187 while ((zipEntry = zin.getNextEntry()) != null)
188 {
189 String path = zipEntry.getName();
190 if (path.endsWith(".class"))
191 {
192 entries.add(path.substring(0, path.length() - ".class".length()));
193 final Clazz cls = new Clazz(path, new InputStreamResource(new BufferedInputStream(new UnclosableFilterInputStream(zin))));
194 final ScanResult scanResult = ClassBinaryScanner.scanClassBinary(cls);
195
196 superClassNames.add(scanResult.getSuperClass());
197 for (String ref : scanResult.getReferredClasses())
198 {
199 String name = TransformStageUtils.jarPathToClassName(ref + ".class");
200 if (allHostComponents.contains(name))
201 {
202 matchedHostComponents.add(name);
203 }
204 }
205 }
206 else if (path.endsWith(".jar") && innerJarPaths.contains(path))
207 {
208 findUsedHostComponents(allHostComponents, matchedHostComponents, Collections.<String>emptyList(), new UnclosableFilterInputStream(zin));
209 }
210 }
211 }
212 finally
213 {
214 IOUtils.closeQuietly(zin);
215 }
216
217 addHostComponentsUsedInSuperClasses(allHostComponents, matchedHostComponents, entries, superClassNames);
218 }
219
220
221
222
223
224
225
226
227 private void addHostComponentsUsedInSuperClasses(Set<String> allHostComponents, Set<String> matchedHostComponents, Set<String> entries, Set<String> superClassNames)
228 {
229 for (String superClassName : superClassNames)
230 {
231
232 if (!entries.contains(superClassName))
233 {
234 String cls = superClassName.replace('/','.');
235
236
237 if (!cls.startsWith("java.") && !cls.startsWith("javax."))
238 {
239 Class spr;
240 try
241 {
242 spr = ClassLoaderUtils.loadClass(cls, this.getClass());
243 }
244 catch (NoClassDefFoundError e)
245 {
246
247 continue;
248 }
249 catch (ClassNotFoundException e)
250 {
251
252 continue;
253 }
254
255
256 for (Method m : spr.getMethods())
257 {
258 for (Class param : m.getParameterTypes())
259 {
260 if (allHostComponents.contains(param.getName()))
261 {
262 matchedHostComponents.add(param.getName());
263 }
264 }
265 }
266 }
267 }
268 }
269 }
270
271 private List<String> findJarPaths(Manifest mf)
272 {
273 List<String> paths = new ArrayList<String>();
274 String cp = mf.getMainAttributes().getValue(Constants.BUNDLE_CLASSPATH);
275 if (cp != null)
276 {
277 for (String entry : cp.split(","))
278 {
279 entry = entry.trim();
280 if (entry.length() != 1 && entry.endsWith(".jar"))
281 {
282 paths.add(entry);
283 }
284 else if (!".".equals(entry))
285 {
286 log.warn("Non-jar classpath elements not supported: " + entry);
287 }
288 }
289 }
290 return paths;
291 }
292
293
294
295
296 private static class UnclosableFilterInputStream extends FilterInputStream
297 {
298 public UnclosableFilterInputStream(InputStream delegate)
299 {
300 super(delegate);
301 }
302
303 @Override
304 public void close() throws IOException
305 {
306
307 }
308 }
309
310 private String determineId(Set<String> hostComponentNames, String beanName, int iteration)
311 {
312 String id = beanName;
313 if (id == null)
314 {
315 id = "bean" + iteration;
316 }
317
318 id = id.replaceAll("#", "LB");
319
320 if (hostComponentNames.contains(id))
321 {
322 id += iteration;
323 }
324 return id;
325 }
326
327 private boolean isRequiredHostComponent(TransformContext context, String name)
328 {
329 for (HostComponentRegistration registration : context.getRequiredHostComponents())
330 {
331 if (Arrays.asList(registration.getMainInterfaces()).contains(name))
332 {
333 return true;
334 }
335 }
336 return false;
337 }
338 }