Repository: karaf-cellar
Updated Branches:
  refs/heads/cellar-2.3.x 0ddfe3346 -> 2c4b692a7


[KARAF-3614] Refactore the features distributed map to provide a more reliable 
sync behavior


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

Branch: refs/heads/cellar-2.3.x
Commit: 2c4b692a7d6d62778c7e44b8e6b0f6fd6b1fb5e2
Parents: 0ddfe33
Author: Jean-Baptiste Onofré <[email protected]>
Authored: Sat Mar 14 18:14:05 2015 +0100
Committer: Jean-Baptiste Onofré <[email protected]>
Committed: Sat Mar 14 18:14:05 2015 +0100

----------------------------------------------------------------------
 assembly/src/main/resources/groups.cfg          |   4 +-
 .../karaf/cellar/bundle/BundleSynchronizer.java |   2 +-
 .../apache/karaf/cellar/features/Constants.java |   4 +-
 .../karaf/cellar/features/FeatureInfo.java      |  67 ------------
 .../karaf/cellar/features/FeatureState.java     |  54 ++++++++++
 .../cellar/features/FeaturesEventHandler.java   |   8 +-
 .../karaf/cellar/features/FeaturesSupport.java  |  82 ---------------
 .../cellar/features/FeaturesSynchronizer.java   |  64 ++++++------
 .../cellar/features/LocalFeaturesListener.java  |  45 ++++----
 .../features/shell/FeatureCommandSupport.java   |  90 +---------------
 .../features/shell/InstallFeatureCommand.java   |  27 ++++-
 .../features/shell/ListGroupFeatures.java       |  15 ++-
 .../features/shell/UninstallFeatureCommand.java |  26 ++++-
 .../cellar/features/shell/UrlAddCommand.java    |  13 ++-
 .../cellar/features/shell/UrlListCommand.java   |   2 +-
 .../cellar/features/shell/UrlRemoveCommand.java |   9 +-
 .../internal/CellarFeaturesMBeanImpl.java       | 102 ++++++++-----------
 17 files changed, 236 insertions(+), 378 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/2c4b692a/assembly/src/main/resources/groups.cfg
----------------------------------------------------------------------
diff --git a/assembly/src/main/resources/groups.cfg 
b/assembly/src/main/resources/groups.cfg
index 3c3b619..0f397c0 100644
--- a/assembly/src/main/resources/groups.cfg
+++ b/assembly/src/main/resources/groups.cfg
@@ -36,8 +36,8 @@ default.config.blacklist.outbound = 
org.apache.felix.fileinstall*, \
 #
 default.features.whitelist.inbound = *
 default.features.whitelist.outbound = *
-default.features.blacklist.inbound = config,management,hazelcast,cellar*
-default.features.blacklist.outbound = config,management,hazelcast,cellar*
+default.features.blacklist.inbound = none
+default.features.blacklist.outbound = none
 
 #
 # The following properties define the behavior to use when the node joins the 
cluster (the usage of the bootstrap

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/2c4b692a/bundle/src/main/java/org/apache/karaf/cellar/bundle/BundleSynchronizer.java
----------------------------------------------------------------------
diff --git 
a/bundle/src/main/java/org/apache/karaf/cellar/bundle/BundleSynchronizer.java 
b/bundle/src/main/java/org/apache/karaf/cellar/bundle/BundleSynchronizer.java
index ca0f182..fa6b2f1 100644
--- 
a/bundle/src/main/java/org/apache/karaf/cellar/bundle/BundleSynchronizer.java
+++ 
b/bundle/src/main/java/org/apache/karaf/cellar/bundle/BundleSynchronizer.java
@@ -75,7 +75,7 @@ public class BundleSynchronizer extends BundleSupport 
implements Synchronizer {
             }
         }
         if (policy != null && policy.equalsIgnoreCase("node")) {
-            LOGGER.debug("CELLAR BUNDLE: sync policy is set as 'cluster' for 
cluster group " + group.getName());
+            LOGGER.debug("CELLAR BUNDLE: sync policy is set as 'node' for 
cluster group " + group.getName());
             push(group);
         }
     }

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/2c4b692a/features/src/main/java/org/apache/karaf/cellar/features/Constants.java
----------------------------------------------------------------------
diff --git 
a/features/src/main/java/org/apache/karaf/cellar/features/Constants.java 
b/features/src/main/java/org/apache/karaf/cellar/features/Constants.java
index c5a6b55..240db55 100644
--- a/features/src/main/java/org/apache/karaf/cellar/features/Constants.java
+++ b/features/src/main/java/org/apache/karaf/cellar/features/Constants.java
@@ -18,8 +18,8 @@ package org.apache.karaf.cellar.features;
  */
 public class Constants {
 
-    // hazelcast map name
-    public static final String REPOSITORIES_MAP = 
"org.apache.karaf.cellar.repositories";
+    // hazelcast distributed resources name
+    public static final String REPOSITORIES_LIST = 
"org.apache.karaf.cellar.repositories";
     public static final String FEATURES_MAP = 
"org.apache.karaf.cellar.features";
 
     // configuration category

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/2c4b692a/features/src/main/java/org/apache/karaf/cellar/features/FeatureInfo.java
----------------------------------------------------------------------
diff --git 
a/features/src/main/java/org/apache/karaf/cellar/features/FeatureInfo.java 
b/features/src/main/java/org/apache/karaf/cellar/features/FeatureInfo.java
deleted file mode 100644
index 43508bd..0000000
--- a/features/src/main/java/org/apache/karaf/cellar/features/FeatureInfo.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Licensed 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.cellar.features;
-
-import java.io.Serializable;
-
-/**
- * Feature info to be store in a cluster group.
- */
-public class FeatureInfo implements Serializable {
-
-    private String name;
-    private String version;
-
-    public FeatureInfo(String name, String version) {
-        this.name = name;
-        this.version = version;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    public String getVersion() {
-        return version;
-    }
-
-    public void setVersion(String version) {
-        this.version = version;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        FeatureInfo info = (FeatureInfo) o;
-
-        if (name != null ? !name.equals(info.name) : info.name != null) return 
false;
-        if (version != null ? !version.equals(info.version) : info.version != 
null) return false;
-
-        return true;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = name != null ? name.hashCode() : 0;
-        result = 31 * result + (version != null ? version.hashCode() : 0);
-        return result;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/2c4b692a/features/src/main/java/org/apache/karaf/cellar/features/FeatureState.java
----------------------------------------------------------------------
diff --git 
a/features/src/main/java/org/apache/karaf/cellar/features/FeatureState.java 
b/features/src/main/java/org/apache/karaf/cellar/features/FeatureState.java
new file mode 100644
index 0000000..1c2baa2
--- /dev/null
+++ b/features/src/main/java/org/apache/karaf/cellar/features/FeatureState.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed 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.cellar.features;
+
+import java.io.Serializable;
+
+/**
+ * Feature info to be store in a cluster group.
+ */
+public class FeatureState implements Serializable {
+
+    private String name;
+    private String version;
+    private Boolean installed;
+
+    public FeatureState() {
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public void setVersion(String version) {
+        this.version = version;
+    }
+
+    public Boolean getInstalled() {
+        return installed;
+    }
+
+    public void setInstalled(Boolean installed) {
+        this.installed = installed;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/2c4b692a/features/src/main/java/org/apache/karaf/cellar/features/FeaturesEventHandler.java
----------------------------------------------------------------------
diff --git 
a/features/src/main/java/org/apache/karaf/cellar/features/FeaturesEventHandler.java
 
b/features/src/main/java/org/apache/karaf/cellar/features/FeaturesEventHandler.java
index 179d245..dd48a3d 100644
--- 
a/features/src/main/java/org/apache/karaf/cellar/features/FeaturesEventHandler.java
+++ 
b/features/src/main/java/org/apache/karaf/cellar/features/FeaturesEventHandler.java
@@ -58,13 +58,13 @@ public class FeaturesEventHandler extends FeaturesSupport 
implements EventHandle
 
         // check if the handler switch is ON
         if (this.getSwitch().getStatus().equals(SwitchStatus.OFF)) {
-            LOGGER.debug("CELLAR FEATURES_MAP: {} switch is OFF, cluster event 
is not handled", SWITCH_ID);
+            LOGGER.debug("CELLAR FEATURES: {} switch is OFF, cluster event is 
not handled", SWITCH_ID);
             return;
         }
 
         // check if the group is local
         if (!groupManager.isLocalGroup(event.getSourceGroup().getName())) {
-            LOGGER.debug("CELLAR FEATURES_MAP: node is not part of the event 
cluster group {}", event.getSourceGroup().getName());
+            LOGGER.debug("CELLAR FEATURES: node is not part of the event 
cluster group {}", event.getSourceGroup().getName());
             return;
         }
 
@@ -93,10 +93,10 @@ public class FeaturesEventHandler extends FeaturesSupport 
implements EventHandle
                     }
                 } else if 
(FeatureEvent.EventType.FeatureUninstalled.equals(type) && isInstalled) {
                     if (version != null) {
-                        LOGGER.debug("CELLAR FEATURES_MAP: un-installing 
feature {}/{}", name, version);
+                        LOGGER.debug("CELLAR FEATURES_MAP: uninstalling 
feature {}/{}", name, version);
                         featuresService.uninstallFeature(name, version);
                     } else {
-                        LOGGER.debug("CELLAR FEATURES_MAP: un-installing 
feature {}", name);
+                        LOGGER.debug("CELLAR FEATURES_MAP: uninstalling 
feature {}", name);
                         featuresService.uninstallFeature(name);
                     }
                 }

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/2c4b692a/features/src/main/java/org/apache/karaf/cellar/features/FeaturesSupport.java
----------------------------------------------------------------------
diff --git 
a/features/src/main/java/org/apache/karaf/cellar/features/FeaturesSupport.java 
b/features/src/main/java/org/apache/karaf/cellar/features/FeaturesSupport.java
index 43a9307..6a81aee 100644
--- 
a/features/src/main/java/org/apache/karaf/cellar/features/FeaturesSupport.java
+++ 
b/features/src/main/java/org/apache/karaf/cellar/features/FeaturesSupport.java
@@ -81,88 +81,6 @@ public class FeaturesSupport extends CellarSupport {
         return false;
     }
 
-    /**
-     * Push a feature in a cluster group
-     *
-     * @param feature the feature to push to the cluster group.
-     * @param group the cluster group where to push the feature.
-     */
-    public void pushFeature(Feature feature, Group group) {
-        if (feature != null) {
-            String groupName = group.getName();
-            Map<FeatureInfo, Boolean> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
groupName);
-
-            if (isAllowed(group, Constants.CATEGORY, feature.getName(), 
EventType.OUTBOUND)) {
-                if (featuresService != null && clusterFeatures != null) {
-                    FeatureInfo info = new FeatureInfo(feature.getName(), 
feature.getVersion());
-                    Boolean installed = featuresService.isInstalled(feature);
-                    clusterFeatures.put(info, installed);
-                }
-            } else LOGGER.debug("CELLAR FEATURES_MAP: feature {} is marked 
BLOCKED OUTBOUND for cluster group {}", feature.getName(), groupName);
-        } else LOGGER.warn("CELLAR FEATURES_MAP: feature is null");
-    }
-
-    /**
-     * Push a feature in a cluster group.
-     * This version of the method force the bundle status, without looking the 
features service.
-     *
-     * @param feature the feature to push to the cluster group.
-     * @param group the cluster group where to push the feature.
-     * @param force true to force the bundle status as well, false else.
-     */
-    public void pushFeature(Feature feature, Group group, Boolean force) {
-        if (feature != null) {
-            String groupName = group.getName();
-            Map<FeatureInfo, Boolean> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
groupName);
-
-            if (isAllowed(group, Constants.CATEGORY, feature.getName(), 
EventType.OUTBOUND)) {
-                if (featuresService != null && clusterFeatures != null) {
-                    FeatureInfo info = new FeatureInfo(feature.getName(), 
feature.getVersion());
-                    clusterFeatures.put(info, force);
-                }
-            } else LOGGER.debug("CELLAR FEATURES_MAP: feature {} is marked 
BLOCKED OUTBOUND for cluster group {}", feature.getName(), groupName);
-        } else LOGGER.warn("CELLAR FEATURES_MAP: feature is null");
-    }
-
-    /**
-     * Push a features repository in a cluster group.
-     *
-     * @param repository the features repository to push.
-     * @param group the cluster group where to push.
-     */
-    public void pushRepository(Repository repository, Group group) {
-        String groupName = group.getName();
-        List<String> clusterRepositories = 
clusterManager.getList(Constants.REPOSITORIES_MAP + Configurations.SEPARATOR + 
groupName);
-
-        boolean found = false;
-        for (String clusterRepository : clusterRepositories) {
-            if (clusterRepository.equals(repository.getURI().toString())) {
-                found = true;
-                break;
-            }
-        }
-
-        if (!found) {
-            clusterRepositories.add(repository.getURI().toString());
-        }
-    }
-
-    /**
-     * Remove a features repository from a cluster group.
-     *
-     * @param repository the features repository to remove from the cluster 
group.
-     * @param group the cluster group where to remove from.
-     */
-    public void removeRepository(Repository repository, Group group) {
-        String groupName = group.getName();
-        List<String> clusterRepositories = 
clusterManager.getList(Constants.REPOSITORIES_MAP + Configurations.SEPARATOR + 
groupName);
-
-        if (featuresService != null && clusterRepositories != null) {
-            URI uri = repository.getURI();
-            clusterRepositories.remove(uri.toString());
-        }
-    }
-
     public FeaturesService getFeaturesService() {
         return featuresService;
     }

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/2c4b692a/features/src/main/java/org/apache/karaf/cellar/features/FeaturesSynchronizer.java
----------------------------------------------------------------------
diff --git 
a/features/src/main/java/org/apache/karaf/cellar/features/FeaturesSynchronizer.java
 
b/features/src/main/java/org/apache/karaf/cellar/features/FeaturesSynchronizer.java
index 72ca64e..128dec2 100644
--- 
a/features/src/main/java/org/apache/karaf/cellar/features/FeaturesSynchronizer.java
+++ 
b/features/src/main/java/org/apache/karaf/cellar/features/FeaturesSynchronizer.java
@@ -72,7 +72,7 @@ public class FeaturesSynchronizer extends FeaturesSupport 
implements Synchronize
             }
         }
         if (policy != null && policy.equalsIgnoreCase("node")) {
-            LOGGER.debug("CELLAR FEATURE: sync policy is set as 'cluster' for 
cluster group " + group.getName());
+            LOGGER.debug("CELLAR FEATURE: sync policy is set as 'node' for 
cluster group " + group.getName());
             push(group);
         }
     }
@@ -87,37 +87,37 @@ public class FeaturesSynchronizer extends FeaturesSupport 
implements Synchronize
         if (group != null) {
             String groupName = group.getName();
             LOGGER.debug("CELLAR FEATURES_MAP: pulling features repositories 
and features from cluster group {}", groupName);
-            List<String> clusterRepositories = 
clusterManager.getList(Constants.REPOSITORIES_MAP + Configurations.SEPARATOR + 
groupName);
-            Map<FeatureInfo, Boolean> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
groupName);
-            clusterManager.getList(Constants.FEATURES_MAP + 
Configurations.SEPARATOR + groupName);
             ClassLoader originalClassLoader = 
Thread.currentThread().getContextClassLoader();
             try {
                 
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
 
+                List<String> clusterRepositories = 
clusterManager.getList(Constants.REPOSITORIES_LIST + Configurations.SEPARATOR + 
groupName);
+                Map<String, FeatureState> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
groupName);
+
                 // get features URLs from the cluster group
                 if (clusterRepositories != null && 
!clusterRepositories.isEmpty()) {
                     for (String url : clusterRepositories) {
                         try {
                             if (!isRepositoryRegisteredLocally(url)) {
-                                LOGGER.debug("CELLAR FEATURES_MAP: adding 
repository {}", url);
+                                LOGGER.debug("CELLAR FEATURES: adding 
repository {}", url);
                                 featuresService.addRepository(new URI(url));
                             }
                         } catch (MalformedURLException e) {
-                            LOGGER.warn("CELLAR FEATURES_MAP: failed to add 
clusterFeatures repository {} (URL is malformed)", url, e);
+                            LOGGER.warn("CELLAR FEATURES: failed to add 
clusterFeatures repository {} (URL is malformed)", url, e);
                         } catch (Exception e) {
-                            LOGGER.warn("CELLAR FEATURES_MAP: failed to add 
clusterFeatures repository {}", url, e);
+                            LOGGER.warn("CELLAR FEATURES: failed to add 
clusterFeatures repository {}", url, e);
                         }
                     }
                 }
 
                 // get features status from the cluster group
                 if (clusterFeatures != null && !clusterFeatures.isEmpty()) {
-                    for (FeatureInfo info : clusterFeatures.keySet()) {
-                        String name = info.getName();
+                    for (FeatureState state : clusterFeatures.values()) {
+                        String name = state.getName();
                         // check if feature is blocked
                         if (isAllowed(group, Constants.CATEGORY, name, 
EventType.INBOUND)) {
-                            Boolean clusterInstalled = 
clusterFeatures.get(info);
-                            Boolean locallyInstalled = 
isFeatureInstalledLocally(info.getName(), info.getVersion());
+                            Boolean clusterInstalled = state.getInstalled();
+                            Boolean locallyInstalled = 
isFeatureInstalledLocally(state.getName(), state.getVersion());
 
                             // prevent NPE
                             if (clusterInstalled == null) {
@@ -129,22 +129,14 @@ public class FeaturesSynchronizer extends FeaturesSupport 
implements Synchronize
                             // if feature has to be installed locally
                             if (clusterInstalled && !locallyInstalled) {
                                 try {
-                                    LOGGER.debug("CELLAR FEATURES_MAP: 
installing feature {}/{}", info.getName(), info.getVersion());
-                                    
featuresService.installFeature(info.getName(), info.getVersion());
-                                } catch (Exception e) {
-                                    LOGGER.warn("CELLAR FEATURES_MAP: failed 
to install feature {}/{} ", new Object[]{info.getName(), info.getVersion()}, e);
-                                }
-                                // if feature has to be uninstalled locally
-                            } else if (!clusterInstalled && locallyInstalled) {
-                                try {
-                                    LOGGER.debug("CELLAR FEATURES_MAP: 
un-installing feature {}/{}", info.getName(), info.getVersion());
-                                    
featuresService.uninstallFeature(info.getName(), info.getVersion());
+                                    LOGGER.debug("CELLAR FEATURES: installing 
feature {}/{}", state.getName(), state.getVersion());
+                                    
featuresService.installFeature(state.getName(), state.getVersion());
                                 } catch (Exception e) {
-                                    LOGGER.warn("CELLAR FEATURES_MAP: failed 
to uninstall feature {}/{} ", new Object[]{info.getName(), info.getVersion()}, 
e);
+                                    LOGGER.warn("CELLAR FEATURES: failed to 
install feature {}/{} ", new Object[]{state.getName(), state.getVersion()}, e);
                                 }
                             }
                         } else
-                            LOGGER.warn("CELLAR FEATURES_MAP: feature {} is 
marked BLOCKED INBOUND for cluster group {}", name, groupName);
+                            LOGGER.warn("CELLAR FEATURES: feature {} is marked 
BLOCKED INBOUND for cluster group {}", name, groupName);
                     }
                 }
             } finally {
@@ -163,12 +155,14 @@ public class FeaturesSynchronizer extends FeaturesSupport 
implements Synchronize
         if (group != null) {
             String groupName = group.getName();
             LOGGER.debug("CELLAR FEATURES_MAP: pushing features repositories 
and features in cluster group {}.", groupName);
-            clusterManager.getList(Constants.FEATURES_MAP + 
Configurations.SEPARATOR + groupName);
 
             ClassLoader originalClassLoader = 
Thread.currentThread().getContextClassLoader();
             try {
                 
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
 
+                List<String> clusterRepositories = 
clusterManager.getList(Constants.REPOSITORIES_LIST + Configurations.SEPARATOR + 
groupName);
+                Map<String, FeatureState> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
groupName);
+
                 Repository[] repositoryList = new Repository[0];
                 Feature[] featuresList = new Feature[0];
 
@@ -176,22 +170,34 @@ public class FeaturesSynchronizer extends FeaturesSupport 
implements Synchronize
                     repositoryList = featuresService.listRepositories();
                     featuresList = featuresService.listFeatures();
                 } catch (Exception e) {
-                    LOGGER.warn("CELLAR FEATURES_MAP: unable to list 
features", e);
+                    LOGGER.warn("CELLAR FEATURES: unable to list features", e);
                 }
 
                 // push the local features repositories to the cluster group
                 if (repositoryList != null && repositoryList.length > 0) {
                     for (Repository repository : repositoryList) {
-                        pushRepository(repository, group);
-                        LOGGER.debug("CELLAR FEATURES_MAP: pushing repository 
{} in cluster group {}", repository.getName(), group.getName());
+                        if 
(!clusterRepositories.contains(repository.getURI().toString())) {
+                            
clusterRepositories.add(repository.getURI().toString());
+                            LOGGER.debug("CELLAR FEATURES: pushing repository 
{} in cluster group {}", repository.getName(), groupName);
+                        } else {
+                            LOGGER.debug("CELLAR FEATURES: repository {} is 
already in cluster group {}", repository.getName(), groupName);
+                        }
                     }
                 }
 
                 // push the local features status to the cluster group
                 if (featuresList != null && featuresList.length > 0) {
                     for (Feature feature : featuresList) {
-                        pushFeature(feature, group);
-                        LOGGER.debug("CELLAR FEATURES_MAP: pushing feature {} 
in cluster group {}", feature.getName(), group.getName());
+                        if (isAllowed(group, Constants.CATEGORY, 
feature.getName(), EventType.OUTBOUND)) {
+                            FeatureState clusterFeatureState = new 
FeatureState();
+                            clusterFeatureState.setName(feature.getName());
+                            
clusterFeatureState.setVersion(feature.getVersion());
+                            
clusterFeatureState.setInstalled(featuresService.isInstalled(feature));
+                            clusterFeatures.put(feature.getName() + "/" + 
feature.getVersion(), clusterFeatureState);
+                            LOGGER.debug("CELLAR FEATURES: pushing feature {} 
in cluster group {}", feature.getName(), group.getName());
+                        } else {
+                            LOGGER.debug("CELLAR FEATURES: feature {} is 
marked BLOCKED OUTBOUND for cluster group {}", feature.getName(), 
group.getName());
+                        }
                     }
                 }
             } finally {

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/2c4b692a/features/src/main/java/org/apache/karaf/cellar/features/LocalFeaturesListener.java
----------------------------------------------------------------------
diff --git 
a/features/src/main/java/org/apache/karaf/cellar/features/LocalFeaturesListener.java
 
b/features/src/main/java/org/apache/karaf/cellar/features/LocalFeaturesListener.java
index 3419f88..a9dd88a 100644
--- 
a/features/src/main/java/org/apache/karaf/cellar/features/LocalFeaturesListener.java
+++ 
b/features/src/main/java/org/apache/karaf/cellar/features/LocalFeaturesListener.java
@@ -26,6 +26,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.util.Dictionary;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -81,11 +82,16 @@ public class LocalFeaturesListener extends FeaturesSupport 
implements org.apache
                         FeatureEvent.EventType type = event.getType();
 
                         // update the features in the cluster group
+                        Map<String, FeatureState> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
group.getName());
+                        FeatureState clusterFeatureState = new FeatureState();
+                        
clusterFeatureState.setName(event.getFeature().getName());
+                        
clusterFeatureState.setVersion(event.getFeature().getVersion());
                         if 
(FeatureEvent.EventType.FeatureInstalled.equals(event.getType())) {
-                            pushFeature(event.getFeature(), group, true);
+                            clusterFeatureState.setInstalled(Boolean.TRUE);
                         } else {
-                            pushFeature(event.getFeature(), group, false);
+                            clusterFeatureState.setInstalled(Boolean.FALSE);
                         }
+                        clusterFeatures.put(event.getFeature().getName() + "/" 
+ event.getFeature().getVersion(), clusterFeatureState);
 
                         // broadcast the cluster event
                         ClusterFeaturesEvent featureEvent = new 
ClusterFeaturesEvent(name, version, type);
@@ -129,41 +135,38 @@ public class LocalFeaturesListener extends 
FeaturesSupport implements org.apache
                         repositoryEvent.setSourceGroup(group);
                         RepositoryEvent.EventType type = event.getType();
 
+                        List<String> clusterRepositories = 
clusterManager.getList(Constants.REPOSITORIES_LIST + Configurations.SEPARATOR + 
group.getName());
+
                         if 
(RepositoryEvent.EventType.RepositoryAdded.equals(type)) {
                             // update the repositories in the cluster group
-                            pushRepository(event.getRepository(), group);
+                            if 
(!clusterRepositories.contains(event.getRepository().getURI().toString())) {
+                                
clusterRepositories.add(event.getRepository().getURI().toString());
+                            }
                             // update the features in the cluster group
-                            Map<FeatureInfo, Boolean> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
group.getName());
+                            Map<String, FeatureState> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
group.getName());
                             try {
                                 for (Feature feature : 
event.getRepository().getFeatures()) {
                                     // check the feature in the cluster group
-                                    FeatureInfo featureInfo = null;
-                                    for (FeatureInfo clusterFeature : 
clusterFeatures.keySet()) {
-                                        if 
(clusterFeature.getName().equals(feature.getName()) && 
clusterFeature.getVersion().equals(feature.getVersion())) {
-                                            featureInfo = clusterFeature;
-                                            break;
-                                        }
-                                    }
-                                    if (featureInfo == null) {
-                                        featureInfo = new 
FeatureInfo(feature.getName(), feature.getVersion());
-                                        clusterFeatures.put(featureInfo, 
false);
-                                    }
+                                    FeatureState clusterFeatureState = new 
FeatureState();
+                                    
clusterFeatureState.setName(feature.getName());
+                                    
clusterFeatureState.setVersion(feature.getVersion());
+                                    
clusterFeatureState.setInstalled(Boolean.FALSE);
+                                    clusterFeatures.put(feature.getName() + 
"/" + feature.getVersion(), clusterFeatureState);
                                 }
                             } catch (Exception e) {
-                                LOGGER.warn("CELLAR FEATURES_MAP: failed to 
update the cluster group", e);
+                                LOGGER.warn("CELLAR FEATURES: failed to update 
the cluster group", e);
                             }
                         } else {
                             // update the repositories in the cluster group
-                            removeRepository(event.getRepository(), group);
+                            
clusterRepositories.remove(event.getRepository().getURI().toString());
                             // update the features in the cluster group
-                            Map<FeatureInfo, Boolean> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
group.getName());
+                            Map<String, FeatureState> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
group.getName());
                             try {
                                 for (Feature feature : 
event.getRepository().getFeatures()) {
-                                    FeatureInfo info = new 
FeatureInfo(feature.getName(), feature.getVersion());
-                                    clusterFeatures.remove(info);
+                                    clusterFeatures.remove(feature.getName() + 
"/" + feature.getVersion());
                                 }
                             } catch (Exception e) {
-                                LOGGER.warn("CELLAR FEATURES_MAP: failed to 
update the cluster group", e);
+                                LOGGER.warn("CELLAR FEATURES: failed to update 
the cluster group", e);
                             }
                         }
 

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/2c4b692a/features/src/main/java/org/apache/karaf/cellar/features/shell/FeatureCommandSupport.java
----------------------------------------------------------------------
diff --git 
a/features/src/main/java/org/apache/karaf/cellar/features/shell/FeatureCommandSupport.java
 
b/features/src/main/java/org/apache/karaf/cellar/features/shell/FeatureCommandSupport.java
index 4e543fa..6dcf765 100644
--- 
a/features/src/main/java/org/apache/karaf/cellar/features/shell/FeatureCommandSupport.java
+++ 
b/features/src/main/java/org/apache/karaf/cellar/features/shell/FeatureCommandSupport.java
@@ -19,7 +19,7 @@ import org.apache.karaf.cellar.core.Group;
 import org.apache.karaf.cellar.core.event.EventType;
 import org.apache.karaf.cellar.core.shell.CellarCommandSupport;
 import org.apache.karaf.cellar.features.Constants;
-import org.apache.karaf.cellar.features.FeatureInfo;
+import org.apache.karaf.cellar.features.FeatureState;
 import org.apache.karaf.features.Feature;
 import org.apache.karaf.features.FeaturesService;
 import org.osgi.framework.BundleContext;
@@ -33,98 +33,10 @@ import java.util.Map;
  */
 public abstract class FeatureCommandSupport extends CellarCommandSupport {
 
-    protected static final transient Logger LOGGER = 
LoggerFactory.getLogger(FeatureCommandSupport.class);
-
     protected FeaturesService featuresService;
     protected BundleContext bundleContext;
 
     /**
-     * Forces the features status for a specific group.
-     * Why? Its required if no group member currently in the cluster.
-     * If a member of the group joins later, it won't find the change, unless 
we force it.
-     *
-     * @param groupName the cluster group name.
-     * @param feature the feature name.
-     * @param version the feature version.
-     * @param status true to installed, false to uninstalled.
-     */
-    public Boolean updateFeatureStatus(String groupName, String feature, 
String version, Boolean status) {
-        Boolean result = Boolean.FALSE;
-        ClassLoader originalClassLoader = 
Thread.currentThread().getContextClassLoader();
-        try {
-            
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
-            Group group = groupManager.findGroupByName(groupName);
-            if (group == null || group.getNodes().isEmpty()) {
-
-                FeatureInfo info = new FeatureInfo(feature, version);
-                Map<FeatureInfo, Boolean> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
groupName);
-                // check the existing configuration
-                if (version == null || (version.trim().length() < 1)) {
-                    for (FeatureInfo f : clusterFeatures.keySet()) {
-                        if (f.getName().equals(feature)) {
-                            version = f.getVersion();
-                            info.setVersion(version);
-                        }
-                    }
-                }
-
-                // check the features service
-                try {
-                    for (Feature f : featuresService.listFeatures()) {
-                        if (f.getName().equals(feature)) {
-                            version = f.getVersion();
-                            info.setVersion(version);
-                        }
-                    }
-                } catch (Exception e) {
-                    LOGGER.error("Error while browsing features", e);
-                }
-
-                if (info.getVersion() != null && 
(info.getVersion().trim().length() > 0)) {
-                    clusterFeatures.put(info, status);
-                    result = Boolean.TRUE;
-                }
-            }
-        } finally {
-            Thread.currentThread().setContextClassLoader(originalClassLoader);
-        }
-        return result;
-    }
-
-    /**
-     * Check if a feature is present in a cluster group.
-     *
-     * @param groupName the cluster group.
-     * @param feature the feature name.
-     * @param version the feature version.
-     * @return true if the feature exists in the cluster group, false else.
-     */
-    public boolean featureExists(String groupName, String feature, String 
version) {
-        ClassLoader originalClassLoader = 
Thread.currentThread().getContextClassLoader();
-        try {
-            
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
-            Map<FeatureInfo, Boolean> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
groupName);
-
-            if (clusterFeatures == null)
-                return false;
-
-            for (FeatureInfo clusterFeature : clusterFeatures.keySet()) {
-                if (version == null) {
-                    if (clusterFeature.getName().equals(feature))
-                        return true;
-                } else {
-                    if (clusterFeature.getName().equals(feature) && 
clusterFeature.getVersion().equals(version))
-                        return true;
-                }
-            }
-
-            return false;
-        } finally {
-            Thread.currentThread().setContextClassLoader(originalClassLoader);
-        }
-    }
-
-    /**
      * Check if a feature event is allowed.
      *
      * @param group the cluster group.

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/2c4b692a/features/src/main/java/org/apache/karaf/cellar/features/shell/InstallFeatureCommand.java
----------------------------------------------------------------------
diff --git 
a/features/src/main/java/org/apache/karaf/cellar/features/shell/InstallFeatureCommand.java
 
b/features/src/main/java/org/apache/karaf/cellar/features/shell/InstallFeatureCommand.java
index 7d00bab..2e0fa1f 100644
--- 
a/features/src/main/java/org/apache/karaf/cellar/features/shell/InstallFeatureCommand.java
+++ 
b/features/src/main/java/org/apache/karaf/cellar/features/shell/InstallFeatureCommand.java
@@ -14,6 +14,7 @@
 package org.apache.karaf.cellar.features.shell;
 
 import org.apache.felix.gogo.commands.Option;
+import org.apache.karaf.cellar.core.Configurations;
 import org.apache.karaf.cellar.core.Group;
 import org.apache.karaf.cellar.core.control.SwitchStatus;
 import org.apache.karaf.cellar.core.event.EventProducer;
@@ -22,8 +23,11 @@ import org.apache.karaf.cellar.features.ClusterFeaturesEvent;
 import org.apache.karaf.cellar.features.Constants;
 import org.apache.felix.gogo.commands.Argument;
 import org.apache.felix.gogo.commands.Command;
+import org.apache.karaf.cellar.features.FeatureState;
 import org.apache.karaf.features.FeatureEvent;
 
+import java.util.Map;
+
 @Command(scope = "cluster", name = "feature-install", description = "Install a 
feature in a cluster group")
 public class InstallFeatureCommand extends FeatureCommandSupport {
 
@@ -59,8 +63,19 @@ public class InstallFeatureCommand extends 
FeatureCommandSupport {
             return null;
         }
 
+        Map<String, FeatureState> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
groupName);
+
+        // try to resolve the feature version if not provided
+        if (version == null) {
+            for (FeatureState state : clusterFeatures.values()) {
+                if (state.getName().equals(feature)) {
+                    version = state.getVersion();
+                }
+            }
+        }
+
         // check if the feature exists in the map
-        if (!featureExists(groupName, feature, version)) {
+        if (!clusterFeatures.containsKey(feature + "/" + version)) {
             if (version != null)
                 System.err.println("Feature " + feature + "/" + version + " 
doesn't exist in the cluster group " + groupName);
             else System.err.println("Feature " + feature + " doesn't exist in 
the cluster group " + groupName);
@@ -74,7 +89,15 @@ public class InstallFeatureCommand extends 
FeatureCommandSupport {
         }
 
         // update the features in the cluster group
-        updateFeatureStatus(groupName, feature, version, true);
+        FeatureState clusterFeatureState = clusterFeatures.get(feature + "/" + 
version);
+        if (clusterFeatureState == null) {
+            clusterFeatureState = new FeatureState();
+            clusterFeatureState.setName(feature);
+            clusterFeatureState.setVersion(version);
+        }
+        clusterFeatureState.setInstalled(Boolean.TRUE);
+        clusterFeatures.put(feature + "/" + version, clusterFeatureState);
+        // TODO does it make sense to also update the cluster bundles, I don't 
think so ...
 
         // broadcast the cluster event
         ClusterFeaturesEvent event = new ClusterFeaturesEvent(feature, 
version, noClean, noRefresh, FeatureEvent.EventType.FeatureInstalled);

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/2c4b692a/features/src/main/java/org/apache/karaf/cellar/features/shell/ListGroupFeatures.java
----------------------------------------------------------------------
diff --git 
a/features/src/main/java/org/apache/karaf/cellar/features/shell/ListGroupFeatures.java
 
b/features/src/main/java/org/apache/karaf/cellar/features/shell/ListGroupFeatures.java
index a2b2262..1269d91 100644
--- 
a/features/src/main/java/org/apache/karaf/cellar/features/shell/ListGroupFeatures.java
+++ 
b/features/src/main/java/org/apache/karaf/cellar/features/shell/ListGroupFeatures.java
@@ -17,7 +17,7 @@ import org.apache.felix.gogo.commands.Option;
 import org.apache.karaf.cellar.core.Configurations;
 import org.apache.karaf.cellar.core.Group;
 import org.apache.karaf.cellar.features.Constants;
-import org.apache.karaf.cellar.features.FeatureInfo;
+import org.apache.karaf.cellar.features.FeatureState;
 import org.apache.felix.gogo.commands.Argument;
 import org.apache.felix.gogo.commands.Command;
 
@@ -47,23 +47,22 @@ public class ListGroupFeatures extends 
FeatureCommandSupport {
         try {
             
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
 
-            Map<FeatureInfo, Boolean> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
groupName);
+            Map<String, FeatureState> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
groupName);
             if (clusterFeatures != null && !clusterFeatures.isEmpty()) {
                 System.out.println("Features in cluster group " + groupName);
                 System.out.println(String.format(HEADER_FORMAT, "Status", 
"Version", "Name"));
-                for (FeatureInfo info : clusterFeatures.keySet()) {
-                    String name = info.getName();
-                    String version = info.getVersion();
+                for (FeatureState state : clusterFeatures.values()) {
+                    String name = state.getName();
+                    String version = state.getVersion();
                     String statusString = "";
-                    boolean status = clusterFeatures.get(info);
-                    if (status) {
+                    if (state.getInstalled()) {
                         statusString = "installed";
                     } else {
                         statusString = "uninstalled";
                     }
                     if (version == null)
                         version = "";
-                    if (!installed || (installed && status)) {
+                    if (!installed || (installed && state.getInstalled())) {
                         System.out.println(String.format(OUTPUT_FORMAT, 
statusString, version, name));
                     }
                 }

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/2c4b692a/features/src/main/java/org/apache/karaf/cellar/features/shell/UninstallFeatureCommand.java
----------------------------------------------------------------------
diff --git 
a/features/src/main/java/org/apache/karaf/cellar/features/shell/UninstallFeatureCommand.java
 
b/features/src/main/java/org/apache/karaf/cellar/features/shell/UninstallFeatureCommand.java
index de1b6de..9c50a2f 100644
--- 
a/features/src/main/java/org/apache/karaf/cellar/features/shell/UninstallFeatureCommand.java
+++ 
b/features/src/main/java/org/apache/karaf/cellar/features/shell/UninstallFeatureCommand.java
@@ -13,6 +13,7 @@
  */
 package org.apache.karaf.cellar.features.shell;
 
+import org.apache.karaf.cellar.core.Configurations;
 import org.apache.karaf.cellar.core.Group;
 import org.apache.karaf.cellar.core.control.SwitchStatus;
 import org.apache.karaf.cellar.core.event.EventProducer;
@@ -21,8 +22,11 @@ import org.apache.karaf.cellar.features.ClusterFeaturesEvent;
 import org.apache.karaf.cellar.features.Constants;
 import org.apache.felix.gogo.commands.Argument;
 import org.apache.felix.gogo.commands.Command;
+import org.apache.karaf.cellar.features.FeatureState;
 import org.apache.karaf.features.FeatureEvent;
 
+import java.util.Map;
+
 @Command(scope = "cluster", name = "features-uninstall", description = 
"Uninstall a feature from a cluster group")
 public class UninstallFeatureCommand extends FeatureCommandSupport {
 
@@ -52,8 +56,19 @@ public class UninstallFeatureCommand extends 
FeatureCommandSupport {
             return null;
         }
 
+        Map<String, FeatureState> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
groupName);
+
+        // try to resolve the feature version if not provided
+        if (version == null) {
+            for (FeatureState state : clusterFeatures.values()) {
+                if (state.getName().equals(feature)) {
+                    version = state.getVersion();
+                }
+            }
+        }
+
         // check if the feature exists in the map
-        if (!featureExists(groupName, feature, version)) {
+        if (!clusterFeatures.containsKey(feature + "/" + version)) {
             if (version != null)
                 System.err.println("Feature " + feature + "/" + version + " 
doesn't exist in the cluster group " + groupName);
             else System.err.println("Feature " + feature + " doesn't exist in 
the cluster group " + groupName);
@@ -67,7 +82,14 @@ public class UninstallFeatureCommand extends 
FeatureCommandSupport {
         }
 
         // update the features in the cluster group
-        updateFeatureStatus(groupName, feature, version, false);
+        FeatureState clusterFeatureState = clusterFeatures.get(feature + "/" + 
version);
+        if (clusterFeatureState == null) {
+            clusterFeatureState = new FeatureState();
+            clusterFeatureState.setName(feature);
+            clusterFeatureState.setVersion(version);
+        }
+        clusterFeatureState.setInstalled(Boolean.FALSE);
+        clusterFeatures.put(feature + "/" + version, clusterFeatureState);
 
         // broadcast the cluster event
         ClusterFeaturesEvent event = new ClusterFeaturesEvent(feature, 
version, FeatureEvent.EventType.FeatureUninstalled);

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/2c4b692a/features/src/main/java/org/apache/karaf/cellar/features/shell/UrlAddCommand.java
----------------------------------------------------------------------
diff --git 
a/features/src/main/java/org/apache/karaf/cellar/features/shell/UrlAddCommand.java
 
b/features/src/main/java/org/apache/karaf/cellar/features/shell/UrlAddCommand.java
index 027798a..6449a39 100644
--- 
a/features/src/main/java/org/apache/karaf/cellar/features/shell/UrlAddCommand.java
+++ 
b/features/src/main/java/org/apache/karaf/cellar/features/shell/UrlAddCommand.java
@@ -22,7 +22,7 @@ import org.apache.karaf.cellar.core.control.SwitchStatus;
 import org.apache.karaf.cellar.core.event.EventProducer;
 import org.apache.karaf.cellar.features.ClusterRepositoryEvent;
 import org.apache.karaf.cellar.features.Constants;
-import org.apache.karaf.cellar.features.FeatureInfo;
+import org.apache.karaf.cellar.features.FeatureState;
 import org.apache.karaf.features.Feature;
 import org.apache.karaf.features.Repository;
 import org.apache.karaf.features.RepositoryEvent;
@@ -64,9 +64,9 @@ public class UrlAddCommand extends FeatureCommandSupport {
         try {
             
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
             // get the repositories in the cluster group
-            List<String> clusterRepositories = 
clusterManager.getList(Constants.REPOSITORIES_MAP + Configurations.SEPARATOR + 
groupName);
+            List<String> clusterRepositories = 
clusterManager.getList(Constants.REPOSITORIES_LIST + Configurations.SEPARATOR + 
groupName);
             // get the features in the cluster group
-            Map<FeatureInfo, Boolean> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
groupName);
+            Map<String, FeatureState> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
groupName);
 
             for (String url : urls) {
                 // check if the URL is already registered
@@ -112,8 +112,11 @@ public class UrlAddCommand extends FeatureCommandSupport {
 
                     // update the features in the cluster group
                     for (Feature feature : repository.getFeatures()) {
-                        FeatureInfo info = new FeatureInfo(feature.getName(), 
feature.getVersion());
-                        clusterFeatures.put(info, false);
+                        FeatureState state = new FeatureState();
+                        state.setName(feature.getName());
+                        state.setVersion(feature.getVersion());
+                        state.setInstalled(Boolean.FALSE);
+                        clusterFeatures.put(feature.getName() + "/" + 
feature.getVersion(), state);
                     }
 
                     // un-register the repository if it's not local registered

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/2c4b692a/features/src/main/java/org/apache/karaf/cellar/features/shell/UrlListCommand.java
----------------------------------------------------------------------
diff --git 
a/features/src/main/java/org/apache/karaf/cellar/features/shell/UrlListCommand.java
 
b/features/src/main/java/org/apache/karaf/cellar/features/shell/UrlListCommand.java
index 7bc2fd8..fd8860e 100644
--- 
a/features/src/main/java/org/apache/karaf/cellar/features/shell/UrlListCommand.java
+++ 
b/features/src/main/java/org/apache/karaf/cellar/features/shell/UrlListCommand.java
@@ -37,7 +37,7 @@ public class UrlListCommand extends FeatureCommandSupport {
         }
 
         // get the repositories in the cluster group
-        List<String> clusterRepositories = 
clusterManager.getList(Constants.REPOSITORIES_MAP + Configurations.SEPARATOR + 
groupName);
+        List<String> clusterRepositories = 
clusterManager.getList(Constants.REPOSITORIES_LIST + Configurations.SEPARATOR + 
groupName);
 
         for (String clusterRepository : clusterRepositories) {
             System.out.println(clusterRepository);

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/2c4b692a/features/src/main/java/org/apache/karaf/cellar/features/shell/UrlRemoveCommand.java
----------------------------------------------------------------------
diff --git 
a/features/src/main/java/org/apache/karaf/cellar/features/shell/UrlRemoveCommand.java
 
b/features/src/main/java/org/apache/karaf/cellar/features/shell/UrlRemoveCommand.java
index f52df52..6bbd365 100644
--- 
a/features/src/main/java/org/apache/karaf/cellar/features/shell/UrlRemoveCommand.java
+++ 
b/features/src/main/java/org/apache/karaf/cellar/features/shell/UrlRemoveCommand.java
@@ -22,7 +22,7 @@ import org.apache.karaf.cellar.core.control.SwitchStatus;
 import org.apache.karaf.cellar.core.event.EventProducer;
 import org.apache.karaf.cellar.features.ClusterRepositoryEvent;
 import org.apache.karaf.cellar.features.Constants;
-import org.apache.karaf.cellar.features.FeatureInfo;
+import org.apache.karaf.cellar.features.FeatureState;
 import org.apache.karaf.features.Feature;
 import org.apache.karaf.features.Repository;
 import org.apache.karaf.features.RepositoryEvent;
@@ -61,9 +61,9 @@ public class UrlRemoveCommand extends FeatureCommandSupport {
         }
 
         // get the repositories in the cluster group
-        List<String> clusterRepositories = 
clusterManager.getList(Constants.REPOSITORIES_MAP + Configurations.SEPARATOR + 
groupName);
+        List<String> clusterRepositories = 
clusterManager.getList(Constants.REPOSITORIES_LIST + Configurations.SEPARATOR + 
groupName);
         // get the features in the cluster group
-        Map<FeatureInfo, Boolean> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
groupName);
+        Map<String, FeatureState> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
groupName);
 
         for (String url : urls) {
             // looking for the URL in the list
@@ -109,8 +109,7 @@ public class UrlRemoveCommand extends FeatureCommandSupport 
{
 
                 // update the features in the cluster group
                 for (Feature feature : repository.getFeatures()) {
-                    FeatureInfo info = new FeatureInfo(feature.getName(), 
feature.getVersion());
-                    clusterFeatures.remove(info);
+                    clusterFeatures.remove(feature.getName() + "/" + 
feature.getVersion());
                 }
 
                 // un-register the repository if it's not local registered

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/2c4b692a/management/src/main/java/org/apache/karaf/cellar/management/internal/CellarFeaturesMBeanImpl.java
----------------------------------------------------------------------
diff --git 
a/management/src/main/java/org/apache/karaf/cellar/management/internal/CellarFeaturesMBeanImpl.java
 
b/management/src/main/java/org/apache/karaf/cellar/management/internal/CellarFeaturesMBeanImpl.java
index efc250c..f578940 100644
--- 
a/management/src/main/java/org/apache/karaf/cellar/management/internal/CellarFeaturesMBeanImpl.java
+++ 
b/management/src/main/java/org/apache/karaf/cellar/management/internal/CellarFeaturesMBeanImpl.java
@@ -21,7 +21,7 @@ import org.apache.karaf.cellar.core.event.EventType;
 import org.apache.karaf.cellar.features.ClusterFeaturesEvent;
 import org.apache.karaf.cellar.features.ClusterRepositoryEvent;
 import org.apache.karaf.cellar.features.Constants;
-import org.apache.karaf.cellar.features.FeatureInfo;
+import org.apache.karaf.cellar.features.FeatureState;
 import org.apache.karaf.cellar.management.CellarFeaturesMBean;
 import org.apache.karaf.features.*;
 import org.osgi.framework.BundleEvent;
@@ -117,43 +117,35 @@ public class CellarFeaturesMBeanImpl extends 
StandardMBean implements CellarFeat
             
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
 
             // get the features in the cluster group
-            Map<FeatureInfo, Boolean> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
groupName);
+            Map<String, FeatureState> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
groupName);
 
-            // check if the feature exist
-            FeatureInfo feature = null;
-            for (FeatureInfo info : clusterFeatures.keySet()) {
-                if (version == null) {
-                    if (info.getName().equals(name)) {
-                        feature = info;
-                        break;
-                    }
-                } else {
-                    if (info.getName().equals(name) && 
info.getVersion().equals(version)) {
-                        feature = info;
-                        break;
+            // try to resolve version if not provided
+            if (version == null) {
+                for (FeatureState state : clusterFeatures.values()) {
+                    if (state.getName().equals(name)) {
+                        version = state.getVersion();
                     }
                 }
             }
 
-            if (feature == null) {
-                if (version == null)
-                    throw new IllegalArgumentException("Feature " + name + " 
doesn't exist in cluster group " + groupName);
-                else
-                    throw new IllegalArgumentException("Feature " + name + "/" 
+ version + " doesn't exist in cluster group " + groupName);
+            // check if the feature exists
+            if (!clusterFeatures.containsKey(name + "/" + version)) {
+                throw new IllegalArgumentException("Feature " + name + "/" + 
version + " doesn't exist in cluster group " + groupName);
             }
 
             // update the features in the cluster group
-            clusterFeatures.put(feature, true);
+            FeatureState featureState = clusterFeatures.get(name + "/" + 
version);
+            featureState.setInstalled(Boolean.TRUE);
+            clusterFeatures.put(name + "/" + version, featureState);
             try {
-                // update the bundles in the cluster group
                 // TODO does it make really sense
-                List<BundleInfo> bundles = 
featuresService.getFeature(feature.getName(), version).getBundles();
+                List<BundleInfo> bundles = 
featuresService.getFeature(featureState.getName(), 
featureState.getVersion()).getBundles();
                 Map<String, BundleState> clusterBundles = 
clusterManager.getMap(org.apache.karaf.cellar.bundle.Constants.BUNDLE_MAP + 
Configurations.SEPARATOR + groupName);
                 for (BundleInfo bundle : bundles) {
-                    BundleState state = new BundleState();
-                    state.setLocation(bundle.getLocation());
-                    state.setStatus(BundleEvent.STARTED);
-                    clusterBundles.put(bundle.toString(), state);
+                    BundleState bundleState = new BundleState();
+                    bundleState.setLocation(bundle.getLocation());
+                    bundleState.setStatus(BundleEvent.STARTED);
+                    clusterBundles.put(bundle.toString(), bundleState);
                 }
             } catch (Exception e) {
                 // ignore
@@ -210,33 +202,26 @@ public class CellarFeaturesMBeanImpl extends 
StandardMBean implements CellarFeat
             
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
 
             // get the features in the cluster group
-            Map<FeatureInfo, Boolean> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
groupName);
+            Map<String, FeatureState> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
groupName);
 
-            // check if the feature exist
-            FeatureInfo feature = null;
-            for (FeatureInfo info : clusterFeatures.keySet()) {
-                if (version == null) {
-                    if (info.getName().equals(name)) {
-                        feature = info;
-                        break;
-                    }
-                } else {
-                    if (info.getName().equals(name) && 
info.getVersion().equals(version)) {
-                        feature = info;
-                        break;
+            // resolved the version if not provided
+            if (version == null) {
+                for (FeatureState featureState : clusterFeatures.values()) {
+                    if (featureState.getName().equals(name)) {
+                        version = featureState.getVersion();
                     }
                 }
             }
 
-            if (feature == null) {
-                if (version == null)
-                    throw new IllegalArgumentException("Feature " + name + " 
doesn't exist in cluster group " + groupName);
-                else
-                    throw new IllegalArgumentException("Feature " + name + "/" 
+ version + " doesn't exist in cluster group " + groupName);
+            // check if the feature exist
+            if (!clusterFeatures.containsKey(name + "/" + version)) {
+                throw new IllegalArgumentException("Feature " + name + "/" + 
version + " doesn't exist in cluster group " + groupName);
             }
 
             // update the cluster group
-            clusterFeatures.put(feature, false);
+            FeatureState featureState = clusterFeatures.get(name + "/" + 
version);
+            featureState.setInstalled(Boolean.FALSE);
+            clusterFeatures.put(name + "/" + version, featureState);
         } finally {
             Thread.currentThread().setContextClassLoader(originalClassLoader);
         }
@@ -265,13 +250,12 @@ public class CellarFeaturesMBeanImpl extends 
StandardMBean implements CellarFeat
         ClassLoader originalClassLoader = 
Thread.currentThread().getContextClassLoader();
         
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
         try {
-            Map<FeatureInfo, Boolean> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
group);
+            Map<String, FeatureState> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
group);
             if (clusterFeatures != null && !clusterFeatures.isEmpty()) {
-                for (FeatureInfo feature : clusterFeatures.keySet()) {
-                    boolean installed = clusterFeatures.get(feature);
+                for (FeatureState featureState : clusterFeatures.values()) {
                     CompositeData data = new 
CompositeDataSupport(compositeType,
                             new String[]{"name", "version", "installed"},
-                            new Object[]{feature.getName(), 
feature.getVersion(), installed});
+                            new Object[]{featureState.getName(), 
featureState.getVersion(), featureState.getInstalled()});
                     table.put(data);
                 }
             }
@@ -290,7 +274,7 @@ public class CellarFeaturesMBeanImpl extends StandardMBean 
implements CellarFeat
         }
 
         // get the distributed URLs list
-        List<String> clusterRepositories = 
clusterManager.getList(Constants.REPOSITORIES_MAP + Configurations.SEPARATOR + 
groupName);
+        List<String> clusterRepositories = 
clusterManager.getList(Constants.REPOSITORIES_LIST + Configurations.SEPARATOR + 
groupName);
 
         List<String> result = new ArrayList<String>();
         for (String url : clusterRepositories) {
@@ -322,9 +306,9 @@ public class CellarFeaturesMBeanImpl extends StandardMBean 
implements CellarFeat
         try {
             
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
             // get the features repositories in the cluster group
-            List<String> clusterRepositories = 
clusterManager.getList(Constants.REPOSITORIES_MAP + Configurations.SEPARATOR + 
groupName);
+            List<String> clusterRepositories = 
clusterManager.getList(Constants.REPOSITORIES_LIST + Configurations.SEPARATOR + 
groupName);
             // get the features in the cluster group
-            Map<FeatureInfo, Boolean> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
groupName);
+            Map<String, FeatureState> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
groupName);
 
             // check if the URL is already registered
             boolean found = false;
@@ -368,8 +352,11 @@ public class CellarFeaturesMBeanImpl extends StandardMBean 
implements CellarFeat
 
                 // update the features in the cluster group
                 for (Feature feature : repository.getFeatures()) {
-                    FeatureInfo info = new FeatureInfo(feature.getName(), 
feature.getVersion());
-                    clusterFeatures.put(info, false);
+                    FeatureState state = new FeatureState();
+                    state.setName(feature.getName());
+                    state.setVersion(feature.getVersion());
+                    state.setInstalled(Boolean.FALSE);
+                    clusterFeatures.put(feature.getName() + "/" + 
feature.getVersion(), state);
                 }
 
                 // un-register the repository if it's not local registered
@@ -408,9 +395,9 @@ public class CellarFeaturesMBeanImpl extends StandardMBean 
implements CellarFeat
         }
 
         // get the features repositories in the cluster group
-        List<String> clusterRepositories = 
clusterManager.getList(Constants.REPOSITORIES_MAP + Configurations.SEPARATOR + 
groupName);
+        List<String> clusterRepositories = 
clusterManager.getList(Constants.REPOSITORIES_LIST + Configurations.SEPARATOR + 
groupName);
         // get the features in the cluster group
-        Map<FeatureInfo, Boolean> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
groupName);
+        Map<String, FeatureState> clusterFeatures = 
clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + 
groupName);
 
         // looking for the URL in the list
         boolean found = false;
@@ -454,8 +441,7 @@ public class CellarFeaturesMBeanImpl extends StandardMBean 
implements CellarFeat
 
             // update the features in the cluster group
             for (Feature feature : repository.getFeatures()) {
-                FeatureInfo info = new FeatureInfo(feature.getName(), 
feature.getVersion());
-                clusterFeatures.remove(info);
+                clusterFeatures.remove(feature.getName() + "/" + 
feature.getVersion());
             }
 
             // un-register the repository if it's not local registered

Reply via email to