This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-maven-launchpad-plugin.git
commit 0a673309c82f87de538340ab05d6c54bf81cc7f1 Author: Bertrand Delacretaz <[email protected]> AuthorDate: Thu Oct 24 14:22:15 2013 +0000 SLING-3205 - factor out BundleListContentProvider to make it easier to test, and add tests. No functional changes so far git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1535390 13f79535-47bb-0310-9956-ffa450edef68 --- pom.xml | 6 + .../AbstractLaunchpadStartingMojo.java | 147 ++---------- .../projectsupport/BundleListContentProvider.java | 183 ++++++++++++++ .../BundleListContentProviderTest.java | 265 +++++++++++++++++++++ src/test/resources/test-bundle-list.xml | 76 ++++++ 5 files changed, 547 insertions(+), 130 deletions(-) diff --git a/pom.xml b/pom.xml index 11ff2af..90eb8f2 100644 --- a/pom.xml +++ b/pom.xml @@ -220,5 +220,11 @@ </exclusion> </exclusions> </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <version>1.9.5</version> + <scope>test</scope> + </dependency> </dependencies> </project> diff --git a/src/main/java/org/apache/sling/maven/projectsupport/AbstractLaunchpadStartingMojo.java b/src/main/java/org/apache/sling/maven/projectsupport/AbstractLaunchpadStartingMojo.java index 2b45cc6..3c5e22b 100644 --- a/src/main/java/org/apache/sling/maven/projectsupport/AbstractLaunchpadStartingMojo.java +++ b/src/main/java/org/apache/sling/maven/projectsupport/AbstractLaunchpadStartingMojo.java @@ -19,33 +19,23 @@ package org.apache.sling.maven.projectsupport; import static org.apache.felix.framework.util.FelixConstants.LOG_LEVEL_PROP; import java.io.File; -import java.io.FileFilter; import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; import java.util.Map; import java.util.Properties; -import java.util.Set; import org.apache.felix.framework.Logger; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.logging.Log; import org.apache.maven.shared.filtering.MavenFilteringException; import org.apache.maven.shared.filtering.PropertyUtils; import org.apache.sling.launchpad.api.LaunchpadContentProvider; import org.apache.sling.launchpad.base.impl.Sling; import org.apache.sling.launchpad.base.shared.Notifiable; import org.apache.sling.launchpad.base.shared.SharedConstants; -import org.apache.sling.maven.projectsupport.bundlelist.v1_0_0.Bundle; import org.apache.sling.maven.projectsupport.bundlelist.v1_0_0.BundleList; -import org.apache.sling.maven.projectsupport.bundlelist.v1_0_0.StartLevel; import org.osgi.framework.BundleException; public abstract class AbstractLaunchpadStartingMojo extends AbstractUsingBundleListMojo implements Notifiable { @@ -103,129 +93,26 @@ public abstract class AbstractLaunchpadStartingMojo extends AbstractUsingBundleL */ private File resourceProviderRoot; - private LaunchpadContentProvider resourceProvider = new LaunchpadContentProvider() { - - public Iterator<String> getChildren(String path) { - if (path.equals(BUNDLE_PATH_PREFIX)) { - final Set<String> levels = new HashSet<String>(); - for (final StartLevel level : getInitializedBundleList().getStartLevels()) { - // we treat the boot level as level 1 - if ( level.getStartLevel() == -1 ) { - levels.add(BUNDLE_PATH_PREFIX + "/1/"); - } else { - levels.add(BUNDLE_PATH_PREFIX + "/" + level.getLevel() + "/"); - } - } - return levels.iterator(); - } else if (path.equals("resources/corebundles")) { - List<String> empty = Collections.emptyList(); - return empty.iterator(); - } else if (path.equals(CONFIG_PATH_PREFIX)) { - if (getConfigDirectory().exists() && getConfigDirectory().isDirectory()) { - File[] configFiles = getConfigDirectory().listFiles(new FileFilter() { - - public boolean accept(File file) { - return file.isFile(); - } - }); - - List<String> fileNames = new ArrayList<String>(); - for (File cfgFile : configFiles) { - if (cfgFile.isFile()) { - fileNames.add(CONFIG_PATH_PREFIX + "/" + cfgFile.getName()); - } - } - - return fileNames.iterator(); - - } else { - List<String> empty = Collections.emptyList(); - return empty.iterator(); - } - } else if (path.startsWith(BUNDLE_PATH_PREFIX)) { - final String startLevelInfo = path.substring(BUNDLE_PATH_PREFIX.length() + 1); - try { - final int startLevel = Integer.parseInt(startLevelInfo); - - final List<String> bundles = new ArrayList<String>(); - for (final StartLevel level : getInitializedBundleList().getStartLevels()) { - if (level.getStartLevel() == startLevel || (startLevel == 1 && level.getStartLevel() == -1)) { - for (final Bundle bundle : level.getBundles()) { - final ArtifactDefinition d = new ArtifactDefinition(bundle, startLevel); - try { - final Artifact artifact = getArtifact(d); - bundles.add(artifact.getFile().toURI().toURL().toExternalForm()); - } catch (Exception e) { - getLog().error("Unable to resolve artifact ", e); - } - } - } - } - return bundles.iterator(); + private LaunchpadContentProvider resourceProvider = new BundleListContentProvider(resourceProviderRoot) { - } catch (NumberFormatException e) { - // we ignore this - } - } else if (path.equals("resources") ) { - final Set<String> subDirs = new HashSet<String>(); - subDirs.add(BUNDLE_PATH_PREFIX); - subDirs.add(CONFIG_PATH_PREFIX); - subDirs.add("resources/corebundles"); - return subDirs.iterator(); - } - - getLog().warn("un-handlable path " + path); - return null; + @Override + BundleList getInitializedBundleList() { + return AbstractLaunchpadStartingMojo.this.getInitializedBundleList(); } - public URL getResource(String path) { - if (path.startsWith(CONFIG_PATH_PREFIX)) { - File configFile = new File(getConfigDirectory(), path.substring(CONFIG_PATH_PREFIX.length() + 1)); - if (configFile.exists()) { - try { - return configFile.toURI().toURL(); - } catch (MalformedURLException e) { - // ignore this one - } - } - } - - File resourceFile = new File(resourceProviderRoot, path); - if (resourceFile.exists()) { - try { - return resourceFile.toURI().toURL(); - } catch (MalformedURLException e) { - getLog().error("Unable to create URL for file", e); - return null; - } - } else { - URL fromClasspath = getClass().getResource("/" + path); - if (fromClasspath != null) { - return fromClasspath; - } - - try { - return new URL(path); - } catch (MalformedURLException e) { - return null; - } - } - + @Override + File getConfigDirectory() { + return AbstractLaunchpadStartingMojo.this.getConfigDirectory(); } - - public InputStream getResourceAsStream(String path) { - URL res = this.getResource(path); - if (res != null) { - try { - return res.openStream(); - } catch (IOException ioe) { - // ignore this one - } - } - - // no resource - return null; - + + @Override + Artifact getArtifact(ArtifactDefinition def) throws MojoExecutionException { + return AbstractLaunchpadStartingMojo.this.getArtifact(def); + } + + @Override + Log getLog() { + return AbstractLaunchpadStartingMojo.this.getLog(); } }; diff --git a/src/main/java/org/apache/sling/maven/projectsupport/BundleListContentProvider.java b/src/main/java/org/apache/sling/maven/projectsupport/BundleListContentProvider.java new file mode 100644 index 0000000..cfe1c46 --- /dev/null +++ b/src/main/java/org/apache/sling/maven/projectsupport/BundleListContentProvider.java @@ -0,0 +1,183 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.apache.sling.maven.projectsupport; + +import static org.apache.sling.maven.projectsupport.AbstractUsingBundleListMojo.BUNDLE_PATH_PREFIX; +import static org.apache.sling.maven.projectsupport.AbstractUsingBundleListMojo.CONFIG_PATH_PREFIX; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.logging.Log; +import org.apache.sling.launchpad.api.LaunchpadContentProvider; +import org.apache.sling.maven.projectsupport.bundlelist.v1_0_0.Bundle; +import org.apache.sling.maven.projectsupport.bundlelist.v1_0_0.BundleList; +import org.apache.sling.maven.projectsupport.bundlelist.v1_0_0.StartLevel; + +/** LaunchpadContentProvider that provides resources based on a BundleList + * and other resources specific to this module. + */ +abstract class BundleListContentProvider implements LaunchpadContentProvider { + + private final File resourceProviderRoot; + + BundleListContentProvider(File resourceProviderRoot) { + this.resourceProviderRoot = resourceProviderRoot; + } + + public Iterator<String> getChildren(String path) { + if (path.equals(BUNDLE_PATH_PREFIX)) { + final Set<String> levels = new HashSet<String>(); + for (final StartLevel level : getInitializedBundleList().getStartLevels()) { + // we treat the boot level as level 1 + if ( level.getStartLevel() == -1 ) { + levels.add(BUNDLE_PATH_PREFIX + "/1/"); + } else { + levels.add(BUNDLE_PATH_PREFIX + "/" + level.getLevel() + "/"); + } + } + return levels.iterator(); + } else if (path.equals("resources/corebundles")) { + List<String> empty = Collections.emptyList(); + return empty.iterator(); + } else if (path.equals(CONFIG_PATH_PREFIX)) { + if (getConfigDirectory().exists() && getConfigDirectory().isDirectory()) { + File[] configFiles = getConfigDirectory().listFiles(new FileFilter() { + + public boolean accept(File file) { + return file.isFile(); + } + }); + + List<String> fileNames = new ArrayList<String>(); + for (File cfgFile : configFiles) { + if (cfgFile.isFile()) { + fileNames.add(CONFIG_PATH_PREFIX + "/" + cfgFile.getName()); + } + } + + return fileNames.iterator(); + + } else { + List<String> empty = Collections.emptyList(); + return empty.iterator(); + } + } else if (path.startsWith(BUNDLE_PATH_PREFIX)) { + final String startLevelInfo = path.substring(BUNDLE_PATH_PREFIX.length() + 1); + try { + final int startLevel = Integer.parseInt(startLevelInfo); + + final List<String> bundles = new ArrayList<String>(); + for (final StartLevel level : getInitializedBundleList().getStartLevels()) { + if (level.getStartLevel() == startLevel || (startLevel == 1 && level.getStartLevel() == -1)) { + for (final Bundle bundle : level.getBundles()) { + final ArtifactDefinition d = new ArtifactDefinition(bundle, startLevel); + try { + final Artifact artifact = getArtifact(d); + bundles.add(artifact.getFile().toURI().toURL().toExternalForm()); + } catch (Exception e) { + getLog().error("Unable to resolve artifact ", e); + } + } + } + } + return bundles.iterator(); + + } catch (NumberFormatException e) { + // we ignore this + } + } else if (path.equals("resources") ) { + final Set<String> subDirs = new HashSet<String>(); + subDirs.add(BUNDLE_PATH_PREFIX); + subDirs.add(CONFIG_PATH_PREFIX); + subDirs.add("resources/corebundles"); + return subDirs.iterator(); + } + + getLog().warn("un-handlable path " + path); + return null; + } + + public URL getResource(String path) { + if (path.startsWith(CONFIG_PATH_PREFIX)) { + File configFile = new File(getConfigDirectory(), path.substring(CONFIG_PATH_PREFIX.length() + 1)); + if (configFile.exists()) { + try { + return configFile.toURI().toURL(); + } catch (MalformedURLException e) { + // ignore this one + } + } + } + + File resourceFile = new File(resourceProviderRoot, path); + if (resourceFile.exists()) { + try { + return resourceFile.toURI().toURL(); + } catch (MalformedURLException e) { + getLog().error("Unable to create URL for file", e); + return null; + } + } else { + URL fromClasspath = getClass().getResource("/" + path); + if (fromClasspath != null) { + return fromClasspath; + } + + try { + return new URL(path); + } catch (MalformedURLException e) { + return null; + } + } + + } + + public InputStream getResourceAsStream(String path) { + URL res = this.getResource(path); + if (res != null) { + try { + return res.openStream(); + } catch (IOException ioe) { + // ignore this one + } + } + + // no resource + return null; + } + + abstract BundleList getInitializedBundleList(); + + abstract File getConfigDirectory(); + + abstract Artifact getArtifact(ArtifactDefinition def) throws MojoExecutionException; + + abstract Log getLog(); +} \ No newline at end of file diff --git a/src/test/java/org/apache/sling/maven/projectsupport/BundleListContentProviderTest.java b/src/test/java/org/apache/sling/maven/projectsupport/BundleListContentProviderTest.java new file mode 100644 index 0000000..7cd9039 --- /dev/null +++ b/src/test/java/org/apache/sling/maven/projectsupport/BundleListContentProviderTest.java @@ -0,0 +1,265 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sling.maven.projectsupport; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.logging.Log; +import org.apache.sling.launchpad.api.LaunchpadContentProvider; +import org.apache.sling.maven.projectsupport.bundlelist.v1_0_0.BundleList; +import org.apache.sling.maven.projectsupport.bundlelist.v1_0_0.StartLevel; +import org.apache.sling.maven.projectsupport.bundlelist.v1_0_0.io.xpp3.BundleListXpp3Reader; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.mockito.Mockito; + +/** Test the BundleListContentProvider */ +public class BundleListContentProviderTest { + private static BundleList bundleList; + + public static final String TEST_BUNDLE_LIST = "test-bundle-list.xml"; + public static final int BUNDLES_IN_TEST_BUNDLE_LIST = 11; + + private LaunchpadContentProvider provider; + private File resourceProviderRoot; + private File resourceProviderFile; + + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + private static final String [] CONFIG_FILES = { + "file1.txt", + "file2.cfg", + "someFile.properties" + }; + + @BeforeClass + public static void parseBundleList() throws Exception { + final BundleListXpp3Reader reader = new BundleListXpp3Reader(); + final InputStream is = BundleListContentProviderTest.class.getClassLoader().getResourceAsStream(TEST_BUNDLE_LIST); + assertNotNull("Expecting " + TEST_BUNDLE_LIST + " to be found", is); + try { + bundleList = reader.read(is); + } finally { + is.close(); + } + } + + @Before + public void setupTemporaryFiles() throws IOException { + for(String filename: CONFIG_FILES) { + final File f = getConfigFile(filename); + f.createNewFile(); + assertTrue("Expecting temporary config file to have been created: " + f.getAbsolutePath(), f.exists()); + } + + resourceProviderRoot = new File(tempFolder.getRoot(), "RESOURCE_PROVIDER_ROOT"); + resourceProviderRoot.mkdirs(); + resourceProviderFile = new File(resourceProviderRoot, "RP_FILE_" + System.currentTimeMillis()); + resourceProviderFile.createNewFile(); + } + + private File getConfigFile(String name) { + return new File(tempFolder.getRoot(), name); + } + + @Before + public void setupProvider() { + final Log log = Mockito.mock(Log.class); + provider = new BundleListContentProvider(resourceProviderRoot) { + + @Override + BundleList getInitializedBundleList() { + return bundleList; + } + + @Override + File getConfigDirectory() { + return tempFolder.getRoot(); + } + + @Override + Artifact getArtifact(ArtifactDefinition def) throws MojoExecutionException { + final Artifact a = Mockito.mock(Artifact.class); + final String fakeName = new StringBuilder() + .append("/") + .append(def.getArtifactId()) + .append("/") + .append(def.getStartLevel()) + .append("/") + .append(def.getRunModes()) + .toString(); + Mockito.when(a.getFile()).thenReturn(new File(fakeName)); + return a; + } + + @Override + Log getLog() { + return log; + } + }; + } + + private void assertChildren(String path, String ...expected) { + final List<String> kids = new ArrayList<String>(); + final Iterator<String> it = provider.getChildren(path); + if(expected.length == 0) { + assertTrue("Expecting no children for " + path, it == null || !it.hasNext()); + } else { + while(it.hasNext()) { + kids.add(it.next()); + } + for(String exp : expected) { + assertTrue("Expecting " + exp + " in children of " + path + " (result=" + kids + ")", kids.contains(exp)); + } + } + assertEquals("Expecting the correct number of children for " + path, expected.length, kids.size()); + } + + @Test + public void testParsedBundlesCount() { + int counter = 0; + for(StartLevel level : bundleList.getStartLevels()) { + counter += level.getBundles().size(); + } + assertEquals(BUNDLES_IN_TEST_BUNDLE_LIST, counter); + } + + @Test + public void testRoot() { + assertChildren("resources", + "resources/bundles", + "resources/corebundles", + "resources/config"); + } + + @Test + public void testBundles() { + assertChildren("resources/bundles", + "resources/bundles/0/", + "resources/bundles/1/", + "resources/bundles/5/", + "resources/bundles/15/"); + } + + @Test + public void testCoreBundles() { + assertChildren("resources/corebundles"); + } + + @Test + public void testConfig() { + assertChildren("resources/config", + "resources/config/file1.txt", + "resources/config/file2.cfg", + "resources/config/someFile.properties"); + } + + @Test + public void testBundles0() { + assertChildren("resources/bundles/0", + "file:/commons-io/0/null", + "file:/commons-fileupload/0/null", + "file:/commons-collections/0/null", + "file:/org.apache.sling.installer.provider.jcr/0/test,dev"); + } + + @Test + public void testBundles1() { + assertChildren("resources/bundles/1", + "file:/slf4j-api/1/null", + "file:/org.apache.sling.commons.log/1/null"); + } + + @Test + public void testBundles5() { + assertChildren("resources/bundles/5", + "file:/org.apache.sling.extensions.webconsolebranding/5/dev", + "file:/org.apache.sling.extensions.webconsolesecurityprovider/5/test"); + } + + @Test + public void testBundles15() { + assertChildren("resources/bundles/15", + "file:/org.apache.sling.jcr.oak.server/15/oak", + "file:/guava/15/jackrabbit", + "file:/jsr305/15/oak,jackrabbit"); + } + + @Test + public void testConfigResource() throws Exception { + final URL url = provider.getResource("resources/config/file1.txt"); + assertNotNull("Expecting config resource to be found", url); + assertEquals(getConfigFile("file1.txt").toURI().toURL().toExternalForm(), url.toExternalForm()); + } + + @Test + public void testResourceProviderResource() throws Exception { + final URL url = provider.getResource(resourceProviderFile.getName()); + assertNotNull("Expecting resource provider file to be found", url); + assertEquals(resourceProviderFile.toURI().toURL().toExternalForm(), url.toExternalForm()); + } + + @Test + public void testClasspathResource() throws Exception { + final URL url = provider.getResource(TEST_BUNDLE_LIST); + assertNotNull("Expecting classpath resource to be found", url); + assertTrue(url.toExternalForm().endsWith(TEST_BUNDLE_LIST)); + } + + @Test + public void testClasspathResourceAsStream() throws Exception { + final InputStream is = provider.getResourceAsStream(TEST_BUNDLE_LIST); + assertNotNull("Expecting classpath resource stream to be found", is); + is.close(); + } + + @Test + public void testNotFoundStream() throws Exception { + final InputStream is = provider.getResourceAsStream("resources/config/NOT_HERE.txt"); + assertNull("Expecting null stream for non-existent resource", is); + } + + @Test + public void testURLResource() throws Exception { + final String urlStr = "http://www.perdu.com"; + final URL url = provider.getResource(urlStr); + assertNotNull("Expecting URL resource to be found", url); + assertEquals(new URL(urlStr), url); + } + + @Test + public void testNullResult() { + assertNull(provider.getChildren("/FOO/bar")); + } +} diff --git a/src/test/resources/test-bundle-list.xml b/src/test/resources/test-bundle-list.xml new file mode 100644 index 0000000..8c3b87c --- /dev/null +++ b/src/test/resources/test-bundle-list.xml @@ -0,0 +1,76 @@ +<?xml version="1.0"?> +<bundles> + <startLevel level="boot"> + <!-- bootstrap bundles, must allow the installer core to start --> + <bundle> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <version>1.6.4</version> + </bundle> + <bundle> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.commons.log</artifactId> + <version>3.0.3-SNAPSHOT</version> + </bundle> + </startLevel> + + <startLevel level="0"> + <bundle> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + <version>1.4</version> + </bundle> + <bundle> + <groupId>commons-fileupload</groupId> + <artifactId>commons-fileupload</artifactId> + <version>1.2.2</version> + </bundle> + <bundle> + <groupId>commons-collections</groupId> + <artifactId>commons-collections</artifactId> + <version>3.2.1</version> + </bundle> + <bundle> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.installer.provider.jcr</artifactId> + <version>3.1.6</version> + <runModes>test,dev</runModes> + </bundle> + </startLevel> + + <startLevel level="5"> + <bundle> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.extensions.webconsolebranding</artifactId> + <version>1.0.0</version> + <runModes>dev</runModes> + </bundle> + <bundle> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.extensions.webconsolesecurityprovider</artifactId> + <version>1.0.0</version> + <runModes>test</runModes> + </bundle> + </startLevel> + + <startLevel level="15"> + <bundle> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.jcr.oak.server</artifactId> + <version>0.0.1-SNAPSHOT</version> + <runModes>oak</runModes> + </bundle> + <bundle> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + <version>14.0.1</version> + <runModes>jackrabbit</runModes> + </bundle> + <bundle> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> + <version>2.0.0</version> + <runModes>oak,jackrabbit</runModes> + </bundle> + </startLevel> +</bundles> -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
