Repository: karaf
Updated Branches:
  refs/heads/master ea95ef65d -> b982479dd


[KARAF-2952] Add a lifecycle for features

The commands needs a bit of love...


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

Branch: refs/heads/master
Commit: b982479dd7b39e6e9800761dcf2bb6ea2b3a1444
Parents: ea95ef6
Author: Guillaume Nodet <gno...@gmail.com>
Authored: Tue Apr 29 15:44:40 2014 +0200
Committer: Guillaume Nodet <gno...@gmail.com>
Committed: Tue Apr 29 15:45:00 2014 +0200

----------------------------------------------------------------------
 .../features/command/StartFeaturesCommand.java  |  69 +++++
 .../features/command/StopFeaturesCommand.java   |  68 +++++
 .../apache/karaf/features/FeaturesService.java  |  11 +
 .../features/internal/region/Subsystem.java     |   1 +
 .../internal/resolver/ResourceUtils.java        |  10 +-
 .../internal/service/FeaturesServiceImpl.java   | 256 +++++++++++++------
 .../karaf/features/internal/service/State.java  |  12 +-
 .../features/internal/service/StateStorage.java |  18 ++
 .../karaf/features/internal/util/MapUtils.java  |  11 +-
 shell/commands/pom.xml                          |   3 +
 10 files changed, 374 insertions(+), 85 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/b982479d/features/command/src/main/java/org/apache/karaf/features/command/StartFeaturesCommand.java
----------------------------------------------------------------------
diff --git 
a/features/command/src/main/java/org/apache/karaf/features/command/StartFeaturesCommand.java
 
b/features/command/src/main/java/org/apache/karaf/features/command/StartFeaturesCommand.java
new file mode 100644
index 0000000..38b5a41
--- /dev/null
+++ 
b/features/command/src/main/java/org/apache/karaf/features/command/StartFeaturesCommand.java
@@ -0,0 +1,69 @@
+/*
+ * 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 org.apache.karaf.features.FeaturesService;
+import org.apache.karaf.features.command.completers.AvailableFeatureCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Command(scope = "feature", name = "start", description = "Start features with 
the specified name and version.")
+@Service
+public class StartFeaturesCommand extends FeaturesCommandSupport {
+
+    @Argument(index = 0, name = "feature", description = "The name and version 
of the features to install. A feature id looks like name/version. The version 
is optional.", required = true, multiValued = true)
+    @Completion(AvailableFeatureCompleter.class)
+    List<String> features;
+
+    @Option(name = "-v", aliases = "--verbose", description = "Explain what is 
being done", required = false, multiValued = false)
+    boolean verbose;
+
+    @Option(name = "-t", aliases = "--simulate", description = "Perform a 
simulation only", required = false, multiValued = false)
+    boolean simulate;
+
+    @Option(name = "-g", aliases = "--region", description = "Region to 
install to")
+    String region;
+
+    protected void doExecute(FeaturesService admin) throws Exception {
+        EnumSet<FeaturesService.Option> options = 
EnumSet.noneOf(FeaturesService.Option.class);
+        if (simulate) {
+            options.add(FeaturesService.Option.Simulate);
+        }
+        if (verbose) {
+            options.add(FeaturesService.Option.Verbose);
+        }
+        Map<String, Map<String, FeaturesService.RequestedState>> stateChanges 
= new HashMap<>();
+        if (region == null) {
+            region = FeaturesService.ROOT_REGION;
+        }
+        Map<String, FeaturesService.RequestedState> regionChanges = new 
HashMap<>();
+        for (String feature : features) {
+            regionChanges.put(feature, FeaturesService.RequestedState.Started);
+        }
+        stateChanges.put(region, regionChanges);
+        admin.updateFeaturesState(stateChanges, options);
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/b982479d/features/command/src/main/java/org/apache/karaf/features/command/StopFeaturesCommand.java
----------------------------------------------------------------------
diff --git 
a/features/command/src/main/java/org/apache/karaf/features/command/StopFeaturesCommand.java
 
b/features/command/src/main/java/org/apache/karaf/features/command/StopFeaturesCommand.java
new file mode 100644
index 0000000..0afdf07
--- /dev/null
+++ 
b/features/command/src/main/java/org/apache/karaf/features/command/StopFeaturesCommand.java
@@ -0,0 +1,68 @@
+/*
+ * 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.List;
+import java.util.Map;
+
+import org.apache.karaf.features.FeaturesService;
+import org.apache.karaf.features.command.completers.AvailableFeatureCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Command(scope = "feature", name = "stop", description = "Start features with 
the specified name and version.")
+@Service
+public class StopFeaturesCommand extends FeaturesCommandSupport {
+
+    @Argument(index = 0, name = "feature", description = "The name and version 
of the features to install. A feature id looks like name/version. The version 
is optional.", required = true, multiValued = true)
+    @Completion(AvailableFeatureCompleter.class)
+    List<String> features;
+
+    @Option(name = "-v", aliases = "--verbose", description = "Explain what is 
being done", required = false, multiValued = false)
+    boolean verbose;
+
+    @Option(name = "-t", aliases = "--simulate", description = "Perform a 
simulation only", required = false, multiValued = false)
+    boolean simulate;
+
+    @Option(name = "-g", aliases = "--region", description = "Region to 
install to")
+    String region;
+
+    protected void doExecute(FeaturesService admin) throws Exception {
+        EnumSet<FeaturesService.Option> options = 
EnumSet.noneOf(FeaturesService.Option.class);
+        if (simulate) {
+            options.add(FeaturesService.Option.Simulate);
+        }
+        if (verbose) {
+            options.add(FeaturesService.Option.Verbose);
+        }
+        Map<String, Map<String, FeaturesService.RequestedState>> stateChanges 
= new HashMap<>();
+        if (region == null) {
+            region = FeaturesService.ROOT_REGION;
+        }
+        Map<String, FeaturesService.RequestedState> regionChanges = new 
HashMap<>();
+        for (String feature : features) {
+            regionChanges.put(feature, 
FeaturesService.RequestedState.Resolved);
+        }
+        stateChanges.put(region, regionChanges);
+        admin.updateFeaturesState(stateChanges, options);
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/b982479d/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 d8f3d50..4366036 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
@@ -18,6 +18,7 @@ package org.apache.karaf.features;
 
 import java.net.URI;
 import java.util.EnumSet;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -25,6 +26,8 @@ import java.util.Set;
  */
 public interface FeaturesService {
 
+    public static final String ROOT_REGION = "root";
+
     enum Option {
         NoFailOnFeatureNotFound,
         NoAutoRefreshManagedBundles,
@@ -36,6 +39,12 @@ public interface FeaturesService {
         Verbose
     }
 
+    enum RequestedState {
+        Installed,
+        Resolved,
+        Started
+    }
+
     /**
      * Validate repository contents.
      *
@@ -86,6 +95,8 @@ public interface FeaturesService {
 
     void uninstallFeatures(Set<String> features, String region, 
EnumSet<Option> options) throws Exception;
 
+    void updateFeaturesState(Map<String, Map<String, RequestedState>> 
stateChanges, EnumSet<Option> options) throws Exception;
+
     Feature[] listFeatures() throws Exception;
 
     Feature[] listRequiredFeatures() throws Exception;

http://git-wip-us.apache.org/repos/asf/karaf/blob/b982479d/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 d4e362f..bfe170e 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
@@ -298,6 +298,7 @@ public class Subsystem extends ResourceImpl {
             for (Conditional cond : feature.getConditional()) {
                 FeatureResource resCond = FeatureResource.build(feature, cond, 
featureResolutionRange, bundles);
                 addIdentityRequirement(this, resCond, false);
+                addIdentityRequirement(resCond, this, true);
                 installable.add(resCond);
                 resConds.put(cond, resCond);
             }

http://git-wip-us.apache.org/repos/asf/karaf/blob/b982479d/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 b22f9b8..9657689 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,7 +21,6 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.felix.utils.version.VersionRange;
-import org.osgi.framework.Constants;
 import org.osgi.framework.Version;
 import org.osgi.resource.Capability;
 import org.osgi.resource.Resource;
@@ -29,6 +28,9 @@ import org.osgi.resource.Resource;
 import static 
org.osgi.framework.namespace.IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE;
 import static 
org.osgi.framework.namespace.IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE;
 import static 
org.osgi.framework.namespace.IdentityNamespace.IDENTITY_NAMESPACE;
+import static org.osgi.resource.Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE;
+import static org.osgi.resource.Namespace.RESOLUTION_MANDATORY;
+import static org.osgi.resource.Namespace.RESOLUTION_OPTIONAL;
 import static 
org.osgi.service.repository.ContentNamespace.CAPABILITY_URL_ATTRIBUTE;
 import static org.osgi.service.repository.ContentNamespace.CONTENT_NAMESPACE;
 
@@ -89,9 +91,9 @@ public final class ResourceUtils {
         for (Capability cap : required.getCapabilities(null)) {
             if (cap.getNamespace().equals(IDENTITY_NAMESPACE)) {
                 Map<String, Object> attributes = cap.getAttributes();
-                Map<String, String> dirs = new HashMap<String, String>();
-                dirs.put(Constants.RESOLUTION_DIRECTIVE, mandatory ? 
Constants.RESOLUTION_MANDATORY : Constants.RESOLUTION_OPTIONAL);
-                Map<String, Object> attrs = new HashMap<String, Object>();
+                Map<String, String> dirs = new HashMap<>();
+                dirs.put(REQUIREMENT_RESOLUTION_DIRECTIVE, mandatory ? 
RESOLUTION_MANDATORY : RESOLUTION_OPTIONAL);
+                Map<String, Object> attrs = new HashMap<>();
                 attrs.put(IDENTITY_NAMESPACE, 
attributes.get(IDENTITY_NAMESPACE));
                 attrs.put(CAPABILITY_TYPE_ATTRIBUTE, 
attributes.get(CAPABILITY_TYPE_ATTRIBUTE));
                 Version version = (Version) 
attributes.get(CAPABILITY_VERSION_ATTRIBUTE);

http://git-wip-us.apache.org/repos/asf/karaf/blob/b982479d/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 d406726..d902a0e 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
@@ -78,6 +78,7 @@ import org.osgi.framework.wiring.BundleRevision;
 import org.osgi.framework.wiring.BundleWire;
 import org.osgi.framework.wiring.BundleWiring;
 import org.osgi.framework.wiring.FrameworkWiring;
+import org.osgi.resource.Capability;
 import org.osgi.resource.Resource;
 import org.osgi.resource.Wire;
 import org.slf4j.Logger;
@@ -85,12 +86,12 @@ import org.slf4j.LoggerFactory;
 
 import static org.apache.felix.resolver.Util.getSymbolicName;
 import static org.apache.felix.resolver.Util.getVersion;
+import static 
org.apache.karaf.features.internal.resolver.ResourceUtils.TYPE_SUBSYSTEM;
 import static 
org.apache.karaf.features.internal.resolver.ResourceUtils.getFeatureId;
 import static org.apache.karaf.features.internal.resolver.ResourceUtils.getUri;
 import static 
org.apache.karaf.features.internal.service.StateStorage.toStringStringSetMap;
 import static org.apache.karaf.features.internal.util.MapUtils.addToMapSet;
 import static org.apache.karaf.features.internal.util.MapUtils.apply;
-import static org.apache.karaf.features.internal.util.MapUtils.contains;
 import static org.apache.karaf.features.internal.util.MapUtils.copy;
 import static org.apache.karaf.features.internal.util.MapUtils.diff;
 import static org.apache.karaf.features.internal.util.MapUtils.flatten;
@@ -102,14 +103,16 @@ import static org.osgi.framework.Bundle.STARTING;
 import static org.osgi.framework.Bundle.STOPPING;
 import static org.osgi.framework.Bundle.STOP_TRANSIENT;
 import static org.osgi.framework.Bundle.UNINSTALLED;
+import static 
org.osgi.framework.namespace.IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE;
+import static 
org.osgi.framework.namespace.IdentityNamespace.IDENTITY_NAMESPACE;
+import static org.osgi.resource.Namespace.CAPABILITY_EFFECTIVE_DIRECTIVE;
+import static org.osgi.resource.Namespace.EFFECTIVE_ACTIVE;
 
 /**
  *
  */
 public class FeaturesServiceImpl implements FeaturesService {
 
-    public static final String ROOT_REGION = "root";
-
     public static final String UPDATE_SNAPSHOTS_NONE = "none";
     public static final String UPDATE_SNAPSHOTS_CRC = "crc";
     public static final String UPDATE_SNAPSHOTS_ALWAYS = "always";
@@ -232,7 +235,8 @@ public class FeaturesServiceImpl implements FeaturesService 
{
         }
         // Resolve
         try {
-            doInstallFeaturesInThread(requestedFeatures, copyState(), options);
+            Map<String, Map<String, RequestedState>> stateChanges = 
Collections.emptyMap();
+            doInstallFeaturesInThread(requestedFeatures, stateChanges, 
copyState(), options);
         } catch (Exception e) {
             LOGGER.warn("Error updating state", e);
         }
@@ -756,7 +760,8 @@ public class FeaturesServiceImpl implements FeaturesService 
{
             required.put(region, fl);
         }
         fl.addAll(featuresToAdd);
-        doInstallFeaturesInThread(required, state, options);
+        Map<String, Map<String, RequestedState>> stateChanges = 
Collections.emptyMap();
+        doInstallFeaturesInThread(required, stateChanges, state, options);
     }
 
     public void uninstallFeatures(Set<String> features, String region, 
EnumSet<Option> options) throws Exception {
@@ -819,7 +824,14 @@ public class FeaturesServiceImpl implements 
FeaturesService {
         if (fl.isEmpty()) {
             required.remove(region);
         }
-        doInstallFeaturesInThread(required, state, options);
+        Map<String, Map<String, RequestedState>> stateChanges = 
Collections.emptyMap();
+        doInstallFeaturesInThread(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);
     }
 
     private State copyState() {
@@ -845,6 +857,7 @@ public class FeaturesServiceImpl implements FeaturesService 
{
      * 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 {
         ExecutorService executor = Executors.newCachedThreadPool();
@@ -852,7 +865,7 @@ public class FeaturesServiceImpl implements FeaturesService 
{
             executor.submit(new Callable<Object>() {
                 @Override
                 public Object call() throws Exception {
-                    doInstallFeatures(features, state, options);
+                    doInstallFeatures(features, stateChanges, state, options);
                     return null;
                 }
             }).get();
@@ -920,9 +933,10 @@ public class FeaturesServiceImpl implements 
FeaturesService {
         return state;
     }
 
-    public void doInstallFeatures(Map<String, Set<String>> requestedFeatures,  
// all request features
-                                  State state,              // current state
-                                  EnumSet<Option> options             // 
installation options
+    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
     ) throws Exception {
 
         boolean noRefreshUnmanaged = 
options.contains(Option.NoAutoRefreshUnmanagedBundles);
@@ -933,6 +947,8 @@ public class FeaturesServiceImpl implements FeaturesService 
{
         boolean simulate = options.contains(Option.Simulate);
         boolean noManageBundles = options.contains(Option.NoAutoManageBundles);
 
+        // TODO: add an option to unmanage bundles instead of uninstalling 
those
+
         DeploymentState dstate = getDeploymentState();
 
         Map<String, Set<Long>> managedBundles = copy(state.managedBundles);
@@ -959,24 +975,44 @@ public class FeaturesServiceImpl implements 
FeaturesService {
         Map<String, Set<String>> newFeatures = diff(installedFeatures, 
state.installedFeatures);
         Map<String, Set<String>> delFeatures = diff(state.installedFeatures, 
installedFeatures);
 
-        // Compute information for each bundle
-        Map<String, Map<String, BundleInfo>> bundleInfos = 
resolver.getBundleInfos();
-
-        // Get all resources that will be used to satisfy the old features set
-        // If noStart is true, we don't want to start the newly installed 
features
-        // but we still want old features to be started.
-        Set<Resource> resourceLinkedToOldFeatures = new HashSet<>();
-        if (noStart) {
-            for (Map.Entry<String, Set<Resource>> entry : 
featuresPerRegion.entrySet()) {
-                String region = entry.getKey();
-                for (Resource resource : entry.getValue()) {
-                    String id = getFeatureId(resource);
-                    if (contains(state.installedFeatures, region, id)) {
-                        addTransitive(resource, resourceLinkedToOldFeatures, 
resolver.getWiring());
+        //
+        // Compute requested features state
+        //
+        Map<String, Map<String, String>> stateFeatures = 
copy(state.stateFeatures);
+        for (Map.Entry<String, Set<String>> entry : delFeatures.entrySet()) {
+            Map<String, String> map = stateFeatures.get(entry.getKey());
+            if (map != null) {
+                map.entrySet().removeAll(entry.getValue());
+                if (map.isEmpty()) {
+                    stateFeatures.remove(entry.getKey());
+                }
+            }
+        }
+        for (Map.Entry<String, Map<String, RequestedState>> entry1 : 
stateChanges.entrySet()) {
+            String region = entry1.getKey();
+            Map<String, String> regionStates = stateFeatures.get(region);
+            if (regionStates != null) {
+                for (Map.Entry<String, RequestedState> entry2 : 
entry1.getValue().entrySet()) {
+                    String feature = entry2.getKey();
+                    if (regionStates.containsKey(feature)) {
+                        regionStates.put(feature, entry2.getValue().name());
                     }
                 }
             }
         }
+        for (Map.Entry<String, Set<String>> entry : newFeatures.entrySet()) {
+            for (String feature : entry.getValue()) {
+                Map<String, String> map = stateFeatures.get(entry.getKey());
+                if (map == null) {
+                    map = new HashMap<>();
+                    stateFeatures.put(entry.getKey(), map);
+                }
+                map.put(feature, noStart ? RequestedState.Installed.name() : 
RequestedState.Started.name());
+            }
+        }
+
+        // Compute information for each bundle
+        Map<String, Map<String, BundleInfo>> bundleInfos = 
resolver.getBundleInfos();
 
         //
         // Compute deployment
@@ -1081,6 +1117,67 @@ public class FeaturesServiceImpl implements 
FeaturesService {
         //
 
         //
+        // Compute bundle states
+        //
+        Map<Resource, RequestedState> states = new HashMap<>();
+        for (Map.Entry<String, Set<Resource>> entry : 
resolver.getFeaturesPerRegions().entrySet()) {
+            String region = entry.getKey();
+            Map<String, String> fss = stateFeatures.get(region);
+            for (Resource feature : entry.getValue()) {
+                String fs = fss.get(getFeatureId(feature));
+                propagateState(states, feature, RequestedState.valueOf(fs), 
resolver);
+            }
+        }
+        states.keySet().retainAll(resolver.getBundles().keySet());
+        //
+        // Compute bundles to start, stop and resolve
+        //
+        for (Map.Entry<Resource, RequestedState> entry : states.entrySet()) {
+            Bundle bundle = deployment.resToBnd.get(entry.getKey());
+            if (bundle != null) {
+                switch (entry.getValue()) {
+                case Started:
+                    toResolve.add(bundle);
+                    toStart.add(bundle);
+                    break;
+                case Resolved:
+                    toResolve.add(bundle);
+                    toStop.add(bundle);
+                    break;
+                }
+            }
+        }
+        //
+        // Compute bundle all start levels and start levels to update
+        //
+        FrameworkStartLevel fsl = 
systemBundleContext.getBundle().adapt(FrameworkStartLevel.class);
+        int initialBundleStartLevel = fsl.getInitialBundleStartLevel();
+        int currentStartLevel = fsl.getStartLevel();
+        Map<Resource, Integer> startLevels = new HashMap<>();
+        Map<Bundle, Integer> toUpdateStartLevel = new HashMap<>();
+        for (Map.Entry<String, Set<Resource>> entry : 
resolver.getBundlesPerRegions().entrySet()) {
+            String region = entry.getKey();
+            for (Resource resource : entry.getValue()) {
+                BundleInfo bi = bundleInfos.get(region).get(getUri(resource));
+                if (bi != null) {
+                    int sl = bi.getStartLevel() > 0 ? bi.getStartLevel() : 
initialBundleStartLevel;
+                    startLevels.put(resource, sl);
+                    Bundle bundle = deployment.resToBnd.get(resource);
+                    if (bundle != null) {
+                        int curSl = 
bundle.adapt(BundleStartLevel.class).getStartLevel();
+                        if (sl != curSl) {
+                            toUpdateStartLevel.put(bundle, sl);
+                            if (sl > currentStartLevel) {
+                                toStop.add(bundle);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+
+        //
         // Handle updates on the FeaturesService bundle
         //
         RegionDeployment rootRegionDeployment = 
deployment.regions.get(ROOT_REGION);
@@ -1133,40 +1230,6 @@ public class FeaturesServiceImpl implements 
FeaturesService {
         //
 
         //
-        // Find start levels to update
-        //
-        Map<Bundle, Integer> toUpdateStartLevel = new HashMap<>();
-        {
-            FrameworkStartLevel fsl = 
systemBundleContext.getBundle().adapt(FrameworkStartLevel.class);
-            for (Map.Entry<Resource, String> entry : 
resolver.getBundles().entrySet()) {
-                Resource resource = entry.getKey();
-                Bundle bundle = deployment.resToBnd.get(resource);
-                String region = entry.getValue();
-                BundleInfo bi = bundleInfos.get(region).get(getUri(resource));
-                if (bundle != null && bi != null) {
-                    int sl;
-                    if (bi.getStartLevel() > 0) {
-                        sl = bi.getStartLevel();
-                    } else {
-                        sl = fsl.getInitialBundleStartLevel();
-                    }
-                    BundleStartLevel bundleStartLevel = 
bundle.adapt(BundleStartLevel.class);
-                    if (bundleStartLevel.getStartLevel() != sl) {
-                        toUpdateStartLevel.put(bundle, sl);
-                        if (sl > fsl.getStartLevel()) {
-                            toStop.add(bundle);
-                        }
-                    }
-                    if (bi.isStart()) {
-                        toStart.add(bundle);
-                    } else {
-                        toStop.add(bundle);
-                    }
-                }
-            }
-        }
-
-        //
         // Stop bundles by chunks
         //
         for (RegionDeployment regionDeployment : deployment.regions.values()) {
@@ -1355,15 +1418,17 @@ public class FeaturesServiceImpl implements 
FeaturesService {
                             && isUpdateable(resource) && 
!deployment.bundleChecksums.containsKey(bundle.getBundleId())) {
                         deployment.bundleChecksums.put(bundle.getBundleId(), 
crc);
                     }
-                    BundleInfo bi = bundleInfos.get(entry.getKey()).get(uri);
-                    if (bi != null && bi.getStartLevel() > 0) {
-                        
bundle.adapt(BundleStartLevel.class).setStartLevel(bi.getStartLevel());
-                    }
-                    toResolve.add(bundle);
-                    if (resourceLinkedToOldFeatures.contains(resource)) {
-                        toStart.add(bundle);
-                    } else if (!noStart && (bi == null || bi.isStart())) {
+                    int startLevel = startLevels.get(resource);
+                    
bundle.adapt(BundleStartLevel.class).setStartLevel(startLevel);
+                    RequestedState reqState = states.get(resource);
+                    switch (reqState) {
+                    case Started:
+                        toResolve.add(bundle);
                         toStart.add(bundle);
+                        break;
+                    case Resolved:
+                        toResolve.add(bundle);
+                        break;
                     }
                 }
             }
@@ -1379,6 +1444,8 @@ public class FeaturesServiceImpl implements 
FeaturesService {
             this.state.requestedFeatures.putAll(requestedFeatures);
             this.state.installedFeatures.clear();
             this.state.installedFeatures.putAll(installedFeatures);
+            this.state.stateFeatures.clear();
+            this.state.stateFeatures.putAll(stateFeatures);
             this.state.managedBundles.clear();
             this.state.managedBundles.putAll(managedBundles);
             saveState();
@@ -1467,6 +1534,55 @@ public class FeaturesServiceImpl implements 
FeaturesService {
         print("Done.", verbose);
     }
 
+    private void propagateState(Map<Resource, RequestedState> states, Resource 
resource, RequestedState state, SubsystemResolver resolver) {
+        if (!isSubsystem(resource)) {
+            RequestedState reqState = mergeStates(state, states.get(resource));
+            if (reqState != states.get(resource)) {
+                states.put(resource, reqState);
+                for (Wire wire : resolver.getWiring().get(resource)) {
+                    Resource provider = wire.getProvider();
+                    RequestedState stateToMerge;
+                    String region = resolver.getBundles().get(provider);
+                    BundleInfo bi = region != null ? 
resolver.getBundleInfos().get(region).get(getUri(provider)) : null;
+                    if (reqState == RequestedState.Started) {
+                        String effective = 
wire.getCapability().getDirectives().get(CAPABILITY_EFFECTIVE_DIRECTIVE);
+                        // If there is an active effective capability or a 
requirement from the feature
+                        // and if the bundle is flagged as to start, start it
+                        if ((EFFECTIVE_ACTIVE.equals(effective) || 
IDENTITY_NAMESPACE.equals(wire.getCapability().getNamespace()))
+                                && (bi == null || bi.isStart())) {
+                            stateToMerge = RequestedState.Started;
+                        } else {
+                            stateToMerge = RequestedState.Resolved;
+                        }
+                    } else {
+                        stateToMerge = reqState;
+                    }
+                    propagateState(states, provider, stateToMerge, resolver);
+                }
+            }
+        }
+    }
+
+    private boolean isSubsystem(Resource resource) {
+        for (Capability cap : resource.getCapabilities(null)) {
+            if (cap.getNamespace().equals(IDENTITY_NAMESPACE)) {
+                String type = (String) 
cap.getAttributes().get(CAPABILITY_TYPE_ATTRIBUTE);
+                return (type != null) && type.equals(TYPE_SUBSYSTEM);
+            }
+        }
+        return false;
+    }
+
+    private RequestedState mergeStates(RequestedState s1, RequestedState s2) {
+        if (s1 == RequestedState.Started || s2 == RequestedState.Started) {
+            return RequestedState.Started;
+        }
+        if (s1 == RequestedState.Resolved || s2 == RequestedState.Resolved) {
+            return RequestedState.Resolved;
+        }
+        return RequestedState.Installed;
+    }
+
     private void computeBundlesToRefresh(Set<Bundle> toRefresh, 
Collection<Bundle> bundles, Map<Resource, Bundle> resources, Map<Resource, 
List<Wire>> resolution) {
         int size;
         do {
@@ -1510,14 +1626,6 @@ public class FeaturesServiceImpl implements 
FeaturesService {
         } while (toRefresh.size() > size);
     }
 
-    private void addTransitive(Resource resource, Set<Resource> resources, 
Map<Resource, List<Wire>> resolution) {
-        if (resources.add(resource)) {
-            for (Wire wire : resolution.get(resource)) {
-                addTransitive(wire.getProvider(), resources, resolution);
-            }
-        }
-    }
-
     private void print(String message, boolean verbose) {
         LOGGER.info(message);
         if (verbose) {

http://git-wip-us.apache.org/repos/asf/karaf/blob/b982479d/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 387e040..fa1fd18 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
@@ -27,11 +27,12 @@ import org.apache.karaf.features.internal.util.MapUtils;
 public class State {
 
     public final AtomicBoolean bootDone = new AtomicBoolean();
-    public final Set<String> repositories = new TreeSet<String>();
-    public final Map<String, Set<String>> requestedFeatures = new 
HashMap<String, Set<String>>();
-    public final Map<String, Set<String>> installedFeatures = new 
HashMap<String, Set<String>>();
-    public final Map<String, Set<Long>> managedBundles = new HashMap<String, 
Set<Long>>();
-    public final Map<Long, Long> bundleChecksums = new HashMap<Long, Long>();
+    public final Set<String> repositories = new TreeSet<>();
+    public final Map<String, Set<String>> requestedFeatures = 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<>();
+    public final Map<Long, Long> bundleChecksums = new HashMap<>();
 
     public State copy() {
         State state = new State();
@@ -39,6 +40,7 @@ public class State {
         MapUtils.copy(repositories, state.repositories);
         MapUtils.copy(requestedFeatures, state.requestedFeatures);
         MapUtils.copy(installedFeatures, state.installedFeatures);
+        MapUtils.copy(stateFeatures, state.stateFeatures);
         MapUtils.copy(managedBundles, state.managedBundles);
         MapUtils.copy(bundleChecksums, state.bundleChecksums);
         return state;

http://git-wip-us.apache.org/repos/asf/karaf/blob/b982479d/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 c94bb0b..e085b65 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
@@ -44,6 +44,7 @@ public abstract class StateStorage {
                 state.repositories.addAll(toStringSet((Collection) 
json.get("repositories")));
                 state.requestedFeatures.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")));
                 state.bundleChecksums.putAll(toLongLongMap((Map) 
json.get("checksums")));
             }
@@ -60,6 +61,7 @@ public abstract class StateStorage {
                 json.put("repositories", state.repositories);
                 json.put("features", state.requestedFeatures);
                 json.put("installed", state.installedFeatures);
+                json.put("state", state.stateFeatures);
                 json.put("managed", state.managedBundles);
                 json.put("checksums", toStringLongMap(state.bundleChecksums));
                 JsonWriter.write(os, json);
@@ -71,6 +73,22 @@ public abstract class StateStorage {
 
     protected abstract OutputStream getOutputStream() throws IOException;
 
+    static Map<String, Map<String, String>> toStringStringStringMapMap(Map<?, 
?> map) {
+        Map<String, Map<String, String>> nm = new HashMap<>();
+        for (Map.Entry entry : map.entrySet()) {
+            nm.put(entry.getKey().toString(), toStringStringMap((Map) 
entry.getValue()));
+        }
+        return nm;
+    }
+
+    static Map<String, String> toStringStringMap(Map<?, ?> map) {
+        Map<String, String> nm = new HashMap<>();
+        for (Map.Entry entry : map.entrySet()) {
+            nm.put(entry.getKey().toString(), entry.getValue().toString());
+        }
+        return nm;
+    }
+
     static Map<String, Set<String>> toStringStringSetMap(Map<?, ?> map) {
         Map<String, Set<String>> nm = new HashMap<>();
         for (Map.Entry entry : map.entrySet()) {

http://git-wip-us.apache.org/repos/asf/karaf/blob/b982479d/features/core/src/main/java/org/apache/karaf/features/internal/util/MapUtils.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/main/java/org/apache/karaf/features/internal/util/MapUtils.java
 
b/features/core/src/main/java/org/apache/karaf/features/internal/util/MapUtils.java
index efde48d..3288e84 100644
--- 
a/features/core/src/main/java/org/apache/karaf/features/internal/util/MapUtils.java
+++ 
b/features/core/src/main/java/org/apache/karaf/features/internal/util/MapUtils.java
@@ -53,7 +53,10 @@ public final class MapUtils {
     public static <U, T> Set<U> apply(Set<T> set, Function<T, U> function) {
         Set<U> result = new HashSet<>(set.size());
         for (T t : set) {
-            result.add(function.apply(t));
+            U u = function.apply(t);
+            if (u != null) {
+                result.add(u);
+            }
         }
         return result;
     }
@@ -61,7 +64,11 @@ public final class MapUtils {
     public static <S, T, U> Map<T, U> build(Collection<S> col, Function<S, T> 
key, Function<S, U> value) {
         Map<T, U> result = new HashMap<>(col.size());
         for (S s : col) {
-            result.put(key.apply(s), value.apply(s));
+            T t = key.apply(s);
+            U u = value.apply(s);
+            if (t != null && u != null) {
+                result.put(t, u);
+            }
         }
         return result;
     }

http://git-wip-us.apache.org/repos/asf/karaf/blob/b982479d/shell/commands/pom.xml
----------------------------------------------------------------------
diff --git a/shell/commands/pom.xml b/shell/commands/pom.xml
index 96589d0..93505ef 100644
--- a/shell/commands/pom.xml
+++ b/shell/commands/pom.xml
@@ -106,6 +106,9 @@
                         <Bundle-Activator>
                             org.apache.karaf.shell.commands.impl.info.Activator
                         </Bundle-Activator>
+                        <Require-Capability>
+                            
osgi.service;filter:="(objectClass=org.jledit.EditorFactory)";effective:=active
+                        </Require-Capability>
                         <Karaf-Commands>
                             org.apache.karaf.shell.commands.impl
                         </Karaf-Commands>

Reply via email to