1 package com.atlassian.plugin.osgi.factory.transform;
2
3 import aQute.lib.osgi.Analyzer;
4 import aQute.lib.osgi.Builder;
5 import aQute.lib.osgi.Jar;
6 import com.atlassian.plugin.PluginInformation;
7 import com.atlassian.plugin.PluginManager;
8 import com.atlassian.plugin.PluginParseException;
9 import com.atlassian.plugin.osgi.hostcomponents.HostComponentRegistration;
10 import com.atlassian.plugin.osgi.hostcomponents.PropertyBuilder;
11 import com.atlassian.plugin.osgi.hostcomponents.ContextClassLoaderStrategy;
12 import com.atlassian.plugin.osgi.util.OsgiHeaderUtil;
13 import com.atlassian.plugin.parsers.XmlDescriptorParser;
14 import org.apache.log4j.Level;
15 import org.apache.log4j.Logger;
16 import org.apache.commons.lang.Validate;
17 import org.dom4j.*;
18 import org.dom4j.io.OutputFormat;
19 import org.dom4j.io.SAXReader;
20 import org.dom4j.io.XMLWriter;
21 import org.osgi.framework.Constants;
22
23 import java.io.*;
24 import java.net.URL;
25 import java.net.URLClassLoader;
26 import java.util.*;
27 import java.util.jar.JarEntry;
28 import java.util.jar.JarFile;
29 import java.util.jar.Manifest;
30 import java.util.zip.ZipEntry;
31 import java.util.zip.ZipInputStream;
32 import java.util.zip.ZipOutputStream;
33
34
35
36
37
38 public class DefaultPluginTransformer implements PluginTransformer
39 {
40
41 static final String ATLASSIAN_PLUGIN_SPRING_XML = "META-INF/spring/atlassian-plugins-spring.xml";
42
43 private static final Logger log = Logger.getLogger(DefaultPluginTransformer.class);
44
45 private final List<SpringTransformer> springTransformers = Arrays.asList(
46 new ComponentSpringTransformer(),
47 new ComponentImportSpringTransformer(),
48 new HostComponentSpringTransformer(),
49 new ModuleTypeSpringTransformer()
50 );
51
52
53
54
55
56
57
58
59 public File transform(File pluginJar, List<HostComponentRegistration> regs) throws PluginTransformationException
60 {
61 Validate.notNull(pluginJar, "The plugin jar is required");
62 Validate.notNull(regs, "The host component registrations are required");
63 final JarFile jar;
64 try
65 {
66 jar = new JarFile(pluginJar);
67 }
68 catch (IOException e)
69 {
70 throw new PluginTransformationException("Plugin is not a valid jar file", e);
71 }
72
73
74 Map<String,byte[]> filesToAdd = new HashMap<String, byte[]>();
75
76
77 URL atlassianPluginsXmlUrl = null;
78 try
79 {
80 final ClassLoader cl = new URLClassLoader(new URL[]{pluginJar.toURL()}, null);
81 atlassianPluginsXmlUrl = cl.getResource(PluginManager.PLUGIN_DESCRIPTOR_FILENAME);
82 if (atlassianPluginsXmlUrl == null)
83 throw new IllegalStateException("Cannot find atlassian-plugins.xml in jar");
84
85 log.info("Generating the manifest for plugin "+pluginJar.getName());
86 filesToAdd.put("META-INF/MANIFEST.MF", generateManifest(atlassianPluginsXmlUrl.openStream(), pluginJar, regs));
87 }
88 catch (PluginParseException e)
89 {
90 throw new PluginTransformationException("Unable to generate manifest", e);
91 }
92 catch (IOException e)
93 {
94 throw new PluginTransformationException("Unable to read existing plugin jar manifest", e);
95 }
96
97
98 if (jar.getEntry(ATLASSIAN_PLUGIN_SPRING_XML) == null) {
99 try
100 {
101 log.info("Generating "+ATLASSIAN_PLUGIN_SPRING_XML + " for plugin "+pluginJar.getName());
102 filesToAdd.put(ATLASSIAN_PLUGIN_SPRING_XML, generateSpringXml(atlassianPluginsXmlUrl.openStream(), regs));
103 }
104 catch (DocumentException e)
105 {
106 throw new PluginTransformationException("Unable to generate host component spring XML", e);
107 }
108 catch (IOException e)
109 {
110 throw new PluginTransformationException("Unable to open atlassian-plugins.xml", e);
111 }
112 }
113
114
115 try
116 {
117 if (log.isDebugEnabled())
118 {
119 StringBuilder sb = new StringBuilder();
120 sb.append("Overriding files in ").append(pluginJar.getName()).append(":\n");
121 for (Map.Entry<String,byte[]> entry : filesToAdd.entrySet())
122 {
123 sb.append("==").append(entry.getKey()).append("==\n");
124 sb.append(new String(entry.getValue()));
125 }
126 log.debug(sb.toString());
127 }
128 return addFilesToExistingZip(pluginJar, filesToAdd);
129 } catch (IOException e)
130 {
131 throw new PluginTransformationException("Unable to add files to plugin jar");
132 }
133
134 }
135
136
137
138
139
140
141
142
143 byte[] generateSpringXml(InputStream in, List<HostComponentRegistration> regs) throws DocumentException
144 {
145 Document springDoc = DocumentHelper.createDocument();
146 Element root = springDoc.addElement("beans");
147
148 root.addNamespace("beans", "http://www.springframework.org/schema/beans");
149 root.addNamespace("osgi", "http://www.springframework.org/schema/osgi");
150 root.addNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
151 root.addAttribute(new QName("schemaLocation", new Namespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")),
152 "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd\n" +
153 "http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd");
154 root.setName("beans:beans");
155 root.addAttribute("default-autowire", "autodetect");
156
157 SAXReader reader = new SAXReader();
158 Document pluginDoc = reader.read(in);
159
160 for (SpringTransformer springTransformer : springTransformers)
161 {
162 springTransformer.transform(regs, pluginDoc, springDoc);
163 }
164
165 ByteArrayOutputStream bout = new ByteArrayOutputStream();
166 OutputFormat format = OutputFormat.createPrettyPrint();
167
168 try
169 {
170 XMLWriter writer = new XMLWriter(bout, format );
171 writer.write(springDoc );
172 } catch (IOException e)
173 {
174 throw new PluginTransformationException("Unable to print generated Spring XML", e);
175 }
176
177 return bout.toByteArray();
178 }
179
180
181
182
183
184
185
186
187
188
189 byte[] generateManifest(InputStream descriptorStream, File file, List<HostComponentRegistration> regs) throws PluginParseException
190 {
191
192 Builder builder = new Builder();
193 try
194 {
195 builder.setJar(file);
196 String referrers = OsgiHeaderUtil.findReferredPackages(regs);
197
198
199 referrers += "com.atlassian.plugin.osgi.external,com.atlassian.plugin,";
200 if (isOsgiBundle(builder.getJar().getManifest()))
201 {
202 String imports = addReferrersToImports(builder.getJar().getManifest().getMainAttributes().getValue(Constants.IMPORT_PACKAGE), referrers);
203 builder.setProperty(Constants.IMPORT_PACKAGE, imports);
204 builder.mergeManifest(builder.getJar().getManifest());
205 } else
206 {
207 PluginInformationDescriptorParser parser = new PluginInformationDescriptorParser(descriptorStream);
208 PluginInformation info = parser.getPluginInformation();
209
210 Properties properties = new Properties();
211
212
213 properties.put("Spring-Context", "*;timeout=60");
214 properties.put(Analyzer.BUNDLE_SYMBOLICNAME, parser.getKey());
215 properties.put(Analyzer.IMPORT_PACKAGE, "*;resolution:=optional");
216 properties.put(Analyzer.EXPORT_PACKAGE, "*");
217 properties.put(Analyzer.BUNDLE_VERSION, info.getVersion());
218
219
220 properties.put(Analyzer.REMOVE_HEADERS, Analyzer.INCLUDE_RESOURCE);
221
222 header(properties, Analyzer.BUNDLE_DESCRIPTION, info.getDescription());
223 header(properties, Analyzer.BUNDLE_NAME, parser.getKey());
224 header(properties, Analyzer.BUNDLE_VENDOR, info.getVendorName());
225 header(properties, Analyzer.BUNDLE_DOCURL, info.getVendorUrl());
226
227
228 StringBuilder classpath = new StringBuilder();
229 classpath.append(".");
230 JarFile jarfile = new JarFile(file);
231 for (Enumeration<JarEntry> e = jarfile.entries(); e.hasMoreElements(); )
232 {
233 JarEntry entry = e.nextElement();
234 if (entry.getName().startsWith("META-INF/lib/") && entry.getName().endsWith(".jar"))
235 classpath.append(",").append(entry.getName());
236 }
237 header(properties, Analyzer.BUNDLE_CLASSPATH, classpath.toString());
238
239
240 properties.putAll(processBundleInstructions(parser.getDocument()));
241
242
243 properties.put(Analyzer.IMPORT_PACKAGE, addReferrersToImports(properties.getProperty(Analyzer.IMPORT_PACKAGE), referrers));
244 builder.setProperties(properties);
245 }
246
247
248 builder.calcManifest();
249 Jar jar = builder.build();
250 ByteArrayOutputStream bout = new ByteArrayOutputStream();
251 jar.writeManifest(bout);
252 return bout.toByteArray();
253
254 } catch (Exception t)
255 {
256 throw new PluginParseException("Unable to process plugin to generate OSGi manifest", t);
257 }
258 }
259
260 private boolean isOsgiBundle(Manifest manifest) throws IOException
261 {
262 return manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME) != null;
263 }
264
265 private Map<String,String> processBundleInstructions(Document document)
266 {
267 Map<String,String> instructions = new HashMap<String,String>();
268 Element pluginInfo = document.getRootElement().element("plugin-info");
269 if (pluginInfo != null)
270 {
271 Element instructionRoot = pluginInfo.element("bundle-instructions");
272 if (instructionRoot != null)
273 {
274 List<Element> instructionsElement = instructionRoot.elements();
275 for (Element instructionElement : instructionsElement)
276 {
277 String name = instructionElement.getName();
278 String value = instructionElement.getTextTrim();
279 instructions.put(name, value);
280 }
281 }
282 }
283 return instructions;
284 }
285
286 private String addReferrersToImports(String imports, String referrers)
287 {
288 if (imports != null && imports.length() > 0)
289 imports = referrers + imports;
290 else
291 imports = referrers.substring(0, referrers.length() - 1);
292 return imports;
293 }
294
295
296
297
298
299
300
301
302
303 static File addFilesToExistingZip(File zipFile,
304 Map<String,byte[]> files) throws IOException {
305
306 File tempFile = File.createTempFile(zipFile.getName(), null);
307
308 byte[] buf = new byte[1024];
309
310 ZipInputStream zin = new ZipInputStream(new FileInputStream(zipFile));
311 ZipOutputStream out = new ZipOutputStream(new FileOutputStream(tempFile));
312
313 ZipEntry entry = zin.getNextEntry();
314 while (entry != null)
315 {
316 String name = entry.getName();
317 if (!files.containsKey(name))
318 {
319
320 out.putNextEntry(new ZipEntry(name));
321
322 int len;
323 while ((len = zin.read(buf)) > 0)
324 out.write(buf, 0, len);
325 }
326 entry = zin.getNextEntry();
327 }
328
329 zin.close();
330
331 for (Map.Entry<String,byte[]> fentry : files.entrySet())
332 {
333 InputStream in = new ByteArrayInputStream(fentry.getValue());
334
335 out.putNextEntry(new ZipEntry(fentry.getKey()));
336
337 int len;
338 while ((len = in.read(buf)) > 0) {
339 out.write(buf, 0, len);
340 }
341
342 out.closeEntry();
343 in.close();
344 }
345
346 out.close();
347 return tempFile;
348 }
349
350
351 private static void header(Properties properties, String key, Object value)
352 {
353 if (value == null)
354 return;
355
356 if (value instanceof Collection && ((Collection) value).isEmpty())
357 return;
358
359 properties.put(key, value.toString().replaceAll("[\r\n]", ""));
360 }
361
362
363
364
365 private static class PluginInformationDescriptorParser extends XmlDescriptorParser
366 {
367
368
369
370
371 public PluginInformationDescriptorParser(InputStream source) throws PluginParseException
372 {
373 super(source);
374 }
375
376 public PluginInformation getPluginInformation()
377 {
378 return createPluginInformation(getDocument().getRootElement().element("plugin-info"));
379 }
380
381 @Override
382 public Document getDocument()
383 {
384 return super.getDocument();
385 }
386 }
387 }