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:02:47 UTC

[sling-org-apache-sling-feature-resolver] 03/20: SLING-7521 Order bundles in the generated app based on feature order and start order

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 568557a62e33d52a7ffc5582d75e45bfe4002148
Author: David Bosschaert <bo...@adobe.com>
AuthorDate: Mon Mar 5 15:05:23 2018 +0000

    SLING-7521 Order bundles in the generated app based on feature order and start order
    
    Order resource (bundles and features) in the resulting application based on the order
    of resolved features and then also in the order of the start order within the feature.
---
 pom.xml                                            |  18 +++
 .../sling/feature/resolver/FrameworkResolver.java  | 164 ++++++++++++++-------
 .../resolver/impl/AbstractResourceImpl.java        |  50 +++++++
 .../feature/resolver/impl/BundleResourceImpl.java  | 129 ++++++++++------
 .../feature/resolver/impl/FeatureResourceImpl.java | 153 +++++++++++++++++++
 .../feature/resolver/impl/ResolveContextImpl.java  |  16 +-
 .../feature/resolver/FrameworkResolverTest.java    |  85 ++++++++---
 .../resolver/impl/BundleResourceImplTest.java      |  43 ++++--
 .../resolver/impl/ResolveContextImplTest.java      |  18 +--
 src/test/resources/feature3.json                   |  12 +-
 src/test/resources/feature4.json                   |   5 +
 src/test/resources/feature5.json                   |  13 ++
 src/test/resources/feature6.json                   |  12 ++
 13 files changed, 569 insertions(+), 149 deletions(-)

diff --git a/pom.xml b/pom.xml
index 7d379f9..d4bcdf8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -73,6 +73,12 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.osgi</artifactId>
+            <version>2.4.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.service.resolver</artifactId>
             <version>1.0.1</version>
@@ -96,5 +102,17 @@
             <version>5.6.10</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.configurator</artifactId>
+            <version>0.0.1-SNAPSHOT</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.johnzon</artifactId>
+            <version>1.0.0</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
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 f62c3db..249f4f2 100644
--- a/src/main/java/org/apache/sling/feature/resolver/FrameworkResolver.java
+++ b/src/main/java/org/apache/sling/feature/resolver/FrameworkResolver.java
@@ -16,25 +16,15 @@
  */
 package org.apache.sling.feature.resolver;
 
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.ServiceLoader;
-import java.util.Set;
-
 import org.apache.sling.feature.Artifact;
 import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.FeatureResource;
 import org.apache.sling.feature.analyser.BundleDescriptor;
 import org.apache.sling.feature.analyser.impl.BundleDescriptorImpl;
 import org.apache.sling.feature.process.FeatureResolver;
 import org.apache.sling.feature.resolver.impl.BundleResourceImpl;
+import org.apache.sling.feature.resolver.impl.FeatureResourceImpl;
 import org.apache.sling.feature.resolver.impl.ResolveContextImpl;
 import org.apache.sling.feature.support.ArtifactManager;
 import org.osgi.framework.BundleContext;
@@ -44,6 +34,7 @@ import org.osgi.framework.launch.Framework;
 import org.osgi.framework.launch.FrameworkFactory;
 import org.osgi.framework.namespace.BundleNamespace;
 import org.osgi.framework.namespace.HostNamespace;
+import org.osgi.framework.namespace.IdentityNamespace;
 import org.osgi.framework.namespace.PackageNamespace;
 import org.osgi.framework.wiring.BundleRevision;
 import org.osgi.resource.Capability;
@@ -53,10 +44,19 @@ import org.osgi.resource.Wire;
 import org.osgi.service.resolver.ResolutionException;
 import org.osgi.service.resolver.Resolver;
 
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+
 public class FrameworkResolver implements FeatureResolver {
     private final ArtifactManager artifactManager;
     private final Resolver resolver;
-    private final Resource frameworkResource;
+    private final FeatureResource frameworkResource;
     private final Framework framework;
 
     public FrameworkResolver(ArtifactManager am, Map<String, String> frameworkProperties) {
@@ -72,10 +72,13 @@ public class FrameworkResolver implements FeatureResolver {
             BundleContext ctx = framework.getBundleContext();
 
             // Create a resource representing the framework
+            Map<String, List<Capability>> capabilities = new HashMap<>();
             BundleRevision br = framework.adapt(BundleRevision.class);
-            List<Capability> caps = br.getCapabilities(PackageNamespace.PACKAGE_NAMESPACE);
-            frameworkResource = new BundleResourceImpl(
-                    Collections.singletonMap(PackageNamespace.PACKAGE_NAMESPACE, caps), Collections.emptyMap());
+            capabilities.put(PackageNamespace.PACKAGE_NAMESPACE, br.getCapabilities(PackageNamespace.PACKAGE_NAMESPACE));
+            capabilities.put(BundleNamespace.BUNDLE_NAMESPACE, br.getCapabilities(BundleNamespace.BUNDLE_NAMESPACE));
+            capabilities.put(IdentityNamespace.IDENTITY_NAMESPACE, br.getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE));
+            frameworkResource = new BundleResourceImpl(framework.getSymbolicName(), framework.getVersion(), null, null,
+                    capabilities, Collections.emptyMap());
 
             int i=0;
             while (i < 20) {
@@ -101,65 +104,89 @@ public class FrameworkResolver implements FeatureResolver {
     }
 
     @Override
-    public List<Feature> orderFeatures(List<Feature> features) {
+    public List<FeatureResource> orderResources(List<Feature> features) {
         try {
-            return internalOrderFeatures(features);
+            return internalOrderResources(features);
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
 
-    public List<Feature> internalOrderFeatures(List<Feature> features) throws IOException {
-        Map<Resource, Feature> bundleMap = new HashMap<>();
+    public List<FeatureResource> internalOrderResources(List<Feature> features) throws IOException {
+        Map<Feature, FeatureResource> featureMap = new HashMap<>();
+        Map<FeatureResource, Feature> resourceMap = new HashMap<>();
         for (Feature f : features) {
+            FeatureResourceImpl fr = new FeatureResourceImpl(f);
+            resourceMap.put(fr, f);
+            featureMap.put(f, fr);
+
             for (Artifact b : f.getBundles()) {
                 BundleDescriptor bd = getBundleDescriptor(artifactManager, b);
-                Resource r = new BundleResourceImpl(bd);
-                bundleMap.put(r, f);
+                FeatureResource r = new BundleResourceImpl(bd, f);
+                resourceMap.put(r, f);
             }
         }
 
-        Set<Resource> availableBundles = new HashSet<>(bundleMap.keySet());
-        // Add these to the available features
+        Map<String, FeatureResource> idVerMap = new HashMap<>();
+        for (FeatureResource fr : resourceMap.keySet()) {
+            idVerMap.put(fr.getId() + ":" + fr.getVersion(), fr);
+        }
+
+        // Add these too
         Artifact lpa = new Artifact(ArtifactId.parse("org.apache.sling/org.apache.sling.launchpad.api/1.2.0"));
-        availableBundles.add(new BundleResourceImpl(getBundleDescriptor(artifactManager, lpa)));
-        availableBundles.add(frameworkResource);
+        idVerMap.put("org.apache.sling.launchpad.api:1.2.0", new BundleResourceImpl(getBundleDescriptor(artifactManager, lpa), null));
+        idVerMap.put(framework.getSymbolicName() + ":" + framework.getVersion(), frameworkResource);
 
-        List<Resource> orderedBundles = new LinkedList<>();
+        List<FeatureResource> orderedResources = new LinkedList<>();
         try {
-            for (Resource bundle : bundleMap.keySet()) {
-                if (orderedBundles.contains(bundle)) {
+            for (FeatureResource resource : resourceMap.keySet()) {
+                if (orderedResources.contains(resource)) {
                     // Already handled
                     continue;
                 }
-                Map<Resource, List<Wire>> deps = resolver.resolve(new ResolveContextImpl(bundle, availableBundles));
+                Map<Resource, List<Wire>> deps = resolver.resolve(new ResolveContextImpl(resource, idVerMap.values()));
 
                 for (Map.Entry<Resource, List<Wire>> entry : deps.entrySet()) {
-                    Resource curBundle = entry.getKey();
+                    if (resource.equals(entry.getKey()))
+                        continue;
 
-                    if (!bundleMap.containsKey(curBundle)) {
-                        // This is some synthesized bundle. Ignoring.
+                    Resource depResource = entry.getKey();
+                    FeatureResource curResource = getFeatureResource(depResource, idVerMap);
+                    if (curResource == null)
                         continue;
-                    }
 
-                    if (!orderedBundles.contains(curBundle)) {
-                        orderedBundles.add(curBundle);
+                    if (!orderedResources.contains(curResource)) {
+                        orderedResources.add(curResource);
                     }
 
                     for (Wire w : entry.getValue()) {
-                        Resource provBundle = w.getProvider();
-                        int curBundleIdx = orderedBundles.indexOf(curBundle);
-                        int newBundleIdx = orderedBundles.indexOf(provBundle);
+                        FeatureResource provBundle = getFeatureResource(w.getProvider(), idVerMap);
+                        if (provBundle == null)
+                            continue;
+
+                        int curBundleIdx = orderedResources.indexOf(curResource);
+                        int newBundleIdx = orderedResources.indexOf(provBundle);
                         if (newBundleIdx >= 0) {
                             if (curBundleIdx < newBundleIdx) {
                                 // If the list already contains the providing but after the current bundle, remove it there to move it before the current bundle
-                                orderedBundles.remove(provBundle);
+                                orderedResources.remove(provBundle);
                             } else {
                                 // If the providing bundle is already before the current bundle, then no need to change anything
                                 continue;
                             }
                         }
-                        orderedBundles.add(curBundleIdx, provBundle);
+                        orderedResources.add(curBundleIdx, provBundle);
+                    }
+                }
+
+                // All of the dependencies of the resource have been added, now add the resource itself
+                if (!orderedResources.contains(resource)) {
+                    Feature associatedFeature = resource.getFeature();
+                    if (resource.equals(featureMap.get(associatedFeature))) {
+                        // The resource is a feature resource, don't add this one by itself.
+                    }
+                    else {
+                        orderedResources.add(resource);
                     }
                 }
             }
@@ -168,35 +195,62 @@ public class FrameworkResolver implements FeatureResolver {
         }
 
         // Sort the fragments so that fragments are started before the host bundle
-        for (int i=0; i<orderedBundles.size(); i++) {
-            Resource r = orderedBundles.get(i);
+        for (int i=0; i<orderedResources.size(); i++) {
+            Resource r = orderedResources.get(i);
             List<Requirement> reqs = r.getRequirements(HostNamespace.HOST_NAMESPACE);
             if (reqs.size() > 0) {
                 // This is a fragment
                 Requirement req = reqs.iterator().next(); // TODO handle more host requirements
                 String bsn = req.getAttributes().get(HostNamespace.HOST_NAMESPACE).toString(); // TODO this is not valid, should obtain from filter
-                int idx = getBundleIndex(orderedBundles, bsn); // TODO check for filter too
+                int idx = getBundleIndex(orderedResources, bsn); // TODO check for filter too
                 if (idx < i) {
                     // the fragment is after the host, and should be moved to be before the host
-                    Resource frag = orderedBundles.remove(i);
-                    orderedBundles.add(idx, frag);
+                    FeatureResource frag = orderedResources.remove(i);
+                    orderedResources.add(idx, frag);
                 }
             }
         }
 
-        List<Feature> orderedFeatures = new ArrayList<>();
-        for (Resource r : orderedBundles) {
-            Feature f = bundleMap.get(r);
-            if (f != null) {
-                if (!orderedFeatures.contains(f)) {
-                    orderedFeatures.add(f);
-                }
+        // Add the features at the appropriate place to the ordered resources list
+        for (int i=0; i<orderedResources.size(); i++) {
+            FeatureResource r = orderedResources.get(i);
+            FeatureResource associatedFeature = featureMap.get(r.getFeature());
+            if (associatedFeature == null)
+                continue;
+
+            int idx = orderedResources.indexOf(associatedFeature);
+            if (idx > i) {
+                orderedResources.remove(idx);
+                orderedResources.add(i, associatedFeature);
+            } else if (idx == -1) {
+                orderedResources.add(i, associatedFeature);
             }
         }
-        return orderedFeatures;
+
+        // If the framework shows up as a dependency, remove it as it's always there
+        orderedResources.remove(frameworkResource);
+
+        return orderedResources;
+    }
+
+    private FeatureResource getFeatureResource(Resource res, Map<String, FeatureResource> idVerMap) {
+        if (res instanceof FeatureResource)
+            return (FeatureResource) res;
+
+        // Obtain the identity from the resource and look up in the resource
+        List<Capability> caps = res.getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE);
+        if (caps.size() == 0) {
+            return null;
+        }
+        Capability cap = caps.get(0);
+        Map<String, Object> attrs = cap.getAttributes();
+        Object id = attrs.get(IdentityNamespace.IDENTITY_NAMESPACE);
+        Object ver = attrs.get(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE);
+        String idVer = "" + id + ":" + ver;
+        return idVerMap.get(idVer);
     }
 
-    private static int getBundleIndex(List<Resource> bundles, String bundleSymbolicName) {
+    private static int getBundleIndex(List<FeatureResource> bundles, String bundleSymbolicName) {
         for (int i=0; i<bundles.size(); i++) {
             Resource b = bundles.get(i);
             if (bundleSymbolicName.equals(getBundleSymbolicName(b))) {
diff --git a/src/main/java/org/apache/sling/feature/resolver/impl/AbstractResourceImpl.java b/src/main/java/org/apache/sling/feature/resolver/impl/AbstractResourceImpl.java
new file mode 100644
index 0000000..3cc1ca1
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/resolver/impl/AbstractResourceImpl.java
@@ -0,0 +1,50 @@
+/*
+ * 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.impl;
+
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+abstract class AbstractResourceImpl {
+    public List<Requirement> getRequirements(String namespace, Map<String, List<Requirement>> 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;
+    }
+
+    public List<Capability> getCapabilities(String namespace, Map<String, List<Capability>> 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;
+    }
+}
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 16cc812..5053a1d 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
@@ -16,14 +16,9 @@
  */
 package org.apache.sling.feature.resolver.impl;
 
-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;
-
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.FeatureResource;
 import org.apache.sling.feature.OSGiCapability;
 import org.apache.sling.feature.OSGiRequirement;
 import org.apache.sling.feature.analyser.BundleDescriptor;
@@ -32,25 +27,38 @@ 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 org.osgi.resource.Resource;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 /**
  * Implementation of the OSGi Resource interface.
  */
-public class BundleResourceImpl implements Resource {
-    final String hint;
+public class BundleResourceImpl extends AbstractResourceImpl implements FeatureResource {
+    final Artifact artifact;
+    final String bsn;
+    final Version version;
     final Map<String, List<Capability>> capabilities;
     final Map<String, List<Requirement>> requirements;
+    final Feature feature;
 
     /**
      * Create a resource based on a BundleDescriptor.
      * @param bd The BundleDescriptor to represent.
      */
-    public BundleResourceImpl(BundleDescriptor bd) {
-        hint = bd.getBundleSymbolicName() + " " + bd.getBundleVersion();
+    public BundleResourceImpl(BundleDescriptor bd, Feature feat) {
+        artifact = bd.getArtifact();
+        bsn = bd.getBundleSymbolicName();
+        version = bd.getArtifact().getId().getOSGiVersion();
+        feature = feat;
+
         Map<String, List<Capability>> caps = new HashMap<>();
         for (Capability c : bd.getCapabilities()) {
             List<Capability> l = caps.get(c.getNamespace());
@@ -67,16 +75,24 @@ public class BundleResourceImpl implements Resource {
             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, bd.getBundleSymbolicName());
-            attrs.put(PackageNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE, new Version(bd.getBundleVersion()));
+            attrs.put(PackageNamespace.CAPABILITY_BUNDLE_SYMBOLICNAME_ATTRIBUTE, bsn);
+            attrs.put(PackageNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE, version);
             pkgCaps.add(new OSGiCapability(this, PackageNamespace.PACKAGE_NAMESPACE, attrs, Collections.emptyMap()));
         }
         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);
+        OSGiCapability idCap = new OSGiCapability(this, IdentityNamespace.IDENTITY_NAMESPACE, idattrs, Collections.emptyMap());
+        caps.put(IdentityNamespace.IDENTITY_NAMESPACE, Collections.singletonList(idCap));
+
         // Add the bundle capability
         Map<String, Object> battrs = new HashMap<>();
-        battrs.put(BundleNamespace.BUNDLE_NAMESPACE, bd.getBundleSymbolicName());
-        battrs.put(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE, new Version(bd.getBundleVersion()));
+        battrs.put(BundleNamespace.BUNDLE_NAMESPACE, bsn);
+        battrs.put(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE, version);
         OSGiCapability bundleCap = new OSGiCapability(this, BundleNamespace.BUNDLE_NAMESPACE, battrs, Collections.emptyMap());
         caps.put(BundleNamespace.BUNDLE_NAMESPACE, Collections.singletonList(bundleCap));
         capabilities = Collections.unmodifiableMap(caps);
@@ -117,48 +133,59 @@ public class BundleResourceImpl implements Resource {
         requirements = Collections.unmodifiableMap(reqs);
     }
 
-    /**
-     * Constructor. Create a resource based on capabilties and requirements.
-     * @param hnt
-     * @param caps The capabilities of the resource.
-     * @param reqs The requirements of the resource.
-     */
-    public BundleResourceImpl(Map<String, List<Capability>> caps, Map<String, List<Requirement>> reqs) {
-        hint = "" + System.identityHashCode(this);
+    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 List<Capability> getCapabilities(String namespace) {
-        if (namespace == null) {
-            return capabilities.values().stream().flatMap(Collection::stream).collect(Collectors.toList());
-        }
+    public Artifact getArtifact() {
+        return artifact;
+    }
+
+    @Override
+    public String getId() {
+        return bsn;
+    }
+
+    @Override
+    public Version getVersion() {
+        return version;
+    }
 
-        List<Capability> caps = capabilities.get(namespace);
-        if (caps == null)
-            return Collections.emptyList();
-        return caps;
+    @Override
+    public List<Capability> getCapabilities(String namespace) {
+        return super.getCapabilities(namespace, capabilities);
     }
 
     @Override
     public List<Requirement> getRequirements(String namespace) {
-        if (namespace == null) {
-            return requirements.values().stream().flatMap(Collection::stream).collect(Collectors.toList());
-        }
+        return super.getRequirements(namespace, requirements);
+    }
 
-        List<Requirement> reqs = requirements.get(namespace);
-        if (reqs == null)
-            return Collections.emptyList();
-        return reqs;
+    @Override
+    public Feature getFeature() {
+        return feature;
     }
 
     @Override
     public int hashCode() {
         final int prime = 31;
         int result = 1;
+        result = prime * result + ((artifact == null) ? 0 : artifact.hashCode());
+        result = prime * result + ((bsn == null) ? 0 : bsn.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;
     }
 
@@ -171,21 +198,41 @@ public class BundleResourceImpl implements Resource {
         if (getClass() != obj.getClass())
             return false;
         BundleResourceImpl other = (BundleResourceImpl) obj;
+        if (artifact == null) {
+            if (other.artifact != null)
+                return false;
+        } else if (!artifact.equals(other.artifact))
+            return false;
+        if (bsn == null) {
+            if (other.bsn != null)
+                return false;
+        } else if (!bsn.equals(other.bsn))
+            return false;
         if (capabilities == null) {
             if (other.capabilities != null)
                 return false;
         } else if (!capabilities.equals(other.capabilities))
             return false;
+        if (feature == null) {
+            if (other.feature != null)
+                return false;
+        } else if (!feature.equals(other.feature))
+            return false;
         if (requirements == null) {
             if (other.requirements != null)
                 return false;
         } else if (!requirements.equals(other.requirements))
             return false;
+        if (version == null) {
+            if (other.version != null)
+                return false;
+        } else if (!version.equals(other.version))
+            return false;
         return true;
     }
 
     @Override
     public String toString() {
-        return "BundleResourceImpl [" + hint + "]";
+        return "BundleResourceImpl [bsn=" + bsn + ", version=" + version + "]";
     }
 }
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
new file mode 100644
index 0000000..dc5c6b9
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/resolver/impl/FeatureResourceImpl.java
@@ -0,0 +1,153 @@
+/*
+ * 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.impl;
+
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.FeatureResource;
+import org.apache.sling.feature.OSGiCapability;
+import org.apache.sling.feature.OSGiRequirement;
+import org.osgi.framework.Version;
+import org.osgi.framework.namespace.IdentityNamespace;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class FeatureResourceImpl extends AbstractResourceImpl implements FeatureResource {
+    private final Artifact artifact;
+    private final Feature feature;
+    private final Map<String, List<Capability>> capabilities;
+    private final Map<String, List<Requirement>> requirements;
+
+    public FeatureResourceImpl(Feature f) {
+        artifact = new Artifact(f.getId());
+        feature = f;
+
+        capabilities = new HashMap<>();
+        for (Capability r : f.getCapabilities()) {
+            List<Capability> l = capabilities.get(r.getNamespace());
+            if (l == null) {
+                l = new ArrayList<>();
+                capabilities.put(r.getNamespace(), l);
+            }
+            l.add(new OSGiCapability(this, r));
+        }
+
+        // Add the identity capability
+        Map<String, Object> idattrs = new HashMap<>();
+        idattrs.put(IdentityNamespace.IDENTITY_NAMESPACE, getId());
+        idattrs.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, "sling.feature");
+        idattrs.put(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, getVersion());
+        idattrs.put(IdentityNamespace.CAPABILITY_DESCRIPTION_ATTRIBUTE, f.getDescription());
+        idattrs.put(IdentityNamespace.CAPABILITY_LICENSE_ATTRIBUTE, f.getLicense());
+        OSGiCapability idCap = new OSGiCapability(this, IdentityNamespace.IDENTITY_NAMESPACE, idattrs, Collections.emptyMap());
+        capabilities.put(IdentityNamespace.IDENTITY_NAMESPACE, Collections.singletonList(idCap));
+
+        requirements = new HashMap<>();
+        for (Requirement r : f.getRequirements()) {
+            List<Requirement> l = requirements.get(r.getNamespace());
+            if (l == null) {
+                l = new ArrayList<>();
+                requirements.put(r.getNamespace(), l);
+            }
+            l.add(new OSGiRequirement(this, r));
+        }
+    }
+
+    @Override
+    public String getId() {
+        return artifact.getId().getArtifactId();
+    }
+
+    @Override
+    public Version getVersion() {
+        return artifact.getId().getOSGiVersion();
+    }
+
+    @Override
+    public Artifact getArtifact() {
+        return artifact;
+    }
+
+    @Override
+    public Feature getFeature() {
+        return feature;
+    }
+
+    @Override
+    public List<Capability> getCapabilities(String namespace) {
+        return super.getCapabilities(namespace, capabilities);
+    }
+
+    @Override
+    public List<Requirement> getRequirements(String namespace) {
+        return super.getRequirements(namespace, requirements);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((artifact == null) ? 0 : artifact.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());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        FeatureResourceImpl other = (FeatureResourceImpl) obj;
+        if (artifact == null) {
+            if (other.artifact != null)
+                return false;
+        } else if (!artifact.equals(other.artifact))
+            return false;
+        if (capabilities == null) {
+            if (other.capabilities != null)
+                return false;
+        } else if (!capabilities.equals(other.capabilities))
+            return false;
+        if (feature == null) {
+            if (other.feature != null)
+                return false;
+        } else if (!feature.equals(other.feature))
+            return false;
+        if (requirements == null) {
+            if (other.requirements != null)
+                return false;
+        } else if (!requirements.equals(other.requirements))
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "FeatureResourceImpl [artifact=" + artifact + "]";
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/resolver/impl/ResolveContextImpl.java b/src/main/java/org/apache/sling/feature/resolver/impl/ResolveContextImpl.java
index 91f4183..f76db32 100644
--- a/src/main/java/org/apache/sling/feature/resolver/impl/ResolveContextImpl.java
+++ b/src/main/java/org/apache/sling/feature/resolver/impl/ResolveContextImpl.java
@@ -16,12 +16,6 @@
  */
 package org.apache.sling.feature.resolver.impl;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
 import org.osgi.framework.Filter;
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.InvalidSyntaxException;
@@ -32,19 +26,25 @@ import org.osgi.resource.Wiring;
 import org.osgi.service.resolver.HostedCapability;
 import org.osgi.service.resolver.ResolveContext;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
 /**
  * Implementation of the OSGi ResolveContext for use with the OSGi Resolver.
  */
 public class ResolveContextImpl extends ResolveContext {
     private final Resource bundle;
-    private final Collection<Resource> availableResources;
+    private final Collection<? extends Resource> availableResources;
 
     /**
      * Constructor.
      * @param mainResource The main resource to resolve.
      * @param available The available resources to provide dependencies.
      */
-    public ResolveContextImpl(Resource mainResource, Collection<Resource> available) {
+    public ResolveContextImpl(Resource mainResource, Collection<? extends Resource> available) {
         bundle = mainResource;
         availableResources = available;
     }
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 3f28644..290263e 100644
--- a/src/test/java/org/apache/sling/feature/resolver/FrameworkResolverTest.java
+++ b/src/test/java/org/apache/sling/feature/resolver/FrameworkResolverTest.java
@@ -16,30 +16,30 @@
  */
 package org.apache.sling.feature.resolver;
 
-import static org.junit.Assert.assertEquals;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.FeatureResource;
+import org.apache.sling.feature.process.FeatureResolver;
+import org.apache.sling.feature.support.ArtifactHandler;
+import org.apache.sling.feature.support.ArtifactManager;
+import org.apache.sling.feature.support.ArtifactManagerConfig;
+import org.apache.sling.feature.support.json.FeatureJSONReader;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.Constants;
+import org.osgi.framework.namespace.IdentityNamespace;
 
 import java.io.File;
 import java.io.FileReader;
 import java.net.URL;
-import java.nio.file.FileVisitOption;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.List;
 import java.util.Map;
 
-import org.apache.sling.feature.Feature;
-import org.apache.sling.feature.process.FeatureResolver;
-import org.apache.sling.feature.support.ArtifactHandler;
-import org.apache.sling.feature.support.ArtifactManager;
-import org.apache.sling.feature.support.ArtifactManagerConfig;
-import org.apache.sling.feature.support.json.FeatureJSONReader;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.osgi.framework.Constants;
+import static org.junit.Assert.assertEquals;
 
 public class FrameworkResolverTest {
     private Path tempDir;
@@ -52,7 +52,7 @@ public class FrameworkResolverTest {
     @After
     public void tearDown() throws Exception {
         // Delete the temp dir again
-        Files.walk(tempDir, FileVisitOption.FOLLOW_LINKS)
+        Files.walk(tempDir)
             .sorted(Comparator.reverseOrder())
             .map(Path::toFile)
             .forEach(File::delete);
@@ -67,23 +67,70 @@ public class FrameworkResolverTest {
         ArtifactManager am = ArtifactManager.getArtifactManager(new ArtifactManagerConfig());
         try (FeatureResolver fr = new FrameworkResolver(am, getFrameworkProps())) {
             assertEquals(Collections.emptyList(),
-                    fr.orderFeatures(Collections.emptyList()));
+                    fr.orderResources(Collections.emptyList()));
         }
     }
 
     @Test
-    public void testOrderFeatures() throws Exception {
+    public void testOrderResources() throws Exception {
         ArtifactManager am = ArtifactManager.getArtifactManager(new ArtifactManagerConfig());
 
         Feature f1 = readFeature("/feature1.json", am);
         Feature f2 = readFeature("/feature2.json", am);
         Feature f3 = readFeature("/feature3.json", am);
 
+        StringBuilder expectedBundles = new StringBuilder();
+        expectedBundles.append("slf4j.simple 1.7.25\n");
+        expectedBundles.append("slf4j.api 1.7.25\n");
+        expectedBundles.append("org.apache.sling.commons.logservice 1.0.6\n");
+        expectedBundles.append("org.apache.commons.io 2.6.0\n");
+        expectedBundles.append("org.apache.felix.http.servlet-api 1.1.2\n");
+
+        StringBuilder expectedResources = new StringBuilder();
+        expectedResources.append("feature3 1.0.0\n");
+        expectedResources.append("feature2 1.0.0\n");
+        expectedResources.append("feature1 1.0.0\n");
+
+        StringBuilder actualBundles = new StringBuilder();
+        StringBuilder actualResources = new StringBuilder();
+        try (FeatureResolver fr = new FrameworkResolver(am, getFrameworkProps())) {
+            for(FeatureResource ordered : fr.orderResources(Arrays.asList(f1, f2, f3))) {
+                if (IdentityNamespace.TYPE_BUNDLE.equals(
+                        ordered.getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE).iterator().next().
+                        getAttributes().get(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE))) {
+                    actualBundles.append(ordered.getId() + " " + ordered.getVersion() + "\n");
+                } else {
+                    actualResources.append(ordered.getId() + " " + ordered.getVersion() + "\n");
+                }
+            }
+        }
+        assertEquals(expectedBundles.toString(), actualBundles.toString());
+        assertEquals(expectedResources.toString(), actualResources.toString());
+    }
+
+    @Test
+    public void testOrderResourcesWithFeatureProvidingCapability() throws Exception {
+        ArtifactManager am = ArtifactManager.getArtifactManager(new ArtifactManagerConfig());
+
+        Feature f4 = readFeature("/feature4.json", am);
+        Feature f5 = readFeature("/feature5.json", am);
+        Feature f6 = readFeature("/feature6.json", am);
+
+        StringBuilder expectedResources = new StringBuilder();
+        expectedResources.append("feature5 1.0.0\n");
+        expectedResources.append("feature4 1.0.0\n");
+        expectedResources.append("org.apache.sling.commons.logservice 1.0.6\n");
+        expectedResources.append("org.apache.felix.http.servlet-api 1.1.2\n");
+        expectedResources.append("feature6 1.0.0\n");
+        expectedResources.append("org.apache.commons.io 2.6.0\n");
+
+        StringBuilder actualResources = new StringBuilder();
         try (FeatureResolver fr = new FrameworkResolver(am, getFrameworkProps())) {
-            List<Feature> ordered = fr.orderFeatures(Arrays.asList(f1, f2, f3));
-            List<Feature> expected = Arrays.asList(f3, f2, f1);
-            assertEquals(expected, ordered);
+            for(FeatureResource ordered : fr.orderResources(Arrays.asList(f4, f5, f6))) {
+                actualResources.append(ordered.getId() + " " + ordered.getVersion() + "\n");
+            }
         }
+        assertEquals(expectedResources.toString(), actualResources.toString());
     }
 
     private Feature readFeature(final String res,
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 08deb2c..68f572e 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
@@ -16,21 +16,10 @@
  */
 package org.apache.sling.feature.resolver.impl;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import java.lang.reflect.Field;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
 import org.apache.sling.feature.Artifact;
 import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.FeatureResource;
 import org.apache.sling.feature.OSGiCapability;
 import org.apache.sling.feature.OSGiRequirement;
 import org.apache.sling.feature.analyser.BundleDescriptor;
@@ -38,6 +27,7 @@ import org.apache.sling.feature.analyser.Descriptor;
 import org.apache.sling.feature.analyser.impl.BundleDescriptorImpl;
 import org.apache.sling.feature.support.util.PackageInfo;
 import org.junit.Test;
+import org.mockito.Mockito;
 import org.osgi.framework.Version;
 import org.osgi.framework.namespace.BundleNamespace;
 import org.osgi.framework.namespace.PackageNamespace;
@@ -45,6 +35,20 @@ import org.osgi.resource.Capability;
 import org.osgi.resource.Requirement;
 import org.osgi.resource.Resource;
 
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
 public class BundleResourceImplTest {
     @Test
     public void testResource() {
@@ -64,7 +68,9 @@ public class BundleResourceImplTest {
         Requirement r1 = new OSGiRequirement("ns.1",
                 Collections.emptyMap(), Collections.singletonMap("mydir", "myvalue"));
         List<Requirement> reqList = Collections.singletonList(r1);
-        Resource res = new BundleResourceImpl(caps,
+        Artifact art = Mockito.mock(Artifact.class);
+        Feature feat = Mockito.mock(Feature.class);
+        FeatureResource res = new BundleResourceImpl("a.b.c", "1.2.3", art, feat, caps,
                 Collections.singletonMap("ns.1", reqList));
 
         assertEquals(0, res.getCapabilities("nonexistent").size());
@@ -77,6 +83,11 @@ public class BundleResourceImplTest {
         assertTrue(mergedCaps.containsAll(capLst1));
         assertTrue(mergedCaps.containsAll(capLst2));
         assertEquals(reqList, res.getRequirements(null));
+
+        assertEquals("a.b.c", res.getId());
+        assertEquals(new Version("1.2.3"), res.getVersion());
+        assertSame(art, res.getArtifact());
+        assertSame(feat, res.getFeature());
     }
 
     @Test
@@ -96,7 +107,7 @@ public class BundleResourceImplTest {
         bd.getImportedPackages().add(im1);
         bd.getImportedPackages().add(im2);
 
-        Resource res = new BundleResourceImpl(bd);
+        Resource res = new BundleResourceImpl(bd, null);
         assertNotNull(
                 getCapAttribute(res, BundleNamespace.BUNDLE_NAMESPACE, BundleNamespace.BUNDLE_NAMESPACE));
         assertEquals(new Version("1.2.3"),
@@ -155,7 +166,7 @@ public class BundleResourceImplTest {
         Set<Requirement> reqs = new HashSet<>(Arrays.asList(req1, req2));
         BundleDescriptorImpl bd = new BundleDescriptorImpl(artifact, Collections.emptySet(), reqs, caps);
 
-        Resource res = new BundleResourceImpl(bd);
+        Resource res = new BundleResourceImpl(bd, null);
 
         assertEquals(caps, new HashSet<>(res.getCapabilities("org.example.cap1")));
         assertEquals(Collections.singleton(req1),
diff --git a/src/test/java/org/apache/sling/feature/resolver/impl/ResolveContextImplTest.java b/src/test/java/org/apache/sling/feature/resolver/impl/ResolveContextImplTest.java
index b63d14b..5e0f6f6 100644
--- a/src/test/java/org/apache/sling/feature/resolver/impl/ResolveContextImplTest.java
+++ b/src/test/java/org/apache/sling/feature/resolver/impl/ResolveContextImplTest.java
@@ -16,11 +16,6 @@
  */
 package org.apache.sling.feature.resolver.impl;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -40,10 +35,15 @@ import org.osgi.resource.Resource;
 import org.osgi.service.resolver.HostedCapability;
 import org.osgi.service.resolver.ResolveContext;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
 public class ResolveContextImplTest {
     @Test
     public void testMandatory() {
-        Resource mainRes = new BundleResourceImpl(Collections.emptyMap(), Collections.emptyMap());
+        Resource mainRes = new BundleResourceImpl("a", "1", null, null, Collections.emptyMap(), Collections.emptyMap());
         List<Resource> available = Arrays.asList();
         ResolveContext ctx = new ResolveContextImpl(mainRes, available);
 
@@ -57,7 +57,7 @@ public class ResolveContextImplTest {
         Resource res3 = exportBundle("org.foo", "1.0.0.TESTING");
         Resource res4 = exportBundle("org.foo", "1.9");
 
-        Resource mainRes = new BundleResourceImpl(Collections.emptyMap(), Collections.emptyMap());
+        Resource mainRes = new BundleResourceImpl("b", "2", null, null, Collections.emptyMap(), Collections.emptyMap());
         List<Resource> available = Arrays.asList(res1, res2, res3, res4);
         ResolveContext ctx = new ResolveContextImpl(mainRes, available);
 
@@ -79,7 +79,7 @@ public class ResolveContextImplTest {
         attrs.put(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE, new Version(version));
         Capability cap = new OSGiCapability(PackageNamespace.PACKAGE_NAMESPACE,
                 attrs, Collections.emptyMap());
-        return new BundleResourceImpl(
+        return new BundleResourceImpl("c", "3", null, null,
                 Collections.singletonMap(PackageNamespace.PACKAGE_NAMESPACE,
                         Collections.singletonList(cap)),
                 Collections.emptyMap());
@@ -126,4 +126,4 @@ public class ResolveContextImplTest {
                 Collections.singletonMap("effective", "active"));
         assertFalse(ctx.isEffective(req3));
     }
-}
\ No newline at end of file
+}
diff --git a/src/test/resources/feature3.json b/src/test/resources/feature3.json
index 33a09a8..014d228 100644
--- a/src/test/resources/feature3.json
+++ b/src/test/resources/feature3.json
@@ -1,5 +1,15 @@
 {
     "id": "org.apache.sling.test.features/feature3/1.0.0",
     "bundles": 
-        ["org.slf4j/slf4j-simple/1.7.25"]
+        [
+            "org.slf4j/slf4j-simple/1.7.25",
+            {
+                "id": "org.apache.felix/org.apache.felix.http.servlet-api/1.1.2",
+                "start-order" : 10
+            },
+            {
+                "id": "commons-io/commons-io/2.6",
+                "start-order" : 5
+            }
+        ]
 }
\ No newline at end of file
diff --git a/src/test/resources/feature4.json b/src/test/resources/feature4.json
new file mode 100644
index 0000000..decb8b2
--- /dev/null
+++ b/src/test/resources/feature4.json
@@ -0,0 +1,5 @@
+{
+    "id": "org.apache.sling.test.features/feature4/1.0.0",
+    "bundles": 
+        ["org.apache.sling/org.apache.sling.commons.logservice/1.0.6"]
+}
\ No newline at end of file
diff --git a/src/test/resources/feature5.json b/src/test/resources/feature5.json
new file mode 100644
index 0000000..49b4f50
--- /dev/null
+++ b/src/test/resources/feature5.json
@@ -0,0 +1,13 @@
+{
+    "id": "org.apache.sling.test.features/feature5/1.0.0",
+    "capabilities": [
+        {
+            "namespace": "osgi.wiring.package",
+            "attributes": {
+                "osgi.wiring.package": "org.slf4j",
+                "version:Version": "1.7.2"
+            }
+        }
+    ],
+    "bundles": ["org.apache.felix/org.apache.felix.http.servlet-api/1.1.2"] 
+}
\ No newline at end of file
diff --git a/src/test/resources/feature6.json b/src/test/resources/feature6.json
new file mode 100644
index 0000000..f8f5e18
--- /dev/null
+++ b/src/test/resources/feature6.json
@@ -0,0 +1,12 @@
+{
+    "id": "org.apache.sling.test.features/feature6/1.0.0",
+    "requirements": [
+        {
+            "namespace": "osgi.wiring.package",
+            "directives": {
+                "filter": "(&(osgi.wiring.package=org.slf4j)(version>=1.5.0)(!(version>=2.0.0)))"
+            }
+        }
+    ],
+    "bundles": ["commons-io/commons-io/2.6"]
+}
\ No newline at end of file

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