You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by da...@apache.org on 2018/04/27 10:03:01 UTC

[sling-org-apache-sling-feature-resolver] 17/20: [Sling Feature Model] Refactor FeatureUtil out of the support module

This is an automated email from the ASF dual-hosted git repository.

davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-resolver.git

commit 9ea233ded636a87b5fa525aea964828f8be81325
Author: David Bosschaert <da...@gmail.com>
AuthorDate: Wed Apr 25 13:40:18 2018 +0100

    [Sling Feature Model] Refactor FeatureUtil out of the support module
    
    Also move the Resolver API to the resolver module.
---
 .../resolver/ApplicationResolverAssembler.java     | 128 +++++++++++++++++++++
 .../sling/feature/resolver/FeatureResolver.java    |  38 ++++++
 .../sling/feature/resolver/FeatureResource.java    |  52 +++++++++
 .../sling/feature/resolver/FrameworkResolver.java  |   2 -
 .../feature/resolver/impl/BundleResourceImpl.java  |   2 +-
 .../feature/resolver/impl/FeatureResourceImpl.java |   2 +-
 .../sling/feature/resolver/AnalyserTest.java       | 114 ++++++++++++++++++
 .../feature/resolver/FrameworkResolverTest.java    |   2 -
 .../feature/resolver/TestBundleResourceImpl.java}  |  88 +++++---------
 .../resolver/impl/BundleResourceImplTest.java      |   2 +-
 src/test/resources/feature_complete.json           |  82 +++++++++++++
 src/test/resources/feature_incomplete.json         |  82 +++++++++++++
 12 files changed, 529 insertions(+), 65 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/resolver/ApplicationResolverAssembler.java b/src/main/java/org/apache/sling/feature/resolver/ApplicationResolverAssembler.java
new file mode 100644
index 0000000..84c1e42
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/resolver/ApplicationResolverAssembler.java
@@ -0,0 +1,128 @@
+/*
+ * 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.feature.resolver;
+
+import org.apache.sling.feature.Application;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.builder.ApplicationBuilder;
+import org.apache.sling.feature.builder.BuilderContext;
+import org.apache.sling.feature.builder.FeatureProvider;
+import org.apache.sling.feature.io.ArtifactHandler;
+import org.apache.sling.feature.io.ArtifactManager;
+import org.apache.sling.feature.io.IOUtils;
+import org.apache.sling.feature.io.json.FeatureJSONReader;
+import org.apache.sling.feature.io.json.FeatureJSONReader.SubstituteVariables;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class ApplicationResolverAssembler {
+    /**
+     * Assemble an application based on the given files.
+     *
+     * Read the features and assemble the application
+     * @param app The optional application to use as a base.
+     * @param featureFiles The feature files.
+     * @param artifactManager The artifact manager
+     * @param fr
+     * @return The assembled application
+     * @throws IOException If a feature can't be read or no feature is found.
+     * @see #getFeatureFiles(File, String...)
+     */
+    public static Application assembleApplication(
+            Application app,
+            final ArtifactManager artifactManager,
+            final FeatureResolver fr,
+            final String... featureFiles)
+    throws IOException {
+        final List<Feature> features = new ArrayList<>();
+        for(final String initFile : featureFiles) {
+            final Feature f = IOUtils.getFeature(initFile, artifactManager, SubstituteVariables.RESOLVE);
+            features.add(f);
+        }
+
+        return assembleApplication(app, artifactManager, fr, features.toArray(new Feature[0]));
+    }
+
+    public static Feature[] sortFeatures(final FeatureResolver fr,
+            final Feature... features) {
+        final List<Feature> featureList = new ArrayList<>();
+        for(final Feature f : features) {
+            featureList.add(f);
+        }
+
+        final List<Feature> sortedFeatures;
+        if (fr != null) {
+            // order by dependency chain
+            final List<FeatureResource> sortedResources = fr.orderResources(featureList);
+
+            sortedFeatures = new ArrayList<>();
+            for (final FeatureResource rsrc : sortedResources) {
+                Feature f = rsrc.getFeature();
+                if (!sortedFeatures.contains(f)) {
+                    sortedFeatures.add(rsrc.getFeature());
+                }
+            }
+        } else {
+            sortedFeatures = featureList;
+            Collections.sort(sortedFeatures);
+        }
+        return sortedFeatures.toArray(new Feature[sortedFeatures.size()]);
+    }
+
+    public static Application assembleApplication(
+            Application app,
+            final ArtifactManager artifactManager,
+            final FeatureResolver fr,
+            final Feature... features)
+    throws IOException {
+        if ( features.length == 0 ) {
+            throw new IOException("No features found.");
+        }
+
+        app = ApplicationBuilder.assemble(app, new BuilderContext(new FeatureProvider() {
+
+            @Override
+            public Feature provide(final ArtifactId id) {
+                try {
+                    final ArtifactHandler handler = artifactManager.getArtifactHandler("mvn:" + id.toMvnPath());
+                    try (final FileReader r = new FileReader(handler.getFile())) {
+                        final Feature f = FeatureJSONReader.read(r, handler.getUrl(), SubstituteVariables.RESOLVE);
+                        return f;
+                    }
+
+                } catch (final IOException e) {
+                    // ignore
+                }
+                return null;
+            }
+        }), sortFeatures(fr, features));
+
+        // check framework
+        if ( app.getFramework() == null ) {
+            // use hard coded Apache Felix
+            app.setFramework(IOUtils.getFelixFrameworkId(null));
+        }
+
+        return app;
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/resolver/FeatureResolver.java b/src/main/java/org/apache/sling/feature/resolver/FeatureResolver.java
new file mode 100644
index 0000000..f40a9b1
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/resolver/FeatureResolver.java
@@ -0,0 +1,38 @@
+/*
+ * 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.feature.resolver;
+
+import java.util.List;
+
+import org.apache.sling.feature.Feature;
+
+/**
+ * A resolver that can perform operations on the feature model.
+ */
+public interface FeatureResolver extends AutoCloseable {
+    /**
+     * Order the resources in list of features by their dependency chain.
+     * Each feature and its components are resolved. Then all the resources
+     * in the feature are ordered so that each resource is placed before
+     * the requiring feature/resources in the result.
+     *
+     * @param features
+     *            The features to order.
+     * @return The ordered resources from the features.
+     */
+    List<FeatureResource> orderResources(List<Feature> features);
+}
diff --git a/src/main/java/org/apache/sling/feature/resolver/FeatureResource.java b/src/main/java/org/apache/sling/feature/resolver/FeatureResource.java
new file mode 100644
index 0000000..b420805
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/resolver/FeatureResource.java
@@ -0,0 +1,52 @@
+/*
+ * 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.feature.resolver;
+
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.Feature;
+import org.osgi.framework.Version;
+import org.osgi.resource.Resource;
+
+/**
+ * A Resource that is associated with an Maven Artifact and belongs to a Feature.
+ */
+public interface FeatureResource extends Resource {
+    /**
+     * Obtain the ID of the resource. If the resource is a bundle then this
+     * is the bundle symbolic name.
+     * @return The ID of the resource.
+     */
+    String getId();
+
+    /**
+     * Obtain the version of the resource.
+     * @return The version of the resource.
+     */
+    Version getVersion();
+
+    /**
+     * Obtain the associated (Maven) Artifact.
+     * @return The artifact for this Resource.
+     */
+    Artifact getArtifact();
+
+    /**
+     * Obtain the feature that contains this resource.
+     * @return The feature that contains the resource.
+     */
+    Feature getFeature();
+}
diff --git a/src/main/java/org/apache/sling/feature/resolver/FrameworkResolver.java b/src/main/java/org/apache/sling/feature/resolver/FrameworkResolver.java
index 88efe0c..da09e69 100644
--- a/src/main/java/org/apache/sling/feature/resolver/FrameworkResolver.java
+++ b/src/main/java/org/apache/sling/feature/resolver/FrameworkResolver.java
@@ -38,8 +38,6 @@ import org.apache.sling.feature.resolver.impl.FeatureResourceImpl;
 import org.apache.sling.feature.resolver.impl.ResolveContextImpl;
 import org.apache.sling.feature.scanner.BundleDescriptor;
 import org.apache.sling.feature.scanner.impl.BundleDescriptorImpl;
-import org.apache.sling.feature.support.resolver.FeatureResolver;
-import org.apache.sling.feature.support.resolver.FeatureResource;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;
 import org.osgi.framework.Constants;
diff --git a/src/main/java/org/apache/sling/feature/resolver/impl/BundleResourceImpl.java b/src/main/java/org/apache/sling/feature/resolver/impl/BundleResourceImpl.java
index 5e28c79..901b141 100644
--- a/src/main/java/org/apache/sling/feature/resolver/impl/BundleResourceImpl.java
+++ b/src/main/java/org/apache/sling/feature/resolver/impl/BundleResourceImpl.java
@@ -20,8 +20,8 @@ import org.apache.felix.utils.resource.CapabilityImpl;
 import org.apache.felix.utils.resource.RequirementImpl;
 import org.apache.sling.feature.Artifact;
 import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.resolver.FeatureResource;
 import org.apache.sling.feature.scanner.BundleDescriptor;
-import org.apache.sling.feature.support.resolver.FeatureResource;
 import org.apache.sling.feature.support.util.PackageInfo;
 import org.osgi.framework.Version;
 import org.osgi.framework.VersionRange;
diff --git a/src/main/java/org/apache/sling/feature/resolver/impl/FeatureResourceImpl.java b/src/main/java/org/apache/sling/feature/resolver/impl/FeatureResourceImpl.java
index f61e6e3..4acee42 100644
--- a/src/main/java/org/apache/sling/feature/resolver/impl/FeatureResourceImpl.java
+++ b/src/main/java/org/apache/sling/feature/resolver/impl/FeatureResourceImpl.java
@@ -20,7 +20,7 @@ import org.apache.felix.utils.resource.CapabilityImpl;
 import org.apache.felix.utils.resource.RequirementImpl;
 import org.apache.sling.feature.Artifact;
 import org.apache.sling.feature.Feature;
-import org.apache.sling.feature.support.resolver.FeatureResource;
+import org.apache.sling.feature.resolver.FeatureResource;
 import org.osgi.framework.Version;
 import org.osgi.framework.namespace.IdentityNamespace;
 import org.osgi.resource.Capability;
diff --git a/src/test/java/org/apache/sling/feature/resolver/AnalyserTest.java b/src/test/java/org/apache/sling/feature/resolver/AnalyserTest.java
new file mode 100644
index 0000000..c4b8110
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/resolver/AnalyserTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.feature.resolver;
+
+import org.apache.sling.feature.Application;
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.analyser.Analyser;
+import org.apache.sling.feature.io.ArtifactManager;
+import org.apache.sling.feature.io.ArtifactManagerConfig;
+import org.apache.sling.feature.io.json.FeatureJSONReader;
+import org.apache.sling.feature.io.json.FeatureJSONReader.SubstituteVariables;
+import org.apache.sling.feature.scanner.BundleDescriptor;
+import org.apache.sling.feature.scanner.Scanner;
+import org.apache.sling.feature.scanner.impl.BundleDescriptorImpl;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.fail;
+
+public class AnalyserTest {
+    @Test
+    public void testAnalyserWithCompleteFeature() throws Exception {
+        final Scanner scanner = new Scanner(new ArtifactManagerConfig());
+        final Analyser analyser = new Analyser(scanner);
+        try ( final Reader reader = new InputStreamReader(AnalyserTest.class.getResourceAsStream("/feature_complete.json"),
+                "UTF-8") ) {
+            Feature feature = FeatureJSONReader.read(reader, "feature", SubstituteVariables.RESOLVE);
+
+            Application app = ApplicationResolverAssembler.assembleApplication(null, ArtifactManager.getArtifactManager(new ArtifactManagerConfig()),
+                    getTestResolver(), feature);
+
+            analyser.analyse(app);
+        }
+    }
+
+    @Test
+    public void testAnalyserWithInCompleteFeature() throws Exception {
+        final Scanner scanner = new Scanner(new ArtifactManagerConfig());
+        final Analyser analyser = new Analyser(scanner);
+        try ( final Reader reader = new InputStreamReader(AnalyserTest.class.getResourceAsStream("/feature_incomplete.json"),
+                "UTF-8") ) {
+            Feature feature = FeatureJSONReader.read(reader, "feature", SubstituteVariables.RESOLVE);
+
+            Application app = ApplicationResolverAssembler.assembleApplication(null, ArtifactManager.getArtifactManager(new ArtifactManagerConfig()),
+                    getTestResolver(), feature);
+
+            try {
+                analyser.analyse(app);
+
+                fail("Expected an exception");
+            }
+            catch (Exception ex) {
+                // Pass
+            }
+        }
+    }
+
+    private FeatureResolver getTestResolver() {
+        return new FeatureResolver() {
+            @Override
+            public void close() throws Exception {
+            }
+
+            @Override
+            public List<FeatureResource> orderResources(List<Feature> features) {
+                try {
+                    // Just return the resources in the same order as they are listed in the features
+                    List<FeatureResource> l = new ArrayList<>();
+
+                    for (Feature f : features) {
+                        for (Artifact a : f.getBundles()) {
+                            BundleDescriptor bd = getBundleDescriptor(ArtifactManager.getArtifactManager(new ArtifactManagerConfig()), a);
+                            l.add(new TestBundleResourceImpl(bd, f));
+                        }
+                    }
+
+                    return l;
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+
+            private BundleDescriptor getBundleDescriptor(ArtifactManager artifactManager, Artifact b) throws IOException {
+                final File file = artifactManager.getArtifactHandler(b.getId().toMvnUrl()).getFile();
+                if ( file == null ) {
+                    throw new IOException("Unable to find file for " + b.getId());
+                }
+
+                return new BundleDescriptorImpl(b, file, -1);
+            }
+        };
+    }
+}
diff --git a/src/test/java/org/apache/sling/feature/resolver/FrameworkResolverTest.java b/src/test/java/org/apache/sling/feature/resolver/FrameworkResolverTest.java
index 7094876..8365101 100644
--- a/src/test/java/org/apache/sling/feature/resolver/FrameworkResolverTest.java
+++ b/src/test/java/org/apache/sling/feature/resolver/FrameworkResolverTest.java
@@ -22,8 +22,6 @@ import org.apache.sling.feature.io.ArtifactManager;
 import org.apache.sling.feature.io.ArtifactManagerConfig;
 import org.apache.sling.feature.io.json.FeatureJSONReader;
 import org.apache.sling.feature.io.json.FeatureJSONReader.SubstituteVariables;
-import org.apache.sling.feature.support.resolver.FeatureResolver;
-import org.apache.sling.feature.support.resolver.FeatureResource;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Ignore;
diff --git a/src/main/java/org/apache/sling/feature/resolver/impl/BundleResourceImpl.java b/src/test/java/org/apache/sling/feature/resolver/TestBundleResourceImpl.java
similarity index 72%
copy from src/main/java/org/apache/sling/feature/resolver/impl/BundleResourceImpl.java
copy to src/test/java/org/apache/sling/feature/resolver/TestBundleResourceImpl.java
index 5e28c79..86beec4 100644
--- a/src/main/java/org/apache/sling/feature/resolver/impl/BundleResourceImpl.java
+++ b/src/test/java/org/apache/sling/feature/resolver/TestBundleResourceImpl.java
@@ -14,34 +14,34 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.sling.feature.resolver.impl;
+package org.apache.sling.feature.resolver;
 
 import org.apache.felix.utils.resource.CapabilityImpl;
 import org.apache.felix.utils.resource.RequirementImpl;
 import org.apache.sling.feature.Artifact;
 import org.apache.sling.feature.Feature;
 import org.apache.sling.feature.scanner.BundleDescriptor;
-import org.apache.sling.feature.support.resolver.FeatureResource;
 import org.apache.sling.feature.support.util.PackageInfo;
 import org.osgi.framework.Version;
 import org.osgi.framework.VersionRange;
 import org.osgi.framework.namespace.BundleNamespace;
 import org.osgi.framework.namespace.ExecutionEnvironmentNamespace;
-import org.osgi.framework.namespace.IdentityNamespace;
 import org.osgi.framework.namespace.PackageNamespace;
 import org.osgi.resource.Capability;
 import org.osgi.resource.Requirement;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
- * Implementation of the OSGi Resource interface.
+ * Implementation of the OSGi Resource interface, used by the test
  */
-public class BundleResourceImpl extends AbstractResourceImpl implements FeatureResource {
+public class TestBundleResourceImpl implements FeatureResource {
     final Artifact artifact;
     final String bsn;
     final Version version;
@@ -53,7 +53,7 @@ public class BundleResourceImpl extends AbstractResourceImpl implements FeatureR
      * Create a resource based on a BundleDescriptor.
      * @param bd The BundleDescriptor to represent.
      */
-    public BundleResourceImpl(BundleDescriptor bd, Feature feat) {
+    public TestBundleResourceImpl(BundleDescriptor bd, Feature feat) {
         artifact = bd.getArtifact();
         bsn = bd.getBundleSymbolicName();
         version = bd.getArtifact().getId().getOSGiVersion();
@@ -75,24 +75,16 @@ public class BundleResourceImpl extends AbstractResourceImpl implements FeatureR
             Map<String, Object> attrs = new HashMap<>();
             attrs.put(PackageNamespace.PACKAGE_NAMESPACE, exported.getName());
             attrs.put(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE, exported.getPackageVersion());
-            attrs.put(PackageNamespace.CAPABILITY_BUNDLE_SYMBOLICNAME_ATTRIBUTE, bsn);
-            attrs.put(PackageNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE, version);
+            attrs.put(PackageNamespace.CAPABILITY_BUNDLE_SYMBOLICNAME_ATTRIBUTE, bd.getBundleSymbolicName());
+            attrs.put(PackageNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE, new Version(bd.getBundleVersion()));
             pkgCaps.add(new CapabilityImpl(this, PackageNamespace.PACKAGE_NAMESPACE, null, attrs));
         }
         caps.put(PackageNamespace.PACKAGE_NAMESPACE, Collections.unmodifiableList(pkgCaps));
 
-        // Add the identity capability
-        Map<String, Object> idattrs = new HashMap<>();
-        idattrs.put(IdentityNamespace.IDENTITY_NAMESPACE, bsn);
-        idattrs.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, IdentityNamespace.TYPE_BUNDLE);
-        idattrs.put(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, version);
-        Capability idCap = new CapabilityImpl(this, IdentityNamespace.IDENTITY_NAMESPACE, null, idattrs);
-        caps.put(IdentityNamespace.IDENTITY_NAMESPACE, Collections.singletonList(idCap));
-
         // Add the bundle capability
         Map<String, Object> battrs = new HashMap<>();
-        battrs.put(BundleNamespace.BUNDLE_NAMESPACE, bsn);
-        battrs.put(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE, version);
+        battrs.put(BundleNamespace.BUNDLE_NAMESPACE, bd.getBundleSymbolicName());
+        battrs.put(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE, new Version(bd.getBundleVersion()));
         Capability bundleCap = new CapabilityImpl(this, BundleNamespace.BUNDLE_NAMESPACE, null, battrs);
         caps.put(BundleNamespace.BUNDLE_NAMESPACE, Collections.singletonList(bundleCap));
         capabilities = Collections.unmodifiableMap(caps);
@@ -133,19 +125,6 @@ public class BundleResourceImpl extends AbstractResourceImpl implements FeatureR
         requirements = Collections.unmodifiableMap(reqs);
     }
 
-    public BundleResourceImpl(String sn, String ver, Artifact art, Feature feat, Map<String, List<Capability>> caps, Map<String, List<Requirement>> reqs) {
-        this(sn, new Version(ver), art, feat, caps, reqs);
-    }
-
-    public BundleResourceImpl(String sn, Version ver, Artifact art, Feature feat, Map<String, List<Capability>> caps, Map<String, List<Requirement>> reqs) {
-        artifact = art;
-        bsn = sn;
-        version = ver;
-        feature = feat;
-        capabilities = caps;
-        requirements = reqs;
-    }
-
     @Override
     public Artifact getArtifact() {
         return artifact;
@@ -163,12 +142,26 @@ public class BundleResourceImpl extends AbstractResourceImpl implements FeatureR
 
     @Override
     public List<Capability> getCapabilities(String namespace) {
-        return super.getCapabilities(namespace, capabilities);
+        if (namespace == null) {
+            return capabilities.values().stream().flatMap(Collection::stream).collect(Collectors.toList());
+        }
+
+        List<Capability> caps = capabilities.get(namespace);
+        if (caps == null)
+            return Collections.emptyList();
+        return caps;
     }
 
     @Override
     public List<Requirement> getRequirements(String namespace) {
-        return super.getRequirements(namespace, requirements);
+        if (namespace == null) {
+            return requirements.values().stream().flatMap(Collection::stream).collect(Collectors.toList());
+        }
+
+        List<Requirement> reqs = requirements.get(namespace);
+        if (reqs == null)
+            return Collections.emptyList();
+        return reqs;
     }
 
     @Override
@@ -182,30 +175,9 @@ public class BundleResourceImpl extends AbstractResourceImpl implements FeatureR
         int result = 1;
         result = prime * result + ((artifact == null) ? 0 : artifact.hashCode());
         result = prime * result + ((bsn == null) ? 0 : bsn.hashCode());
-
-        if (capabilities != null) {
-            // Don't delegate to the capabilities to compute their hashcode since that results in an endless loop
-            for (List<Capability> lc : capabilities.values()) {
-                for (Capability c : lc) {
-                    result = prime * result + c.getNamespace().hashCode();
-                    result = prime * result + c.getAttributes().hashCode();
-                    result = prime * result + c.getDirectives().hashCode();
-                }
-            }
-        }
-
-        if (requirements != null) {
-            // Don't delegate to the requirements to compute their hashcode since that results in an endless loop
-            for (List<Requirement> lr : requirements.values()) {
-                for (Requirement r : lr) {
-                    result = prime * result + r.getNamespace().hashCode();
-                    result = prime * result + r.getAttributes().hashCode();
-                    result = prime * result + r.getDirectives().hashCode();
-                }
-            }
-        }
-
+        result = prime * result + ((capabilities == null) ? 0 : capabilities.hashCode());
         result = prime * result + ((feature == null) ? 0 : feature.hashCode());
+        result = prime * result + ((requirements == null) ? 0 : requirements.hashCode());
         result = prime * result + ((version == null) ? 0 : version.hashCode());
         return result;
     }
@@ -218,7 +190,7 @@ public class BundleResourceImpl extends AbstractResourceImpl implements FeatureR
             return false;
         if (getClass() != obj.getClass())
             return false;
-        BundleResourceImpl other = (BundleResourceImpl) obj;
+        TestBundleResourceImpl other = (TestBundleResourceImpl) obj;
         if (artifact == null) {
             if (other.artifact != null)
                 return false;
@@ -254,6 +226,6 @@ public class BundleResourceImpl extends AbstractResourceImpl implements FeatureR
 
     @Override
     public String toString() {
-        return "BundleResourceImpl [bsn=" + bsn + ", version=" + version + "]";
+        return "BundleResourceImpl [" + bsn + " " + version + "]";
     }
 }
diff --git a/src/test/java/org/apache/sling/feature/resolver/impl/BundleResourceImplTest.java b/src/test/java/org/apache/sling/feature/resolver/impl/BundleResourceImplTest.java
index 3ca9870..eb307d2 100644
--- a/src/test/java/org/apache/sling/feature/resolver/impl/BundleResourceImplTest.java
+++ b/src/test/java/org/apache/sling/feature/resolver/impl/BundleResourceImplTest.java
@@ -21,10 +21,10 @@ import org.apache.felix.utils.resource.RequirementImpl;
 import org.apache.sling.feature.Artifact;
 import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.resolver.FeatureResource;
 import org.apache.sling.feature.scanner.BundleDescriptor;
 import org.apache.sling.feature.scanner.Descriptor;
 import org.apache.sling.feature.scanner.impl.BundleDescriptorImpl;
-import org.apache.sling.feature.support.resolver.FeatureResource;
 import org.apache.sling.feature.support.util.PackageInfo;
 import org.junit.Test;
 import org.mockito.Mockito;
diff --git a/src/test/resources/feature_complete.json b/src/test/resources/feature_complete.json
new file mode 100644
index 0000000..6271a9c
--- /dev/null
+++ b/src/test/resources/feature_complete.json
@@ -0,0 +1,82 @@
+{
+    "id" : "test/test.complete/0.1",
+
+    "bundles" : [
+        {
+          "id" : "org.apache.sling/org.apache.sling.commons.log/5.0.0",
+          "start-order" : 1
+        },
+        {
+          "id" : "org.apache.sling/org.apache.sling.commons.logservice/1.0.6",
+          "start-order" : 1
+        },
+        {
+          "id" : "org.slf4j/jcl-over-slf4j/1.7.21",
+          "start-order" : 1
+        },
+        {
+          "id" : "org.slf4j/log4j-over-slf4j/1.7.21",
+          "start-order" : 1
+        },
+        {
+          "id" : "org.slf4j/slf4j-api/1.7.21",
+          "start-order" : 1
+        },
+        {
+          "id" : "org.apache.felix/org.apache.felix.configadmin/1.8.14",
+          "start-order" : 1
+        },
+        {
+          "id" : "org.apache.felix/org.apache.felix.eventadmin/1.4.8",
+          "start-order" : 4
+        },
+        {
+          "id" : "org.apache.felix/org.apache.felix.metatype/1.1.2",
+          "start-order" : 4
+        },
+        {
+          "id" : "org.apache.felix/org.apache.felix.scr/2.0.12",
+          "start-order" : 4
+        },
+        {
+          "id" : "org.apache.felix/org.apache.felix.http.jetty/3.4.2",
+          "start-order" : 5
+        },
+        {
+          "id" : "org.apache.felix/org.apache.felix.http.servlet-api/1.1.2",
+          "start-order" : 5
+        },
+        {
+          "id" : "commons-io/commons-io/2.5",
+          "start-order" : 5
+        },
+        {
+          "id" : "commons-fileupload/commons-fileupload/1.3.2",
+          "start-order" : 5
+        },
+        {
+          "id" : "org.apache.felix/org.apache.felix.inventory/1.0.4",
+          "start-order" : 5
+        },
+        {
+          "id" : "org.apache.felix/org.apache.felix.webconsole.plugins.ds/2.0.6",
+          "start-order" : 5
+        },
+        {
+          "id" : "org.apache.felix/org.apache.felix.webconsole.plugins.event/1.1.6",
+          "start-order" : 5
+        },
+        {
+          "id" : "org.apache.felix/org.apache.felix.webconsole.plugins.packageadmin/1.0.4",
+          "start-order" : 5
+        },
+        {
+          "id" : "org.apache.felix/org.apache.felix.webconsole/4.3.4",
+          "start-order" : 5
+        },
+        {
+          "id" : "org.apache.sling/org.apache.sling.commons.log.webconsole/1.0.0",
+          "start-order" : 5
+        }
+    ]
+}
diff --git a/src/test/resources/feature_incomplete.json b/src/test/resources/feature_incomplete.json
new file mode 100644
index 0000000..514e878
--- /dev/null
+++ b/src/test/resources/feature_incomplete.json
@@ -0,0 +1,82 @@
+{
+    "id" : "test/test.incomplete/0.1",
+
+    "bundles" : [
+        {
+          "id" : "org.apache.sling/org.apache.sling.commons.log/5.0.0",
+          "start-order" : 1
+        },
+        {
+          "id" : "org.apache.sling/org.apache.sling.commons.logservice/1.0.6",
+          "start-order" : 1
+        },
+        {
+          "id" : "org.slf4j/jcl-over-slf4j/1.7.21",
+          "start-order" : 1
+        },
+        {
+          "id" : "org.slf4j/log4j-over-slf4j/1.7.21",
+          "start-order" : 1
+        },
+        {
+          "id" : "org.slf4j/slf4j-api/1.7.21",
+          "start-order" : 1
+        },
+        {
+          "id" : "org.apache.felix/org.apache.felix.configadmin/1.8.14",
+          "start-order" : 1
+        },
+        {
+          "id" : "org.apache.felix/org.apache.felix.eventadmin/1.4.8",
+          "start-order" : 4
+        },
+        {
+          "id" : "org.apache.felix/org.apache.felix.metatype/1.1.2",
+          "start-order" : 4
+        },
+        {
+          "id" : "org.apache.felix/org.apache.felix.scr/2.0.12",
+          "start-order" : 4
+        },
+        {
+          "id" : "org.apache.felix/org.apache.felix.http.servlet-api/1.1.2",
+          "start-order" : 5
+        },
+        {
+          "id" : "commons-io/commons-io/2.5",
+          "start-order" : 5
+        },
+        {
+          "id" : "commons-fileupload/commons-fileupload/1.3.2",
+          "start-order" : 5
+        },
+        {
+          "id" : "org.apache.felix/org.apache.felix.inventory/1.0.4",
+          "start-order" : 5
+        },
+        {
+          "id" : "org.apache.felix/org.apache.felix.webconsole.plugins.ds/2.0.6",
+          "start-order" : 5
+        },
+        {
+          "id" : "org.apache.felix/org.apache.felix.webconsole.plugins.event/1.1.6",
+          "start-order" : 5
+        },
+        {
+          "id" : "org.apache.felix/org.apache.felix.webconsole.plugins.packageadmin/1.0.4",
+          "start-order" : 5
+        },
+        {
+          "id" : "org.apache.felix/org.apache.felix.webconsole/4.3.4",
+          "start-order" : 5
+        },
+        {
+          "id" : "org.apache.sling/org.apache.sling.commons.log.webconsole/1.0.0",
+          "start-order" : 5
+        },
+        {
+          "id" : "org.apache.sling/org.apache.sling.i18n/2.5.8",
+          "start-order" : 6
+        }
+    ]
+}

-- 
To stop receiving notification emails like this one, please contact
davidb@apache.org.