package org.osgi.service.indexer.impl; import static java.util.Collections.singletonMap; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyListOf; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.isA; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; import java.net.URI; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.osgi.framework.FrameworkUtil; import org.osgi.service.indexer.Capability; import org.osgi.service.indexer.Requirement; import org.osgi.service.indexer.Resource; import org.osgi.service.indexer.ResourceAnalyzer; import org.osgi.service.indexer.ResourceIndexer; import org.osgi.service.log.LogService; import junit.framework.TestCase; public class TestIndexer extends TestCase { /** * Test if the resolver is not affected if we use a dummy resolver */ @SuppressWarnings("deprecation") public void testResolverUnaffected() throws Exception { RepoIndex indexer = new RepoIndex(); String without = index(indexer); indexer = new RepoIndex(); indexer.setURLResolver(new URLResolver() { @Override public URI resolver(File artifact) throws Exception { return null; } }); String with = index(indexer); assertEquals("Should be the same without a resolver or a resolver that returns null", without, with); indexer = new RepoIndex(); indexer.setURLResolver(new URLResolver() { @Override public URI resolver(File artifact) throws Exception { throw new Exception(); } }); with = index(indexer); assertEquals("Should be the same without a resolver or a resolver that returns null", without, with); } /** * Test if the resolver can change the URLs */ @SuppressWarnings("deprecation") public void testResolverEffect() throws Exception { RepoIndex indexer = new RepoIndex(); indexer.setURLResolver(new URLResolver() { @Override public URI resolver(File artifact) throws Exception { return new URI("xyz://FOOBAR/" + artifact.getName()); } }); String with = index(indexer); Pattern p = Pattern.compile("xyz://FOOBAR/(03-export.jar|06-requirebundle.jar)(?:\"|')"); Matcher m = p.matcher(with); int n = 0; while (m.find()) { n++; } assertEquals("There are two files indexed", 2, n); } public void testFragmentBsnVersion() throws Exception { assertFragmentMatch("testdata/fragment-01.txt", "testdata/01-bsn+version.jar"); } public void testFragmentBsnVersionWithScrewyPath() throws Exception { assertFragmentMatch("testdata/fragment-01.txt", "testdata/../testdata/01-bsn+version.jar"); } public void testFragmentBsnVersionWithBundleOutsideTheParentPath() throws Exception { RepoIndex indexer = new RepoIndex(); StringWriter writer = new StringWriter(); indexer.indexFragment(Collections.singleton(new File("generated/../testdata/01-bsn+version.jar")), writer, singletonMap(RepoIndex.ROOT_URL, new File("testdata").getAbsoluteFile().toURI().toURL().toString())); String expected = Utils.readStream(new FileInputStream("testdata/fragment-01-relative.txt")); assertEquals(expected, writer.toString().trim()); } public void testFragmentLocalization() throws Exception { assertFragmentMatch("testdata/fragment-02.txt", "testdata/02-localization.jar"); } public void testFragmentExport() throws Exception { assertFragmentMatch("testdata/fragment-03.txt", "testdata/03-export.jar"); } public void testFragmentExportWithUses() throws Exception { assertFragmentMatch("testdata/fragment-04.txt", "testdata/04-export+uses.jar"); } public void testFragmentImport() throws Exception { assertFragmentMatch("testdata/fragment-05.txt", "testdata/05-import.jar"); } public void testFragmentRequireBundle() throws Exception { assertFragmentMatch("testdata/fragment-06.txt", "testdata/06-requirebundle.jar"); } public void testFragmentOptionalImport() throws Exception { assertFragmentMatch("testdata/fragment-07.txt", "testdata/07-optionalimport.jar"); } public void testFragmentFragmentHost() throws Exception { assertFragmentMatch("testdata/fragment-08.txt", "testdata/08-fragmenthost.jar"); } public void testFragmentSingletonBundle() throws Exception { assertFragmentMatch("testdata/fragment-09.txt", "testdata/09-singleton.jar"); } public void testFragmentExportService() throws Exception { assertFragmentMatch("testdata/fragment-10.txt", "testdata/10-exportservice.jar"); } public void testFragmentImportService() throws Exception { assertFragmentMatch("testdata/fragment-11.txt", "testdata/11-importservice.jar"); } public void testFragmentForbidFragments() throws Exception { assertFragmentMatch("testdata/fragment-12.txt", "testdata/12-nofragments.jar"); } public void testFragmentBREE() throws Exception { assertFragmentMatch("testdata/fragment-13.txt", "testdata/13-bree.jar"); } public void testFragmentProvideRequireCap() throws Exception { assertFragmentMatch("testdata/fragment-14.txt", "testdata/14-provide-require-cap.jar"); } public void testFragmentRequireSCR() throws Exception { assertFragmentMatch("testdata/fragment-15.txt", "testdata/15-scr.jar"); } public void testFragmentOptionalRequireBundle() throws Exception { assertFragmentMatch("testdata/fragment-16.txt", "testdata/16-optionalrequirebundle.jar"); } public void testFragmentRequireSCR1_0() throws Exception { assertFragmentMatch("testdata/fragment-scr1_0.txt", "testdata/scr1_0.jar"); } public void testFragmentRequireSCR1_1() throws Exception { assertFragmentMatch("testdata/fragment-scr1_1.txt", "testdata/scr1_1.jar"); } public void testFragmentRequireSCR1_2() throws Exception { assertFragmentMatch("testdata/fragment-scr1_2.txt", "testdata/scr1_2.jar"); } public void testFragmentSCRServices() throws Exception { assertFragmentMatch("testdata/fragment-scr_services.txt", "testdata/scr_services.jar"); } private static void assertFragmentMatch(String expectedPath, String jarPath) throws Exception { RepoIndex indexer = new RepoIndex(); assertFragmentMatch(indexer, expectedPath, jarPath); } private static void assertFragmentMatch(RepoIndex indexer, String expectedPath, String jarPath) throws Exception { StringWriter writer = new StringWriter(); indexer.indexFragment(Collections.singleton(new File(jarPath)), writer, null); String expected = Utils.readStream(new FileInputStream(expectedPath)); assertEquals(expected, writer.toString().trim()); } public void testEmptyIndex() throws Exception { RepoIndex indexer = new RepoIndex(); ByteArrayOutputStream out = new ByteArrayOutputStream(); Set<File> files = Collections.emptySet(); Map<String,String> config = new HashMap<String,String>(); config.put(RepoIndex.REPOSITORY_INCREMENT_OVERRIDE, "0"); config.put(ResourceIndexer.REPOSITORY_NAME, "empty"); config.put(ResourceIndexer.PRETTY, "true"); indexer.index(files, out, config); String expected = Utils.readStream(new FileInputStream("testdata/empty.txt")); assertEquals(expected, out.toString()); } public void testFullIndex() throws Exception { RepoIndex indexer = new RepoIndex(); String decompressed = index(indexer); String unpackedXML = Utils.readStream(new FileInputStream("testdata/unpacked.xml")); String expected = unpackedXML.replaceAll("\\r?\\n|\\t", ""); assertEquals(expected, decompressed); } private String index(RepoIndex indexer) throws Exception, IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); Set<File> files = new LinkedHashSet<File>(); files.add(new File("testdata/03-export.jar")); files.add(new File("testdata/06-requirebundle.jar")); Map<String,String> config = new HashMap<String,String>(); config.put(RepoIndex.REPOSITORY_INCREMENT_OVERRIDE, "0"); config.put(ResourceIndexer.REPOSITORY_NAME, "full-c+f"); indexer.index(files, out, config); String decompressed = Utils.decompress(out.toByteArray()); return decompressed; } public void testFullIndexPrettyCompressedPermutations() throws Exception { Boolean pretties[] = { null, Boolean.FALSE, Boolean.TRUE }; Boolean compressions[] = { null, Boolean.FALSE, Boolean.TRUE }; boolean outPretties[] = { false, false, false, true, false, false, true, true, true }; boolean outCompressions[] = { true, false, true, false, false, true, false, false, true }; String expectedPretty = Utils.readStream(new FileInputStream("testdata/full-03+06.txt")); String expectedNotPretty = Utils.readStream(new FileInputStream("testdata/full-03+06-not-pretty.txt")); RepoIndex indexer = new RepoIndex(); ByteArrayOutputStream out = new ByteArrayOutputStream(); Set<File> files = new LinkedHashSet<File>(); files.add(new File("testdata/03-export.jar")); files.add(new File("testdata/06-requirebundle.jar")); Map<String,String> config = new HashMap<String,String>(); int outIndex = 0; for (Boolean pretty : pretties) { for (Boolean compression : compressions) { config.put(RepoIndex.REPOSITORY_INCREMENT_OVERRIDE, "0"); config.put(ResourceIndexer.REPOSITORY_NAME, "full-c+f"); if (pretty != null) { config.put(ResourceIndexer.PRETTY, pretty.toString().toLowerCase()); } if (compression != null) { config.put(ResourceIndexer.COMPRESSED, compression.toString().toLowerCase()); } indexer.index(files, out, config); String expected = outPretties[outIndex] ? expectedPretty : expectedNotPretty; if (!outCompressions[outIndex]) { assertEquals("pretty/compression = " + pretty + "/" + compression, expected, out.toString()); } else { assertEquals("pretty/compression = " + pretty + "/" + compression, expected, Utils.decompress(out.toByteArray())); } config.clear(); out.reset(); outIndex++; } } } public void testAddAnalyzer() throws Exception { RepoIndex indexer = new RepoIndex(); indexer.addAnalyzer(new WibbleAnalyzer(), null); StringWriter writer = new StringWriter(); LinkedHashSet<File> files = new LinkedHashSet<File>(); files.add(new File("testdata/01-bsn+version.jar")); files.add(new File("testdata/02-localization.jar")); indexer.indexFragment(files, writer, null); String expected = Utils.readStream(new FileInputStream("testdata/fragment-wibble.txt")); assertEquals(expected, writer.toString().trim()); } public void testAddAnalyzerWithFilter() throws Exception { RepoIndex indexer = new RepoIndex(); indexer.addAnalyzer(new WibbleAnalyzer(), FrameworkUtil.createFilter("(location=*sion.jar)")); StringWriter writer = new StringWriter(); LinkedHashSet<File> files = new LinkedHashSet<File>(); files.add(new File("testdata/01-bsn+version.jar")); files.add(new File("testdata/02-localization.jar")); indexer.indexFragment(files, writer, null); String expected = Utils.readStream(new FileInputStream("testdata/fragment-wibble-filtered.txt")); assertEquals(expected, writer.toString().trim()); } public void testAnalyzerException() throws Exception { RepoIndex indexer = new RepoIndex(); indexer.addAnalyzer(new BadAnalyzer(), null); StringWriter writer = new StringWriter(); LinkedHashSet<File> files = new LinkedHashSet<File>(); files.add(new File("testdata/01-bsn+version.jar")); files.add(new File("testdata/02-localization.jar")); indexer.indexFragment(files, writer, null); String xml = writer.toString().trim(); String comment = " <!-- Error calling analyzer \"org.osgi.service.indexer.impl.BadAnalyzer\" on resource testdata/01-bsn+version.jar with message java.lang.IllegalStateException: Bwa Ha Ha Ha! and stack: java.lang.IllegalStateException: Bwa Ha Ha Ha!"; assertTrue(String.format("Did not contain the correct comment %s in %s", comment, xml), xml.contains(comment)); comment = " <!-- Error calling analyzer \"org.osgi.service.indexer.impl.BadAnalyzer\" on resource testdata/02-localization.jar with message java.lang.IllegalStateException: Bwa Ha Ha Ha! and stack: java.lang.IllegalStateException: Bwa Ha Ha Ha!"; assertTrue(String.format("Did not contain the correct comment %s in %s", comment, xml), xml.contains(comment)); } public void testRootInSubdirectory() throws Exception { RepoIndex indexer = new RepoIndex(); Map<String,String> props = new HashMap<String,String>(); props.put(ResourceIndexer.ROOT_URL, new File("testdata").getAbsoluteFile().toURI().toURL().toString()); StringWriter writer = new StringWriter(); indexer.indexFragment(Collections.singleton(new File("testdata/01-bsn+version.jar")), writer, props); String expected = Utils.readStream(new FileInputStream("testdata/fragment-subdir1.txt")); assertEquals(expected, writer.toString().trim()); } public void testRootInSubSubdirectory() throws Exception { RepoIndex indexer = new RepoIndex(); Map<String,String> props = new HashMap<String,String>(); props.put(ResourceIndexer.ROOT_URL, new File("testdata").getAbsoluteFile().toURI().toURL().toString()); StringWriter writer = new StringWriter(); indexer.indexFragment(Collections.singleton(new File("testdata/subdir/01-bsn+version.jar")), writer, props); String expected = Utils.readStream(new FileInputStream("testdata/fragment-subdir2.txt")); assertEquals(expected, writer.toString().trim()); } public void testLogErrorsFromAnalyzer() throws Exception { ResourceAnalyzer badAnalyzer = new ResourceAnalyzer() { public void analyzeResource(Resource resource, List<Capability> capabilities, List<Requirement> requirements) throws Exception { throw new Exception("Bang!"); } }; ResourceAnalyzer goodAnalyzer = mock(ResourceAnalyzer.class); LogService log = mock(LogService.class); RepoIndex indexer = new RepoIndex(log); indexer.addAnalyzer(badAnalyzer, null); indexer.addAnalyzer(goodAnalyzer, null); // Run the indexer Map<String,String> props = new HashMap<String,String>(); props.put(ResourceIndexer.ROOT_URL, new File("testdata").getAbsoluteFile().toURI().toURL().toString()); StringWriter writer = new StringWriter(); indexer.indexFragment(Collections.singleton(new File("testdata/subdir/01-bsn+version.jar")), writer, props); // The "good" analyzer should have been called verify(goodAnalyzer).analyzeResource(any(Resource.class), anyListOf(Capability.class), anyListOf(Requirement.class)); // The log service should have been notified about the exception ArgumentCaptor<Exception> exceptionCaptor = ArgumentCaptor.forClass(Exception.class); verify(log).log(eq(LogService.LOG_ERROR), any(String.class), exceptionCaptor.capture()); assertEquals("Bang!", exceptionCaptor.getValue().getMessage()); } public void testBundleOutsideRootDirectory() throws Exception { LogService log = mock(LogService.class); RepoIndex indexer = new RepoIndex(log); Map<String,String> props = new HashMap<String,String>(); props.put(ResourceIndexer.ROOT_URL, new File("testdata/subdir").getAbsoluteFile().toURI().toURL().toString()); StringWriter writer = new StringWriter(); indexer.indexFragment(Collections.singleton(new File("testdata/01-bsn+version.jar")), writer, props); verify(log).log(eq(LogService.LOG_ERROR), anyString(), isA(IllegalArgumentException.class)); } public void testRemoveDisallowed() throws Exception { LogService log = mock(LogService.class); RepoIndex indexer = new RepoIndex(log); indexer.addAnalyzer(new NaughtyAnalyzer(), null); Map<String,String> props = new HashMap<String,String>(); props.put(ResourceIndexer.ROOT_URL, new File("testdata").getAbsoluteFile().toURI().toURL().toString()); StringWriter writer = new StringWriter(); indexer.indexFragment(Collections.singleton(new File("testdata/subdir/01-bsn+version.jar")), writer, props); verify(log).log(eq(LogService.LOG_ERROR), anyString(), isA(UnsupportedOperationException.class)); } public void testRecogniseFelixSCR() throws Exception { RepoIndex indexer = new RepoIndex(); indexer.addAnalyzer(new KnownBundleAnalyzer(), FrameworkUtil.createFilter("(name=*)")); assertFragmentMatch(indexer, "testdata/org.apache.felix.scr-1.6.0.xml", "testdata/org.apache.felix.scr-1.6.0.jar"); } public void testRecogniseAriesBlueprint() throws Exception { RepoIndex indexer = new RepoIndex(); indexer.addAnalyzer(new KnownBundleAnalyzer(), FrameworkUtil.createFilter("(name=*)")); assertFragmentMatch(indexer, "testdata/org.apache.aries.blueprint-1.0.0.xml", "testdata/org.apache.aries.blueprint-1.0.0.jar"); } public void testRecogniseGeminiBlueprint() throws Exception { RepoIndex indexer = new RepoIndex(); indexer.addAnalyzer(new KnownBundleAnalyzer(), FrameworkUtil.createFilter("(name=*)")); assertFragmentMatch(indexer, "testdata/gemini-blueprint-extender-1.0.0.RELEASE.xml", "testdata/gemini-blueprint-extender-1.0.0.RELEASE.jar"); } public void testRecogniseFelixJetty() throws Exception { RepoIndex indexer = new RepoIndex(); indexer.addAnalyzer(new KnownBundleAnalyzer(), FrameworkUtil.createFilter("(name=*)")); assertFragmentMatch(indexer, "testdata/org.apache.felix.http.jetty-2.2.0.xml", "testdata/org.apache.felix.http.jetty-2.2.0.jar"); } public void testMacroExpansion() throws Exception { Properties props = new Properties(); try (InputStream in = new FileInputStream("testdata/known-bundles.properties")) { props.load(in); } RepoIndex indexer = new RepoIndex(); indexer.addAnalyzer(new KnownBundleAnalyzer(props), FrameworkUtil.createFilter("(name=*)")); assertFragmentMatch(indexer, "testdata/org.apache.felix.eventadmin-1.2.14.xml", "testdata/org.apache.felix.eventadmin-1.2.14.jar"); } public void testFragmentRequireBlueprint() throws Exception { assertFragmentMatch("testdata/fragment-17.txt", "testdata/17-blueprint1.jar"); } public void testFragmentRequireBlueprintUsingHeader() throws Exception { assertFragmentMatch("testdata/fragment-18.txt", "testdata/18-blueprint2.jar"); } public void testFragmentBundleNativeCode() throws Exception { assertFragmentMatch("testdata/fragment-19.txt", "testdata/19-bundlenativecode.jar"); } public void testFragmentBundleNativeCodeOptional() throws Exception { assertFragmentMatch("testdata/fragment-20.txt", "testdata/20-bundlenativecode-optional.jar"); } public void testFragmentPlainJar() throws Exception { LogService mockLog = Mockito.mock(LogService.class); RepoIndex indexer = new RepoIndex(mockLog); indexer.addAnalyzer(new KnownBundleAnalyzer(), FrameworkUtil.createFilter("(name=*)")); assertFragmentMatch(indexer, "testdata/fragment-plainjar.txt", "testdata/jcip-annotations.jar"); Mockito.verifyZeroInteractions(mockLog); } public void testFragmentPlainJarWithVersion() throws Exception { assertFragmentMatch("testdata/fragment-plainjar-versioned.txt", "testdata/jcip-annotations-2.5.6.wibble.jar"); } public void testImportServiceOptional() throws Exception { assertFragmentMatch("testdata/org.apache.felix.eventadmin-1.3.2.xml", "testdata/org.apache.felix.eventadmin-1.3.2.jar"); } }