1   package com.atlassian.plugin.osgi.factory.transform.stage;
2   
3   import com.atlassian.plugin.PluginAccessor;
4   import com.atlassian.plugin.PluginParseException;
5   import com.atlassian.plugin.JarPluginArtifact;
6   import com.atlassian.plugin.util.PluginUtils;
7   import com.atlassian.plugin.osgi.factory.transform.TransformContext;
8   import com.atlassian.plugin.osgi.factory.transform.model.SystemExports;
9   import com.atlassian.plugin.osgi.factory.OsgiPlugin;
10  import com.atlassian.plugin.osgi.hostcomponents.HostComponentRegistration;
11  import com.atlassian.plugin.test.PluginJarBuilder;
12  
13  import org.apache.log4j.Logger;
14  import org.apache.log4j.spi.Filter;
15  import org.apache.commons.logging.Log;
16  import org.osgi.framework.Constants;
17  import static org.mockito.Mockito.mock;
18  import static org.mockito.Mockito.verify;
19  import static org.mockito.Matchers.contains;
20  
21  import java.io.ByteArrayInputStream;
22  import java.io.File;
23  import java.io.IOException;
24  import java.io.PrintStream;
25  import java.io.ByteArrayOutputStream;
26  import java.net.URISyntaxException;
27  import java.util.Arrays;
28  import java.util.Collection;
29  import java.util.Collections;
30  import java.util.jar.Attributes;
31  import java.util.jar.Manifest;
32  import java.lang.reflect.Field;
33  
34  import javax.print.attribute.AttributeSet;
35  
36  import junit.framework.TestCase;
37  
38  public class TestGenerateManifestStage extends TestCase
39  {
40      private GenerateManifestStage stage;
41  
42      @Override
43      public void setUp()
44      {
45          stage = new GenerateManifestStage();
46      }
47  
48      public void testGenerateManifest() throws Exception
49      {
50          final File file = new PluginJarBuilder()
51                  .addFormattedResource(
52                          "atlassian-plugin.xml",
53                          "<atlassian-plugin key='com.atlassian.plugins.example' name='Example Plugin'>",
54                          "  <plugin-info>",
55                          "    <description>",
56                          "      A sample plugin for demonstrating the file format.",
57                          "    </description>",
58                          "    <version>1.1</version>",
59                          "    <vendor name='Atlassian Software Systems Pty Ltd' url='http://www.atlassian.com'/>",
60                          "  </plugin-info>",
61                          "</atlassian-plugin>")
62                  .addFormattedJava(
63                          "com.mycompany.myapp.Foo",
64                          "package com.mycompany.myapp; public class Foo {}")
65                  .build();
66  
67          final TransformContext context = new TransformContext(Collections.<HostComponentRegistration> emptyList(), SystemExports.NONE, new JarPluginArtifact(file),
68              null, PluginAccessor.Descriptor.FILENAME);
69          context.setShouldRequireSpring(true);
70  
71          final Attributes attrs = executeStage(context);
72  
73          assertEquals("1.1", attrs.getValue(Constants.BUNDLE_VERSION));
74          assertEquals("com.atlassian.plugins.example", attrs.getValue(Constants.BUNDLE_SYMBOLICNAME));
75          assertEquals("A sample plugin for demonstrating the file format.", attrs.getValue(Constants.BUNDLE_DESCRIPTION));
76          assertEquals("Atlassian Software Systems Pty Ltd", attrs.getValue(Constants.BUNDLE_VENDOR));
77          assertEquals("http://www.atlassian.com", attrs.getValue(Constants.BUNDLE_DOCURL));
78          assertEquals(null, attrs.getValue(Constants.EXPORT_PACKAGE));
79          assertEquals(".", attrs.getValue(Constants.BUNDLE_CLASSPATH));
80          assertEquals("com.atlassian.plugins.example", attrs.getValue(OsgiPlugin.ATLASSIAN_PLUGIN_KEY));
81          assertEquals("*;timeout:=60", attrs.getValue("Spring-Context"));
82          assertEquals(null, attrs.getValue(Constants.IMPORT_PACKAGE));
83  
84      }
85  
86      public void testGenerateManifestWithProperInferredImports() throws Exception
87      {
88  
89          final File file = new PluginJarBuilder().addPluginInformation("someKey", "someName", "1.0").build();
90          final TransformContext context = new TransformContext(null, SystemExports.NONE, new JarPluginArtifact(file), null, PluginAccessor.Descriptor.FILENAME);
91          context.getExtraImports().add(AttributeSet.class.getPackage().getName());
92          final Attributes attrs = executeStage(context);
93  
94          assertTrue(attrs.getValue(Constants.IMPORT_PACKAGE).contains(AttributeSet.class.getPackage().getName()));
95  
96      }
97  
98      public void testGenerateManifestWithCustomTimeout() throws Exception
99      {
100         try
101         {
102             System.setProperty(PluginUtils.ATLASSIAN_PLUGINS_ENABLE_WAIT, "333");
103             stage = new GenerateManifestStage();
104             final File file = new PluginJarBuilder().addPluginInformation("someKey", "someName", "1.0").build();
105             final TransformContext context = new TransformContext(null, SystemExports.NONE, new JarPluginArtifact(file), null, PluginAccessor.Descriptor.FILENAME);
106             context.setShouldRequireSpring(true);
107             final Attributes attrs = executeStage(context);
108 
109             assertEquals("*;timeout:=333", attrs.getValue("Spring-Context"));
110         }
111         finally
112         {
113             System.clearProperty(PluginUtils.ATLASSIAN_PLUGINS_ENABLE_WAIT);
114         }
115 
116     }
117 
118     public void testGenerateManifestMergeHostComponentImportsWithExisting() throws Exception
119     {
120         final File plugin = new PluginJarBuilder("plugin")
121                 .addFormattedResource("META-INF/MANIFEST.MF",
122                         "Manifest-Version: 1.0",
123                         "Import-Package: javax.swing",
124                         "Bundle-SymbolicName: my.foo.symbolicName",
125                         "Bundle-ClassPath: .,foo")
126                 .addResource("foo/bar.txt", "Something")
127                 .addPluginInformation("innerjarcp", "Some name", "1.0")
128                 .build();
129 
130         final TransformContext context = new TransformContext(null, SystemExports.NONE, new JarPluginArtifact(plugin), null, PluginAccessor.Descriptor.FILENAME);
131         context.getExtraImports().add(AttributeSet.class.getPackage().getName());
132         final Attributes attrs = executeStage(context);
133         assertEquals("my.foo.symbolicName", attrs.getValue(Constants.BUNDLE_SYMBOLICNAME));
134         assertEquals(".,foo", attrs.getValue(Constants.BUNDLE_CLASSPATH));
135         assertEquals("innerjarcp", attrs.getValue(OsgiPlugin.ATLASSIAN_PLUGIN_KEY));
136         final String importPackage = attrs.getValue(Constants.IMPORT_PACKAGE);
137         assertTrue(importPackage.contains(AttributeSet.class.getPackage().getName()+","));
138         assertTrue(importPackage.contains("javax.swing"));
139 
140     }
141 
142     public void testGenerateManifestNoSpring() throws Exception
143     {
144         final File plugin = new PluginJarBuilder("plugin")
145                 .addFormattedResource("META-INF/MANIFEST.MF",
146                         "Manifest-Version: 1.0",
147                         "Bundle-SymbolicName: my.foo.symbolicName",
148                         "Bundle-Version: 1.0",
149                         "Bundle-ClassPath: .,foo")
150                 .addResource("foo/bar.txt", "Something")
151                 .addPluginInformation("innerjarcp", "Some name", "1.0")
152                 .build();
153 
154         final TransformContext context = new TransformContext(null, SystemExports.NONE, new JarPluginArtifact(plugin), null, PluginAccessor.Descriptor.FILENAME);
155         final Attributes attrs = executeStage(context);
156         assertEquals("innerjarcp", attrs.getValue("Atlassian-Plugin-Key"));
157 
158     }
159 
160     public void testGenerateManifestInvalidVersionWithExisting() throws Exception
161     {
162         final File plugin = new PluginJarBuilder("plugin")
163                 .addFormattedResource("META-INF/MANIFEST.MF",
164                         "Manifest-Version: 1.0",
165                         "Bundle-SymbolicName: my.foo.symbolicName",
166                         "Bundle-Version: beta1",
167                         "Bundle-ClassPath: .,foo\n")
168                 .addResource("foo/bar.txt", "Something")
169                 .addPluginInformation("innerjarcp", "Some name", "1.0")
170                 .build();
171 
172         final TransformContext context = new TransformContext(null, SystemExports.NONE, new JarPluginArtifact(plugin), null, PluginAccessor.Descriptor.FILENAME);
173         try
174         {
175             executeStage(context);
176             fail("Should have complained about bad plugin version");
177         }
178         catch (PluginParseException ex)
179         {
180             ex.printStackTrace();
181             // expected
182         }
183     }
184 
185     public void testGenerateManifestInvalidVersion() throws Exception
186     {
187         final File plugin = new PluginJarBuilder("plugin")
188                 .addPluginInformation("innerjarcp", "Some name", "beta1.0")
189                 .build();
190 
191         final TransformContext context = new TransformContext(null, SystemExports.NONE, new JarPluginArtifact(plugin), null, PluginAccessor.Descriptor.FILENAME);
192         try
193         {
194             executeStage(context);
195             fail("Should have complained about bad plugin version");
196         }
197         catch (PluginParseException ex)
198         {
199             ex.printStackTrace();
200             // expected
201         }
202 
203     }
204 
205     public void testGenerateManifestSimplePluginNoSpring() throws Exception
206     {
207         final File plugin = new PluginJarBuilder("plugin")
208                 .addResource("foo/bar.txt", "Something")
209                 .addPluginInformation("innerjarcp", "Some name", "1.0")
210                 .build();
211 
212         final TransformContext context = new TransformContext(null, SystemExports.NONE, new JarPluginArtifact(plugin), null, PluginAccessor.Descriptor.FILENAME);
213         final Attributes attrs = executeStage(context);
214         assertEquals("innerjarcp", attrs.getValue("Atlassian-Plugin-Key"));
215         assertNull(attrs.getValue("Spring-Context"));
216 
217     }
218 
219     public void testGenerateManifestWarnNoSpringWithExisting() throws Exception
220     {
221         Log log = mock(Log.class);
222         GenerateManifestStage.log = log;
223         
224         final File plugin = new PluginJarBuilder("plugin")
225                 .addFormattedResource("META-INF/MANIFEST.MF",
226                         "Manifest-Version: 1.0",
227                         "Import-Package: javax.swing",
228                         "Bundle-SymbolicName: my.foo.symbolicName",
229                         "Bundle-ClassPath: .,foo")
230                 .addResource("foo/bar.txt", "Something")
231                 .addPluginInformation("innerjarcp", "Some name", "1.0")
232                 .build();
233 
234         final TransformContext context = new TransformContext(null, SystemExports.NONE, new JarPluginArtifact(plugin), null, PluginAccessor.Descriptor.FILENAME);
235         context.setShouldRequireSpring(true);
236         context.getExtraImports().add(AttributeSet.class.getPackage().getName());
237 
238         Attributes attrs = executeStage(context);
239         assertEquals("innerjarcp", attrs.getValue(OsgiPlugin.ATLASSIAN_PLUGIN_KEY));
240         verify(log).debug(contains("'Spring-Context' is missing"));
241     }
242 
243     public void testGenerateManifestWarnNoTimeout() throws Exception
244     {
245         Log log = mock(Log.class);
246         GenerateManifestStage.log = log;
247 
248         final File plugin = new PluginJarBuilder("plugin")
249                 .addFormattedResource("META-INF/MANIFEST.MF",
250                         "Manifest-Version: 1.0",
251                         "Import-Package: javax.swing",
252                         "Bundle-SymbolicName: my.foo.symbolicName",
253                         "Spring-Context: *",
254                         "Bundle-ClassPath: .,foo")
255                 .addResource("foo/bar.txt", "Something")
256                 .addPluginInformation("innerjarcp", "Some name", "1.0")
257                 .build();
258 
259         final TransformContext context = new TransformContext(null, SystemExports.NONE, new JarPluginArtifact(plugin), null, PluginAccessor.Descriptor.FILENAME);
260         context.setShouldRequireSpring(true);
261         context.getExtraImports().add(AttributeSet.class.getPackage().getName());
262         executeStage(context);
263         verify(log).warn(contains("Please add ';timeout:=60'"));
264     }
265 
266 
267     public void testGenerateManifest_innerjars() throws URISyntaxException, PluginParseException, IOException
268     {
269         final File innerJar = new PluginJarBuilder("innerjar1").build();
270         final File innerJar2 = new PluginJarBuilder("innerjar2").build();
271         final File plugin = new PluginJarBuilder("plugin")
272                 .addFile("META-INF/lib/innerjar.jar", innerJar)
273                 .addFile("META-INF/lib/innerjar2.jar", innerJar2)
274                 .addPluginInformation("innerjarcp", "Some name", "1.0")
275                 .build();
276 
277         final TransformContext context = new TransformContext(null, SystemExports.NONE, new JarPluginArtifact(plugin), null, PluginAccessor.Descriptor.FILENAME);
278         final Attributes attrs = executeStage(context);
279 
280         final Collection classpathEntries = Arrays.asList(attrs.getValue(Constants.BUNDLE_CLASSPATH).split(","));
281         assertEquals(3, classpathEntries.size());
282         assertTrue(classpathEntries.contains("."));
283         assertTrue(classpathEntries.contains("META-INF/lib/innerjar.jar"));
284         assertTrue(classpathEntries.contains("META-INF/lib/innerjar2.jar"));
285     }
286 
287     public void testGenerateManifest_innerjarsInImports() throws Exception, PluginParseException, IOException
288     {
289         final File innerJar = new PluginJarBuilder("innerjar")
290                 .addFormattedJava("my.Foo",
291                         "package my;",
292                         "import org.apache.log4j.Logger;",
293                         "public class Foo{",
294                         "   Logger log;",
295                         "}")
296                 .build();
297         assertNotNull(innerJar);
298         final File plugin = new PluginJarBuilder("plugin")
299                 .addJava("my.Bar", "package my;import org.apache.log4j.spi.Filter; public class Bar{Filter log;}")
300                 .addFile("META-INF/lib/innerjar.jar", innerJar)
301                 .addPluginInformation("innerjarcp", "Some name", "1.0")
302                 .build();
303 
304         final TransformContext context = new TransformContext(null, SystemExports.NONE, new JarPluginArtifact(plugin), null, PluginAccessor.Descriptor.FILENAME);
305         final Attributes attrs = executeStage(context);
306 
307         assertEquals("1.0", attrs.getValue(Constants.BUNDLE_VERSION));
308         assertEquals("innerjarcp", attrs.getValue(Constants.BUNDLE_SYMBOLICNAME));
309 
310         final Collection classpathEntries = Arrays.asList(attrs.getValue(Constants.BUNDLE_CLASSPATH).split(","));
311         assertEquals(2, classpathEntries.size());
312         assertTrue(classpathEntries.contains("."));
313         assertTrue(classpathEntries.contains("META-INF/lib/innerjar.jar"));
314 
315         final Collection imports = Arrays.asList(attrs.getValue("Import-Package").split(","));
316         assertEquals(2, imports.size());
317         assertTrue(imports.contains(Logger.class.getPackage().getName() + ";resolution:=\"optional\""));
318         assertTrue(imports.contains(Filter.class.getPackage().getName() + ";resolution:=\"optional\""));
319     }
320 
321     public void testGenerateManifestWithBundleInstructions() throws Exception
322     {
323         final File plugin = new PluginJarBuilder("plugin")
324                 .addPluginInformation("test.plugin", "test.plugin", "1.0")
325                 .addJava("foo.MyClass", "package foo; public class MyClass{}")
326                 .addJava("foo.internal.MyPrivateClass", "package foo.internal; public class MyPrivateClass{}")
327                 .build();
328 
329         final TransformContext context = new TransformContext(null, SystemExports.NONE, new JarPluginArtifact(plugin), null, PluginAccessor.Descriptor.FILENAME);
330         context.getBndInstructions().put("Export-Package", "!*.internal.*,*");
331         final Attributes attrs = executeStage(context);
332         assertEquals("test.plugin", attrs.getValue(Constants.BUNDLE_SYMBOLICNAME));
333         assertEquals("foo", attrs.getValue(Constants.EXPORT_PACKAGE));
334     }
335 
336     public void testGenerateManifestWithHostAndExternalImports() throws Exception
337     {
338         final File plugin = new PluginJarBuilder("plugin")
339                 .addPluginInformation("test.plugin", "test.plugin", "1.0")
340                 .build();
341 
342         SystemExports exports = new SystemExports("foo.bar,foo.baz;version=\"1.0\"");
343         final TransformContext context = new TransformContext(null, exports, new JarPluginArtifact(plugin), null, PluginAccessor.Descriptor.FILENAME);
344         context.getBndInstructions().put("Import-Package", "foo.bar,foo.baz");
345         final Attributes attrs = executeStage(context);
346         assertEquals("test.plugin", attrs.getValue(Constants.BUNDLE_SYMBOLICNAME));
347 
348         String imports = attrs.getValue(Constants.IMPORT_PACKAGE);
349         assertTrue(imports.contains("foo.baz;version=\"[1.0,1.0]\""));
350         assertTrue(imports.contains("foo.bar"));
351     }
352 
353     private Attributes executeStage(final TransformContext context) throws IOException
354     {
355         stage.execute(context);
356         final Manifest mf = new Manifest(new ByteArrayInputStream(context.getFileOverrides().get("META-INF/MANIFEST.MF")));
357         final Attributes attrs = mf.getMainAttributes();
358         return attrs;
359     }
360 }