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 e.addAttribute("value", "(&(bean-name=" + beanName + ")(" + ComponentRegistrar.HOST_COMPONENT_FLAG + "=true))");
124
125 Element listProp = osgiService.addElement("beans:property");
126 listProp.addAttribute("name", "interfaces");
127 Element list = listProp.addElement("beans:list");
128 for (String inf : reg.getMainInterfaces())
129 {
130 Element tmp = list.addElement("beans:value");
131 tmp.setText(inf);
132 }
133
134 Element bundleContextProp = osgiService.addElement("beans:property");
135 bundleContextProp.addAttribute("name", "bundleContext");
136 bundleContextProp.addAttribute("ref", "bundleContext");
137 }
138 }
139 addImportsForMatchedHostComponents(matchedRegistrations, context.getSystemExports(), context.getExtraImports());
140 if (root.elements().size() > 0)
141 {
142 context.setShouldRequireSpring(true);
143 context.getFileOverrides().put(SPRING_XML, SpringHelper.documentToBytes(doc));
144 }
145 }
146 }
147
148 private void addImportsForMatchedHostComponents(List<HostComponentRegistration> matchedRegistrations,
149 SystemExports systemExports, List<String> extraImports)
150 {
151 try
152 {
153 Set<String> referredPackages = OsgiHeaderUtil.findReferredPackageNames(matchedRegistrations);
154
155 for (String pkg:referredPackages)
156 {
157 extraImports.add(systemExports.getFullExport(pkg));
158 }
159 }
160 catch (IOException e)
161 {
162 throw new PluginTransformationException("Unable to scan for host component referred packages", e);
163 }
164 }
165
166
167 private Set<String> convertRegistrationsToSet(List<HostComponentRegistration> regs)
168 {
169 Set<String> interfaceNames = new HashSet<String>();
170 if (regs != null)
171 {
172 for (HostComponentRegistration reg : regs)
173 {
174 interfaceNames.addAll(Arrays.asList(reg.getMainInterfaces()));
175 }
176 }
177 return interfaceNames;
178 }
179
180 private void findUsedHostComponents(Set<String> allHostComponents, Set<String> matchedHostComponents, List<String> innerJarPaths, InputStream
181 jarStream) throws IOException
182 {
183 Set<String> entries = new HashSet<String>();
184 Set<String> superClassNames = new HashSet<String>();
185 ZipInputStream zin = null;
186 try
187 {
188 zin = new ZipInputStream(new BufferedInputStream(jarStream));
189 ZipEntry zipEntry;
190 while ((zipEntry = zin.getNextEntry()) != null)
191 {
192 String path = zipEntry.getName();
193 if (path.endsWith(".class"))
194 {
195 entries.add(path.substring(0, path.length() - ".class".length()));
196 final Clazz cls = new Clazz(path, new InputStreamResource(new BufferedInputStream(new UnclosableFilterInputStream(zin))));
197 final ScanResult scanResult = ClassBinaryScanner.scanClassBinary(cls);
198
199 superClassNames.add(scanResult.getSuperClass());
200 for (String ref : scanResult.getReferredClasses())
201 {
202 String name = TransformStageUtils.jarPathToClassName(ref + ".class");
203 if (allHostComponents.contains(name))
204 {
205 matchedHostComponents.add(name);
206 }
207 }
208 }
209 else if (path.endsWith(".jar") && innerJarPaths.contains(path))
210 {
211 findUsedHostComponents(allHostComponents, matchedHostComponents, Collections.<String>emptyList(), new UnclosableFilterInputStream(zin));
212 }
213 }
214 }
215 finally
216 {
217 IOUtils.closeQuietly(zin);
218 }
219
220 addHostComponentsUsedInSuperClasses(allHostComponents, matchedHostComponents, entries, superClassNames);
221 }
222
223
224
225
226
227
228
229
230 private void addHostComponentsUsedInSuperClasses(Set<String> allHostComponents, Set<String> matchedHostComponents, Set<String> entries, Set<String> superClassNames)
231 {
232 for (String superClassName : superClassNames)
233 {
234
235 if (!entries.contains(superClassName))
236 {
237 String cls = superClassName.replace('/','.');
238
239
240 if (!cls.startsWith("java."))
241 {
242 Class spr;
243 try
244 {
245 spr = ClassLoaderUtils.loadClass(cls, this.getClass());
246 }
247 catch (ClassNotFoundException e)
248 {
249
250 continue;
251 }
252
253
254 for (Method m : spr.getMethods())
255 {
256 for (Class param : m.getParameterTypes())
257 {
258 if (allHostComponents.contains(param.getName()))
259 {
260 matchedHostComponents.add(param.getName());
261 }
262 }
263 }
264 }
265 }
266 }
267 }
268
269 private List<String> findJarPaths(Manifest mf)
270 {
271 List<String> paths = new ArrayList<String>();
272 String cp = mf.getMainAttributes().getValue(Constants.BUNDLE_CLASSPATH);
273 if (cp != null)
274 {
275 for (String entry : cp.split(","))
276 {
277 entry = entry.trim();
278 if (entry.length() != 1 && entry.endsWith(".jar"))
279 {
280 paths.add(entry);
281 }
282 else if (!".".equals(entry))
283 {
284 log.warn("Non-jar classpath elements not supported: " + entry);
285 }
286 }
287 }
288 return paths;
289 }
290
291
292
293
294 private static class UnclosableFilterInputStream extends FilterInputStream
295 {
296 public UnclosableFilterInputStream(InputStream delegate)
297 {
298 super(delegate);
299 }
300
301 @Override
302 public void close() throws IOException
303 {
304
305 }
306 }
307
308 private String determineId(Set<String> hostComponentNames, String beanName, int iteration)
309 {
310 String id = beanName;
311 if (id == null)
312 {
313 id = "bean" + iteration;
314 }
315
316 id = id.replaceAll("#", "LB");
317
318 if (hostComponentNames.contains(id))
319 {
320 id += iteration;
321 }
322 return id;
323 }
324
325 private boolean isRequiredHostComponent(TransformContext context, String name)
326 {
327 for (HostComponentRegistration registration : context.getRequiredHostComponents())
328 {
329 if (Arrays.asList(registration.getMainInterfaces()).contains(name))
330 {
331 return true;
332 }
333 }
334 return false;
335 }
336 }