You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by jb...@apache.org on 2015/01/13 10:33:16 UTC

karaf-cellar git commit: [KARAF-2242] Improve cluster list commands/MBean to display location (cluster or local node) and block policy (in/out)

Repository: karaf-cellar
Updated Branches:
  refs/heads/master ab7b4eebb -> da4a03ae4


[KARAF-2242] Improve cluster list commands/MBean to display location (cluster or local node) and block policy (in/out)


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

Branch: refs/heads/master
Commit: da4a03ae4278875a7dddc3dde48470e26308db97
Parents: ab7b4ee
Author: Jean-Baptiste Onofré <jb...@apache.org>
Authored: Tue Jan 13 10:32:29 2015 +0100
Committer: Jean-Baptiste Onofré <jb...@apache.org>
Committed: Tue Jan 13 10:32:29 2015 +0100

----------------------------------------------------------------------
 .../internal/CellarBundleMBeanImpl.java         | 174 ++++++++++++++++---
 .../bundle/shell/BundleCommandSupport.java      |  90 +++++++++-
 .../cellar/bundle/shell/ListBundleCommand.java  |  77 ++++++--
 .../cellar/bundle/shell/StartBundleCommand.java |   2 +-
 .../cellar/bundle/shell/StopBundleCommand.java  |   2 +-
 .../bundle/shell/UninstallBundleCommand.java    |   2 +-
 .../resources/OSGI-INF/blueprint/management.xml |   1 +
 .../OSGI-INF/blueprint/shell-bundle.xml         |   1 +
 .../karaf/cellar/config/shell/ListCommand.java  | 122 ++++++++++++-
 .../OSGI-INF/blueprint/shell-config.xml         |   1 +
 .../internal/CellarFeaturesMBeanImpl.java       | 111 +++++++++++-
 .../features/shell/ListFeaturesCommand.java     | 134 +++++++++++++-
 .../cellar/features/shell/RepoListCommand.java  | 106 ++++++++++-
 .../OSGI-INF/blueprint/shell-features.xml       |   3 +
 14 files changed, 755 insertions(+), 71 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/da4a03ae/bundle/src/main/java/org/apache/karaf/cellar/bundle/management/internal/CellarBundleMBeanImpl.java
----------------------------------------------------------------------
diff --git a/bundle/src/main/java/org/apache/karaf/cellar/bundle/management/internal/CellarBundleMBeanImpl.java b/bundle/src/main/java/org/apache/karaf/cellar/bundle/management/internal/CellarBundleMBeanImpl.java
index d01b045..aa8587f 100644
--- a/bundle/src/main/java/org/apache/karaf/cellar/bundle/management/internal/CellarBundleMBeanImpl.java
+++ b/bundle/src/main/java/org/apache/karaf/cellar/bundle/management/internal/CellarBundleMBeanImpl.java
@@ -17,10 +17,13 @@ import org.apache.karaf.cellar.bundle.BundleState;
 import org.apache.karaf.cellar.bundle.ClusterBundleEvent;
 import org.apache.karaf.cellar.bundle.Constants;
 import org.apache.karaf.cellar.core.*;
+import org.apache.karaf.cellar.core.control.ManageGroupAction;
 import org.apache.karaf.cellar.core.control.SwitchStatus;
 import org.apache.karaf.cellar.core.event.EventProducer;
 import org.apache.karaf.cellar.core.event.EventType;
 import org.apache.karaf.cellar.bundle.management.CellarBundleMBean;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleEvent;
 import org.osgi.service.cm.ConfigurationAdmin;
 
@@ -29,6 +32,7 @@ import javax.management.StandardMBean;
 import javax.management.openmbean.*;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.jar.JarInputStream;
@@ -45,6 +49,7 @@ public class CellarBundleMBeanImpl extends StandardMBean implements CellarBundle
     private GroupManager groupManager;
     private ConfigurationAdmin configurationAdmin;
     private EventProducer eventProducer;
+    private BundleContext bundleContext;
 
     public CellarBundleMBeanImpl() throws NotCompliantMBeanException {
         super(CellarBundleMBean.class);
@@ -82,6 +87,14 @@ public class CellarBundleMBeanImpl extends StandardMBean implements CellarBundle
         this.eventProducer = eventProducer;
     }
 
+    public BundleContext getBundleContext() {
+        return bundleContext;
+    }
+
+    public void setBundleContext(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+    }
+
     @Override
     public void install(String groupName, String location) throws Exception {
         this.install(groupName, location, false);
@@ -164,6 +177,11 @@ public class CellarBundleMBeanImpl extends StandardMBean implements CellarBundle
             throw new IllegalStateException("Cluster event producer is OFF for this node");
         }
 
+        CellarSupport support = new CellarSupport();
+        support.setClusterManager(this.clusterManager);
+        support.setGroupManager(this.groupManager);
+        support.setConfigurationAdmin(this.configurationAdmin);
+
         // update the cluster group
         ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
         Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
@@ -171,7 +189,7 @@ public class CellarBundleMBeanImpl extends StandardMBean implements CellarBundle
         try {
             Map<String, BundleState> clusterBundles = clusterManager.getMap(Constants.BUNDLE_MAP + Configurations.SEPARATOR + groupName);
 
-            List<String> bundles = selector(id, clusterBundles);
+            List<String> bundles = selector(id, gatherBundles(groupName));
 
             for (String bundle : bundles) {
                 BundleState state = clusterBundles.get(bundle);
@@ -181,10 +199,6 @@ public class CellarBundleMBeanImpl extends StandardMBean implements CellarBundle
                 String location = state.getLocation();
 
                 // check if the bundle location is allowed outbound
-                CellarSupport support = new CellarSupport();
-                support.setClusterManager(this.clusterManager);
-                support.setGroupManager(this.groupManager);
-                support.setConfigurationAdmin(this.configurationAdmin);
                 if (!support.isAllowed(group, Constants.CATEGORY, location, EventType.OUTBOUND)) {
                     continue;
                 }
@@ -218,6 +232,10 @@ public class CellarBundleMBeanImpl extends StandardMBean implements CellarBundle
             throw new IllegalStateException("Cluster event producer is OFF for this node");
         }
 
+        CellarSupport support = new CellarSupport();
+        support.setClusterManager(this.clusterManager);
+        support.setGroupManager(this.groupManager);
+
         // update the cluster group
         ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
         Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
@@ -225,7 +243,7 @@ public class CellarBundleMBeanImpl extends StandardMBean implements CellarBundle
 
             Map<String, BundleState> clusterBundles = clusterManager.getMap(Constants.BUNDLE_MAP + Configurations.SEPARATOR + groupName);
 
-            List<String> bundles = selector(id, clusterBundles);
+            List<String> bundles = selector(id, gatherBundles(groupName));
 
             for (String bundle : bundles) {
                 BundleState state = clusterBundles.get(bundle);
@@ -235,9 +253,6 @@ public class CellarBundleMBeanImpl extends StandardMBean implements CellarBundle
                 String location = state.getLocation();
 
                 // check if the bundle location is allowed
-                CellarSupport support = new CellarSupport();
-                support.setClusterManager(this.clusterManager);
-                support.setGroupManager(this.groupManager);
                 support.setConfigurationAdmin(this.configurationAdmin);
                 if (!support.isAllowed(group, Constants.CATEGORY, location, EventType.OUTBOUND)) {
                     continue;
@@ -271,13 +286,18 @@ public class CellarBundleMBeanImpl extends StandardMBean implements CellarBundle
             throw new IllegalStateException("Cluster event producer is OFF for this node");
         }
 
+        CellarSupport support = new CellarSupport();
+        support.setClusterManager(this.clusterManager);
+        support.setGroupManager(this.groupManager);
+        support.setConfigurationAdmin(this.configurationAdmin);
+
         // update the cluster group
         ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
         Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
         try {
             Map<String, BundleState> clusterBundles = clusterManager.getMap(Constants.BUNDLE_MAP + Configurations.SEPARATOR + groupName);
 
-            List<String> bundles = selector(id, clusterBundles);
+            List<String> bundles = selector(id, gatherBundles(groupName));
 
             for (String bundle : bundles) {
                 BundleState state = clusterBundles.get(bundle);
@@ -287,10 +307,6 @@ public class CellarBundleMBeanImpl extends StandardMBean implements CellarBundle
                 String location = state.getLocation();
 
                 // check if the bundle location is allowed outbound
-                CellarSupport support = new CellarSupport();
-                support.setClusterManager(this.clusterManager);
-                support.setGroupManager(this.groupManager);
-                support.setConfigurationAdmin(this.configurationAdmin);
                 if (!support.isAllowed(group, Constants.CATEGORY, location, EventType.OUTBOUND)) {
                     continue;
                 }
@@ -313,19 +329,29 @@ public class CellarBundleMBeanImpl extends StandardMBean implements CellarBundle
     @Override
     public TabularData getBundles(String groupName) throws Exception {
         CompositeType compositeType = new CompositeType("Bundle", "Karaf Cellar bundle",
-                new String[]{"id", "name", "version", "status", "location"},
-                new String[]{"ID of the bundle", "Name of the bundle", "Version of the bundle", "Current status of the bundle", "Location of the bundle"},
-                new OpenType[]{SimpleType.INTEGER, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING});
+                new String[]{"id", "name", "version", "status", "location", "located", "blocked"},
+                new String[]{"ID of the bundle", "Name of the bundle", "Version of the bundle", "Current status of the bundle", "Location of the bundle", "Where the bundle is located (cluster or local node)", "The bundle blocked policy"},
+                new OpenType[]{SimpleType.INTEGER, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING});
         TabularType tableType = new TabularType("Bundles", "Table of all Karaf Cellar bundles", compositeType,
                 new String[]{"name", "version"});
         TabularData table = new TabularDataSupport(tableType);
 
+        Group group = groupManager.findGroupByName(groupName);
+        if (group == null) {
+            throw new IllegalArgumentException("Cluster group " + groupName + " doesn't exist");
+        }
+
+        CellarSupport support = new CellarSupport();
+        support.setClusterManager(clusterManager);
+        support.setConfigurationAdmin(configurationAdmin);
+        support.setGroupManager(groupManager);
+
         ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
         Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
         try {
-            Map<String, BundleState> clusterBundles = clusterManager.getMap(Constants.BUNDLE_MAP + Configurations.SEPARATOR + groupName);
+            Map<String, ExtendedBundleState> bundles = gatherBundles(groupName);
             int id = 0;
-            for (String bundle : clusterBundles.keySet()) {
+            for (String bundle : bundles.keySet()) {
                 String[] tokens = bundle.split("/");
                 String name = null;
                 String version = null;
@@ -335,7 +361,7 @@ public class CellarBundleMBeanImpl extends StandardMBean implements CellarBundle
                 } else {
                     name = bundle;
                 }
-                BundleState state = clusterBundles.get(bundle);
+                ExtendedBundleState state = bundles.get(bundle);
                 String status;
                 switch (state.getStatus()) {
                     case BundleEvent.INSTALLED:
@@ -363,9 +389,30 @@ public class CellarBundleMBeanImpl extends StandardMBean implements CellarBundle
                         status = "";
                         break;
                 }
+
+                String located = "";
+                boolean local = state.isLocal();
+                boolean cluster = state.isCluster();
+                if (local && cluster)
+                    located = "cluster/local";
+                if (local && !cluster)
+                    located = "local";
+                if (cluster && !local)
+                    located = "cluster";
+
+                String blocked = "";
+                boolean inbound = support.isAllowed(group, Constants.CATEGORY, state.getLocation(), EventType.INBOUND);
+                boolean outbound = support.isAllowed(group, Constants.CATEGORY, state.getLocation(), EventType.OUTBOUND);
+                if (!inbound && !outbound)
+                    blocked = "in/out";
+                if (!inbound && outbound)
+                    blocked = "in";
+                if (outbound && !inbound)
+                    blocked = "out";
+
                 CompositeData data = new CompositeDataSupport(compositeType,
-                        new String[]{"id", "name", "version", "status", "location"},
-                        new Object[]{id, name, version, status, state.getLocation()});
+                        new String[]{"id", "name", "version", "status", "location", "located", "blocked"},
+                        new Object[]{id, name, version, status, state.getLocation(), located, blocked});
                 table.put(data);
                 id++;
             }
@@ -380,7 +427,7 @@ public class CellarBundleMBeanImpl extends StandardMBean implements CellarBundle
      *
      * @return the bundle key is the distributed bundle map.
      */
-    protected List<String> selector(String id, Map<String, BundleState> clusterBundles) {
+    protected List<String> selector(String id, Map<String, ExtendedBundleState> clusterBundles) {
         List<String> bundles = new ArrayList<String>();
 
         addMatchingBundles(id, bundles, clusterBundles);
@@ -388,7 +435,7 @@ public class CellarBundleMBeanImpl extends StandardMBean implements CellarBundle
         return bundles;
     }
 
-    protected void addMatchingBundles(String id, List<String> bundles, Map<String, BundleState> clusterBundles) {
+    protected void addMatchingBundles(String id, List<String> bundles, Map<String, ExtendedBundleState> clusterBundles) {
 
         // id is a number
         Pattern pattern = Pattern.compile("^\\d+$");
@@ -489,4 +536,83 @@ public class CellarBundleMBeanImpl extends StandardMBean implements CellarBundle
         }
     }
 
+    private Map<String, ExtendedBundleState> gatherBundles(String groupName) {
+        Map<String, ExtendedBundleState> bundles = new HashMap<String, ExtendedBundleState>();
+
+        // retrieve bundles from the cluster
+        Map<String, BundleState> clusterBundles = clusterManager.getMap(Constants.BUNDLE_MAP + Configurations.SEPARATOR + groupName);
+        for (String key : clusterBundles.keySet()) {
+            BundleState state = clusterBundles.get(key);
+            ExtendedBundleState extendedState = new ExtendedBundleState();
+            extendedState.setName(state.getName());
+            extendedState.setStatus(state.getStatus());
+            extendedState.setLocation(state.getLocation());
+            extendedState.setData(state.getData());
+            extendedState.setCluster(true);
+            extendedState.setLocal(false);
+            bundles.put(key, extendedState);
+        }
+
+        // retrieve local bundles
+        for (Bundle bundle : bundleContext.getBundles()) {
+            String key = bundle.getSymbolicName() + "/" + bundle.getVersion().toString();
+            if (bundles.containsKey(key)) {
+                ExtendedBundleState extendedState = bundles.get(key);
+                extendedState.setLocal(true);
+            } else {
+                ExtendedBundleState extendedState = new ExtendedBundleState();
+
+                // get the bundle name or location.
+                String name = (String) bundle.getHeaders().get(org.osgi.framework.Constants.BUNDLE_NAME);
+                // if there is no name, then default to symbolic name.
+                name = (name == null) ? bundle.getSymbolicName() : name;
+                // if there is no symbolic name, resort to location.
+                name = (name == null) ? bundle.getLocation() : name;
+                extendedState.setName(name);
+                extendedState.setLocation(bundle.getLocation());
+                int status = bundle.getState();
+                if (status == Bundle.ACTIVE)
+                    status = BundleEvent.STARTED;
+                if (status == Bundle.INSTALLED)
+                    status = BundleEvent.INSTALLED;
+                if (status == Bundle.RESOLVED)
+                    status = BundleEvent.RESOLVED;
+                if (status == Bundle.STARTING)
+                    status = BundleEvent.STARTING;
+                if (status == Bundle.UNINSTALLED)
+                    status = BundleEvent.UNINSTALLED;
+                if (status == Bundle.STOPPING)
+                    status = BundleEvent.STARTED;
+                extendedState.setStatus(status);
+                extendedState.setCluster(false);
+                extendedState.setLocal(true);
+                bundles.put(key, extendedState);
+            }
+        }
+
+        return bundles;
+    }
+
+    class ExtendedBundleState extends BundleState {
+
+        private boolean cluster;
+        private boolean local;
+
+        public boolean isCluster() {
+            return cluster;
+        }
+
+        public void setCluster(boolean cluster) {
+            this.cluster = cluster;
+        }
+
+        public boolean isLocal() {
+            return local;
+        }
+
+        public void setLocal(boolean local) {
+            this.local = local;
+        }
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/da4a03ae/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/BundleCommandSupport.java
----------------------------------------------------------------------
diff --git a/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/BundleCommandSupport.java b/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/BundleCommandSupport.java
index 6295743..245208c 100644
--- a/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/BundleCommandSupport.java
+++ b/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/BundleCommandSupport.java
@@ -14,10 +14,15 @@
 package org.apache.karaf.cellar.bundle.shell;
 
 import org.apache.karaf.cellar.bundle.BundleState;
+import org.apache.karaf.cellar.bundle.Constants;
+import org.apache.karaf.cellar.core.Configurations;
 import org.apache.karaf.cellar.core.shell.CellarCommandSupport;
 import org.apache.karaf.shell.commands.Argument;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleEvent;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.regex.Matcher;
@@ -28,7 +33,7 @@ public abstract class BundleCommandSupport extends CellarCommandSupport {
     @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
     String groupName;
 
-    @Argument(index = 1, name = "ids", description = "The list of bundle (identified by IDs or name or name/version) separated by whitespaces", required = true, multiValued = true)
+    @Argument(index = 1, name = "ids", description = "The list of bundle (identified by IDs or name or name/version) separated by whitespaces", required = false, multiValued = true)
     List<String> ids;
 
     protected abstract Object doExecute() throws Exception;
@@ -38,7 +43,7 @@ public abstract class BundleCommandSupport extends CellarCommandSupport {
      *
      * @return the bundle key is the distributed bundle map.
      */
-    protected List<String> selector(Map<String, BundleState> clusterBundles) {
+    protected List<String> selector(Map<String, ExtendedBundleState> clusterBundles) {
         List<String> bundles = new ArrayList<String>();
 
         if (ids != null && !ids.isEmpty()) {
@@ -54,7 +59,7 @@ public abstract class BundleCommandSupport extends CellarCommandSupport {
         return bundles;
     }
 
-    protected void addMatchingBundles(String id, List<String> bundles, Map<String, BundleState> clusterBundles) {
+    protected void addMatchingBundles(String id, List<String> bundles, Map<String, ExtendedBundleState> clusterBundles) {
 
         // id is a number
         Pattern pattern = Pattern.compile("^\\d+$");
@@ -155,4 +160,83 @@ public abstract class BundleCommandSupport extends CellarCommandSupport {
         }
     }
 
+    protected Map<String, ExtendedBundleState> gatherBundles() {
+        Map<String, ExtendedBundleState> bundles = new HashMap<String, ExtendedBundleState>();
+
+        // retrieve bundles from the cluster
+        Map<String, BundleState> clusterBundles = clusterManager.getMap(Constants.BUNDLE_MAP + Configurations.SEPARATOR + groupName);
+        for (String key : clusterBundles.keySet()) {
+            BundleState state = clusterBundles.get(key);
+            ExtendedBundleState extendedState = new ExtendedBundleState();
+            extendedState.setName(state.getName());
+            extendedState.setStatus(state.getStatus());
+            extendedState.setLocation(state.getLocation());
+            // extendedState.setData(state.getData());
+            extendedState.setCluster(true);
+            extendedState.setLocal(false);
+            bundles.put(key, extendedState);
+        }
+
+        // retrieve local bundles
+        for (Bundle bundle : bundleContext.getBundles()) {
+            String key = bundle.getSymbolicName() + "/" + bundle.getVersion().toString();
+            if (bundles.containsKey(key)) {
+                ExtendedBundleState extendedState = bundles.get(key);
+                extendedState.setLocal(true);
+            } else {
+                ExtendedBundleState extendedState = new ExtendedBundleState();
+
+                // get the bundle name or location.
+                String name = (String) bundle.getHeaders().get(org.osgi.framework.Constants.BUNDLE_NAME);
+                // if there is no name, then default to symbolic name.
+                name = (name == null) ? bundle.getSymbolicName() : name;
+                // if there is no symbolic name, resort to location.
+                name = (name == null) ? bundle.getLocation() : name;
+                extendedState.setName(name);
+                extendedState.setLocation(bundle.getLocation());
+                int status = bundle.getState();
+                if (status == Bundle.ACTIVE)
+                    status = BundleEvent.STARTED;
+                if (status == Bundle.INSTALLED)
+                    status = BundleEvent.INSTALLED;
+                if (status == Bundle.RESOLVED)
+                    status = BundleEvent.RESOLVED;
+                if (status == Bundle.STARTING)
+                    status = BundleEvent.STARTING;
+                if (status == Bundle.UNINSTALLED)
+                    status = BundleEvent.UNINSTALLED;
+                if (status == Bundle.STOPPING)
+                    status = BundleEvent.STARTED;
+                extendedState.setStatus(status);
+                extendedState.setCluster(false);
+                extendedState.setLocal(true);
+                bundles.put(key, extendedState);
+            }
+        }
+
+        return bundles;
+    }
+
+    class ExtendedBundleState extends BundleState {
+
+        private boolean cluster;
+        private boolean local;
+
+        public boolean isCluster() {
+            return cluster;
+        }
+
+        public void setCluster(boolean cluster) {
+            this.cluster = cluster;
+        }
+
+        public boolean isLocal() {
+            return local;
+        }
+
+        public void setLocal(boolean local) {
+            this.local = local;
+        }
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/da4a03ae/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/ListBundleCommand.java
----------------------------------------------------------------------
diff --git a/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/ListBundleCommand.java b/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/ListBundleCommand.java
index e4b5a76..7b4241d 100644
--- a/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/ListBundleCommand.java
+++ b/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/ListBundleCommand.java
@@ -13,12 +13,10 @@
  */
 package org.apache.karaf.cellar.bundle.shell;
 
-import org.apache.karaf.cellar.bundle.BundleState;
 import org.apache.karaf.cellar.bundle.Constants;
-import org.apache.karaf.cellar.core.Configurations;
+import org.apache.karaf.cellar.core.CellarSupport;
 import org.apache.karaf.cellar.core.Group;
-import org.apache.karaf.cellar.core.shell.CellarCommandSupport;
-import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.cellar.core.event.EventType;
 import org.apache.karaf.shell.commands.Command;
 import org.apache.karaf.shell.commands.Option;
 import org.apache.karaf.shell.table.ShellTable;
@@ -27,10 +25,7 @@ import org.osgi.framework.BundleEvent;
 import java.util.Map;
 
 @Command(scope = "cluster", name = "bundle-list", description = "List the bundles in a cluster group")
-public class ListBundleCommand extends CellarCommandSupport {
-
-    @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
-    String groupName;
+public class ListBundleCommand extends  BundleCommandSupport {
 
     @Option(name = "-s", aliases = {}, description = "Shows the symbolic name", required = false, multiValued = false)
     boolean showSymbolicName;
@@ -38,6 +33,15 @@ public class ListBundleCommand extends CellarCommandSupport {
     @Option(name = "-l", aliases = {}, description = "Shows the location", required = false, multiValued = false)
     boolean showLocation;
 
+    @Option(name = "--cluster", description = "Shows only bundles on the cluster", required = false, multiValued = false)
+    boolean onlyCluster;
+
+    @Option(name = "--local", description = "Shows only bundles on the local node", required = false, multiValued = false)
+    boolean onlyLocal;
+
+    @Option(name = "--blocked", description = "Shows only blocked bundles", required = false, multiValued = false)
+    boolean onlyBlocked;
+
     @Override
     protected Object doExecute() throws Exception {
         // check if the group exists
@@ -47,17 +51,24 @@ public class ListBundleCommand extends CellarCommandSupport {
             return null;
         }
 
+        CellarSupport support = new CellarSupport();
+        support.setClusterManager(clusterManager);
+        support.setGroupManager(groupManager);
+        support.setConfigurationAdmin(configurationAdmin);
+
         ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
         Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
 
         try {
-            Map<String, BundleState> clusterBundles = clusterManager.getMap(Constants.BUNDLE_MAP + Configurations.SEPARATOR + groupName);
-            if (clusterBundles != null && !clusterBundles.isEmpty()) {
+            Map<String, ExtendedBundleState> bundles = gatherBundles();
+            if (bundles != null && !bundles.isEmpty()) {
                 System.out.println(String.format("Bundles in cluster group " + groupName));
 
                 ShellTable table = new ShellTable();
                 table.column("ID").alignRight();
                 table.column("State");
+                table.column("Located");
+                table.column("Blocked");
                 table.column("Version");
                 if (showLocation) {
                     table.column("Location");
@@ -68,7 +79,7 @@ public class ListBundleCommand extends CellarCommandSupport {
                 }
 
                 int id = 0;
-                for (String bundle : clusterBundles.keySet()) {
+                for (String bundle : bundles.keySet()) {
                     String[] tokens = bundle.split("/");
                     String symbolicName = null;
                     String version = null;
@@ -79,8 +90,7 @@ public class ListBundleCommand extends CellarCommandSupport {
                         symbolicName = bundle;
                         version = "";
                     }
-                    BundleState state = clusterBundles.get(bundle);
-
+                    ExtendedBundleState state = bundles.get(bundle);
                     String status;
                     switch (state.getStatus()) {
                         case BundleEvent.INSTALLED:
@@ -108,13 +118,48 @@ public class ListBundleCommand extends CellarCommandSupport {
                             status = "";
                             break;
                     }
+
+                    String located = "";
+                    boolean cluster = state.isCluster();
+                    boolean local = state.isLocal();
+                    if (cluster && local)
+                        located = "cluster/local";
+                    if (cluster && !local) {
+                        located = "cluster";
+                        if (onlyLocal) {
+                            id++;
+                            continue;
+                        }
+                    }
+                    if (local && !cluster) {
+                        located = "local";
+                        if (onlyCluster) {
+                            id++;
+                            continue;
+                        }
+                    }
+
+                    String blocked = "";
+                    boolean inbound = support.isAllowed(group, Constants.CATEGORY, state.getLocation(), EventType.INBOUND);
+                    boolean outbound = support.isAllowed(group, Constants.CATEGORY, state.getLocation(), EventType.OUTBOUND);
+                    if (inbound && outbound && onlyBlocked) {
+                        id++;
+                        continue;
+                    }
+                    if (!inbound && !outbound)
+                        blocked = "in/out";
+                    if (!inbound && outbound)
+                        blocked = "in";
+                    if (outbound && !inbound)
+                        blocked = "out";
+
                     if (showLocation) {
-                        table.addRow().addContent(id, status, version, state.getLocation());
+                        table.addRow().addContent(id, status, located, blocked, version, state.getLocation());
                     } else {
                         if (showSymbolicName) {
-                            table.addRow().addContent(id, status, version, symbolicName);
+                            table.addRow().addContent(id, status, located, blocked, version, symbolicName);
                         } else {
-                            table.addRow().addContent(id, status, version, state.getName());
+                            table.addRow().addContent(id, status, located, blocked, version, state.getName());
                         }
                     }
                     id++;

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/da4a03ae/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/StartBundleCommand.java
----------------------------------------------------------------------
diff --git a/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/StartBundleCommand.java b/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/StartBundleCommand.java
index 23cd418..552d753 100644
--- a/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/StartBundleCommand.java
+++ b/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/StartBundleCommand.java
@@ -55,7 +55,7 @@ public class StartBundleCommand extends BundleCommandSupport {
         try {
             Map<String, BundleState> clusterBundles = clusterManager.getMap(Constants.BUNDLE_MAP + Configurations.SEPARATOR + groupName);
 
-            List<String> bundles = selector(clusterBundles);
+            List<String> bundles = selector(gatherBundles());
 
             for (String bundle : bundles) {
                 BundleState state = clusterBundles.get(bundle);

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/da4a03ae/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/StopBundleCommand.java
----------------------------------------------------------------------
diff --git a/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/StopBundleCommand.java b/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/StopBundleCommand.java
index ed94092..d91aa9a 100644
--- a/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/StopBundleCommand.java
+++ b/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/StopBundleCommand.java
@@ -54,7 +54,7 @@ public class StopBundleCommand extends BundleCommandSupport {
         try {
             Map<String, BundleState> clusterBundles = clusterManager.getMap(Constants.BUNDLE_MAP + Configurations.SEPARATOR + groupName);
 
-            List<String> bundles = selector(clusterBundles);
+            List<String> bundles = selector(gatherBundles());
 
             for (String bundle : bundles) {
                 BundleState state = clusterBundles.get(bundle);

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/da4a03ae/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/UninstallBundleCommand.java
----------------------------------------------------------------------
diff --git a/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/UninstallBundleCommand.java b/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/UninstallBundleCommand.java
index 47cfe8c..760a58a 100644
--- a/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/UninstallBundleCommand.java
+++ b/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/UninstallBundleCommand.java
@@ -55,7 +55,7 @@ public class UninstallBundleCommand extends BundleCommandSupport {
         try {
             Map<String, BundleState> clusterBundles = clusterManager.getMap(Constants.BUNDLE_MAP + Configurations.SEPARATOR + groupName);
 
-            List<String> bundles = selector(clusterBundles);
+            List<String> bundles = selector(gatherBundles());
 
             for (String bundle : bundles) {
                 BundleState state = clusterBundles.get(bundle);

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/da4a03ae/bundle/src/main/resources/OSGI-INF/blueprint/management.xml
----------------------------------------------------------------------
diff --git a/bundle/src/main/resources/OSGI-INF/blueprint/management.xml b/bundle/src/main/resources/OSGI-INF/blueprint/management.xml
index f6d12ca..02ceeb7 100644
--- a/bundle/src/main/resources/OSGI-INF/blueprint/management.xml
+++ b/bundle/src/main/resources/OSGI-INF/blueprint/management.xml
@@ -25,6 +25,7 @@
         <property name="groupManager" ref="groupManager"/>
         <property name="eventProducer" ref="eventProducer"/>
         <property name="configurationAdmin" ref="configurationAdmin"/>
+        <property name="bundleContext" ref="blueprintBundleContext"/>
     </bean>
     <service ref="cellarBundleMBean" auto-export="interfaces">
         <service-properties>

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/da4a03ae/bundle/src/main/resources/OSGI-INF/blueprint/shell-bundle.xml
----------------------------------------------------------------------
diff --git a/bundle/src/main/resources/OSGI-INF/blueprint/shell-bundle.xml b/bundle/src/main/resources/OSGI-INF/blueprint/shell-bundle.xml
index 662b8d5..2ca4cad 100644
--- a/bundle/src/main/resources/OSGI-INF/blueprint/shell-bundle.xml
+++ b/bundle/src/main/resources/OSGI-INF/blueprint/shell-bundle.xml
@@ -19,6 +19,7 @@
             <action class="org.apache.karaf.cellar.bundle.shell.ListBundleCommand">
                 <property name="clusterManager" ref="clusterManager"/>
                 <property name="groupManager" ref="groupManager"/>
+                <property name="configurationAdmin" ref="configurationAdmin"/>
             </action>
             <completers>
                 <ref component-id="allGroupsCompleter"/>

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/da4a03ae/config/src/main/java/org/apache/karaf/cellar/config/shell/ListCommand.java
----------------------------------------------------------------------
diff --git a/config/src/main/java/org/apache/karaf/cellar/config/shell/ListCommand.java b/config/src/main/java/org/apache/karaf/cellar/config/shell/ListCommand.java
index 8ef55bf..41ba78a 100644
--- a/config/src/main/java/org/apache/karaf/cellar/config/shell/ListCommand.java
+++ b/config/src/main/java/org/apache/karaf/cellar/config/shell/ListCommand.java
@@ -13,14 +13,18 @@
  */
 package org.apache.karaf.cellar.config.shell;
 
+import org.apache.karaf.cellar.config.ConfigurationSupport;
 import org.apache.karaf.cellar.config.Constants;
 import org.apache.karaf.cellar.core.Configurations;
 import org.apache.karaf.cellar.core.Group;
+import org.apache.karaf.cellar.core.event.EventType;
 import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
 import org.apache.karaf.shell.commands.Option;
+import org.osgi.service.cm.Configuration;
 
 import java.util.Enumeration;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
 
@@ -36,6 +40,15 @@ public class ListCommand extends ConfigCommandSupport {
     @Option(name = "-m", aliases = {"--minimal"}, description = "Don't display the properties of each configuration", required = false, multiValued = false)
     boolean minimal;
 
+    @Option(name = "--cluster", description = "Shows only configurations on the cluster", required = false, multiValued = false)
+    boolean onlyCluster;
+
+    @Option(name = "--local", description = "Shows only configurations on the local node", required = false, multiValued = false)
+    boolean onlyLocal;
+
+    @Option(name = "--blocked", description = "Shows only blocked configurations", required = false, multiValued = false)
+    boolean onlyBlocked;
+
     @Override
     protected Object doExecute() throws Exception {
         // check if the group exists
@@ -45,15 +58,52 @@ public class ListCommand extends ConfigCommandSupport {
             return null;
         }
 
-        Map<String, Properties> clusterConfigurations = clusterManager.getMap(Constants.CONFIGURATION_MAP + Configurations.SEPARATOR + groupName);
+        ConfigurationSupport support = new ConfigurationSupport();
+        support.setClusterManager(clusterManager);
+        support.setGroupManager(groupManager);
+        support.setConfigurationAdmin(configurationAdmin);
+
+        Map<String, ConfigurationState> configurations = gatherConfigurations();
 
-        if (clusterConfigurations != null && !clusterConfigurations.isEmpty()) {
-            for (String pid : clusterConfigurations.keySet()) {
+        if (configurations != null && !configurations.isEmpty()) {
+            for (String pid : configurations.keySet()) {
                 if (searchPid == null || (searchPid != null && searchPid.equals(pid))) {
+                    ConfigurationState state = configurations.get(pid);
+
+                    String located = "";
+                    boolean cluster = state.isCluster();
+                    boolean local = state.isLocal();
+                    if (cluster && local)
+                        located = "cluster/local";
+                    if (cluster && ! local) {
+                        located = "cluster";
+                        if (onlyLocal)
+                            continue;
+                    }
+                    if (local && !cluster) {
+                        located = "local";
+                        if (onlyCluster)
+                            continue;
+                    }
+
+                    String blocked = "";
+                    boolean inbound = support.isAllowed(group, Constants.CATEGORY, pid, EventType.INBOUND);
+                    boolean outbound = support.isAllowed(group, Constants.CATEGORY, pid, EventType.OUTBOUND);
+                    if (inbound && outbound && onlyBlocked)
+                        continue;
+                    if (!inbound && !outbound)
+                        blocked = "in/out";
+                    if (!inbound && outbound)
+                        blocked = "in";
+                    if (!outbound && inbound)
+                        blocked = "out";
+
                     System.out.println("----------------------------------------------------------------");
                     System.out.println("Pid:            " + pid);
+                    System.out.println("Located:        " + located);
+                    System.out.println("Blocked:        " + blocked);
                     if (!minimal) {
-                        Properties properties = clusterConfigurations.get(pid);
+                        Properties properties = state.getProperties();
                         if (properties != null) {
                             System.out.println("Properties:");
                             for (Enumeration e = properties.keys(); e.hasMoreElements(); ) {
@@ -69,4 +119,68 @@ public class ListCommand extends ConfigCommandSupport {
         return null;
     }
 
+    private Map<String, ConfigurationState> gatherConfigurations() throws Exception {
+        Map<String, ConfigurationState> configurations = new HashMap<String, ConfigurationState>();
+
+        // retrieve cluster configurations
+        Map<String, Properties> clusterConfigurations = clusterManager.getMap(Constants.CONFIGURATION_MAP + Configurations.SEPARATOR + groupName);
+        for (String key : clusterConfigurations.keySet()) {
+            Properties properties = clusterConfigurations.get(key);
+            ConfigurationState state = new ConfigurationState();
+            state.setProperties(properties);
+            state.setCluster(true);
+            state.setLocal(false);
+            configurations.put(key, state);
+        }
+
+        // retrieve local configurations
+        for (Configuration configuration : configurationAdmin.listConfigurations(null)) {
+            String key = configuration.getPid();
+            if (configurations.containsKey(key)) {
+                ConfigurationState state = configurations.get(key);
+                state.setLocal(true);
+            } else {
+                ConfigurationState state = new ConfigurationState();
+                state.setLocal(true);
+                state.setCluster(false);
+                ConfigurationSupport support = new ConfigurationSupport();
+                state.setProperties(support.dictionaryToProperties(configuration.getProperties()));
+                configurations.put(key, state);
+            }
+        }
+
+        return configurations;
+    }
+
+    class ConfigurationState {
+
+        private Properties properties;
+        private boolean cluster;
+        private boolean local;
+
+        public Properties getProperties() {
+            return properties;
+        }
+
+        public void setProperties(Properties properties) {
+            this.properties = properties;
+        }
+
+        public boolean isCluster() {
+            return cluster;
+        }
+
+        public void setCluster(boolean cluster) {
+            this.cluster = cluster;
+        }
+
+        public boolean isLocal() {
+            return local;
+        }
+
+        public void setLocal(boolean local) {
+            this.local = local;
+        }
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/da4a03ae/config/src/main/resources/OSGI-INF/blueprint/shell-config.xml
----------------------------------------------------------------------
diff --git a/config/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/config/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index 66cc159..fc8a0ad 100644
--- a/config/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/config/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -21,6 +21,7 @@
             <action class="org.apache.karaf.cellar.config.shell.ListCommand">
                 <property name="clusterManager" ref="clusterManager"/>
                 <property name="groupManager" ref="groupManager"/>
+                <property name="configurationAdmin" ref="configurationAdmin"/>
             </action>
             <completers>
                 <ref component-id="allGroupsCompleter"/>

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/da4a03ae/features/src/main/java/org/apache/karaf/cellar/features/management/internal/CellarFeaturesMBeanImpl.java
----------------------------------------------------------------------
diff --git a/features/src/main/java/org/apache/karaf/cellar/features/management/internal/CellarFeaturesMBeanImpl.java b/features/src/main/java/org/apache/karaf/cellar/features/management/internal/CellarFeaturesMBeanImpl.java
index 7aebf5b..cbc054f 100644
--- a/features/src/main/java/org/apache/karaf/cellar/features/management/internal/CellarFeaturesMBeanImpl.java
+++ b/features/src/main/java/org/apache/karaf/cellar/features/management/internal/CellarFeaturesMBeanImpl.java
@@ -32,6 +32,7 @@ import javax.management.StandardMBean;
 import javax.management.openmbean.*;
 import java.net.URI;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -267,10 +268,23 @@ public class CellarFeaturesMBeanImpl extends StandardMBean implements CellarFeat
     }
 
     @Override
-    public TabularData getFeatures(String group) throws Exception {
+    public TabularData getFeatures(String groupName) throws Exception {
+
+        Group group = groupManager.findGroupByName(groupName);
+        if (group == null) {
+            throw new IllegalArgumentException("Cluster group " + groupName + " doesn't exist");
+        }
+
+        CellarSupport support = new CellarSupport();
+        support.setClusterManager(clusterManager);
+        support.setGroupManager(groupManager);
+        support.setConfigurationAdmin(configurationAdmin);
+
         CompositeType featuresType = new CompositeType("Feature", "Karaf Cellar feature",
-                new String[]{"name", "version", "installed"},
-                new String[]{"Name of the feature", "Version of the feature", "Whether the feature is installed or not"},
+                new String[]{"name", "version", "installed", "located", "blocked"},
+                new String[]{"Name of the feature", "Version of the feature", "Whether the feature is installed or not",
+                        "Location of the feature (on the cluster or the local node)",
+                        "Feature block policy"},
                 new OpenType[]{SimpleType.STRING, SimpleType.STRING, SimpleType.BOOLEAN});
 
         TabularType tabularType = new TabularType("Features", "Table of all Karaf Cellar features",
@@ -280,12 +294,33 @@ public class CellarFeaturesMBeanImpl extends StandardMBean implements CellarFeat
         ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
         Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
         try {
-            Map<String, FeatureState> clusterFeatures = clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + group);
-            if (clusterFeatures != null && !clusterFeatures.isEmpty()) {
-                for (FeatureState feature : clusterFeatures.values()) {
+            Map<String, ExtendedFeatureState> features = gatherFeatures(groupName);
+            if (features != null && !features.isEmpty()) {
+                for (ExtendedFeatureState feature : features.values()) {
+
+                    String located = "";
+                    boolean cluster = feature.isCluster();
+                    boolean local = feature.isLocal();
+                    if (cluster && local)
+                        located = "cluster/local";
+                    if (cluster && !local)
+                        located = "cluster";
+                    if (local && !cluster)
+                        located = "local";
+
+                    String blocked = "";
+                    boolean inbound = support.isAllowed(group, Constants.CATEGORY, feature.getName() + "/" + feature.getVersion(), EventType.INBOUND);
+                    boolean outbound = support.isAllowed(group, Constants.CATEGORY, feature.getName() + "/" + feature.getVersion(), EventType.OUTBOUND);
+                    if (!inbound && !outbound)
+                        blocked = "in/out";
+                    if (!inbound && outbound)
+                        blocked = "in";
+                    if (!outbound && inbound)
+                        blocked = "out";
+
                     CompositeData data = new CompositeDataSupport(featuresType,
-                            new String[]{"name", "version", "installed"},
-                            new Object[]{feature.getName(), feature.getVersion(), feature.isInstalled()});
+                            new String[]{"name", "version", "installed", "located", "blocked"},
+                            new Object[]{feature.getName(), feature.getVersion(), feature.isInstalled(), located, blocked});
                     table.put(data);
                 }
             }
@@ -296,6 +331,44 @@ public class CellarFeaturesMBeanImpl extends StandardMBean implements CellarFeat
         return table;
     }
 
+    private Map<String, ExtendedFeatureState> gatherFeatures(String groupName) throws Exception {
+        Map<String, ExtendedFeatureState> features = new HashMap<String, ExtendedFeatureState>();
+
+        // get cluster features
+        Map<String, FeatureState> clusterFeatures = clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + groupName);
+        for (String key : clusterFeatures.keySet()) {
+            FeatureState state = clusterFeatures.get(key);
+            ExtendedFeatureState extendedState = new ExtendedFeatureState();
+            extendedState.setName(state.getName());
+            extendedState.setInstalled(state.isInstalled());
+            extendedState.setVersion(state.getVersion());
+            extendedState.setCluster(true);
+            extendedState.setLocal(true);
+            features.put(key, extendedState);
+        }
+
+        // get local features
+        for (Feature feature : featuresService.listFeatures()) {
+            String key = feature.getName() + "/" + feature.getVersion();
+            if (features.containsKey(key)) {
+                ExtendedFeatureState extendedState = features.get(key);
+                extendedState.setLocal(true);
+            } else {
+                ExtendedFeatureState extendedState = new ExtendedFeatureState();
+                extendedState.setCluster(false);
+                extendedState.setLocal(true);
+                extendedState.setName(feature.getName());
+                extendedState.setVersion(feature.getVersion());
+                if (featuresService.isInstalled(feature))
+                    extendedState.setInstalled(true);
+                else extendedState.setInstalled(false);
+                features.put(key, extendedState);
+            }
+        }
+
+        return features;
+    }
+
     @Override
     public List<String> getRepositories(String groupName) throws Exception {
         // check if the group exists
@@ -486,4 +559,26 @@ public class CellarFeaturesMBeanImpl extends StandardMBean implements CellarFeat
         }
     }
 
+    class ExtendedFeatureState extends FeatureState {
+
+        private boolean cluster;
+        private boolean local;
+
+        public boolean isCluster() {
+            return cluster;
+        }
+
+        public void setCluster(boolean cluster) {
+            this.cluster = cluster;
+        }
+
+        public boolean isLocal() {
+            return local;
+        }
+
+        public void setLocal(boolean local) {
+            this.local = local;
+        }
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/da4a03ae/features/src/main/java/org/apache/karaf/cellar/features/shell/ListFeaturesCommand.java
----------------------------------------------------------------------
diff --git a/features/src/main/java/org/apache/karaf/cellar/features/shell/ListFeaturesCommand.java b/features/src/main/java/org/apache/karaf/cellar/features/shell/ListFeaturesCommand.java
index 20bff51..5816f30 100644
--- a/features/src/main/java/org/apache/karaf/cellar/features/shell/ListFeaturesCommand.java
+++ b/features/src/main/java/org/apache/karaf/cellar/features/shell/ListFeaturesCommand.java
@@ -13,11 +13,15 @@
  */
 package org.apache.karaf.cellar.features.shell;
 
+import org.apache.karaf.cellar.core.CellarSupport;
 import org.apache.karaf.cellar.core.Configurations;
 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.FeatureState;
+import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.FeaturesService;
 import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
 import org.apache.karaf.shell.commands.Option;
@@ -40,6 +44,17 @@ public class ListFeaturesCommand extends CellarCommandSupport {
     @Option(name = "--no-format", description = "Disable table rendered output", required = false, multiValued = false)
     boolean noFormat;
 
+    @Option(name = "--cluster", description = "Shows only features on the cluster", required = false, multiValued = false)
+    boolean onlyCluster;
+
+    @Option(name = "--local", description = "Shows only features on the local node", required = false, multiValued = false)
+    boolean onlyLocal;
+
+    @Option(name = "--blocked", description = "Shows only blocked features", required = false, multiValued = false)
+    boolean onlyBlocked;
+
+    private FeaturesService featuresService;
+
     @Override
     protected Object doExecute() throws Exception {
         Group group = groupManager.findGroupByName(groupName);
@@ -48,35 +63,70 @@ public class ListFeaturesCommand extends CellarCommandSupport {
             return null;
         }
 
+        CellarSupport support = new CellarSupport();
+        support.setClusterManager(clusterManager);
+        support.setGroupManager(groupManager);
+        support.setConfigurationAdmin(configurationAdmin);
+
         ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
         try {
             Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
 
-            Map<String, FeatureState> clusterFeatures = clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + groupName);
+            Map<String, ExtendedFeatureState> features = gatherFeatures();
 ;
-            if (clusterFeatures != null && !clusterFeatures.isEmpty()) {
+            if (features != null && !features.isEmpty()) {
 
                 ShellTable table = new ShellTable();
                 table.column("Name");
                 table.column("Version");
                 table.column("Installed");
+                table.column("Located");
+                table.column("Blocked");
 
-                List<FeatureState> featureStates = new ArrayList<FeatureState>(clusterFeatures.values());
+                List<ExtendedFeatureState> featureStates = new ArrayList<ExtendedFeatureState>(features.values());
                 if (ordered) {
                     Collections.sort(featureStates, new FeatureComparator());
                 }
-                for (FeatureState info : featureStates) {
+                for (ExtendedFeatureState info : featureStates) {
 
                     String name = info.getName();
                     String version = info.getVersion();
                     boolean isInstalled = info.isInstalled();
+
+                    String located = "";
+                    boolean cluster = info.isCluster();
+                    boolean local = info.isLocal();
+                    if (cluster && local)
+                        located = "cluster/local";
+                    if (local && !cluster) {
+                        located = "local";
+                        if (onlyCluster)
+                            continue;
+                    }
+                    if (cluster && !local) {
+                        located = "cluster";
+                        if (onlyLocal)
+                            continue;
+                    }
+
+                    String blocked = "";
+                    boolean inbound = support.isAllowed(group, Constants.CATEGORY, name + "/" + version, EventType.INBOUND);
+                    boolean outbound = support.isAllowed(group, Constants.CATEGORY, name + "/" + version, EventType.OUTBOUND);
+                    if (inbound && outbound && onlyBlocked)
+                        continue;
+                    if (!inbound && !outbound)
+                        blocked = "in/out";
+                    if (!inbound && outbound)
+                        blocked = "in";
+                    if (!outbound && inbound)
+                        blocked = "out";
+
                     if (version == null)
                         version = "";
                     if (!installed || (installed && isInstalled)) {
-                        table.addRow().addContent(
-                                name,
-                                version,
-                                isInstalled ? "x" : "");
+                        table.addRow().addContent(name, version,
+                                isInstalled ? "x" : " ",
+                                located, blocked);
                     }
                 }
 
@@ -88,10 +138,78 @@ public class ListFeaturesCommand extends CellarCommandSupport {
         return null;
     }
 
+    private Map<String, ExtendedFeatureState> gatherFeatures() throws Exception {
+        Map<String, ExtendedFeatureState> features = new HashMap<String, ExtendedFeatureState>();
+
+        // get cluster features
+        Map<String, FeatureState> clusterFeatures = clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + groupName);
+        for (String key : clusterFeatures.keySet()) {
+            FeatureState state = clusterFeatures.get(key);
+            ExtendedFeatureState extendedState = new ExtendedFeatureState();
+            extendedState.setName(state.getName());
+            extendedState.setInstalled(state.isInstalled());
+            extendedState.setVersion(state.getVersion());
+            extendedState.setCluster(true);
+            extendedState.setLocal(true);
+            features.put(key, extendedState);
+        }
+
+        // get local features
+        for (Feature feature : featuresService.listFeatures()) {
+            String key = feature.getName() + "/" + feature.getVersion();
+            if (features.containsKey(key)) {
+                ExtendedFeatureState extendedState = features.get(key);
+                extendedState.setLocal(true);
+            } else {
+                ExtendedFeatureState extendedState = new ExtendedFeatureState();
+                extendedState.setCluster(false);
+                extendedState.setLocal(true);
+                extendedState.setName(feature.getName());
+                extendedState.setVersion(feature.getVersion());
+                if (featuresService.isInstalled(feature))
+                    extendedState.setInstalled(true);
+                else extendedState.setInstalled(false);
+                features.put(key, extendedState);
+            }
+        }
+
+        return features;
+    }
+
+    public FeaturesService getFeaturesService() {
+        return featuresService;
+    }
+
+    public void setFeaturesService(FeaturesService featuresService) {
+        this.featuresService = featuresService;
+    }
+
     class FeatureComparator implements Comparator<FeatureState> {
         public int compare(FeatureState f1, FeatureState f2) {
             return f1.getName().toLowerCase().compareTo(f2.getName().toLowerCase());
         }
     }
 
+    class ExtendedFeatureState extends FeatureState {
+
+        private boolean cluster;
+        private boolean local;
+
+        public boolean isCluster() {
+            return cluster;
+        }
+
+        public void setCluster(boolean cluster) {
+            this.cluster = cluster;
+        }
+
+        public boolean isLocal() {
+            return local;
+        }
+
+        public void setLocal(boolean local) {
+            this.local = local;
+        }
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/da4a03ae/features/src/main/java/org/apache/karaf/cellar/features/shell/RepoListCommand.java
----------------------------------------------------------------------
diff --git a/features/src/main/java/org/apache/karaf/cellar/features/shell/RepoListCommand.java b/features/src/main/java/org/apache/karaf/cellar/features/shell/RepoListCommand.java
index 790f5e2..770919d 100644
--- a/features/src/main/java/org/apache/karaf/cellar/features/shell/RepoListCommand.java
+++ b/features/src/main/java/org/apache/karaf/cellar/features/shell/RepoListCommand.java
@@ -17,12 +17,14 @@ import org.apache.karaf.cellar.core.Configurations;
 import org.apache.karaf.cellar.core.Group;
 import org.apache.karaf.cellar.core.shell.CellarCommandSupport;
 import org.apache.karaf.cellar.features.Constants;
+import org.apache.karaf.features.FeaturesService;
+import org.apache.karaf.features.Repository;
 import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
 import org.apache.karaf.shell.commands.Option;
 import org.apache.karaf.shell.table.ShellTable;
 
-import java.util.List;
+import java.util.HashMap;
 import java.util.Map;
 
 @Command(scope = "cluster", name = "feature-repo-list", description = "List the features repositories in a cluster group")
@@ -34,6 +36,14 @@ public class RepoListCommand extends CellarCommandSupport {
     @Option(name = "--no-format", description = "Disable table rendered output", required = false, multiValued = false)
     boolean noFormat;
 
+    @Option(name = "--local", description = "Shows only features repositories local to the node", required = false, multiValued = false)
+    boolean onlyLocal;
+
+    @Option(name = "--cluster", description = "Shows only features repositories on the cluster", required = false, multiValued = false)
+    boolean onlyCluster;
+
+    private FeaturesService featuresService;
+
     @Override
     protected Object doExecute() throws Exception {
         // check if the group  exists
@@ -43,18 +53,104 @@ public class RepoListCommand extends CellarCommandSupport {
             return null;
         }
 
-        // get the features repositories in the cluster group
-        Map<String, String> clusterRepositories = clusterManager.getMap(Constants.REPOSITORIES_MAP + Configurations.SEPARATOR + groupName);
+        Map<String, RepositoryState> repositories = gatherRepositories();
 
         ShellTable table = new ShellTable();
         table.column("Repository");
+        table.column("Located").alignCenter();
         table.column("URL");
-        for (String url : clusterRepositories.keySet()) {
-            table.addRow().addContent(clusterRepositories.get(url), url);
+        for (String url : repositories.keySet()) {
+            RepositoryState state = repositories.get(url);
+            String located = "";
+            boolean local = state.isLocal();
+            boolean cluster = state.isCluster();
+            if (local && cluster)
+                located = "cluster/local";
+            if (local && !cluster) {
+                if (onlyCluster)
+                    continue;
+                located = "local";
+            }
+            if (cluster && !local) {
+                if (onlyLocal)
+                    continue;
+                located = "cluster";
+            }
+            table.addRow().addContent(state.getName(), located, url);
         }
         table.print(System.out, !noFormat);
 
         return null;
     }
 
+    private Map<String, RepositoryState> gatherRepositories() {
+        Map<String, RepositoryState> repositories = new HashMap<String, RepositoryState>();
+
+        // get the cluster features repositories
+        Map<String, String> clusterRepositories = clusterManager.getMap(Constants.REPOSITORIES_MAP + Configurations.SEPARATOR + groupName);
+        for (String url : clusterRepositories.keySet()) {
+            RepositoryState state = new RepositoryState();
+            state.setCluster(true);
+            state.setLocal(true);
+            state.setName(clusterRepositories.get(url));
+            repositories.put(url, state);
+        }
+
+        // get the local features repositories
+        for (Repository localRepository : featuresService.listRepositories()) {
+            if (repositories.containsKey(localRepository.getURI().toString())) {
+                RepositoryState state = repositories.get(localRepository.getURI().toString());
+                state.setLocal(true);
+            } else {
+                RepositoryState state = new RepositoryState();
+                state.setCluster(false);
+                state.setLocal(true);
+                state.setName(localRepository.getName());
+                repositories.put(localRepository.getURI().toString(), state);
+            }
+        }
+
+        return repositories;
+    }
+
+    public FeaturesService getFeaturesService() {
+        return featuresService;
+    }
+
+    public void setFeaturesService(FeaturesService featuresService) {
+        this.featuresService = featuresService;
+    }
+
+    class RepositoryState {
+
+        private String name;
+        private boolean cluster;
+        private boolean local;
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public boolean isCluster() {
+            return cluster;
+        }
+
+        public void setCluster(boolean cluster) {
+            this.cluster = cluster;
+        }
+
+        public boolean isLocal() {
+            return local;
+        }
+
+        public void setLocal(boolean local) {
+            this.local = local;
+        }
+
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/karaf-cellar/blob/da4a03ae/features/src/main/resources/OSGI-INF/blueprint/shell-features.xml
----------------------------------------------------------------------
diff --git a/features/src/main/resources/OSGI-INF/blueprint/shell-features.xml b/features/src/main/resources/OSGI-INF/blueprint/shell-features.xml
index 0516e00..9b70226 100644
--- a/features/src/main/resources/OSGI-INF/blueprint/shell-features.xml
+++ b/features/src/main/resources/OSGI-INF/blueprint/shell-features.xml
@@ -45,6 +45,8 @@
             <action class="org.apache.karaf.cellar.features.shell.ListFeaturesCommand">
                 <property name="clusterManager" ref="clusterManager"/>
                 <property name="groupManager" ref="groupManager"/>
+                <property name="configurationAdmin" ref="configurationAdmin"/>
+                <property name="featuresService" ref="featuresService"/>
             </action>
             <completers>
                 <ref component-id="allGroupsCompleter"/>
@@ -54,6 +56,7 @@
             <action class="org.apache.karaf.cellar.features.shell.RepoListCommand">
                 <property name="clusterManager" ref="clusterManager"/>
                 <property name="groupManager" ref="groupManager"/>
+                <property name="featuresService" ref="featuresService"/>
             </action>
             <completers>
                 <ref component-id="allGroupsCompleter"/>