1 package com.atlassian.plugin.osgi.factory.transform;
2
3 import com.atlassian.plugin.JarPluginArtifact;
4 import com.atlassian.plugin.PluginAccessor;
5 import com.atlassian.plugin.osgi.container.OsgiContainerManager;
6 import com.atlassian.plugin.osgi.container.impl.DefaultOsgiPersistentCache;
7 import com.atlassian.plugin.osgi.factory.transform.model.SystemExports;
8 import com.atlassian.plugin.osgi.hostcomponents.HostComponentRegistration;
9 import com.atlassian.plugin.osgi.hostcomponents.PropertyBuilder;
10 import com.atlassian.plugin.osgi.hostcomponents.impl.MockRegistration;
11 import com.atlassian.plugin.test.PluginJarBuilder;
12 import com.atlassian.plugin.test.PluginTestUtils;
13 import com.google.common.collect.ImmutableMap;
14 import com.google.common.collect.Sets;
15 import org.dom4j.Document;
16 import org.dom4j.Element;
17 import org.dom4j.io.SAXReader;
18 import org.jaxen.SimpleNamespaceContext;
19 import org.jaxen.XPath;
20 import org.jaxen.dom4j.Dom4jXPath;
21 import org.junit.After;
22 import org.junit.Before;
23 import org.junit.Test;
24 import org.osgi.framework.Constants;
25 import org.osgi.framework.ServiceReference;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 import java.io.File;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.net.URISyntaxException;
33 import java.nio.file.Files;
34 import java.nio.file.Paths;
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import java.util.Enumeration;
38 import java.util.HashMap;
39 import java.util.HashSet;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Set;
43 import java.util.jar.Attributes;
44 import java.util.jar.JarEntry;
45 import java.util.jar.JarFile;
46 import java.util.zip.Deflater;
47 import java.util.zip.ZipEntry;
48 import java.util.zip.ZipFile;
49
50 import static org.hamcrest.MatcherAssert.assertThat;
51 import static org.hamcrest.Matchers.containsString;
52 import static org.hamcrest.Matchers.endsWith;
53 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
54 import static org.hamcrest.Matchers.is;
55 import static org.hamcrest.Matchers.lessThan;
56 import static org.hamcrest.Matchers.not;
57 import static org.hamcrest.core.IsNull.notNullValue;
58 import static org.mockito.Mockito.mock;
59 import static org.mockito.Mockito.when;
60
61 public class TestDefaultPluginTransformer {
62 private static final Logger LOG = LoggerFactory.getLogger(TestDefaultPluginTransformer.class);
63
64 private static final Map<String, String> BEAN_NAMESPACE = ImmutableMap.<String, String>builder().put("beans", "http://www.springframework.org/schema/beans").build();
65 private static final Map<String, String> OSGI_NAMESPACE = ImmutableMap.<String, String>builder().put("osgi", "http://www.eclipse.org/gemini/blueprint/schema/blueprint").build();
66
67 private static final String BEAN_XPATH = "//beans:bean";
68 private static final String OSGI_REF_XPATH = "//osgi:reference";
69
70 private DefaultPluginTransformer transformer;
71 private File tmpDir;
72
73 @Before
74 public void setUp() throws Exception {
75 OsgiContainerManager osgiContainerManager = mock(OsgiContainerManager.class);
76 when(osgiContainerManager.getRegisteredServices()).thenReturn(new ServiceReference[0]);
77 tmpDir = PluginTestUtils.createTempDirectory("plugin-transformer");
78 transformer = new DefaultPluginTransformer(new DefaultOsgiPersistentCache(tmpDir), SystemExports.NONE, null, PluginAccessor.Descriptor.FILENAME, osgiContainerManager);
79 }
80
81 @After
82 public void tearDown() throws Exception {
83 tmpDir = null;
84 transformer = null;
85 }
86
87 @Test
88 public void testAddFilesToZip() throws URISyntaxException, IOException {
89 final File file = PluginTestUtils.getFileForResource("myapp-1.0-plugin.jar");
90
91 final Map<String, byte[]> files = new HashMap<String, byte[]>() {
92 {
93 put("foo", "bar".getBytes());
94 }
95 };
96 final File copy = transformer.addFilesToExistingZip(file, files);
97 assertThat(copy, notNullValue());
98 assertThat(copy.getName(), not(is(file.getName())));
99 assertThat(copy.length(), not(is(file.length())));
100
101 try (final ZipFile zip = new ZipFile(copy)) {
102 final ZipEntry entry = zip.getEntry("foo");
103 assertThat(entry, notNullValue());
104 }
105 }
106
107 @Test
108 public void testExistingFilesRetainTimestamps() throws URISyntaxException, IOException {
109
110
111
112
113 final File expectedSource = PluginTestUtils.getFileForResource("myapp-1.0-plugin.jar");
114 final File actualSource = PluginTestUtils.getFileForResource("myapp-1.0-plugin.jar");
115 final Map<String, byte[]> files = new HashMap<>();
116
117
118 try (final ZipFile expectedZip = new ZipFile(expectedSource);
119 final ZipFile actualZip = new ZipFile(transformer.addFilesToExistingZip(actualSource, files))) {
120 Enumeration<? extends ZipEntry> expectedEntries = expectedZip.entries();
121 Enumeration<? extends ZipEntry> actualEntries = actualZip.entries();
122
123 while (expectedEntries.hasMoreElements()) {
124 ZipEntry expected = expectedEntries.nextElement();
125 ZipEntry actual = actualEntries.nextElement();
126 assertThat(actual.getName(), is(expected.getName()));
127 assertThat(actual.getTime(), is(expected.getTime()));
128 }
129 }
130 }
131
132 @Test
133 public void testFilesAddedToZipAreInOrder() throws URISyntaxException, IOException {
134
135
136
137
138 final File file = PluginTestUtils.getFileForResource("myapp-1.0-plugin.jar");
139
140 final Map<String, byte[]> files = new HashMap<String, byte[]>() {
141 {
142 put("testcode/ghi", "aaa".getBytes());
143 put("testcode/abc", "aaa".getBytes());
144 put("testcode/dir/def", "aaa".getBytes());
145 put("testcode/dir/abc", "aaa".getBytes());
146 }
147 };
148
149 List<String> expectedFileOrder = Arrays.asList("testcode/abc", "testcode/dir/abc", "testcode/dir/def", "testcode/ghi");
150 List<String> actualFileOrder = new ArrayList<>();
151
152 try (final ZipFile zip = new ZipFile(transformer.addFilesToExistingZip(file, files))) {
153 Enumeration<? extends ZipEntry> entries = zip.entries();
154 while (entries.hasMoreElements()) {
155 ZipEntry entry = entries.nextElement();
156 if (entry.getName().contains("testcode")) {
157 actualFileOrder.add(entry.getName());
158 }
159 }
160
161 assertThat(actualFileOrder.size(), is(expectedFileOrder.size()));
162 assertThat(actualFileOrder, is(expectedFileOrder));
163 }
164 }
165
166 @Test
167 public void testTransform() throws Exception {
168 final File file = new PluginJarBuilder()
169 .addFormattedJava("my.Foo",
170 "package my;",
171 "public class Foo {",
172 " com.atlassian.plugin.osgi.factory.transform.Fooable bar;",
173 "}")
174 .addPluginInformation("foo", "foo", "1.1")
175 .build();
176
177 final File copy = transformer.transform(new JarPluginArtifact(file), new ArrayList<HostComponentRegistration>() {
178 {
179 add(new StubHostComponentRegistration(Fooable.class));
180 }
181 });
182
183 assertThat(copy, notNullValue(File.class));
184 assertThat(copy.getName().contains(String.valueOf(file.lastModified())), is(true));
185 assertThat(copy.getName().endsWith(".jar"), is(true));
186 assertThat(copy.getParentFile().getParentFile().getAbsolutePath(), is(tmpDir.getAbsolutePath()));
187
188 try (JarFile jar = new JarFile(copy)) {
189 final Attributes attrs = jar.getManifest().getMainAttributes();
190 assertThat(attrs.getValue(Constants.BUNDLE_VERSION), is("1.1"));
191 assertThat(jar.getEntry("META-INF/spring/atlassian-plugins-host-components.xml"), notNullValue());
192 }
193 }
194
195 @Test
196 public void testImportManifestGenerationOnInterfaces() throws Exception {
197 final File innerJar = new PluginJarBuilder()
198 .addFormattedJava("my.innerpackage.InnerPackageInterface1",
199 "package my.innerpackage;",
200 "public interface InnerPackageInterface1 {}")
201 .build();
202
203 final File pluginJar = new PluginJarBuilder()
204 .addFormattedJava("my.MyFooChild",
205 "package my;",
206 "public class MyFooChild extends com.atlassian.plugin.osgi.factory.transform.dummypackage2.DummyClass2 {",
207 "}")
208 .addFormattedJava("my2.MyFooInterface",
209 "package my2;",
210 "public interface MyFooInterface {}")
211 .addFormattedResource("atlassian-plugin.xml",
212 "<atlassian-plugin name='plugin1' key='first' pluginsVersion='2'>",
213 " <plugin-info>",
214 " <version>1.0</version>",
215 " </plugin-info>",
216 " <component key='component1' class='my.MyFooChild' public='true'>",
217 " <interface>com.atlassian.plugin.osgi.factory.transform.dummypackage0.DummyInterface0</interface>",
218 " <interface>com.atlassian.plugin.osgi.factory.transform.dummypackage1.DummyInterface1</interface>",
219 " <interface>my.innerpackage.InnerPackageInterface1</interface>",
220 " <interface>my2.MyFooInterface</interface>",
221 " </component>",
222 "</atlassian-plugin>")
223 .addFile("META-INF/lib/mylib.jar", innerJar)
224 .build();
225
226 File outputFile = transformer.transform(new JarPluginArtifact(pluginJar), new ArrayList<HostComponentRegistration>());
227
228 String importString;
229 try (JarFile outputJar = new JarFile(outputFile)) {
230 importString = outputJar.getManifest().getMainAttributes().getValue(Constants.IMPORT_PACKAGE);
231 }
232
233
234 assertThat(importString, containsString("com.atlassian.plugin.osgi.factory.transform.dummypackage2"));
235
236
237 assertThat(importString, containsString("com.atlassian.plugin.osgi.factory.transform.dummypackage1"));
238
239
240 assertThat(importString, containsString("com.atlassian.plugin.osgi.factory.transform.dummypackage0"));
241
242
243 assertThat(importString, not(containsString("my2.MyFooInterface")));
244
245
246 assertThat(importString, not(containsString("my.innerpackage")));
247 }
248
249 @Test
250 public void testGenerateCacheName() throws IOException {
251 File tmp = File.createTempFile("asdf", ".jar", tmpDir);
252 assertThat(DefaultPluginTransformer.generateCacheName(tmp), endsWith(".jar"));
253 tmp = File.createTempFile("asdf", "asdf", tmpDir);
254 assertThat(DefaultPluginTransformer.generateCacheName(tmp), endsWith(String.valueOf(tmp.lastModified())));
255
256 tmp = File.createTempFile("asdf", "asdf.", tmpDir);
257 assertThat(DefaultPluginTransformer.generateCacheName(tmp), endsWith(String.valueOf(tmp.lastModified())));
258
259 tmp = File.createTempFile("asdf", "asdf.s", tmpDir);
260 assertThat(DefaultPluginTransformer.generateCacheName(tmp), endsWith(".s"));
261 }
262
263 @Test
264 public void testTransformComponentMustNotPerformKeyConversion() throws Exception {
265 File outputJarFile = runTransform();
266
267 assertBeanNames(outputJarFile, "META-INF/spring/atlassian-plugins-components.xml",
268 BEAN_NAMESPACE, BEAN_XPATH,
269 Sets.newHashSet("TESTING1", "testing2", "testing3"));
270 }
271
272 @Test
273 public void testBeansListedInAppearanceOrder() throws Exception {
274
275
276 File outputJarFile = runTransform();
277
278 List<String> actual = runXpath(outputJarFile, "META-INF/spring/atlassian-plugins-components.xml", BEAN_NAMESPACE, BEAN_XPATH);
279 List<String> expected = Arrays.asList("testing3", "TESTING1", "testing2");
280 assertThat(actual, is(expected));
281 }
282
283 @Test
284 public void testTransformImportMustNotPerformKeyConversion() throws Exception {
285 File outputJarFile = runTransform();
286
287 assertBeanNames(outputJarFile, "META-INF/spring/atlassian-plugins-component-imports.xml",
288 OSGI_NAMESPACE, OSGI_REF_XPATH,
289 Sets.newHashSet("TESTING3", "testing4"));
290 }
291
292 @Test
293 public void testTransformHostComponentMustNotPerformKeyConversion() throws Exception {
294 File outputJarFile = runTransform();
295
296 assertBeanNames(outputJarFile, "META-INF/spring/atlassian-plugins-host-components.xml",
297 BEAN_NAMESPACE, BEAN_XPATH,
298 Sets.newHashSet("TESTING5", "testing6", "testing7"));
299 }
300
301 @Test
302 public void testHostComponentsMustBeSorted() throws Exception {
303
304
305 File outputJarFile = runTransform();
306
307 List<String> actual = runXpath(outputJarFile, "META-INF/spring/atlassian-plugins-host-components.xml",
308 BEAN_NAMESPACE, BEAN_XPATH);
309 List<String> expected = Arrays.asList("TESTING5", "testing6", "testing7");
310 assertThat(actual, is(expected));
311 }
312
313 @Test
314 public void testDefaultTransformedJarIsNotCompressed() throws Exception {
315
316
317 final File transformedFile = runTransform();
318 for (final JarEntry jarEntry : JarUtils.getEntries(transformedFile)) {
319
320
321
322 assertThat(jarEntry.getCompressedSize(), greaterThanOrEqualTo(jarEntry.getSize()));
323 }
324 }
325
326 @Test
327 public void testBestSpeedCompressionYieldsCompressedTransformedJar() throws Exception {
328 compressionOptionYieldsCompressedTransformedJar(Deflater.BEST_SPEED);
329 }
330
331 @Test
332 public void testBestSizeCompressionYieldsCompressedTransformedJar() throws Exception {
333 compressionOptionYieldsCompressedTransformedJar(Deflater.BEST_COMPRESSION);
334 }
335
336 @Test
337 public void testMultipleTransformsGenerateSameOutput() throws Exception {
338
339
340 File outputA = runTransform();
341 File outputB = runTransform();
342
343
344 assertThat(outputA.length(), is(outputB.length()));
345
346 byte[] bytesA = Files.readAllBytes(Paths.get(outputA.getAbsolutePath()));
347 byte[] bytesB = Files.readAllBytes(Paths.get(outputB.getAbsolutePath()));
348
349
350 assertThat(bytesA, is(bytesB));
351 }
352
353 public void compressionOptionYieldsCompressedTransformedJar(final int level) throws Exception {
354
355
356
357 System.setProperty(DefaultPluginTransformer.TRANSFORM_COMPRESSION_LEVEL, Integer.toString(level));
358
359 final JarEntry jarEntry = JarUtils.getEntry(runTransform(), PluginAccessor.Descriptor.FILENAME);
360
361
362 assertThat(jarEntry.getCompressedSize(), lessThan(jarEntry.getSize()));
363
364 System.clearProperty(DefaultPluginTransformer.TRANSFORM_COMPRESSION_LEVEL);
365 }
366
367 private File runTransform() throws Exception {
368 final File file = new PluginJarBuilder()
369 .addFormattedJava("my.Foo",
370 "package my;",
371 "import com.atlassian.plugin.osgi.factory.transform.Fooable;",
372 "import com.atlassian.plugin.osgi.factory.transform.FooChild;",
373 "public class Foo {",
374 " private Fooable bar;",
375 " public Foo(Fooable bar, FooChild child) { this.bar = bar;} ",
376 "}")
377 .addFormattedJava("com.atlassian.plugin.osgi.SomeInterface",
378 "package com.atlassian.plugin.osgi;",
379 "public interface SomeInterface {}")
380 .addFormattedResource("atlassian-plugin.xml",
381 "<atlassian-plugin name='plugin1' key='first' pluginsVersion='2'>",
382 " <plugin-info>",
383 " <version>1.0</version>",
384 " </plugin-info>",
385 " <component key='testing3' class='my.Foo'/>",
386 " <component key='TESTING1' class='my.Foo'/>",
387 " <component key='testing2' class='my.Foo'/>",
388 " <component-import key='TESTING3'>",
389 " <interface>com.atlassian.plugin.osgi.SomeInterface</interface>",
390 " </component-import>",
391 " <component-import key='testing4'>",
392 " <interface>com.atlassian.plugin.osgi.SomeInterface</interface>",
393 " </component-import>",
394 "</atlassian-plugin>")
395 .build();
396
397
398 MockRegistration mockReg1 = new MockRegistration(new FooChild(), FooChild.class);
399 mockReg1.getProperties().put(PropertyBuilder.BEAN_NAME, "testing7");
400 MockRegistration mockReg2 = new MockRegistration(new Foo(), Fooable.class);
401 mockReg2.getProperties().put(PropertyBuilder.BEAN_NAME, "TESTING5");
402 MockRegistration mockReg3 = new MockRegistration(new FooChild(), FooChild.class);
403 mockReg3.getProperties().put(PropertyBuilder.BEAN_NAME, "testing6");
404
405 return transformer.transform(new JarPluginArtifact(file), Arrays.<HostComponentRegistration>asList(mockReg1, mockReg2, mockReg3));
406 }
407
408 private List<String> runXpath(File outputJarFile, String springFileLocation,
409 Map<String, String> namespaces, String xpathQuery) throws Exception {
410 List<String> foundBeans = new ArrayList<>();
411
412 try (JarFile jarFile = new JarFile(outputJarFile)) {
413 InputStream inputStream = jarFile.getInputStream(jarFile.getEntry(springFileLocation));
414
415 SAXReader saxReader = new SAXReader();
416 Document document = saxReader.read(inputStream);
417
418 XPath xpath = new Dom4jXPath(xpathQuery);
419 xpath.setNamespaceContext(new SimpleNamespaceContext(namespaces));
420 List<Element> elems = xpath.selectNodes(document);
421 for (Element elem : elems) {
422 foundBeans.add(elem.attribute("id").getValue());
423 }
424 }
425
426 return foundBeans;
427 }
428
429 private void assertBeanNames(File outputJarFile, String springFileLocation,
430 Map<String, String> namespaces, String xpathQuery,
431 Set<String> expectedIds) throws Exception {
432 Set<String> foundBeans = new HashSet<String>(runXpath(outputJarFile, springFileLocation, namespaces, xpathQuery));
433 assertThat(foundBeans, is(expectedIds));
434 }
435 }