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