You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by ge...@apache.org on 2009/10/16 22:08:00 UTC

svn commit: r826062 - in /felix/trunk/karaf/tooling/features-maven-plugin: ./ src/main/java/org/apache/felix/karaf/tooling/features/ src/test/java/org/apache/felix/karaf/tooling/features/

Author: gertv
Date: Fri Oct 16 20:08:00 2009
New Revision: 826062

URL: http://svn.apache.org/viewvc?rev=826062&view=rev
Log:
FELIX-1064: Add a goal to validate a features descriptor to the features-maven-plugin

Added:
    felix/trunk/karaf/tooling/features-maven-plugin/src/main/java/org/apache/felix/karaf/tooling/features/ManifestUtils.java
    felix/trunk/karaf/tooling/features-maven-plugin/src/main/java/org/apache/felix/karaf/tooling/features/ValidateFeaturesMojo.java
    felix/trunk/karaf/tooling/features-maven-plugin/src/test/java/org/apache/felix/karaf/tooling/features/ManifestUtilsTest.java
Modified:
    felix/trunk/karaf/tooling/features-maven-plugin/pom.xml

Modified: felix/trunk/karaf/tooling/features-maven-plugin/pom.xml
URL: http://svn.apache.org/viewvc/felix/trunk/karaf/tooling/features-maven-plugin/pom.xml?rev=826062&r1=826061&r2=826062&view=diff
==============================================================================
--- felix/trunk/karaf/tooling/features-maven-plugin/pom.xml (original)
+++ felix/trunk/karaf/tooling/features-maven-plugin/pom.xml Fri Oct 16 20:08:00 2009
@@ -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>

Added: felix/trunk/karaf/tooling/features-maven-plugin/src/main/java/org/apache/felix/karaf/tooling/features/ManifestUtils.java
URL: http://svn.apache.org/viewvc/felix/trunk/karaf/tooling/features-maven-plugin/src/main/java/org/apache/felix/karaf/tooling/features/ManifestUtils.java?rev=826062&view=auto
==============================================================================
--- felix/trunk/karaf/tooling/features-maven-plugin/src/main/java/org/apache/felix/karaf/tooling/features/ManifestUtils.java (added)
+++ felix/trunk/karaf/tooling/features-maven-plugin/src/main/java/org/apache/felix/karaf/tooling/features/ManifestUtils.java Fri Oct 16 20:08:00 2009
@@ -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;
+    }
+}

Added: felix/trunk/karaf/tooling/features-maven-plugin/src/main/java/org/apache/felix/karaf/tooling/features/ValidateFeaturesMojo.java
URL: http://svn.apache.org/viewvc/felix/trunk/karaf/tooling/features-maven-plugin/src/main/java/org/apache/felix/karaf/tooling/features/ValidateFeaturesMojo.java?rev=826062&view=auto
==============================================================================
--- felix/trunk/karaf/tooling/features-maven-plugin/src/main/java/org/apache/felix/karaf/tooling/features/ValidateFeaturesMojo.java (added)
+++ felix/trunk/karaf/tooling/features-maven-plugin/src/main/java/org/apache/felix/karaf/tooling/features/ValidateFeaturesMojo.java Fri Oct 16 20:08:00 2009
@@ -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);
+            }   
+        }
+    }
+}

Added: felix/trunk/karaf/tooling/features-maven-plugin/src/test/java/org/apache/felix/karaf/tooling/features/ManifestUtilsTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/karaf/tooling/features-maven-plugin/src/test/java/org/apache/felix/karaf/tooling/features/ManifestUtilsTest.java?rev=826062&view=auto
==============================================================================
--- felix/trunk/karaf/tooling/features-maven-plugin/src/test/java/org/apache/felix/karaf/tooling/features/ManifestUtilsTest.java (added)
+++ felix/trunk/karaf/tooling/features-maven-plugin/src/test/java/org/apache/felix/karaf/tooling/features/ManifestUtilsTest.java Fri Oct 16 20:08:00 2009
@@ -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));
+    }
+}