View Javadoc

1   package com.atlassian.plugin.osgi.factory.transform;
2   
3   import com.atlassian.plugin.PluginAccessor;
4   import com.atlassian.plugin.JarPluginArtifact;
5   import com.atlassian.plugin.osgi.factory.transform.stage.ComponentImportSpringStage;
6   import com.atlassian.plugin.osgi.factory.transform.stage.ComponentSpringStage;
7   import com.atlassian.plugin.osgi.factory.transform.stage.HostComponentSpringStage;
8   import com.atlassian.plugin.osgi.hostcomponents.HostComponentRegistration;
9   import com.atlassian.plugin.osgi.container.impl.DefaultOsgiPersistentCache;
10  import com.atlassian.plugin.osgi.container.OsgiContainerManager;
11  import com.atlassian.plugin.osgi.factory.transform.model.SystemExports;
12  import com.atlassian.plugin.osgi.hostcomponents.PropertyBuilder;
13  import com.atlassian.plugin.osgi.hostcomponents.impl.MockRegistration;
14  import com.atlassian.plugin.test.PluginJarBuilder;
15  import com.atlassian.plugin.test.PluginTestUtils;
16  
17  import com.google.common.collect.ImmutableMap;
18  import com.google.common.collect.Sets;
19  import org.dom4j.Document;
20  import org.dom4j.Element;
21  import org.dom4j.io.SAXReader;
22  import org.jaxen.SimpleNamespaceContext;
23  import org.jaxen.XPath;
24  import org.jaxen.dom4j.Dom4jXPath;
25  import org.osgi.framework.Constants;
26  import org.osgi.framework.ServiceReference;
27  import static org.mockito.Mockito.mock;
28  import static org.mockito.Mockito.when;
29  
30  import java.io.File;
31  import java.io.IOException;
32  import java.io.InputStream;
33  import java.net.URISyntaxException;
34  import java.util.ArrayList;
35  import java.util.Arrays;
36  import java.util.HashMap;
37  import java.util.HashSet;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.Set;
41  import java.util.jar.Attributes;
42  import java.util.jar.JarEntry;
43  import java.util.jar.JarFile;
44  import java.util.zip.Deflater;
45  import java.util.zip.ZipEntry;
46  import java.util.zip.ZipFile;
47  
48  import junit.framework.TestCase;
49  import org.slf4j.Logger;
50  import org.slf4j.LoggerFactory;
51  
52  public class TestDefaultPluginTransformer extends TestCase
53  {
54      private static final Logger LOG = LoggerFactory.getLogger(TestDefaultPluginTransformer.class);
55  
56      private DefaultPluginTransformer transformer;
57      private File tmpDir;
58  
59      @Override
60      protected void setUp() throws Exception
61      {
62          super.setUp();
63          OsgiContainerManager osgiContainerManager = mock(OsgiContainerManager.class);
64          when(osgiContainerManager.getRegisteredServices()).thenReturn(new ServiceReference[0]);
65          tmpDir = PluginTestUtils.createTempDirectory("plugin-transformer");
66          transformer = new DefaultPluginTransformer(new DefaultOsgiPersistentCache(tmpDir), SystemExports.NONE, null, PluginAccessor.Descriptor.FILENAME, osgiContainerManager);
67      }
68  
69      @Override
70      protected void tearDown() throws Exception
71      {
72          super.tearDown();
73          tmpDir = null;
74          transformer = null;
75      }
76  
77      public void testAddFilesToZip() throws URISyntaxException, IOException
78      {
79          final File file = PluginTestUtils.getFileForResource("myapp-1.0-plugin.jar");
80  
81          final Map<String, byte[]> files = new HashMap<String, byte[]>()
82          {
83              {
84                  put("foo", "bar".getBytes());
85              }
86          };
87          final File copy = transformer.addFilesToExistingZip(file, files);
88          assertNotNull(copy);
89          assertTrue(!copy.getName().equals(file.getName()));
90          assertTrue(copy.length() != file.length());
91  
92          final ZipFile zip = new ZipFile(copy);
93          try
94          {
95              final ZipEntry entry = zip.getEntry("foo");
96              assertNotNull(entry);
97          }
98          finally
99          {
100             closeQuietly(zip);
101         }
102     }
103 
104     public void testTransform() throws Exception
105     {
106         final File file = new PluginJarBuilder()
107                 .addFormattedJava("my.Foo",
108                         "package my;",
109                         "public class Foo {",
110                         "  com.atlassian.plugin.osgi.factory.transform.Fooable bar;",
111                         "}")
112                 .addPluginInformation("foo", "foo", "1.1")
113                 .build();
114 
115         final File copy = transformer.transform(new JarPluginArtifact(file), new ArrayList<HostComponentRegistration>()
116         {
117             {
118                 add(new StubHostComponentRegistration(Fooable.class));
119             }
120         });
121 
122         assertNotNull(copy);
123         assertTrue(copy.getName().contains(String.valueOf(file.lastModified())));
124         assertTrue(copy.getName().endsWith(".jar"));
125         assertEquals(tmpDir.getAbsolutePath(), copy.getParentFile().getParentFile().getAbsolutePath());
126         final JarFile jar = new JarFile(copy);
127         try
128         {
129             final Attributes attrs = jar.getManifest().getMainAttributes();
130             assertEquals("1.1", attrs.getValue(Constants.BUNDLE_VERSION));
131             assertNotNull(jar.getEntry("META-INF/spring/atlassian-plugins-host-components.xml"));
132         }
133         finally
134         {
135             closeQuietly(jar);
136         }
137     }
138 
139     // TODO: turn this back on PLUG-682
140     public void notTestTransformWithBeanConflictBetweenComponentAndHostComponent() throws Exception
141     {
142         final File file = new PluginJarBuilder()
143                 .addFormattedJava("my.Foo",
144                         "package my;",
145                         "public class Foo {",
146                         "  com.atlassian.plugin.osgi.factory.transform.Fooable bar;",
147                         "}")
148                 .addFormattedResource("atlassian-plugin.xml",
149                         "<atlassian-plugin name='plugin1' key='first' pluginsVersion='2'>",
150                         "    <plugin-info>",
151                         "        <version>1.0</version>",
152                         "    </plugin-info>",
153                         "<component key='host_component1' class='my.Foo'/>",
154                         "</atlassian-plugin>")
155                 .build();
156 
157         try
158         {
159             transformer.transform(new JarPluginArtifact(file), new ArrayList<HostComponentRegistration>()
160             {
161                 {
162                     HostComponentRegistration reg = new StubHostComponentRegistration(Fooable.class);
163                     reg.getProperties().put(PropertyBuilder.BEAN_NAME, "host_component1");
164                     add(reg);
165                 }
166             });
167 
168             fail(PluginTransformationException.class.getSimpleName() + " expected");
169         }
170         catch (PluginTransformationException e)
171         {
172             // good, now check the content inside the error message.
173 
174             // this check looks weird since it relies on message scraping
175             // but without all the information expected here, users would not be able to figure out what went wrong.
176             LOG.info(e.toString());
177             e.getMessage().contains("host_component1");
178             e.getMessage().contains(ComponentSpringStage.BEAN_SOURCE);
179             e.getMessage().contains(HostComponentSpringStage.BEAN_SOURCE);
180         }
181     }
182 
183     // TODO: turn this back on PLUG-682
184     public void notTestTransformWithBeanConflictBetweenComponentAndImportComponent() throws Exception
185     {
186         final File file = new PluginJarBuilder()
187                 .addFormattedJava("my.Foo",
188                         "package my;",
189                         "public class Foo {",
190                         "  com.atlassian.plugin.osgi.factory.transform.Fooable bar;",
191                         "}")
192                 .addFormattedJava("com.atlassian.plugin.osgi.SomeInterface",
193                                   "package com.atlassian.plugin.osgi;",
194                                   "public interface SomeInterface {}")
195                 .addFormattedResource("atlassian-plugin.xml",
196                         "<atlassian-plugin name='plugin1' key='first' pluginsVersion='2'>",
197                         "    <plugin-info>",
198                         "        <version>1.0</version>",
199                         "    </plugin-info>",
200                         "<component key='component1' class='my.Foo'/>",
201                         "<component-import key='component1'>",
202                         "    <interface>com.atlassian.plugin.osgi.SomeInterface</interface>",
203                         "</component-import>",
204                         "</atlassian-plugin>")
205                 .build();
206 
207         try
208         {
209             transformer.transform(new JarPluginArtifact(file), new ArrayList<HostComponentRegistration>());
210             fail(PluginTransformationException.class.getSimpleName() + " expected");
211         }
212         catch (PluginTransformationException e)
213         {
214             // good, now check the content inside the error message.
215 
216             // this check looks weird since it relies on message scraping
217             // but without all the information expected here, users would not be able to figure out what went wrong.
218             LOG.info(e.toString());
219             e.getMessage().contains("component1");
220             e.getMessage().contains(ComponentSpringStage.BEAN_SOURCE);
221             e.getMessage().contains(ComponentImportSpringStage.BEAN_SOURCE);
222         }
223     }
224 
225     public void testImportManifestGenerationOnInterfaces() throws Exception
226     {
227         final File innerJar = new PluginJarBuilder()
228                 .addFormattedJava("my.innerpackage.InnerPackageInterface1",
229                         "package my.innerpackage;",
230                         "public interface InnerPackageInterface1 {}")
231                 .build();
232 
233         final File pluginJar = new PluginJarBuilder()
234                 .addFormattedJava("my.MyFooChild",
235                         "package my;",
236                         "public class MyFooChild extends com.atlassian.plugin.osgi.factory.transform.dummypackage2.DummyClass2 {",
237                         "}")
238                 .addFormattedJava("my2.MyFooInterface",
239                         "package my2;",
240                         "public interface MyFooInterface {}")
241                 .addFormattedResource("atlassian-plugin.xml",
242                         "<atlassian-plugin name='plugin1' key='first' pluginsVersion='2'>",
243                         "    <plugin-info>",
244                         "        <version>1.0</version>",
245                         "    </plugin-info>",
246                         "    <component key='component1' class='my.MyFooChild' public='true'>",
247                         "       <interface>com.atlassian.plugin.osgi.factory.transform.dummypackage0.DummyInterface0</interface>",
248                         "       <interface>com.atlassian.plugin.osgi.factory.transform.dummypackage1.DummyInterface1</interface>",
249                         "       <interface>my.innerpackage.InnerPackageInterface1</interface>",
250                         "       <interface>my2.MyFooInterface</interface>",
251                         "    </component>",
252                         "</atlassian-plugin>")
253                 .addFile("META-INF/lib/mylib.jar", innerJar)
254                 .build();
255 
256         File outputFile = transformer.transform(new JarPluginArtifact(pluginJar), new ArrayList<HostComponentRegistration>());
257 
258         JarFile outputJar = new JarFile(outputFile);
259         String importString;
260         try
261         {
262             importString = outputJar.getManifest().getMainAttributes().getValue(Constants.IMPORT_PACKAGE);
263         }
264         finally
265         {
266             closeQuietly(outputJar);
267         }
268 
269         // this should be done by binary scanning.
270         assertTrue(importString.contains("com.atlassian.plugin.osgi.factory.transform.dummypackage2"));
271 
272         // referred to by interface declaration.
273         assertTrue(importString.contains("com.atlassian.plugin.osgi.factory.transform.dummypackage1"));
274 
275         // referred to by interface declaration
276         assertTrue(importString.contains("com.atlassian.plugin.osgi.factory.transform.dummypackage0"));
277 
278         // should not import an interface which exists in plugin itself.
279         assertFalse(importString.contains("my2.MyFooInterface"));
280 
281         // should not import an interface which exists in inner jar.
282         assertFalse(importString.contains("my.innerpackage"));
283     }
284 
285     public void testGenerateCacheName() throws IOException
286     {
287         File tmp = File.createTempFile("asdf", ".jar", tmpDir);
288         assertTrue(DefaultPluginTransformer.generateCacheName(tmp).endsWith(".jar"));
289         tmp = File.createTempFile("asdf", "asdf", tmpDir);
290         assertTrue(DefaultPluginTransformer.generateCacheName(tmp).endsWith(String.valueOf(tmp.lastModified())));
291 
292         tmp = File.createTempFile("asdf", "asdf.", tmpDir);
293         assertTrue(DefaultPluginTransformer.generateCacheName(tmp).endsWith(String.valueOf(tmp.lastModified())));
294 
295         tmp = File.createTempFile("asdf", "asdf.s", tmpDir);
296         assertTrue(DefaultPluginTransformer.generateCacheName(tmp).endsWith(String.valueOf(".s")));
297     }
298 
299     public void testTransformComponentMustNotPerformKeyConversion() throws Exception
300     {
301         File outputJarFile = runTransform();
302 
303         assertBeanNames(outputJarFile, "META-INF/spring/atlassian-plugins-components.xml",
304                         ImmutableMap.<String, String>builder().put("beans", "http://www.springframework.org/schema/beans").build(),
305                         "//beans:bean",
306                         Sets.newHashSet("TESTING1", "testing2"));
307     }
308 
309     public void testTransformImportMustNotPerformKeyConversion() throws Exception
310     {
311         File outputJarFile = runTransform();
312 
313         assertBeanNames(outputJarFile, "META-INF/spring/atlassian-plugins-component-imports.xml",
314                         ImmutableMap.<String, String>builder().put("osgi", "http://www.springframework.org/schema/osgi").build(),
315                         "//osgi:reference",
316                         Sets.newHashSet("TESTING3", "testing4"));
317     }
318 
319     public void testTransformHostComponentMustNotPerformKeyConversion() throws Exception
320     {
321         File outputJarFile = runTransform();
322 
323         assertBeanNames(outputJarFile, "META-INF/spring/atlassian-plugins-host-components.xml",
324                         ImmutableMap.<String, String>builder().put("beans", "http://www.springframework.org/schema/beans").build(),
325                         "//beans:bean",
326                         Sets.newHashSet("TESTING5", "testing6"));
327     }
328 
329     public void testDefaultTransformedJarIsNotCompressed() throws Exception
330     {
331         // This test might be a bit brittle, but i think it's worth persisting with to see how it
332         // pans out. If you're here wondering why it failed, keep this in mind.
333         final File transformedFile = runTransform();
334         for(final JarEntry jarEntry : JarUtils.getEntries(transformedFile))
335         {
336             // It turns out uncompressed jar entries are actually still stored with method
337             // ZipEntry.STORED, so we can't check jarEntry.getMethod().  Also, you do need >= in the
338             // size check, because uncompressed things grow a little.
339             assertTrue(jarEntry.getCompressedSize() >= jarEntry.getSize());
340         }
341     }
342 
343     public void testBestSpeedCompressionYieldsCompressedTransformedJar() throws Exception
344     {
345         compressionOptionYieldsCompressedTransformedJar(Deflater.BEST_SPEED);
346     }
347 
348     public void testBestSizeCompressionYieldsCompressedTransformedJar() throws Exception
349     {
350         compressionOptionYieldsCompressedTransformedJar(Deflater.BEST_COMPRESSION);
351     }
352 
353     public void compressionOptionYieldsCompressedTransformedJar(final int level) throws Exception
354     {
355         // This test is slightly brittle, because it assumes a given file is improved by
356         // compression. However, an xml file is a good guess, and works with a reasonable margin.
357 
358         System.setProperty(DefaultPluginTransformer.TRANSFORM_COMPRESSION_LEVEL, Integer.toString(level));
359 
360         final JarEntry jarEntry = JarUtils.getEntry(runTransform(), PluginAccessor.Descriptor.FILENAME);
361         // It turns out uncompressed jar entries are actually still stored with method
362         // ZipEntry.STORED, so there's no value checking jarEntry.getMethod().
363         assertTrue(jarEntry.getCompressedSize() < jarEntry.getSize());
364 
365         System.clearProperty(DefaultPluginTransformer.TRANSFORM_COMPRESSION_LEVEL);
366     }
367 
368     private File runTransform() throws Exception
369     {
370         final File file = new PluginJarBuilder()
371                 .addFormattedJava("my.Foo",
372                         "package my;",
373                         "import com.atlassian.plugin.osgi.factory.transform.Fooable;",
374                         "import com.atlassian.plugin.osgi.factory.transform.FooChild;",
375                         "public class Foo {",
376                         "  private Fooable bar;",
377                         "  public Foo(Fooable bar, FooChild child) { this.bar = bar;} ",
378                         "}")
379                 .addFormattedJava("com.atlassian.plugin.osgi.SomeInterface",
380                                   "package com.atlassian.plugin.osgi;",
381                                   "public interface SomeInterface {}")
382                 .addFormattedResource("atlassian-plugin.xml",
383                         "<atlassian-plugin name='plugin1' key='first' pluginsVersion='2'>",
384                         "    <plugin-info>",
385                         "        <version>1.0</version>",
386                         "    </plugin-info>",
387                         "   <component key='TESTING1' class='my.Foo'/>",
388                         "   <component key='testing2' class='my.Foo'/>",
389                         "   <component-import key='TESTING3'>",
390                         "       <interface>com.atlassian.plugin.osgi.SomeInterface</interface>",
391                         "   </component-import>",
392                         "   <component-import key='testing4'>",
393                         "       <interface>com.atlassian.plugin.osgi.SomeInterface</interface>",
394                         "   </component-import>",
395                         "</atlassian-plugin>")
396                 .build();
397 
398         MockRegistration mockReg1 = new MockRegistration(new Foo(), Fooable.class);
399         mockReg1.getProperties().put(PropertyBuilder.BEAN_NAME, "TESTING5");
400         MockRegistration mockReg2 = new MockRegistration(new FooChild(), FooChild.class);
401         mockReg2.getProperties().put(PropertyBuilder.BEAN_NAME, "testing6");
402 
403         return transformer.transform(new JarPluginArtifact(file), Arrays.<HostComponentRegistration>asList(mockReg1, mockReg2));
404     }
405 
406     private void assertBeanNames(File outputJarFile, String springFileLocation,
407                                  Map<String, String> namespaces, String xpathQuery,
408                                  Set<String> expectedIds) throws Exception
409     {
410         JarFile jarFile = new JarFile(outputJarFile);
411         Set<String> foundBeanNames = new HashSet<String>();
412         try
413         {
414             InputStream inputStream = jarFile.getInputStream(jarFile.getEntry(springFileLocation));
415 
416             SAXReader saxReader = new SAXReader();
417             Document document = saxReader.read(inputStream);
418 
419             XPath xpath = new Dom4jXPath(xpathQuery);
420             xpath.setNamespaceContext(new SimpleNamespaceContext(namespaces));
421             List<Element> elems = xpath.selectNodes(document);
422             for(Element elem : elems)
423             {
424                 foundBeanNames.add(elem.attribute("id").getValue());
425             }
426         }
427         finally
428         {
429             closeQuietly(jarFile);
430         }
431 
432         assertEquals(expectedIds, foundBeanNames);
433     }
434 
435     /** TODO remove once we're firmly on Java 7, when ZipFile becomes Closeable. */
436     private static void closeQuietly(ZipFile zipFile)
437     {
438         try
439         {
440             zipFile.close();
441         }
442         catch (IOException e)
443         {
444             LOG.debug("Error closing zipFile: {}", zipFile.getName(), e);
445         }
446     }
447 }