You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by gn...@apache.org on 2014/05/26 22:45:09 UTC

[4/5] git commit: [KARAF-3003] Allow deployment based on generic requirements, bundles and bundle dependencies

[KARAF-3003] Allow deployment based on generic requirements, bundles and bundle dependencies

Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/98123912
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/98123912
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/98123912

Branch: refs/heads/master
Commit: 981239128f69f31dbd89cb6ddabf8a416d1606e1
Parents: 714da5e
Author: Guillaume Nodet <gn...@gmail.com>
Authored: Mon May 26 22:44:36 2014 +0200
Committer: Guillaume Nodet <gn...@gmail.com>
Committed: Mon May 26 22:44:36 2014 +0200

----------------------------------------------------------------------
 .../karaf/features/command/RequirementAdd.java  |  85 ++++++++++++
 .../karaf/features/command/RequirementList.java |  59 +++++++++
 .../features/command/RequirementRemove.java     |  85 ++++++++++++
 .../apache/karaf/features/FeaturesService.java  |   6 +
 .../features/internal/region/Subsystem.java     | 130 ++++++++++++++-----
 .../internal/region/SubsystemResolver.java      |  19 +--
 .../internal/resolver/ResourceUtils.java        |  26 ++++
 .../features/internal/service/Deployer.java     |  16 +--
 .../internal/service/FeaturesServiceImpl.java   |  84 ++++++++----
 .../karaf/features/internal/service/State.java  |   6 +-
 .../features/internal/service/StateStorage.java |   6 +-
 .../features/internal/region/SubsystemTest.java |  24 ++++
 .../features/internal/service/DeployerTest.java |  21 ++-
 .../internal/service/StateStorageTest.java      |   4 +-
 14 files changed, 472 insertions(+), 99 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/features/command/src/main/java/org/apache/karaf/features/command/RequirementAdd.java
----------------------------------------------------------------------
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/RequirementAdd.java b/features/command/src/main/java/org/apache/karaf/features/command/RequirementAdd.java
new file mode 100644
index 0000000..c8491b3
--- /dev/null
+++ b/features/command/src/main/java/org/apache/karaf/features/command/RequirementAdd.java
@@ -0,0 +1,85 @@
+/*
+ * 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.karaf.features.command;
+
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.karaf.features.FeaturesService;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Command(scope = "feature", name = "requirement-add", description = "Add provisioning requirements.")
+@Service
+public class RequirementAdd implements Action {
+
+    @Reference
+    private FeaturesService featuresService;
+
+    @Argument(required = true, multiValued = true)
+    List<String> requirements;
+
+    @Option(name = "-r", aliases = "--no-auto-refresh", description = "Do not automatically refresh bundles", required = false, multiValued = false)
+    boolean noRefresh;
+
+    @Option(name = "-s", aliases = "--no-auto-start", description = "Do not start the bundles", required = false, multiValued = false)
+    boolean noStart;
+
+    @Option(name = "-m", aliases = "--no-auto-manage", description = "Do not automatically manage bundles", required = false, multiValued = false)
+    boolean noManage;
+
+    @Option(name = "-v", aliases = "--verbose", description = "Explain what is being done")
+    boolean verbose;
+
+    @Option(name = "-t", aliases = "--simulate", description = "Perform a simulation only")
+    boolean simulate;
+
+    @Option(name = "-g", aliases = "--region", description = "Region to install to")
+    String region;
+
+    @Override
+    public Object execute() throws Exception {
+        EnumSet<FeaturesService.Option> options = EnumSet.noneOf(FeaturesService.Option.class);
+        if (simulate) {
+            options.add(FeaturesService.Option.Simulate);
+        }
+        if (noStart) {
+            options.add(FeaturesService.Option.NoAutoStartBundles);
+        }
+        if (noRefresh) {
+            options.add(FeaturesService.Option.NoAutoRefreshBundles);
+        }
+        if (noManage) {
+            options.add(FeaturesService.Option.NoAutoManageBundles);
+        }
+        if (verbose) {
+            options.add(FeaturesService.Option.Verbose);
+        }
+        Map<String, Set<String>> reqs = new HashMap<>();
+        reqs.put(region == null ? FeaturesService.ROOT_REGION : region, new HashSet<>(requirements));
+        featuresService.addRequirements(reqs, options);
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/features/command/src/main/java/org/apache/karaf/features/command/RequirementList.java
----------------------------------------------------------------------
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/RequirementList.java b/features/command/src/main/java/org/apache/karaf/features/command/RequirementList.java
new file mode 100644
index 0000000..195b385
--- /dev/null
+++ b/features/command/src/main/java/org/apache/karaf/features/command/RequirementList.java
@@ -0,0 +1,59 @@
+/*
+ * 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.karaf.features.command;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.karaf.features.FeaturesService;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.table.ShellTable;
+
+@Command(scope = "feature", name = "requirement-list", description = "List provisioning requirements.")
+@Service
+public class RequirementList implements Action {
+
+    @Reference
+    private FeaturesService featuresService;
+
+    @Option(name = "--no-format", description = "Disable table rendered output")
+    boolean noFormat;
+
+    @Override
+    public Object execute() throws Exception {
+        Map<String, Set<String>> requirements = featuresService.listRequirements();
+
+        ShellTable table = new ShellTable();
+        table.column("Region");
+        table.column("Requirement");
+        table.emptyTableText("No requirements defined");
+
+        for (Map.Entry<String, Set<String>> entry : requirements.entrySet()) {
+            for (String requirement : entry.getValue()) {
+                table.addRow().addContent(entry.getKey(), requirement);
+            }
+        }
+
+        table.print(System.out, !noFormat);
+
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/features/command/src/main/java/org/apache/karaf/features/command/RequirementRemove.java
----------------------------------------------------------------------
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/RequirementRemove.java b/features/command/src/main/java/org/apache/karaf/features/command/RequirementRemove.java
new file mode 100644
index 0000000..a2385e9
--- /dev/null
+++ b/features/command/src/main/java/org/apache/karaf/features/command/RequirementRemove.java
@@ -0,0 +1,85 @@
+/*
+ * 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.karaf.features.command;
+
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.karaf.features.FeaturesService;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Command(scope = "feature", name = "requirement-remove", description = "Remove provisioning requirements.")
+@Service
+public class RequirementRemove implements Action {
+
+    @Reference
+    private FeaturesService featuresService;
+
+    @Argument(required = true, multiValued = true)
+    List<String> requirements;
+
+    @Option(name = "-r", aliases = "--no-auto-refresh", description = "Do not automatically refresh bundles", required = false, multiValued = false)
+    boolean noRefresh;
+
+    @Option(name = "-s", aliases = "--no-auto-start", description = "Do not start the bundles", required = false, multiValued = false)
+    boolean noStart;
+
+    @Option(name = "-m", aliases = "--no-auto-manage", description = "Do not automatically manage bundles", required = false, multiValued = false)
+    boolean noManage;
+
+    @Option(name = "-v", aliases = "--verbose", description = "Explain what is being done")
+    boolean verbose;
+
+    @Option(name = "-t", aliases = "--simulate", description = "Perform a simulation only")
+    boolean simulate;
+
+    @Option(name = "-g", aliases = "--region", description = "Region to install to")
+    String region;
+
+    @Override
+    public Object execute() throws Exception {
+        EnumSet<FeaturesService.Option> options = EnumSet.noneOf(FeaturesService.Option.class);
+        if (simulate) {
+            options.add(FeaturesService.Option.Simulate);
+        }
+        if (noStart) {
+            options.add(FeaturesService.Option.NoAutoStartBundles);
+        }
+        if (noRefresh) {
+            options.add(FeaturesService.Option.NoAutoRefreshBundles);
+        }
+        if (noManage) {
+            options.add(FeaturesService.Option.NoAutoManageBundles);
+        }
+        if (verbose) {
+            options.add(FeaturesService.Option.Verbose);
+        }
+        Map<String, Set<String>> reqs = new HashMap<>();
+        reqs.put(region == null ? FeaturesService.ROOT_REGION : region, new HashSet<>(requirements));
+        featuresService.removeRequirements(reqs, options);
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java b/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java
index e266402..8f40a6d 100644
--- a/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java
+++ b/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java
@@ -93,6 +93,8 @@ public interface FeaturesService {
 
     void installFeatures(Set<String> features, String region, EnumSet<Option> options) throws Exception;
 
+    void addRequirements(Map<String, Set<String>> requirements, EnumSet<Option> options) throws Exception;
+
     void uninstallFeature(String name, EnumSet<Option> options) throws Exception;
 
     void uninstallFeature(String name) throws Exception;
@@ -105,6 +107,8 @@ public interface FeaturesService {
 
     void uninstallFeatures(Set<String> features, String region, EnumSet<Option> options) throws Exception;
 
+    void removeRequirements(Map<String, Set<String>> requirements, EnumSet<Option> options) throws Exception;
+
     void updateFeaturesState(Map<String, Map<String, RequestedState>> stateChanges, EnumSet<Option> options) throws Exception;
 
     Feature[] listFeatures() throws Exception;
@@ -113,6 +117,8 @@ public interface FeaturesService {
 
     Feature[] listInstalledFeatures() throws Exception;
 
+    Map<String, Set<String>> listRequirements();
+
     boolean isRequired(Feature f);
 
     boolean isInstalled(Feature f);

http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/features/core/src/main/java/org/apache/karaf/features/internal/region/Subsystem.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/region/Subsystem.java b/features/core/src/main/java/org/apache/karaf/features/internal/region/Subsystem.java
index f64bc0f..30a789a 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/region/Subsystem.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/region/Subsystem.java
@@ -27,6 +27,8 @@ import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.felix.resolver.Util;
+import org.apache.felix.utils.manifest.Clause;
+import org.apache.felix.utils.manifest.Parser;
 import org.apache.felix.utils.version.VersionRange;
 import org.apache.felix.utils.version.VersionTable;
 import org.apache.karaf.features.BundleInfo;
@@ -46,7 +48,6 @@ import org.apache.karaf.features.internal.resolver.ResourceUtils;
 import org.apache.karaf.features.internal.service.Overrides;
 import org.osgi.framework.BundleException;
 import org.osgi.framework.Version;
-import org.osgi.resource.Capability;
 import org.osgi.resource.Requirement;
 import org.osgi.resource.Resource;
 
@@ -54,6 +55,7 @@ import static org.apache.karaf.features.internal.resolver.ResourceUtils.TYPE_FEA
 import static org.apache.karaf.features.internal.resolver.ResourceUtils.TYPE_SUBSYSTEM;
 import static org.apache.karaf.features.internal.resolver.ResourceUtils.addIdentityRequirement;
 import static org.apache.karaf.features.internal.resolver.ResourceUtils.getUri;
+import static org.apache.karaf.features.internal.resolver.ResourceUtils.toFeatureRequirement;
 import static org.apache.karaf.features.internal.util.MapUtils.addToMapSet;
 import static org.eclipse.equinox.region.RegionFilter.VISIBLE_ALL_NAMESPACE;
 import static org.osgi.framework.namespace.IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE;
@@ -92,6 +94,8 @@ public class Subsystem extends ResourceImpl {
     private final List<Resource> installable = new ArrayList<>();
     private final Map<String, DependencyInfo> dependencies = new HashMap<>();
 
+    private final List<String> bundles = new ArrayList<>();
+
     public Subsystem(String name) {
         super(name, TYPE_SUBSYSTEM, Version.emptyVersion);
         this.name = name;
@@ -183,14 +187,16 @@ public class Subsystem extends ResourceImpl {
             throw new UnsupportedOperationException("Can not create application subsystems inside a feature subsystem");
         }
         // Create subsystem
-        Subsystem as = new Subsystem(getName() + "/" + name, this, acceptDependencies);
+        String childName = getName() + "/" + name;
+        Subsystem as = new Subsystem(childName, this, acceptDependencies);
         children.add(as);
         // Add a requirement to force its resolution
-        Capability identity = as.getCapabilities(IDENTITY_NAMESPACE).iterator().next();
-        Object bsn = identity.getAttributes().get(IDENTITY_NAMESPACE);
+        Map<String, Object> attrs = new HashMap<>();
+        attrs.put(IDENTITY_NAMESPACE, childName);
+        attrs.put(CAPABILITY_TYPE_ATTRIBUTE, TYPE_SUBSYSTEM);
         Requirement requirement = new RequirementImpl(this, IDENTITY_NAMESPACE,
                 Collections.<String, String>emptyMap(),
-                Collections.singletonMap(IDENTITY_NAMESPACE, bsn));
+                attrs);
         addRequirement(requirement);
         // Add it to repo
         installable.add(as);
@@ -205,6 +211,39 @@ public class Subsystem extends ResourceImpl {
         ResourceUtils.addIdentityRequirement(this, name, TYPE_FEATURE, range);
     }
 
+    public void require(String requirement) throws BundleException {
+        int idx = requirement.indexOf(":");
+        String type, req;
+        if (idx >= 0) {
+            type = requirement.substring(0, idx);
+            req = requirement.substring(idx + 1);
+        } else {
+            type = "feature";
+            req = requirement;
+        }
+        switch (type) {
+        case "feature":
+            addRequirement(toFeatureRequirement(req));
+            break;
+        case "requirement":
+            addRequirement(req);
+            break;
+        case "bundle":
+            bundles.add(req);
+            break;
+        }
+    }
+
+    protected void addRequirement(String requirement) throws BundleException {
+        for (Requirement req : ResourceBuilder.parseRequirement(this, requirement)) {
+            Object range = req.getAttributes().get(CAPABILITY_VERSION_ATTRIBUTE);
+            if (range instanceof String) {
+                req.getAttributes().put(CAPABILITY_VERSION_ATTRIBUTE, new VersionRange((String) range));
+            }
+            addRequirement(req);
+        }
+    }
+
     public Map<String, BundleInfo> getBundleInfos() {
         Map<String, BundleInfo> infos = new HashMap<>();
         for (DependencyInfo di : dependencies.values()) {
@@ -286,10 +325,10 @@ public class Subsystem extends ResourceImpl {
         for (Subsystem child : children) {
             child.downloadBundles(manager, overrides, featureResolutionRange);
         }
+        final Map<String, ResourceImpl> bundles = new ConcurrentHashMap<>();
+        final Downloader downloader = manager.createDownloader();
+        final Map<BundleInfo, Conditional> infos = new HashMap<>();
         if (feature != null) {
-            final Map<String, ResourceImpl> bundles = new ConcurrentHashMap<>();
-            final Downloader downloader = manager.createDownloader();
-            final Map<BundleInfo, Conditional> infos = new HashMap<>();
             for (Conditional cond : feature.getConditional()) {
                 for (final BundleInfo bi : cond.getBundles()) {
                     infos.put(bi, cond);
@@ -298,29 +337,41 @@ public class Subsystem extends ResourceImpl {
             for (BundleInfo bi : feature.getBundles()) {
                 infos.put(bi, null);
             }
-            for (Map.Entry<BundleInfo, Conditional> entry : infos.entrySet()) {
-                final BundleInfo bi = entry.getKey();
-                final String loc = bi.getLocation();
-                downloader.download(loc, new DownloadCallback() {
-                    @Override
-                    public void downloaded(StreamProvider provider) throws Exception {
-                        ResourceImpl res = createResource(loc, provider.getMetadata());
-                        bundles.put(loc, res);
-                    }
-                });
-            }
-            for (String override : overrides) {
-                final String loc = Overrides.extractUrl(override);
-                downloader.download(loc, new DownloadCallback() {
-                    @Override
-                    public void downloaded(StreamProvider provider) throws Exception {
-                        ResourceImpl res = createResource(loc, provider.getMetadata());
-                        bundles.put(loc, res);
-                    }
-                });
-            }
-            downloader.await();
-            Overrides.override(bundles, overrides);
+        }
+        for (Map.Entry<BundleInfo, Conditional> entry : infos.entrySet()) {
+            final BundleInfo bi = entry.getKey();
+            final String loc = bi.getLocation();
+            downloader.download(loc, new DownloadCallback() {
+                @Override
+                public void downloaded(StreamProvider provider) throws Exception {
+                    ResourceImpl res = createResource(loc, provider.getMetadata());
+                    bundles.put(loc, res);
+                }
+            });
+        }
+        for (Clause bundle : Parser.parseClauses(this.bundles.toArray(new String[this.bundles.size()]))) {
+            final String loc = bundle.getName();
+            downloader.download(loc, new DownloadCallback() {
+                @Override
+                public void downloaded(StreamProvider provider) throws Exception {
+                    ResourceImpl res = createResource(loc, provider.getMetadata());
+                    bundles.put(loc, res);
+                }
+            });
+        }
+        for (String override : overrides) {
+            final String loc = Overrides.extractUrl(override);
+            downloader.download(loc, new DownloadCallback() {
+                @Override
+                public void downloaded(StreamProvider provider) throws Exception {
+                    ResourceImpl res = createResource(loc, provider.getMetadata());
+                    bundles.put(loc, res);
+                }
+            });
+        }
+        downloader.await();
+        Overrides.override(bundles, overrides);
+        if (feature != null) {
             // Add conditionals
             Map<Conditional, Resource> resConds = new HashMap<>();
             for (Conditional cond : feature.getConditional()) {
@@ -350,6 +401,23 @@ public class Subsystem extends ResourceImpl {
                 }
             }
         }
+        for (Clause bundle : Parser.parseClauses(this.bundles.toArray(new String[this.bundles.size()]))) {
+            final String loc = bundle.getName();
+            boolean dependency = Boolean.parseBoolean(bundle.getAttribute("dependency"));
+            boolean start = bundle.getAttribute("start") == null || Boolean.parseBoolean(bundle.getAttribute("start"));
+            int startLevel = 0;
+            try {
+                startLevel = Integer.parseInt(bundle.getAttribute("start-level"));
+            } catch (NumberFormatException e) {
+                // Ignore
+            }
+            if (dependency) {
+                addDependency(bundles.get(loc), false, start, startLevel);
+            } else {
+                doAddDependency(bundles.get(loc), true, start, startLevel);
+                addIdentityRequirement(this, bundles.get(loc));
+            }
+        }
         // Compute dependencies
         for (DependencyInfo info : dependencies.values()) {
             installable.add(info.resource);

http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolver.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolver.java b/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolver.java
index f8f7bca..5a92494 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolver.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolver.java
@@ -84,18 +84,17 @@ public class SubsystemResolver {
     private RegionDigraph flatDigraph;
     private Map<String, Map<String, BundleInfo>> bundleInfos;
 
-
     public SubsystemResolver(DownloadManager manager) {
         this.manager = manager;
     }
 
     public void prepare(
             Collection<Feature> allFeatures,
-            Map<String, Set<String>> features,
+            Map<String, Set<String>> requirements,
             Map<String, Set<BundleRevision>> system
     ) throws Exception {
         // Build subsystems on the fly
-        for (Map.Entry<String, Set<String>> entry : features.entrySet()) {
+        for (Map.Entry<String, Set<String>> entry : requirements.entrySet()) {
             String[] parts = entry.getKey().split("/");
             if (root == null) {
                 root = new Subsystem(parts[0]);
@@ -106,18 +105,8 @@ public class SubsystemResolver {
             for (int i = 1; i < parts.length; i++) {
                 ss = getOrCreateChild(ss, parts[i]);
             }
-            for (String feature : entry.getValue()) {
-                String name;
-                String range;
-                int idx = feature.indexOf('/');
-                if (idx >= 0) {
-                    name = feature.substring(0, idx);
-                    range = feature.substring(idx + 1);
-                } else {
-                    name = feature;
-                    range = null;
-                }
-                ss.requireFeature(name, range);
+            for (String requirement : entry.getValue()) {
+                ss.require(requirement);
             }
         }
         if (root == null) {

http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceUtils.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceUtils.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceUtils.java
index d3f5527..210c375 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceUtils.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceUtils.java
@@ -21,6 +21,8 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.felix.utils.version.VersionRange;
+import org.apache.felix.utils.version.VersionTable;
+import org.osgi.framework.Constants;
 import org.osgi.framework.Version;
 import org.osgi.resource.Capability;
 import org.osgi.resource.Resource;
@@ -115,4 +117,28 @@ public final class ResourceUtils {
         }
     }
 
+    public static String toFeatureRequirement(String feature) {
+        String[] parts = feature.split("/");
+        Map<String, Object> attrs = new HashMap<>();
+        attrs.put(IDENTITY_NAMESPACE, parts[0]);
+        attrs.put(CAPABILITY_TYPE_ATTRIBUTE, TYPE_FEATURE);
+        if (parts.length > 1) {
+            attrs.put(CAPABILITY_VERSION_ATTRIBUTE, new VersionRange(parts[1]));
+        }
+        Map<String, String> dirs = new HashMap<>();
+        dirs.put(Constants.FILTER_DIRECTIVE, SimpleFilter.convert(attrs).toString());
+        return new RequirementImpl(null, IDENTITY_NAMESPACE, dirs, attrs).toString();
+    }
+
+    public static String toFeatureCapability(String feature) {
+        String[] parts = feature.split("/");
+        Map<String, String> dirs = new HashMap<>();
+        Map<String, Object> attrs = new HashMap<>();
+        attrs.put(IDENTITY_NAMESPACE, parts[0]);
+        attrs.put(CAPABILITY_TYPE_ATTRIBUTE, TYPE_FEATURE);
+        if (parts.length > 1) {
+            attrs.put(CAPABILITY_VERSION_ATTRIBUTE, VersionTable.getVersion(parts[1]));
+        }
+        return new CapabilityImpl(null, IDENTITY_NAMESPACE, dirs, attrs).toString();
+    }
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
index d80fe3d..4d289cb 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
@@ -148,7 +148,7 @@ public class Deployer {
         String updateSnaphots;
         Repository globalRepository;
 
-        Map<String, Set<String>> requestedFeatures;
+        Map<String, Set<String>> requirements;
         Map<String, Map<String, FeaturesService.RequestedState>> stateChanges;
         EnumSet<FeaturesService.Option> options;
     }
@@ -199,13 +199,10 @@ public class Deployer {
                 map(dstate.bundles));
 
         // Resolve
-        // TODO: requirements
-        // TODO: bundles
-
         SubsystemResolver resolver = new SubsystemResolver(manager);
         resolver.prepare(
                 dstate.features.values(),
-                request.requestedFeatures,
+                request.requirements,
                 apply(unmanagedBundles, adapt(BundleRevision.class))
         );
         Set<String> prereqs = resolver.collectPrerequisites();
@@ -242,9 +239,9 @@ public class Deployer {
             newRequest.globalRepository = request.globalRepository;
             newRequest.options = request.options;
             newRequest.overrides = request.overrides;
-            newRequest.requestedFeatures = copy(dstate.state.requestedFeatures);
+            newRequest.requirements = copy(dstate.state.requirements);
             for (String prereq : prereqs) {
-                addToMapSet(newRequest.requestedFeatures, ROOT_REGION, prereq);
+                addToMapSet(newRequest.requirements, ROOT_REGION, prereq);
             }
             newRequest.stateChanges = Collections.emptyMap();
             newRequest.updateSnaphots = request.updateSnaphots;
@@ -671,6 +668,9 @@ public class Deployer {
                         callback.setBundleStartLevel(bundle, startLevel);
                     }
                     FeaturesService.RequestedState reqState = states.get(resource);
+                    if (reqState == null) {
+                        reqState = FeaturesService.RequestedState.Started;
+                    }
                     switch (reqState) {
                     case Started:
                         toResolve.add(bundle);
@@ -689,7 +689,7 @@ public class Deployer {
         //
         State newState = new State();
         newState.bundleChecksums.putAll(deployment.bundleChecksums);
-        newState.requestedFeatures.putAll(request.requestedFeatures);
+        newState.requirements.putAll(request.requirements);
         newState.installedFeatures.putAll(installedFeatures);
         newState.stateFeatures.putAll(stateFeatures);
         newState.managedBundles.putAll(managedBundles);

http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
index 03686aa..ce01342 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
@@ -69,8 +69,10 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import static org.apache.karaf.features.internal.service.StateStorage.toStringStringSetMap;
+import static org.apache.karaf.features.internal.util.MapUtils.add;
 import static org.apache.karaf.features.internal.util.MapUtils.addToMapSet;
 import static org.apache.karaf.features.internal.util.MapUtils.copy;
+import static org.apache.karaf.features.internal.util.MapUtils.remove;
 
 /**
  *
@@ -164,6 +166,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
 
     }
 
+    @SuppressWarnings("unchecked")
     private void checkResolve() {
         if (bundle == null) {
             return; // Most certainly in unit tests
@@ -190,7 +193,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
         // Resolve
         try {
             Map<String, Map<String, RequestedState>> stateChanges = Collections.emptyMap();
-            doInstallFeaturesInThread(requestedFeatures, stateChanges, copyState(), options);
+            doProvisionInThread(requestedFeatures, stateChanges, copyState(), options);
         } catch (Exception e) {
             LOGGER.warn("Error updating state", e);
         }
@@ -206,7 +209,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
         request.put("features", requestedFeatures);
         request.put("options", opts);
         try (
-                FileOutputStream fos = new FileOutputStream(resolveFile);
+                FileOutputStream fos = new FileOutputStream(resolveFile)
         ) {
             JsonWriter.write(fos, request);
         }
@@ -599,9 +602,9 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
 
     @Override
     public boolean isRequired(Feature f) {
-        String id = f.getName() + "/" + new VersionRange(f.getVersion(), true);
+        String id = "feature:" + f.getName() + "/" + new VersionRange(f.getVersion(), true);
         synchronized (lock) {
-            Set<String> features = state.requestedFeatures.get(ROOT_REGION);
+            Set<String> features = state.requirements.get(ROOT_REGION);
             return features != null && features.contains(id);
         }
     }
@@ -678,7 +681,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
     @Override
     public void installFeatures(Set<String> features, String region, EnumSet<Option> options) throws Exception {
         State state = copyState();
-        Map<String, Set<String>> required = copy(state.requestedFeatures);
+        Map<String, Set<String>> required = copy(state.requirements);
         if (region == null || region.isEmpty()) {
             region = ROOT_REGION;
         }
@@ -713,14 +716,16 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
             fl = new HashSet<>();
             required.put(region, fl);
         }
-        fl.addAll(featuresToAdd);
+        for (String feature : featuresToAdd) {
+            fl.add("feature:" + feature);
+        }
         Map<String, Map<String, RequestedState>> stateChanges = Collections.emptyMap();
-        doInstallFeaturesInThread(required, stateChanges, state, options);
+        doProvisionInThread(required, stateChanges, state, options);
     }
 
     public void uninstallFeatures(Set<String> features, String region, EnumSet<Option> options) throws Exception {
         State state = copyState();
-        Map<String, Set<String>> required = copy(state.requestedFeatures);
+        Map<String, Set<String>> required = copy(state.requirements);
         if (region == null || region.isEmpty()) {
             region = ROOT_REGION;
         }
@@ -734,7 +739,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
             List<String> toRemove = new ArrayList<>();
             feature = normalize(feature);
             if (feature.endsWith("/0.0.0")) {
-                String nameSep = feature.substring(0, feature.indexOf("/") + 1);
+                String nameSep = "feature:" + feature.substring(0, feature.indexOf("/") + 1);
                 for (String f : fl) {
                     if (normalize(f).startsWith(nameSep)) {
                         toRemove.add(f);
@@ -743,7 +748,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
             } else {
                 String name = feature.substring(0, feature.indexOf("/"));
                 String version = feature.substring(feature.indexOf("/") + 1);
-                String req = name + "/" + new VersionRange(version, true);
+                String req = "feature:" + name + "/" + new VersionRange(version, true);
                 toRemove.add(req);
             }
             toRemove.retainAll(fl);
@@ -757,7 +762,9 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
                     if (i > 0) {
                         sb.append(", ");
                     }
-                    sb.append(toRemove.get(i));
+                    String f = toRemove.get(i);
+                    String version = f.substring(f.indexOf("/") + 1);
+                    sb.append(version);
                 }
                 sb.append("). Please specify the version to uninstall.");
                 throw new IllegalArgumentException(sb.toString());
@@ -779,13 +786,38 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
             required.remove(region);
         }
         Map<String, Map<String, RequestedState>> stateChanges = Collections.emptyMap();
-        doInstallFeaturesInThread(required, stateChanges, state, options);
+        doProvisionInThread(required, stateChanges, state, options);
     }
 
     @Override
     public void updateFeaturesState(Map<String, Map<String, RequestedState>> stateChanges, EnumSet<Option> options) throws Exception {
         State state = copyState();
-        doInstallFeaturesInThread(copy(state.requestedFeatures), stateChanges, state, options);
+        doProvisionInThread(copy(state.requirements), stateChanges, state, options);
+    }
+
+    @Override
+    public void addRequirements(Map<String, Set<String>> requirements, EnumSet<Option> options) throws Exception {
+        State state = copyState();
+        Map<String, Set<String>> required = copy(state.requirements);
+        add(required, requirements);
+        Map<String, Map<String, RequestedState>> stateChanges = Collections.emptyMap();
+        doProvisionInThread(required, stateChanges, state, options);
+    }
+
+    @Override
+    public void removeRequirements(Map<String, Set<String>> requirements, EnumSet<Option> options) throws Exception {
+        State state = copyState();
+        Map<String, Set<String>> required = copy(state.requirements);
+        remove(required, requirements);
+        Map<String, Map<String, RequestedState>> stateChanges = Collections.emptyMap();
+        doProvisionInThread(required, stateChanges, state, options);
+    }
+
+    @Override
+    public Map<String, Set<String>> listRequirements() {
+        synchronized (lock) {
+            return copy(this.state.requirements);
+        }
     }
 
     private State copyState() {
@@ -810,16 +842,16 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
      * the command may be interrupted while waiting for the refresh to be done, leading
      * to bundles not being started after the refresh.
      */
-    public void doInstallFeaturesInThread(final Map<String, Set<String>> features,
-                                          final Map<String, Map<String, RequestedState>> stateChanges,
-                                          final State state,
-                                          final EnumSet<Option> options) throws Exception {
+    public void doProvisionInThread(final Map<String, Set<String>> requirements,
+                                    final Map<String, Map<String, RequestedState>> stateChanges,
+                                    final State state,
+                                    final EnumSet<Option> options) throws Exception {
         ExecutorService executor = Executors.newCachedThreadPool();
         try {
             executor.submit(new Callable<Object>() {
                 @Override
                 public Object call() throws Exception {
-                    doInstallFeatures(features, stateChanges, state, options);
+                    doProvision(requirements, stateChanges, state, options);
                     return null;
                 }
             }).get();
@@ -888,13 +920,13 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
         return dstate;
     }
 
-    private Deployer.DeploymentRequest getDeploymentRequest(Map<String, Set<String>> requestedFeatures, Map<String, Map<String, RequestedState>> stateChanges, EnumSet<Option> options) {
+    private Deployer.DeploymentRequest getDeploymentRequest(Map<String, Set<String>> requirements, Map<String, Map<String, RequestedState>> stateChanges, EnumSet<Option> options) {
         Deployer.DeploymentRequest request = new Deployer.DeploymentRequest();
         request.bundleUpdateRange = bundleUpdateRange;
         request.featureResolutionRange = featureResolutionRange;
         request.globalRepository = globalRepository;
         request.overrides = Overrides.loadOverrides(overrides);
-        request.requestedFeatures = requestedFeatures;
+        request.requirements = requirements;
         request.stateChanges = stateChanges;
         request.options = options;
         return request;
@@ -902,17 +934,17 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
 
 
 
-    public void doInstallFeatures(Map<String, Set<String>> requestedFeatures,            // all request features
-                                  Map<String, Map<String, RequestedState>> stateChanges, // features state changes
-                                  State state,                                           // current state
-                                  EnumSet<Option> options                                // installation options
+    public void doProvision(Map<String, Set<String>> requirements,                 // all requirements
+                            Map<String, Map<String, RequestedState>> stateChanges, // features state changes
+                            State state,                                           // current state
+                            EnumSet<Option> options                                // installation options
     ) throws Exception {
 
         Set<String> prereqs = new HashSet<>();
         while (true) {
             try {
                 Deployer.DeploymentState dstate = getDeploymentState(state);
-                Deployer.DeploymentRequest request = getDeploymentRequest(requestedFeatures, stateChanges, options);
+                Deployer.DeploymentRequest request = getDeploymentRequest(requirements, stateChanges, options);
                 new Deployer(new SimpleDownloader(), this).deploy(dstate, request);
                 break;
             } catch (Deployer.PartialDeploymentException e) {
@@ -961,7 +993,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
 
     @Override
     public void persistResolveRequest(Deployer.DeploymentRequest request) throws IOException {
-        writeResolve(request.requestedFeatures, request.options);
+        writeResolve(request.requirements, request.options);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java
index b1a3ccd..a059b0b 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java
@@ -28,7 +28,7 @@ public class State {
 
     public final AtomicBoolean bootDone = new AtomicBoolean();
     public final Set<String> repositories = new TreeSet<>();
-    public final Map<String, Set<String>> requestedFeatures = new HashMap<>();
+    public final Map<String, Set<String>> requirements = new HashMap<>();
     public final Map<String, Set<String>> installedFeatures = new HashMap<>();
     public final Map<String, Map<String, String>> stateFeatures = new HashMap<>();
     public final Map<String, Set<Long>> managedBundles = new HashMap<>();
@@ -47,7 +47,7 @@ public class State {
     private static void copy(State from, State to, boolean clear) {
         if (clear) {
             to.repositories.clear();
-            to.requestedFeatures.clear();
+            to.requirements.clear();
             to.installedFeatures.clear();
             to.stateFeatures.clear();
             to.managedBundles.clear();
@@ -55,7 +55,7 @@ public class State {
         }
         to.bootDone.set(from.bootDone.get());
         MapUtils.copy(from.repositories, to.repositories);
-        MapUtils.copy(from.requestedFeatures, to.requestedFeatures);
+        MapUtils.copy(from.requirements, to.requirements);
         MapUtils.copy(from.installedFeatures, to.installedFeatures);
         MapUtils.copy(from.stateFeatures, to.stateFeatures);
         MapUtils.copy(from.managedBundles, to.managedBundles);

http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/features/core/src/main/java/org/apache/karaf/features/internal/service/StateStorage.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/StateStorage.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/StateStorage.java
index e085b65..20e0fcd 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/StateStorage.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/StateStorage.java
@@ -32,7 +32,7 @@ public abstract class StateStorage {
 
     public void load(State state) throws IOException {
         state.repositories.clear();
-        state.requestedFeatures.clear();
+        state.requirements.clear();
         state.installedFeatures.clear();
         state.managedBundles.clear();
         try (
@@ -42,7 +42,7 @@ public abstract class StateStorage {
                 Map json = (Map) JsonReader.read(is);
                 state.bootDone.set((Boolean) json.get("bootDone"));
                 state.repositories.addAll(toStringSet((Collection) json.get("repositories")));
-                state.requestedFeatures.putAll(toStringStringSetMap((Map) json.get("features")));
+                state.requirements.putAll(toStringStringSetMap((Map) json.get("features")));
                 state.installedFeatures.putAll(toStringStringSetMap((Map) json.get("installed")));
                 state.stateFeatures.putAll(toStringStringStringMapMap((Map) json.get("state")));
                 state.managedBundles.putAll(toStringLongSetMap((Map) json.get("managed")));
@@ -59,7 +59,7 @@ public abstract class StateStorage {
                 Map<String, Object> json = new HashMap<>();
                 json.put("bootDone", state.bootDone.get());
                 json.put("repositories", state.repositories);
-                json.put("features", state.requestedFeatures);
+                json.put("features", state.requirements);
                 json.put("installed", state.installedFeatures);
                 json.put("state", state.stateFeatures);
                 json.put("managed", state.managedBundles);

http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java
index e6b5b8f..11f9b0a 100644
--- a/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java
@@ -35,6 +35,8 @@ import org.osgi.resource.Capability;
 import org.osgi.resource.Resource;
 import org.osgi.resource.Wire;
 
+import static org.apache.karaf.features.internal.resolver.ResourceUtils.toFeatureCapability;
+import static org.apache.karaf.features.internal.resolver.ResourceUtils.toFeatureRequirement;
 import static org.apache.karaf.features.internal.util.MapUtils.addToMapSet;
 import static org.junit.Assert.assertEquals;
 
@@ -158,6 +160,28 @@ public class SubsystemTest {
         verify(resolver, expected);
     }
 
+    @Test
+    public void testBundle() throws Exception {
+        RepositoryImpl repo = new RepositoryImpl(getClass().getResource("data1/features.xml").toURI());
+
+        Map<String, Set<String>> features = new HashMap<String, Set<String>>();
+        addToMapSet(features, "root/apps1", "bundle:a");
+        addToMapSet(features, "root/apps1", "bundle:c;dependency=true");
+        Map<String, Set<String>> expected = new HashMap<String, Set<String>>();
+        addToMapSet(expected, "root/apps1", "a/1.0.0");
+        addToMapSet(expected, "root/apps1", "c/1.0.0");
+
+        SubsystemResolver resolver = new SubsystemResolver(new TestDownloadManager(getClass(), "data1"));
+        resolver.prepare(Arrays.asList(repo.getFeatures()),
+                features,
+                Collections.<String, Set<BundleRevision>>emptyMap());
+        resolver.resolve(Collections.<String>emptySet(),
+                FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE,
+                null);
+
+        verify(resolver, expected);
+    }
+
     private void verify(SubsystemResolver resolver, Map<String, Set<String>> expected) {
         Map<String, Set<String>> mapping = getBundleNamesPerRegions(resolver);
         if (!expected.equals(mapping)) {

http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/features/core/src/test/java/org/apache/karaf/features/internal/service/DeployerTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/service/DeployerTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/service/DeployerTest.java
index 723df3b..9478b67 100644
--- a/features/core/src/test/java/org/apache/karaf/features/internal/service/DeployerTest.java
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/service/DeployerTest.java
@@ -22,7 +22,6 @@ import java.net.URL;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Hashtable;
 import java.util.Map;
 import java.util.Set;
@@ -101,8 +100,8 @@ public class DeployerTest {
         request.overrides = Collections.emptySet();
         request.stateChanges = Collections.emptyMap();
         request.updateSnaphots = UPDATE_SNAPSHOTS_NONE;
-        request.requestedFeatures = new HashMap<>();
-        addToMapSet(request.requestedFeatures, ROOT_REGION, f100.getName() + "/" + new VersionRange(f100.getVersion(), true));
+        request.requirements = new HashMap<>();
+        addToMapSet(request.requirements, ROOT_REGION, f100.getName() + "/" + new VersionRange(f100.getVersion(), true));
 
         deployer.deploy(dstate, request);
 
@@ -194,8 +193,8 @@ public class DeployerTest {
         request.overrides = Collections.emptySet();
         request.stateChanges = Collections.emptyMap();
         request.updateSnaphots = UPDATE_SNAPSHOTS_NONE;
-        request.requestedFeatures = new HashMap<>();
-        addToMapSet(request.requestedFeatures, ROOT_REGION, f101.getName() + "/" + new VersionRange(f101.getVersion(), true));
+        request.requirements = new HashMap<>();
+        addToMapSet(request.requirements, ROOT_REGION, f101.getName() + "/" + new VersionRange(f101.getVersion(), true));
 
         deployer.deploy(dstate, request);
 
@@ -252,8 +251,8 @@ public class DeployerTest {
         request.overrides = Collections.emptySet();
         request.stateChanges = Collections.emptyMap();
         request.updateSnaphots = UPDATE_SNAPSHOTS_NONE;
-        request.requestedFeatures = new HashMap<>();
-        addToMapSet(request.requestedFeatures, ROOT_REGION, f1.getName());
+        request.requirements = new HashMap<>();
+        addToMapSet(request.requirements, ROOT_REGION, f1.getName());
 
         deployer.deploy(dstate, request);
 
@@ -314,8 +313,8 @@ public class DeployerTest {
         request.overrides = Collections.emptySet();
         request.stateChanges = Collections.emptyMap();
         request.updateSnaphots = UPDATE_SNAPSHOTS_NONE;
-        request.requestedFeatures = new HashMap<>();
-        addToMapSet(request.requestedFeatures, ROOT_REGION, f2.getName());
+        request.requirements = new HashMap<>();
+        addToMapSet(request.requirements, ROOT_REGION, f2.getName());
 
         try {
             deployer.deploy(dstate, request);
@@ -369,8 +368,8 @@ public class DeployerTest {
         request.overrides = Collections.emptySet();
         request.stateChanges = Collections.emptyMap();
         request.updateSnaphots = UPDATE_SNAPSHOTS_NONE;
-        request.requestedFeatures = new HashMap<>();
-        addToMapSet(request.requestedFeatures, ROOT_REGION, f2.getName());
+        request.requirements = new HashMap<>();
+        addToMapSet(request.requirements, ROOT_REGION, f2.getName());
 
         deployer.deploy(dstate, request);
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/features/core/src/test/java/org/apache/karaf/features/internal/service/StateStorageTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/service/StateStorageTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/service/StateStorageTest.java
index 857936c..fbd819f 100644
--- a/features/core/src/test/java/org/apache/karaf/features/internal/service/StateStorageTest.java
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/service/StateStorageTest.java
@@ -37,7 +37,7 @@ public class StateStorageTest {
         State oldState = new State();
         oldState.bootDone.set(true);
         oldState.bundleChecksums.put(4l, 32794l);
-        oldState.requestedFeatures.put("bar", Collections.singleton("f1"));
+        oldState.requirements.put("bar", Collections.singleton("f1"));
         oldState.managedBundles.put("reg", Collections.singleton(32l));
         oldState.managedBundles.put("reg2", new HashSet<Long>(Arrays.asList(24l, 43l)));
         oldState.repositories.add("repo");
@@ -53,7 +53,7 @@ public class StateStorageTest {
 
         assertEquals(oldState.bootDone.get(), newState.bootDone.get());
         assertEquals(oldState.bundleChecksums, newState.bundleChecksums);
-        assertEquals(oldState.requestedFeatures, newState.requestedFeatures);
+        assertEquals(oldState.requirements, newState.requirements);
         assertEquals(oldState.managedBundles, newState.managedBundles);
         assertEquals(oldState.repositories, newState.repositories);
     }