FELIX-1064: Add a goal to validate a features descriptor to the features-maven-plugin
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@826062 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/karaf/tooling/features-maven-plugin/pom.xml b/karaf/tooling/features-maven-plugin/pom.xml
index 02214c5..d23e187 100644
--- a/karaf/tooling/features-maven-plugin/pom.xml
+++ b/karaf/tooling/features-maven-plugin/pom.xml
@@ -52,6 +52,10 @@
<artifactId>maven-bundle-plugin</artifactId>
</dependency>
<dependency>
+ <groupId>org.apache.felix.karaf.features</groupId>
+ <artifactId>org.apache.felix.karaf.features.core</artifactId>
+ </dependency>
+ <dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<scope>test</scope>
diff --git a/karaf/tooling/features-maven-plugin/src/main/java/org/apache/felix/karaf/tooling/features/ManifestUtils.java b/karaf/tooling/features-maven-plugin/src/main/java/org/apache/felix/karaf/tooling/features/ManifestUtils.java
new file mode 100644
index 0000000..f5f1739
--- /dev/null
+++ b/karaf/tooling/features-maven-plugin/src/main/java/org/apache/felix/karaf/tooling/features/ManifestUtils.java
@@ -0,0 +1,110 @@
+/**
+ *
+ * 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.felix.karaf.tooling.features;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.osgi.impl.bundle.obr.resource.Manifest;
+import org.osgi.impl.bundle.obr.resource.ManifestEntry;
+
+/**
+ * A set of utility methods to ease working with {@link org.osgi.impl.bundle.obr.resource.Manifest} and
+ * {@link org.osgi.impl.bundle.obr.resource.ManifestEntry}
+ */
+public class ManifestUtils {
+
+ private ManifestUtils() {
+ // hide the constructor
+ }
+
+ /**
+ * Get the list of imports from the manifest. If no imports have been defined, this method returns an empty list.
+ *
+ * @param manifest the manifest
+ * @return the list of imports
+ */
+ public static List<ManifestEntry> getImports(Manifest manifest) {
+ if (manifest.getImports() == null) {
+ return new LinkedList<ManifestEntry>();
+ } else {
+ return manifest.getImports();
+ }
+ }
+
+ /**
+ * Get the list of non-optional imports from the manifest.
+ *
+ * @param manifest the manifest
+ * @return the list of non-optional imports
+ */
+ public static List<ManifestEntry> getMandatoryImports(Manifest manifest) {
+ List<ManifestEntry> result = new LinkedList<ManifestEntry>();
+ for (ManifestEntry entry : getImports(manifest)) {
+ if (!isOptional(entry)) {
+ result.add(entry);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Get the list of exports from the manifest. If no exports have been defined, this method returns an empty list.
+ *
+ * @param manifest the manifest
+ * @return the list of exports
+ */
+ public static List<ManifestEntry> getExports(Manifest manifest) {
+ if (manifest.getExports() == null) {
+ return new LinkedList<ManifestEntry>();
+ } else {
+ return manifest.getExports();
+ }
+ }
+
+ /**
+ * Check if a given manifest entry represents an optional import
+ *
+ * @param entry the manifest entry
+ * @return <code>true</code> for an optional import, <code>false</code> for mandatory imports
+ */
+ public static boolean isOptional(ManifestEntry entry) {
+ return "optional".equals(entry.getDirective("resolution"));
+ }
+
+ /**
+ * Check if the manifest contains the mandatory Bundle-Symbolic-Name
+ *
+ * @param manifest the manifest
+ * @return <code>true</code> if the manifest specifies a Bundle-Symbolic-Name
+ */
+ public static boolean isBundle(Manifest manifest) {
+ return manifest.getBsn() != null;
+ }
+
+ public static boolean matches(ManifestEntry requirement, ManifestEntry export) {
+ if (requirement.getName().equals(export.getName())) {
+ if (requirement.getVersion().isRange()) {
+ return requirement.getVersion().compareTo(export.getVersion()) == 0;
+ } else {
+ return requirement.getVersion().compareTo(export.getVersion()) <= 0;
+ }
+ }
+ return false;
+ }
+}
diff --git a/karaf/tooling/features-maven-plugin/src/main/java/org/apache/felix/karaf/tooling/features/ValidateFeaturesMojo.java b/karaf/tooling/features-maven-plugin/src/main/java/org/apache/felix/karaf/tooling/features/ValidateFeaturesMojo.java
new file mode 100644
index 0000000..4568372
--- /dev/null
+++ b/karaf/tooling/features-maven-plugin/src/main/java/org/apache/felix/karaf/tooling/features/ValidateFeaturesMojo.java
@@ -0,0 +1,451 @@
+/**
+ *
+ * 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.felix.karaf.tooling.features;
+
+import static org.apache.felix.karaf.tooling.features.ManifestUtils.getExports;
+import static org.apache.felix.karaf.tooling.features.ManifestUtils.getMandatoryImports;
+import static org.apache.felix.karaf.tooling.features.ManifestUtils.matches;
+
+import java.io.*;
+import java.net.URI;
+import java.util.*;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+import org.apache.felix.karaf.features.Feature;
+import org.apache.felix.karaf.features.Repository;
+import org.apache.felix.karaf.features.internal.RepositoryImpl;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
+import org.apache.maven.artifact.resolver.ArtifactResolutionException;
+import org.apache.maven.artifact.resolver.DefaultArtifactCollector;
+import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.shared.dependency.tree.DependencyNode;
+import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder;
+import org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor;
+import org.osgi.impl.bundle.obr.resource.Manifest;
+import org.osgi.impl.bundle.obr.resource.ManifestEntry;
+
+/**
+ * Validates a features XML file
+ *
+ * @version $Revision: 1.1 $
+ * @goal validate
+ * @execute phase="process-resources"
+ * @requiresDependencyResolution runtime
+ * @inheritByDefault true
+ * @description Validates the features XML file
+ */
+@SuppressWarnings("unchecked")
+public class ValidateFeaturesMojo extends MojoSupport {
+
+ private static final String MVN_URI_PREFIX = "mvn:";
+
+ /**
+ * The dependency tree builder to use.
+ *
+ * @component
+ * @required
+ * @readonly
+ */
+ private DependencyTreeBuilder dependencyTreeBuilder;
+
+ /**
+ * The file to generate
+ *
+ * @parameter default-value="${project.build.directory}/classes/features.xml"
+ */
+ private File file;
+
+ /*
+ * A map to cache the mvn: uris and the artifacts that correspond with them
+ */
+ private Map<String, Artifact> bundles = new HashMap<String, Artifact>();
+
+ /*
+ * A map to cache manifests that have been extracted from the bundles
+ */
+ private Map<Artifact, Manifest> manifests = new HashMap<Artifact, Manifest>();
+
+ /*
+ * The list of features, includes both the features to be validated and the features from included <repository>s
+ */
+ private Features features = new Features();
+
+ /*
+ * The packages exported by the features themselves -- useful when features depend on other features
+ */
+ private Map<String, Set<ManifestEntry>> featureExports = new HashMap<String, Set<ManifestEntry>>();
+
+ /*
+ * The set of packages exported by the system bundle and by Karaf itself
+ */
+ private Set<String> systemExports = new HashSet<String>();
+
+ /**
+ * The Mojo's main method
+ */
+ public void execute() throws MojoExecutionException, MojoFailureException {
+ try {
+ prepare();
+ Repository repository = new RepositoryImpl(file.toURI());
+ analyze(repository);
+ validate(repository);
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new MojoExecutionException(String.format("Unable to validate %s: %s", file.getAbsolutePath(), e.getMessage()), e);
+ }
+
+ }
+
+ /*
+ * Prepare for validation by determing system and Karaf exports
+ */
+ private void prepare() throws Exception {
+ info("== Preparing for validation ==");
+ info(" - getting list of system bundle exports");
+ readSystemPackages();
+ info(" - getting list of provided bundle exports");
+ readProvidedBundles();
+ }
+
+ /*
+ * Analyse the descriptor and any <repository>s that might be part of it
+ */
+ private void analyze(Repository repository) throws Exception {
+ info("== Analyzing feature descriptor ==");
+ info(" - read %s", file.getAbsolutePath());
+
+ features.add(repository.getFeatures());
+
+ for (URI uri : repository.getRepositories()) {
+ Artifact artifact = resolve(uri.toString());
+ Repository dependency = new RepositoryImpl(new File(localRepo.getBasedir(), localRepo.pathOf(artifact)).toURI());
+ getLog().info(String.format(" - adding %d known features from %s", dependency.getFeatures().length, uri));
+ features.add(dependency.getFeatures());
+ // we need to do this to get all the information ready for further processing
+ validateBundlesAvailable(dependency);
+ analyzeExports(dependency);
+ }
+
+ }
+
+ /*
+ * Perform the actual validation
+ */
+ private void validate(Repository repository) throws Exception {
+ info("== Validating feature descriptor ==");
+ info(" - validating %d features", repository.getFeatures().length);
+ info(" - step 1: Checking if all artifacts exist");
+ validateBundlesAvailable(repository);
+ info(" OK: all %d OSGi bundles have been found", bundles.size());
+ info(" - step 2: Checking if all imports for bundles can be resolved");
+ validateImportsExports(repository);
+ info("== Done! ==========================");
+ }
+
+
+ /*
+ * Determine list of exports by bundles that have been marked provided in the pom
+ * //TODO: we probably want to figure this out somewhere from the Karaf build itself instead of putting the burden on the user
+ */
+ private void readProvidedBundles() throws Exception {
+ DependencyNode tree = dependencyTreeBuilder.buildDependencyTree(project, localRepo, factory, artifactMetadataSource, new ArtifactFilter() {
+
+ public boolean include(Artifact artifact) {
+ return true;
+ }
+
+ }, new DefaultArtifactCollector());
+ tree.accept(new DependencyNodeVisitor() {
+ public boolean endVisit(DependencyNode node) {
+ // we want the next sibling too
+ return true;
+ }
+
+ public boolean visit(DependencyNode node) {
+ if (node.getState() != DependencyNode.OMITTED_FOR_CONFLICT) {
+ Artifact artifact = node.getArtifact();
+ info(" scanning %s for exports", artifact);
+ if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope()) && !artifact.getType().equals("pom")) {
+ try {
+ for (ManifestEntry entry : ManifestUtils.getExports(getManifest(artifact))) {
+ getLog().debug(" adding " + entry.getName() + " to list of available packages");
+ systemExports.add(entry.getName());
+ }
+ } catch (ArtifactResolutionException e) {
+ error("Unable to find bundle exports for %s: %s", e, artifact, e.getMessage());
+ } catch (ArtifactNotFoundException e) {
+ error("Unable to find bundle exports for %s: %s", e, artifact, e.getMessage());
+ } catch (IOException e) {
+ error("Unable to find bundle exports for %s: %s", e, artifact, e.getMessage());
+ }
+ }
+ }
+ // we want the children too
+ return true;
+ }
+ });
+ }
+
+ /*
+ * Read system packages from a properties file
+ * //TODO: we should probably grab this file from the Karaf distro itself instead of duplicating it in the plugin
+ */
+ private void readSystemPackages() throws IOException {
+ Properties properties = new Properties();
+ properties.load(getClass().getClassLoader().getResourceAsStream("config.properties"));
+ String packages = (String) properties.get("jre-1.5");
+ for (String pkg : packages.split(";")) {
+ systemExports .add(pkg.trim());
+ }
+ }
+
+ /*
+ * Analyze exports in all features in the repository without validating the features
+ * (e.g. used for <repository> elements found in a descriptor)
+ */
+ private void analyzeExports(Repository repository) throws Exception {
+ for (Feature feature : repository.getFeatures()) {
+ Set<ManifestEntry> exports = new HashSet<ManifestEntry>();
+ for (String bundle : feature.getBundles()) {
+ exports.addAll(getExports(getManifest(bundles.get(bundle))));
+ }
+ info(" scanning feature %s for exports", feature.getName());
+ featureExports.put(feature.getName(), exports);
+ }
+ }
+
+ /*
+ * Check if all the bundles can be downloaded and are actually OSGi bundles and not plain JARs
+ */
+ private void validateBundlesAvailable(Repository repository) throws Exception {
+ for (Feature feature : repository.getFeatures()) {
+ for (String bundle : feature.getBundles()) {
+ // this will throw an exception if the artifact can not be resolved
+ final Artifact artifact = resolve(bundle);
+ bundles.put(bundle, artifact);
+ if (isBundle(artifact)) {
+ manifests.put(artifact, getManifest(artifact));
+ } else {
+ throw new Exception(String.format("%s is not an OSGi bundle", bundle));
+ }
+ }
+ }
+ }
+
+ /*
+ * Validate if all features in a repository have bundles which can be resolved
+ */
+ private void validateImportsExports(Repository repository) throws ArtifactResolutionException, ArtifactNotFoundException, Exception {
+ for (Feature feature : repository.getFeatures()) {
+ // make sure the feature hasn't been validated before as a dependency
+ if (!featureExports.containsKey(feature.getName())) {
+ validateImportsExports(feature);
+ }
+ }
+ }
+
+ /*
+ * Validate if all imports for a feature are being matched with exports
+ */
+ private void validateImportsExports(Feature feature) throws Exception {
+ Map<ManifestEntry, String> imports = new HashMap<ManifestEntry, String>();
+ Set<ManifestEntry> exports = new HashSet<ManifestEntry>();
+ for (Feature dependency : feature.getDependencies()) {
+ if (featureExports.containsKey(dependency.getName())) {
+ exports.addAll(featureExports.get(dependency.getName()));
+ } else {
+ validateImportsExports(features.get(dependency.getName(), dependency.getVersion()));
+ }
+ }
+ for (String bundle : feature.getBundles()) {
+ Manifest meta = manifests.get(bundles.get(bundle));
+ exports.addAll(getExports(meta));
+ for (ManifestEntry entry : getMandatoryImports(meta)) {
+ imports.put(entry, bundle);
+ }
+ }
+
+ // setting up the set of required imports
+ Set<ManifestEntry> requirements = new HashSet<ManifestEntry>();
+ requirements.addAll(imports.keySet());
+
+ // now, let's remove requirements whenever we find a matching export for them
+ for (ManifestEntry element : imports.keySet()) {
+ if (systemExports.contains(element.getName())) {
+ debug("%s is resolved by a system bundle export or provided bundle", element);
+ requirements.remove(element);
+ continue;
+ }
+ for (ManifestEntry export : exports) {
+ if (matches(element, export)) {
+ debug("%s is resolved by export %s", element, export);
+ requirements.remove(element);
+ continue;
+ }
+ debug("%s is not resolved by export %s", element, export);
+ }
+ }
+
+ // if there are any more requirements left here, there's a problem with the feature
+ if (!requirements.isEmpty()) {
+ warn("Failed to validate feature %s", feature.getName());
+ for (ManifestEntry entry : requirements) {
+ warn("No export found to match %s (imported by %s)",
+ entry, imports.get(entry));
+ }
+ throw new Exception(String.format("%d unresolved imports in feature %s",
+ requirements.size(), feature.getName()));
+ }
+ info(" OK: imports resolved for %s", feature.getName());
+ featureExports.put(feature.getName(), exports);
+ }
+
+ /*
+ * Check if the artifact is an OSGi bundle
+ */
+ private boolean isBundle(Artifact artifact) {
+ if (artifact.getArtifactHandler().getPackaging().equals("bundle")) {
+ return true;
+ } else {
+ try {
+ return ManifestUtils.isBundle(getManifest(artifact));
+ } catch (ZipException e) {
+ getLog().debug("Unable to determine if " + artifact + " is a bundle; defaulting to false", e);
+ } catch (IOException e) {
+ getLog().debug("Unable to determine if " + artifact + " is a bundle; defaulting to false", e);
+ } catch (Exception e) {
+ getLog().debug("Unable to determine if " + artifact + " is a bundle; defaulting to false", e);
+ }
+ }
+ return false;
+ }
+
+ /*
+ * Extract the META-INF/MANIFEST.MF file from an artifact
+ */
+ private Manifest getManifest(Artifact artifact) throws ArtifactResolutionException, ArtifactNotFoundException,
+ ZipException, IOException {
+ File localFile = new File(localRepo.pathOf(artifact));
+ ZipFile file;
+ if (localFile.exists()) {
+ // avoid going over to the repository if the file is already on the disk
+ file = new ZipFile(localFile);
+ } else {
+ resolver.resolve(artifact, remoteRepos, localRepo);
+ file = new ZipFile(artifact.getFile());
+ }
+ // let's replace syserr for now to hide warnings being issues by the Manifest reading process
+ PrintStream original = System.err;
+ try {
+ System.setErr(new PrintStream(new ByteArrayOutputStream()));
+ return new Manifest(file.getInputStream(file.getEntry("META-INF/MANIFEST.MF")));
+ } finally {
+ System.setErr(original);
+ }
+ }
+
+ /*
+ * Resolve an artifact, downloading it from remote repositories when necessary
+ */
+ private Artifact resolve(String bundle) throws ArtifactResolutionException, ArtifactNotFoundException {
+ Artifact artifact = getArtifact(bundle);
+ resolver.resolve(artifact, remoteRepos, localRepo);
+ return artifact;
+ }
+
+ /*
+ * Create an artifact for a given mvn: uri
+ */
+ private Artifact getArtifact(String uri) {
+ if (uri.startsWith(MVN_URI_PREFIX)) {
+ uri = uri.substring(MVN_URI_PREFIX.length());
+ }
+ String[] elements = uri.split("/");
+ switch (elements.length) {
+ case 5:
+ return factory.createArtifactWithClassifier(elements[0], elements[1], elements[2], elements[3], elements[4]);
+ case 3:
+ return factory.createArtifact(elements[0], elements[1], elements[2], Artifact.SCOPE_PROVIDED, "jar");
+ default:
+ return null;
+ }
+
+ }
+
+ /*
+ * Helper method for debug logging
+ */
+ private void debug(String message, Object... parms) {
+ if (getLog().isDebugEnabled()) {
+ getLog().debug(String.format(message, parms));
+ }
+ }
+
+ /*
+ * Helper method for info logging
+ */
+ private void info(String message, Object... parms) {
+ getLog().info(String.format(message, parms));
+ }
+
+ /*
+ * Helper method for warn logging
+ */
+ private void warn(String message, Object... parms) {
+ getLog().warn(String.format(message, parms));
+ }
+
+ /*
+ * Helper method for error logging
+ */
+ private void error(String message, Exception error, Object... parms) {
+ getLog().error(String.format(message, parms), error);
+ }
+
+ /*
+ * Convenience collection for holding features
+ */
+ private class Features {
+
+ private List<Feature> features = new LinkedList<Feature>();
+
+ public void add(Feature feature) {
+ features.add(feature);
+ }
+
+ public Feature get(String name, String version) throws Exception {
+ for (Feature feature : features) {
+ if (name.equals(feature.getName()) && version.equals(feature.getVersion())) {
+ return feature;
+ }
+ }
+ throw new Exception(String.format("Unable to find definition for feature %s (version %s)",
+ name, version));
+ }
+
+ public void add(Feature[] array) {
+ for (Feature feature : array) {
+ add(feature);
+ }
+ }
+ }
+}
diff --git a/karaf/tooling/features-maven-plugin/src/test/java/org/apache/felix/karaf/tooling/features/ManifestUtilsTest.java b/karaf/tooling/features-maven-plugin/src/test/java/org/apache/felix/karaf/tooling/features/ManifestUtilsTest.java
new file mode 100644
index 0000000..07b869c
--- /dev/null
+++ b/karaf/tooling/features-maven-plugin/src/test/java/org/apache/felix/karaf/tooling/features/ManifestUtilsTest.java
@@ -0,0 +1,70 @@
+/**
+ *
+ * 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.felix.karaf.tooling.features;
+
+import static org.apache.felix.karaf.tooling.features.ManifestUtils.matches;
+
+import junit.framework.TestCase;
+import org.osgi.impl.bundle.obr.resource.ManifestEntry;
+import org.osgi.impl.bundle.obr.resource.VersionRange;
+
+import java.util.HashMap;
+
+/**
+ * Test cased for {@link org.apache.felix.karaf.tooling.features.ManifestUtils}
+ */
+public class ManifestUtilsTest extends TestCase {
+
+ public void testIsOptional() {
+ ManifestEntry entry = new ManifestEntry("org.apache.karaf.test");
+ assertFalse(ManifestUtils.isOptional(entry));
+
+ entry.directives = new HashMap();
+ assertFalse(ManifestUtils.isOptional(entry));
+
+ entry.directives.put("resolution", "mandatory");
+ assertFalse(ManifestUtils.isOptional(entry));
+
+ entry.directives.put("resolution", "optional");
+ assertTrue(ManifestUtils.isOptional(entry));
+ }
+
+ public void testMatches() {
+ assertFalse(matches(entry("org.apache.karaf.dev"), entry("org.apache.karaf.test")));
+ assertTrue(matches(entry("org.apache.karaf.test"), entry("org.apache.karaf.test")));
+
+ assertFalse(matches(entry("org.apache.karaf.test", "1.2.0"), entry("org.apache.karaf.test", "1.1.0")));
+ assertTrue(matches(entry("org.apache.karaf.test", "1.1.0"), entry("org.apache.karaf.test", "1.1.0")));
+
+ // a single version means >= 1.0.0, so 1.1.O should be a match
+ assertTrue(matches(entry("org.apache.karaf.test", "1.0.0"), entry("org.apache.karaf.test", "1.1.0")));
+
+ assertFalse(matches(entry("org.apache.karaf.test", "[1.1.0, 1.2.0)"), entry("org.apache.karaf.test", "1.0.0")));
+ assertFalse(matches(entry("org.apache.karaf.test", "[1.1.0, 1.2.0)"), entry("org.apache.karaf.test", "1.2.0")));
+ assertTrue(matches(entry("org.apache.karaf.test", "[1.1.0, 1.2.0)"), entry("org.apache.karaf.test", "1.1.0")));
+ assertTrue(matches(entry("org.apache.karaf.test", "[1.1.0, 1.2.0)"), entry("org.apache.karaf.test", "1.1.1")));
+ }
+
+ private ManifestEntry entry(String name) {
+ return new ManifestEntry(name);
+ }
+
+ private ManifestEntry entry(String name, String version) {
+ return new ManifestEntry(name, new VersionRange(version));
+ }
+}