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.hostcomponents.HostComponentRegistration;
7 import com.atlassian.plugin.osgi.hostcomponents.PropertyBuilder;
8 import com.atlassian.plugin.osgi.hostcomponents.ComponentRegistrar;
9 import com.atlassian.plugin.osgi.util.Clazz;
10 import com.atlassian.plugin.osgi.util.OsgiHeaderUtil;
11 import com.atlassian.plugin.PluginParseException;
12 import org.dom4j.Document;
13 import org.dom4j.Element;
14 import org.apache.commons.io.IOUtils;
15 import org.apache.commons.logging.Log;
16 import org.apache.commons.logging.LogFactory;
17 import org.osgi.framework.Constants;
18
19 import java.util.*;
20 import java.util.jar.Manifest;
21 import java.util.zip.ZipInputStream;
22 import java.util.zip.ZipEntry;
23 import java.io.FileInputStream;
24 import java.io.IOException;
25 import java.io.InputStream;
26
27 public class HostComponentSpringStage implements TransformStage
28 {
29 private static final Log log = LogFactory.getLog(HostComponentSpringStage.class);
30
31
32 static final String SPRING_XML = "META-INF/spring/atlassian-plugins-host-components.xml";
33
34 public void execute(TransformContext context) throws PluginTransformationException
35 {
36 if (SpringHelper.shouldGenerateFile(context, SPRING_XML))
37 {
38 Document doc = SpringHelper.createSpringDocument();
39 Set<String> hostComponentInterfaceNames = convertRegistrationsToSet(context.getHostComponentRegistrations());
40 Set<String> matchedInterfaceNames = new HashSet<String>();
41 List<String> innerJarPaths = findJarPaths(context.getManifest());
42 try
43 {
44 findUsedHostComponents(hostComponentInterfaceNames, matchedInterfaceNames, innerJarPaths, new FileInputStream(context.getPluginFile()));
45 }
46 catch (IOException e)
47 {
48 throw new PluginParseException("Unable to scan for host components in plugin classes", e);
49 }
50
51
52 List<HostComponentRegistration> matchedRegistrations = new ArrayList<HostComponentRegistration>();
53 Element root = doc.getRootElement();
54 if (context.getHostComponentRegistrations() != null)
55 {
56 int index = -1;
57 for (HostComponentRegistration reg : context.getHostComponentRegistrations())
58 {
59 index++;
60 boolean found = false;
61 for (String name : reg.getMainInterfaces())
62 {
63 if (matchedInterfaceNames.contains(name))
64 {
65 found = true;
66 }
67 }
68 if (!found)
69 {
70 continue;
71 }
72 matchedRegistrations.add(reg);
73
74 String beanName = reg.getProperties().get(PropertyBuilder.BEAN_NAME);
75
76 Element osgiService = root.addElement("osgi:reference");
77 osgiService.addAttribute("id", determineId(context.getDescriptorDocument(), beanName, index));
78
79 if (beanName != null)
80 {
81 osgiService.addAttribute("filter", "(&(bean-name=" + beanName + ")(" + ComponentRegistrar.HOST_COMPONENT_FLAG + "=true))");
82 }
83
84 Element interfaces = osgiService.addElement("osgi:interfaces");
85 for (String name : reg.getMainInterfaces())
86 {
87 Element e = interfaces.addElement("beans:value");
88 e.setText(name);
89 }
90 }
91 }
92 addImportsForMatchedHostComponents(matchedRegistrations, context.getExtraImports());
93 if (root.elements().size() > 0)
94 {
95 context.getFileOverrides().put(SPRING_XML, SpringHelper.documentToBytes(doc));
96 }
97 }
98 }
99
100 private void addImportsForMatchedHostComponents(List<HostComponentRegistration> matchedRegistrations, List<String> extraImports)
101 {
102 try
103 {
104 String list = OsgiHeaderUtil.findReferredPackages(matchedRegistrations);
105 extraImports.addAll(Arrays.asList(list.split(",")));
106 }
107 catch (IOException e)
108 {
109 throw new PluginTransformationException("Unable to scan for host component referred packages", e);
110 }
111 }
112
113
114 private Set<String> convertRegistrationsToSet(List<HostComponentRegistration> regs)
115 {
116 Set<String> interfaceNames = new HashSet<String>();
117 if (regs != null)
118 {
119 for (HostComponentRegistration reg : regs)
120 {
121 interfaceNames.addAll(Arrays.asList(reg.getMainInterfaces()));
122 }
123 }
124 return interfaceNames;
125 }
126
127 private void findUsedHostComponents(Set<String> allHostComponents, Set<String> matchedHostComponents, List<String> innerJarPaths, InputStream
128 jarStream) throws IOException
129 {
130
131 ZipInputStream zin = null;
132 try
133 {
134 zin = new ZipInputStream(jarStream);
135 ZipEntry zipEntry;
136 while ((zipEntry = zin.getNextEntry()) != null)
137 {
138 String path = zipEntry.getName();
139 if (path.endsWith(".class"))
140 {
141 Clazz cls = new Clazz(path, new UnclosableInputStream(zin));
142 Set<String> referredClasses = cls.getReferredClasses();
143 for (String ref : referredClasses)
144 {
145 String name = ref.replaceAll("/", ".").substring(0, ref.length() - ".class".length());
146 if (allHostComponents.contains(name))
147 {
148 matchedHostComponents.add(name);
149 }
150
151 }
152 }
153 else if (path.endsWith(".jar") && innerJarPaths.contains(path))
154 {
155 findUsedHostComponents(allHostComponents, matchedHostComponents, null, new UnclosableInputStream(zin));
156 }
157 }
158 }
159 finally
160 {
161 IOUtils.closeQuietly(zin);
162 }
163 }
164
165 private List<String> findJarPaths(Manifest mf)
166 {
167 List<String> paths = new ArrayList<String>();
168 String cp = mf.getMainAttributes().getValue(Constants.BUNDLE_CLASSPATH);
169 if (cp != null)
170 {
171 for (String entry : cp.split(","))
172 {
173 entry = entry.trim();
174 if (entry.length() != 1 && entry.endsWith(".jar"))
175 {
176 paths.add(entry);
177 }
178 else
179 {
180 log.warn("Non-jar classpath elements not supported: " + entry);
181 }
182 }
183 }
184 return paths;
185 }
186
187
188
189
190 private static class UnclosableInputStream extends InputStream
191 {
192 private final InputStream delegate;
193
194 public UnclosableInputStream(InputStream delegate)
195 {
196 this.delegate = delegate;
197 }
198
199 public int read() throws IOException
200 {
201 return delegate.read();
202 }
203
204 @Override
205 public void close() throws IOException
206 {
207
208 }
209 }
210
211 private String determineId(Document pluginDoc, String beanName, int iteration)
212 {
213 String id = beanName;
214 if (id == null)
215 {
216 id = "bean" + iteration;
217 }
218
219 id = id.replaceAll("#", "LB");
220
221 for (Object element : pluginDoc.getRootElement().elements("component-import"))
222 {
223 String key = ((Element) element).attributeValue("key");
224 if (id.equals(key))
225 {
226 id += iteration;
227 break;
228 }
229 }
230 return id;
231 }
232 }