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