View Javadoc
1   package com.atlassian.plugin.osgi.container.felix;
2   
3   import com.atlassian.plugin.osgi.container.PackageScannerConfiguration;
4   import com.atlassian.plugin.osgi.container.impl.DefaultPackageScannerConfiguration;
5   import com.atlassian.plugin.osgi.hostcomponents.HostComponentRegistration;
6   import com.atlassian.plugin.osgi.hostcomponents.impl.MockRegistration;
7   import com.atlassian.plugin.testpackage1.Dummy1;
8   import com.atlassian.plugin.util.PluginFrameworkUtils;
9   import com.google.common.collect.ImmutableList;
10  import com.google.common.collect.ImmutableMap;
11  import org.hamcrest.Description;
12  import org.hamcrest.Matcher;
13  import org.hamcrest.Matchers;
14  import org.hamcrest.TypeSafeMatcher;
15  import org.junit.After;
16  import org.junit.Before;
17  import org.junit.Rule;
18  import org.junit.Test;
19  import org.junit.contrib.java.lang.system.RestoreSystemProperties;
20  import org.junit.rules.ExpectedException;
21  import org.junit.runner.RunWith;
22  import org.mockito.Mock;
23  import org.mockito.junit.MockitoJUnitRunner;
24  import org.twdata.pkgscanner.DefaultOsgiVersionConverter;
25  import org.twdata.pkgscanner.ExportPackage;
26  
27  import javax.management.DescriptorAccess;
28  import javax.print.attribute.AttributeSet;
29  import javax.print.attribute.HashAttributeSet;
30  import javax.servlet.ServletContext;
31  import javax.swing.table.DefaultTableModel;
32  import javax.swing.table.TableModel;
33  import java.io.File;
34  import java.net.MalformedURLException;
35  import java.net.URL;
36  import java.net.URLClassLoader;
37  import java.util.Arrays;
38  import java.util.Collection;
39  import java.util.Collections;
40  import java.util.List;
41  
42  import static com.atlassian.plugin.test.Matchers.fileNamed;
43  import static org.apache.commons.io.FileUtils.toFile;
44  import static org.hamcrest.MatcherAssert.assertThat;
45  import static org.hamcrest.Matchers.any;
46  import static org.hamcrest.Matchers.containsString;
47  import static org.hamcrest.Matchers.equalTo;
48  import static org.hamcrest.Matchers.is;
49  import static org.hamcrest.Matchers.not;
50  import static org.hamcrest.Matchers.notNullValue;
51  import static org.mockito.Mockito.mock;
52  import static org.mockito.Mockito.when;
53  
54  @RunWith(MockitoJUnitRunner.class)
55  public class TestExportsBuilder {
56  
57      @Rule
58      public final ExpectedException expectedException = ExpectedException.none();
59      @Rule
60      public final RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();
61  
62      @Mock
63      private ExportsBuilder.CachedExportPackageLoader loader;
64      private DefaultPackageScannerConfiguration configuration;
65      private ExportsBuilder builder;
66  
67      @Before
68      public void setUp() {
69          when(loader.load()).thenReturn(null);
70          // We use the default here, because we want the test to get the default values
71          configuration = new DefaultPackageScannerConfiguration();
72          builder = new ExportsBuilder(loader);
73      }
74  
75      @After
76      public void tearDown() {
77          builder = null;
78      }
79  
80      @Test
81      public void testDetermineExports() {
82          final String exports = builder.determineExports(emptyRegistrations(), configuration);
83          assertThat(exports, not(containsString(",,")));
84      }
85  
86      @Test
87      public void testDetermineExportsIncludeServiceInterfaces() {
88          final List<HostComponentRegistration> registrations = ImmutableList.of(
89                  new MockRegistration(new HashAttributeSet(), AttributeSet.class),
90                  new MockRegistration(new DefaultTableModel(), TableModel.class));
91          final String imports = builder.determineExports(registrations, configuration);
92          assertThat(imports, notNullValue());
93          assertThat(imports, containsString(AttributeSet.class.getPackage().getName()));
94          assertThat(imports, containsString("javax.swing.event"));
95      }
96  
97      private List<HostComponentRegistration> emptyRegistrations() {
98          return ImmutableList.of();
99      }
100 
101     @Test
102     public void testDetermineExportWhileConflictExists() {
103         final DescriptorAccess descriptorAccess = mock(DescriptorAccess.class);
104 
105         final List<HostComponentRegistration> registrations = ImmutableList.of(
106                 new MockRegistration(descriptorAccess, DescriptorAccess.class));
107 
108         configuration.setPackageVersions(ImmutableMap.of("javax.management", "1.2.3"));
109 
110         final String exports = builder.determineExports(registrations, configuration);
111 
112         int packageCount = 0;
113         for (final String imp : exports.split("[,]")) {
114             if (imp.split("[;]")[0].equals("javax.management")) {
115                 packageCount++;
116             }
117         }
118 
119         assertThat("even though the package is found twice, we must export it only once", packageCount, is(1));
120         assertThat("found earlier always wins", exports, containsString(",javax.management,"));
121     }
122 
123     @Test
124     public void testPrecalculatedPackages() {
125         when(loader.load()).thenReturn(new ExportsBuilder.PackageScannerExportsFileLoader("precalc-exports.xml").load());
126         final String exports = builder.determineExports(emptyRegistrations(), configuration);
127         assertThat(exports, containsString("bar;version=1"));
128     }
129 
130     @Test
131     public void testPackagesUnderPluginFrameworkExportedAsPluginFrameworkVersion() {
132         configuration.setPackageVersions(ImmutableMap.of("com.atlassian.plugin.testpackage1", "98.76.54"));
133         configuration.setPackageIncludes(ImmutableList.of("org.slf4j*"));
134 
135         final ImmutableList<HostComponentRegistration> registrations = ImmutableList.of(
136                 new MockRegistration(new Dummy1() {
137                 }, Dummy1.class));
138         final String exports = builder.determineExports(registrations, configuration);
139 
140         final String osgiVersionString = new DefaultOsgiVersionConverter().getVersion(PluginFrameworkUtils.getPluginFrameworkVersion());
141 
142         assertThat("packages under com.atlassian.plugin are exported as the framework version",
143                 exports, containsString("com.atlassian.plugin.testpackage1;version=" + osgiVersionString + ","));
144         assertThat("packages under com.atlassian.plugin are exported as the framework version",
145                 exports, not(containsString("com.atlassian.plugin.testpackage1;version=98.76.54,")));
146     }
147 
148     @Test
149     public void defaultGenerateExportsFindsStandardLog4j() throws Exception {
150         configuration.setServletContext(mockServletContext());
151         configuration.setPackageIncludes(Arrays.asList("javax.*", "org.*"));
152 
153         final Collection<ExportPackage> exports = builder.generateExports(configuration);
154 
155         assertThat(exports, notNullValue());
156         assertThat(exports,
157                 Matchers.hasItem(isExportPackage("org.apache.log4j", "1.2.17", "log4j-1.2.17.jar")));
158     }
159 
160     @Test
161     public void generateExportsFallsThroughToServletContextScanning() throws Exception {
162         configuration.setServletContext(mockServletContext());
163         // Set up the excludes so that we don't find the slf4j jar in the class loader, which makes the code fallback to the
164         // servlet context as mocked above, and we find log4j package in the test jar instead.
165         configuration.setJarIncludes(Arrays.asList("testlog*", "mock*"));
166         configuration.setJarExcludes(Collections.singletonList("log4j*"));
167         configuration.setPackageIncludes(Arrays.asList("javax.*", "org.*"));
168 
169         final Collection<ExportPackage> exports = builder.generateExports(configuration);
170 
171         assertThat(exports, notNullValue());
172         // The type arguments in Matchers.<ExportPackage>hasItem are not redundant in Java 1.6
173         //noinspection RedundantTypeArguments
174         assertThat(exports,
175                 Matchers.<ExportPackage>hasItem(isExportPackage("org.apache.log4j", "1.2.16", "testlog-1.2.16.jar")));
176     }
177 
178     @Test
179     public void generateExportsFailsWhenFallbackServletContextScanningFails() throws Exception {
180         configuration.setServletContext(mockServletContext());
181         configuration.setJarIncludes(Collections.singletonList("testlog4j23*"));
182         configuration.setJarExcludes(Collections.emptyList());
183         configuration.setPackageIncludes(Arrays.asList("javax.*", "org.*"));
184 
185         expectedException.expect(IllegalStateException.class);
186         builder.generateExports(configuration);
187     }
188 
189     @Test
190     public void generateExportsFailsWhenFallbackAndNoServletContext() {
191         // Test failure when no servlet context
192         configuration.setJarIncludes(Collections.singletonList("testlog4j23*"));
193         configuration.setJarExcludes(Collections.emptyList());
194         configuration.setPackageIncludes(Arrays.asList("javax.*", "org.*"));
195 
196         expectedException.expect(IllegalStateException.class);
197         builder.generateExports(configuration);
198     }
199 
200     @Test
201     public void testGenerateExportsWithCorrectServletVersion() throws Exception {
202         configuration.setServletContext(mockServletContext());
203         configuration.setPackageIncludes(Arrays.asList("javax.*", "org.*"));
204 
205         final Collection<ExportPackage> exports = builder.generateExports(configuration);
206 
207         // The type arguments in Matchers.<ExportPackage>hasItem are not redundant in Java 1.6
208         //noinspection RedundantTypeArguments
209         assertThat(exports, Matchers.<ExportPackage>hasItem(isExportPackage("javax.servlet", "5.3.0")));
210         //noinspection RedundantTypeArguments
211         assertThat(exports, Matchers.<ExportPackage>hasItem(isExportPackage("javax.servlet.http", "5.3.0")));
212     }
213 
214     private ServletContext mockServletContext() throws MalformedURLException {
215         final ServletContext context = mock(ServletContext.class);
216         when(context.getMajorVersion()).thenReturn(5);
217         when(context.getMinorVersion()).thenReturn(3);
218         final ClassLoader classLoader = getClass().getClassLoader();
219         when(context.getResource("/WEB-INF/lib")).thenReturn(classLoader.getResource("scanbase/WEB-INF/lib"));
220         when(context.getResource("/WEB-INF/classes")).thenReturn(classLoader.getResource("scanbase/WEB-INF/classes"));
221         return context;
222     }
223 
224     @Test
225     public void testPackagesNotConsidedInPluginsItself() {
226         assertThat(ExportsBuilder.isPluginFrameworkPackage("com.atlassian.jira.not.in.plugins"), is(false));
227         assertThat(ExportsBuilder.isPluginFrameworkPackage("com.atlassian.plugin.osgi"), is(true));
228         assertThat(ExportsBuilder.isPluginFrameworkPackage("com.atlassian.plugin.remotable.cheese"), is(false));
229         assertThat(ExportsBuilder.isPluginFrameworkPackage("com.atlassian.plugin.webresource"), is(false));
230         assertThat(ExportsBuilder.isPluginFrameworkPackage("com.atlassian.plugin.webresource.transformer"), is(false));
231         assertThat(ExportsBuilder.isPluginFrameworkPackage("com.atlassian.plugin.cache.filecache"), is(false));
232         assertThat(ExportsBuilder.isPluginFrameworkPackage("com.atlassian.plugin.cache.filecache.impl"), is(false));
233         assertThat(ExportsBuilder.isPluginFrameworkPackage("com.atlassian.plugin.web"), is(false));
234         assertThat(ExportsBuilder.isPluginFrameworkPackage("com.atlassian.plugin.web.conditions"), is(false));
235     }
236 
237     @Test
238     public void testJarWithImplicitDirectories() {
239         // If you want to test that this test is testing the right thing, it should fail if you enable legacy mode:
240         // System.setProperty("com.atlassian.plugin.export.legacy.scan.mode", "true");
241         // Also ensure that you've not left any detritus from repackaging the associated jar resource around, because
242         // the scanner will find that stuff in the classpath and pick it up.
243 
244         final ImplicitDirectoriesTestHelper helper = new ImplicitDirectoriesTestHelper();
245         final Collection<ExportPackage> exportPackages = helper.getExportPackages(builder);
246 
247         // The type arguments in Matchers.<ExportPackage>hasItem are not redundant in Java 1.6
248         //noinspection RedundantTypeArguments
249         assertThat(exportPackages, Matchers.<ExportPackage>hasItem(helper.getLeafPackageMatcher()));
250         //noinspection RedundantTypeArguments
251         assertThat(exportPackages, not(Matchers.<ExportPackage>hasItem(helper.getIntermediatePackageMatcher())));
252     }
253 
254     @Test
255     public void testLegacyScanMode() {
256         // We want to test that setting the appropriate System property does, in fact, engage legacy mode. Unfortunately,
257         // the only functional difference we can pick up on is the difference in bugs: legacy mode doesn't work with jars
258         // with implicit directories, and non-legacy mode doesn't work with non-URLClassLoaders. Since we already have code
259         // around the former, we go with that and accept the brittle. This has the added benefit of verifying that the test
260         // isn't broken.
261 
262         System.setProperty(ExportsBuilder.getLegacyScanModeProperty(), "true");
263 
264         final ImplicitDirectoriesTestHelper helper = new ImplicitDirectoriesTestHelper();
265         final Collection<ExportPackage> exportPackages = helper.getExportPackages(builder);
266 
267         // The type arguments in Matchers.<ExportPackage>hasItem are not redundant in Java 1.6
268         //noinspection RedundantTypeArguments
269         assertThat(exportPackages, not(Matchers.<ExportPackage>hasItem(helper.getLeafPackageMatcher())));
270         //noinspection RedundantTypeArguments
271         assertThat(exportPackages, not(Matchers.<ExportPackage>hasItem(helper.getIntermediatePackageMatcher())));
272     }
273 
274     @Test
275     public void testMaybeUnwrapJarFileUrl() throws Exception {
276         final String fileUrl = "file:/tmp/xercesImpl-2.11.0.jar";
277 
278         // jar:file: URLs should be unwrapped to "normal" file: URLs
279         final URL unwrapped = ExportsBuilder.maybeUnwrapJarFileUrl(new URL("jar:" + fileUrl + "!/"));
280         assertThat(unwrapped.toString(), equalTo(fileUrl));
281 
282         // file: URLs should be left as-is
283         final URL unchanged = ExportsBuilder.maybeUnwrapJarFileUrl(new URL(fileUrl));
284         assertThat(unchanged.toString(), equalTo(fileUrl));
285     }
286 
287     @Test
288     public void productSuppliedServletVersionIsRespected() throws Exception {
289         configuration.setServletContext(mockServletContext());
290         configuration.setPackageIncludes(Arrays.asList("javax.*", "org.*"));
291         configuration.setPackageVersions(ImmutableMap.<String, String>builder().put("javax.servlet*", "4.6").build());
292 
293         final Collection<ExportPackage> exports = builder.generateExports(configuration);
294 
295         // The type arguments in Matchers.<ExportPackage>hasItem are not redundant in Java 1.6
296         //noinspection RedundantTypeArguments
297         assertThat(exports, Matchers.<ExportPackage>hasItem(isExportPackage("javax.servlet", "4.6.0")));
298         //noinspection RedundantTypeArguments
299         assertThat(exports, Matchers.<ExportPackage>hasItem(isExportPackage("javax.servlet.http", "4.6.0")));
300     }
301 
302     /**
303      * Common handling for tests using the implicit directory jar.
304      */
305     private static class ImplicitDirectoriesTestHelper {
306         private static final String JAR_NAME = "implicitDirectories.jar";
307         private static final String ROOT_PACKAGE = "com.atlassian.implicit";
308         private static final String INTERMEDIATE_PACKAGE = ROOT_PACKAGE + ".intermediate";
309         private static final String LEAF_PACKAGE = INTERMEDIATE_PACKAGE + ".leaf";
310         private final File jarFile;
311         private final ClassLoader classLoader;
312         private final PackageScannerConfiguration configuration;
313 
314         ImplicitDirectoriesTestHelper() {
315             final URL jarUrl = getClass().getClassLoader().getResource(JAR_NAME);
316             jarFile = toFile(jarUrl);
317             configuration = mock(PackageScannerConfiguration.class);
318             when(configuration.getJarIncludes()).thenReturn(ImmutableList.of("*.jar"));
319             when(configuration.getPackageIncludes()).thenReturn(ImmutableList.of("org.slf4j", ROOT_PACKAGE + ".*"));
320             classLoader = new URLClassLoader(new URL[]{jarUrl});
321         }
322 
323         Matcher<ExportPackage> getIntermediatePackageMatcher() {
324             return isExportPackage(INTERMEDIATE_PACKAGE, null, jarFile);
325         }
326 
327         Matcher<ExportPackage> getLeafPackageMatcher() {
328             return isExportPackage(LEAF_PACKAGE, null, jarFile);
329         }
330 
331         Collection<ExportPackage> getExportPackages(final ExportsBuilder exportsBuilder) {
332             Collection<ExportPackage> exportPackages;
333             final ClassLoader savedContextClassLoader = Thread.currentThread().getContextClassLoader();
334             try {
335                 Thread.currentThread().setContextClassLoader(classLoader);
336                 exportPackages = exportsBuilder.generateExports(configuration);
337             } finally {
338                 Thread.currentThread().setContextClassLoader(savedContextClassLoader);
339             }
340             return exportPackages;
341         }
342     }
343 
344     /**
345      * Obtain a matcher for a given package, version but ignore the file.
346      */
347     private static Matcher<ExportPackage> isExportPackage(final String packageName, final String version) {
348         return new ExportPackageMatcher(packageName, version);
349     }
350 
351     /**
352      * Obtain a matcher for a given package, version and file name (ignoring the path).
353      *
354      * Useful for tests which expect to find a package in an some expected jar in the environment.
355      */
356     private static Matcher<ExportPackage> isExportPackage(
357             final String packageName, final String version, final String locationName) {
358         return new ExportPackageMatcher(packageName, version, locationName);
359     }
360 
361     /**
362      * Obtain a matcher for a given package, version and exact absolute file.
363      *
364      * Useful for tests which expect to find a package in an exact known jar in the test data.
365      */
366     private static Matcher<ExportPackage> isExportPackage(
367             final String packageName, final String version, final File location) {
368         return new ExportPackageMatcher(packageName, version, location);
369     }
370 
371     /**
372      * Matcher for an {@link ExportPackage} with given packageName, version, and optionally location.
373      * <p>
374      * This is necessary to avoid hoops since {@link ExportPackage#equals} doesn't use location, and {@link
375      * ExportPackage#compareTo} doesn't use location or version.
376      */
377     private static class ExportPackageMatcher extends TypeSafeMatcher<ExportPackage> {
378         private final String packageName;
379         private final String version;
380         private final Matcher<File> locationMatcher;
381 
382         ExportPackageMatcher(final String packageName, final String version) {
383             this.packageName = packageName;
384             this.version = version;
385             this.locationMatcher = any(File.class);
386         }
387 
388         ExportPackageMatcher(final String packageName, final String version, final String locationName) {
389             this.packageName = packageName;
390             this.version = version;
391             this.locationMatcher = fileNamed(locationName);
392         }
393 
394         ExportPackageMatcher(final String packageName, final String version, final File location) {
395             this.packageName = packageName;
396             this.version = version;
397             this.locationMatcher = equalTo(location);
398         }
399 
400         public boolean matchesSafely(final ExportPackage exportPackage) {
401             return equalTo(packageName).matches(exportPackage.getPackageName())
402                     && equalTo(version).matches(exportPackage.getVersion())
403                     && locationMatcher.matches(exportPackage.getLocation());
404         }
405 
406         public void describeTo(final Description description) {
407             description.appendText("ExportPackage named ");
408             description.appendValue(packageName);
409             description.appendText(" with version ");
410             description.appendValue(version);
411             description.appendText(" and location ");
412             locationMatcher.describeTo(description);
413         }
414     }
415 }