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