View Javadoc

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