Merge branch 'KARAF-3646' of https://github.com/albertocsm/karaf-cellar

Conflicts:
	assembly/src/main/resources/features.xml
	pom.xml
diff --git a/NOTICE b/NOTICE
index addb35c..64cb235 100644
--- a/NOTICE
+++ b/NOTICE
@@ -1,5 +1,5 @@
 Apache Karaf Cellar

-Copyright 2011-2014 The Apache Software Foundation

+Copyright 2011-2015 The Apache Software Foundation

 

 I. Used Software

 

diff --git a/assembly/pom.xml b/assembly/pom.xml
index 703c3c8..9f321a1 100644
--- a/assembly/pom.xml
+++ b/assembly/pom.xml
@@ -33,6 +33,25 @@
     <packaging>pom</packaging>
     <name>Apache Karaf :: Cellar :: Assembly</name>
 
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.karaf.features</groupId>
+            <artifactId>framework</artifactId>
+            <version>${karaf.version}</version>
+            <type>kar</type>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.features</groupId>
+            <artifactId>standard</artifactId>
+            <version>${karaf.version}</version>
+            <classifier>features</classifier>
+            <type>xml</type>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+
     <build>
         <resources>
             <resource>
@@ -45,6 +64,44 @@
         </resources>
         <plugins>
             <plugin>
+                <groupId>org.apache.karaf.tooling</groupId>
+                <artifactId>karaf-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>verify</id>
+                        <phase>process-resources</phase>
+                        <goals>
+                            <goal>verify</goal>
+                        </goals>
+                        <configuration>
+                            <descriptors>
+                                <descriptor>mvn:org.apache.karaf.features/framework/${karaf.version}/xml/features</descriptor>
+                                <descriptor>mvn:org.apache.karaf.features/standard/${karaf.version}/xml/features</descriptor>
+                                <descriptor>file:${project.build.directory}/classes/features.xml</descriptor>
+                            </descriptors>
+                            <distribution>org.apache.karaf.features:framework</distribution>
+                            <javase>1.7</javase>
+                            <framework>
+                                <feature>framework</feature>
+                            </framework>
+                            <features>
+                                <feature>hazelcast</feature>
+                                <feature>cellar-core</feature>
+                                <feature>cellar-hazelcast</feature>
+                                <feature>cellar-shell</feature>
+                                <feature>cellar</feature>
+                                <feature>cellar-dosgi</feature>
+                                <feature>cellar-obr</feature>
+                                <feature>cellar-eventadmin</feature>
+                                <feature>cellar-cloud</feature>
+                                <feature>cellar-webconsole</feature>
+                            </features>
+                            <verifyTransitive>false</verifyTransitive>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-resources-plugin</artifactId>
                 <configuration>
@@ -136,35 +193,6 @@
                     </execution>
                 </executions>
             </plugin>
-            <!--
-            <plugin>
-                <groupId>org.apache.karaf.tooling</groupId>
-                <artifactId>karaf-maven-plugin</artifactId>
-                <version>${karaf.version}</version>
-                <executions>
-                    <execution>
-                        <id>features-generate-descriptor</id>
-                        <phase>compile</phase>
-                        <goals>
-                            <goal>features-generate-descriptor</goal>
-                        </goals>
-                        <configuration>
-                            <addBundlesToPrimaryFeature>false</addBundlesToPrimaryFeature>
-                        </configuration>
-                    </execution>
-                    <execution>
-                        <id>features-create-kar</id>
-                        <phase>package</phase>
-                        <goals>
-                            <goal>features-create-kar</goal>
-                        </goals>
-                        <configuration>
-                            <featuresFile>target/classes/features.xml</featuresFile>
-                        </configuration>
-                    </execution>
-                </executions>
-            </plugin>
-            -->
         </plugins>
     </build>
 
diff --git a/assembly/src/main/resources/features.xml b/assembly/src/main/resources/features.xml
index e0408e6..692c1e9 100644
--- a/assembly/src/main/resources/features.xml
+++ b/assembly/src/main/resources/features.xml
@@ -13,85 +13,84 @@
    See the License for the specific language governing permissions and
    limitations under the License.
 -->
-<features name="karaf-cellar-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://karaf.apache.org/xmlns/features/v1.2.0 http://karaf.apache.org/xmlns/features/v1.2.0">
+<features name="karaf-cellar-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.3.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://karaf.apache.org/xmlns/features/v1.3.0 http://karaf.apache.org/xmlns/features/v1.3.0">
 
     <repository>mvn:org.apache.jclouds.karaf/jclouds-karaf/${jclouds.version}/xml/features</repository>
     <repository>mvn:io.fabric8/karaf-features/${fabric8.version}/xml/features</repository>
     
-    <feature name="cellar-core" description="Karaf clustering core" version="${project.version}" resolver="(obr)">
-        <configfile finalname="/etc/org.apache.karaf.cellar.groups.cfg">
-            mvn:org.apache.karaf.cellar/apache-karaf-cellar/${project.version}/cfg/groups
-        </configfile>
-        <configfile finalname="/etc/org.apache.karaf.cellar.node.cfg">
-            mvn:org.apache.karaf.cellar/apache-karaf-cellar/${project.version}/cfg/node
-        </configfile>
-        <bundle start-level="30">mvn:org.apache.karaf.cellar/org.apache.karaf.cellar.core/${project.version}</bundle>
-        <bundle start-level="31">mvn:org.apache.karaf.cellar/org.apache.karaf.cellar.utils/${project.version}</bundle>
+    <feature name="cellar-core" description="Karaf clustering core" version="${project.version}">
+        <feature>aries-proxy</feature>
+        <feature>shell</feature>
+        <configfile finalname="/etc/org.apache.karaf.cellar.groups.cfg">mvn:org.apache.karaf.cellar/apache-karaf-cellar/${project.version}/cfg/groups</configfile>
+        <configfile finalname="/etc/org.apache.karaf.cellar.node.cfg">mvn:org.apache.karaf.cellar/apache-karaf-cellar/${project.version}/cfg/node</configfile>
+        <bundle>mvn:org.apache.karaf.cellar/org.apache.karaf.cellar.core/${project.version}</bundle>
     </feature>
 
-    <feature name="hazelcast" description="In memory data grid" version="${hazelcast.version}" resolver="(obr)">
-        <configfile finalname="/etc/hazelcast.xml">
-            mvn:org.apache.karaf.cellar/apache-karaf-cellar/${project.version}/xml/hazelcast
-        </configfile>
-        <bundle start-level="30" dependency="true">mvn:org.apache.geronimo.specs/geronimo-jta_1.1_spec/1.1.1</bundle>
-        <bundle start-level="30" dependency="true">mvn:com.eclipsesource.minimal-json/minimal-json/0.9.2</bundle>
-        <bundle start-level="32">mvn:com.hazelcast/hazelcast/${hazelcast.version}</bundle>
+    <feature name="hazelcast" description="In memory data grid" version="${hazelcast.version}">
+        <configfile finalname="/etc/hazelcast.xml">mvn:org.apache.karaf.cellar/apache-karaf-cellar/${project.version}/xml/hazelcast</configfile>
+        <bundle>mvn:org.apache.geronimo.specs/geronimo-jta_1.1_spec/1.1.1</bundle>
+        <bundle>mvn:com.eclipsesource.minimal-json/minimal-json/0.9.2</bundle>
+        <bundle>mvn:com.hazelcast/hazelcast-all/${hazelcast.version}</bundle>
     </feature>
 
-    <feature name="cellar-hazelcast" description="Cellar implementation based on Hazelcast" version="${project.version}" resolver="(obr)">
+    <feature name="cellar-hazelcast" description="Cellar implementation based on Hazelcast" version="${project.version}">
         <feature version="${hazelcast.version}">hazelcast</feature>
         <feature version="${project.version}">cellar-core</feature>
-        <bundle start-level="33">mvn:org.apache.karaf.cellar/org.apache.karaf.cellar.hazelcast/${project.version}</bundle>
+        <bundle>mvn:org.apache.karaf.cellar/org.apache.karaf.cellar.hazelcast/${project.version}</bundle>
+        <bundle>mvn:org.apache.karaf.cellar/org.apache.karaf.cellar.utils/${project.version}</bundle>
     </feature>
 
-    <feature name="cellar-config" description="ConfigAdmin cluster support" version="${project.version}" resolver="(obr)">
-        <feature version="${project.version}">cellar-core</feature>
-        <bundle start-level="40">mvn:org.apache.karaf.cellar/org.apache.karaf.cellar.config/${project.version}</bundle>
+    <feature name="cellar-config" description="ConfigAdmin cluster support" version="${project.version}">
+        <feature>config</feature>
+        <feature>cellar-hazelcast</feature>
+        <bundle>mvn:org.apache.karaf.cellar/org.apache.karaf.cellar.config/${project.version}</bundle>
     </feature>
 
-    <feature name="cellar-features" description="Karaf features cluster support" version="${project.version}" resolver="(obr)">
-        <feature version="${project.version}">cellar-core</feature>
-        <bundle start-level="40">mvn:org.apache.karaf.cellar/org.apache.karaf.cellar.features/${project.version}</bundle>
+    <feature name="cellar-features" description="Karaf features cluster support" version="${project.version}">
+        <feature>feature</feature>
+        <feature>cellar-hazelcast</feature>
+        <bundle>mvn:org.apache.karaf.cellar/org.apache.karaf.cellar.features/${project.version}</bundle>
     </feature>
 
-    <feature name="cellar-bundle" description="Bundle cluster support" version="${project.version}" resolver="(obr)">
-        <feature version="${project.version}">cellar-core</feature>
-        <bundle start-level="40">mvn:org.apache.karaf.cellar/org.apache.karaf.cellar.bundle/${project.version}</bundle>
+    <feature name="cellar-bundle" description="Bundle cluster support" version="${project.version}">
+        <feature>bundle</feature>
+        <feature>cellar-hazelcast</feature>
+        <bundle>mvn:org.apache.karaf.cellar/org.apache.karaf.cellar.bundle/${project.version}</bundle>
     </feature>
 
-    <feature name="cellar-shell" description="Cellar shell support" version="${project.version}" resolver="(obr)">
-        <feature version="${project.version}">cellar-core</feature>
-        <bundle start-level="40">mvn:org.apache.karaf.cellar/org.apache.karaf.cellar.shell/${project.version}</bundle>
+    <feature name="cellar-shell" description="Cellar shell support" version="${project.version}">
+        <feature>shell</feature>
+        <feature>cellar-hazelcast</feature>
+        <bundle>mvn:org.apache.karaf.cellar/org.apache.karaf.cellar.shell/${project.version}</bundle>
     </feature>
 
-    <feature name="cellar" description="Karaf clustering" version="${project.version}" resolver="(obr)">
-        <feature version="${project.version}">cellar-hazelcast</feature>
-        <feature version="${project.version}">cellar-shell</feature>
-        <feature version="${project.version}">cellar-config</feature>
-        <feature version="${project.version}">cellar-bundle</feature>
-        <feature version="${project.version}">cellar-features</feature>
+    <feature name="cellar" description="Karaf clustering" version="${project.version}">
+        <feature>cellar-hazelcast</feature>
+        <feature>cellar-shell</feature>
+        <feature>cellar-config</feature>
+        <feature>cellar-bundle</feature>
+        <feature>cellar-features</feature>
     </feature>
 
-    <feature name="cellar-dosgi" description="DOSGi support" version="${project.version}" resolver="(obr)">
-        <feature version="${project.version}">cellar-core</feature>
-        <bundle start-level="30" dependency="true">wrap:mvn:org.apache.commons/commons-lang3/${apache.commons.lang3.version}</bundle>
+    <feature name="cellar-dosgi" description="DOSGi support" version="${project.version}">
+        <feature>cellar-hazelcast</feature>
         <bundle start-level="40">mvn:org.apache.karaf.cellar/org.apache.karaf.cellar.dosgi/${project.version}</bundle>
     </feature>
 
-    <feature name="cellar-obr" description="OBR cluster support" version="${project.version}" resolver="(obr)">
+    <feature name="cellar-obr" description="OBR cluster support" version="${project.version}">
         <feature>obr</feature>
-        <feature version="${project.version}">cellar-core</feature>
+        <feature>cellar-hazelcast</feature>
         <bundle>mvn:org.apache.karaf.cellar/org.apache.karaf.cellar.obr/${project.version}</bundle>
     </feature>
 
-    <feature name="cellar-eventadmin" description="OSGi events broadcasting in clusters" version="${project.version}" resolver="(obr)">
+    <feature name="cellar-eventadmin" description="OSGi events broadcasting in clusters" version="${project.version}">
         <feature>eventadmin</feature>
-        <feature version="${project.version}">cellar-core</feature>
+        <feature>cellar-hazelcast</feature>
         <bundle>mvn:org.apache.karaf.cellar/org.apache.karaf.cellar.event/${project.version}</bundle>
     </feature>
 
-    <feature name="cellar-cloud" description="Cloud blobstore support in clusters" version="${project.version}" resolver="(obr)">
-        <feature version="${project.version}">cellar-core</feature>
+    <feature name="cellar-cloud" description="Cloud blobstore support in clusters" version="${project.version}">
+        <feature>cellar-hazelcast</feature>
         <feature version="${jclouds.version}">jclouds</feature>
         <!-- Adding S3 as the default Blobstore -->
         <feature>jclouds-aws-s3</feature>
@@ -99,17 +98,23 @@
         <bundle>mvn:org.apache.karaf.cellar/org.apache.karaf.cellar.cloud/${project.version}</bundle>
     </feature>
 
-    <feature name="cellar-kubernetes" description="Cellar kubernetes support in clusters" version="${project.version}" resolver="(obr)">
-        <feature version="${project.version}">cellar-core</feature>
+    <feature name="cellar-kubernetes" description="Cellar kubernetes support in clusters" version="${project.version}">
         <feature>fabric8-kubernetes-api</feature>
         <bundle>mvn:org.apache.karaf.cellar/org.apache.karaf.cellar.kubernetes/${project.version}</bundle>
     </feature>
 
-    <feature name="cellar-webconsole" description="Cellar plugin for Karaf WebConsole" version="${project.version}" resolver="(obr)">
-        <feature version="${project.version}">cellar</feature>
+    <feature name="cellar-webconsole" description="Cellar plugin for Karaf WebConsole" version="${project.version}">
         <feature>webconsole</feature>
+        <feature>cellar-hazelcast</feature>
         <bundle>mvn:org.apache.karaf.cellar/org.apache.karaf.cellar.webconsole/${project.version}</bundle>
     </feature>
 
+    <feature name="cellar-http-balancer" description="Cellar HTTP request balancer" version="${project.version}">
+        <feature>cellar-hazelcast</feature>
+        <feature>http</feature>
+        <feature>http-whiteboard</feature>
+        <bundle>mvn:org.apache.karaf.cellar.http/org.apache.karaf.cellar.http.balancer/${project.version}</bundle>
+    </feature>
+
 </features>
 
diff --git a/assembly/src/main/resources/groups.cfg b/assembly/src/main/resources/groups.cfg
index 62a0cfe..7dac669 100644
--- a/assembly/src/main/resources/groups.cfg
+++ b/assembly/src/main/resources/groups.cfg
@@ -17,17 +17,13 @@
 default.config.whitelist.inbound = *
 default.config.whitelist.outbound = *
 default.config.blacklist.inbound = org.apache.felix.fileinstall*, \
-                                   org.apache.karaf.cellar*, \
                                    org.apache.karaf.management, \
                                    org.apache.karaf.shell, \
-                                   org.ops4j.pax.logging, \
                                    org.ops4j.pax.web, \
                                    org.apache.aries.transaction
 default.config.blacklist.outbound = org.apache.felix.fileinstall*, \
-                                    org.apache.karaf.cellar*, \
                                     org.apache.karaf.management, \
                                     org.apache.karaf.shell, \
-                                    org.ops4j.pax.logging, \
                                     org.ops4j.pax.web, \
                                     org.apache.aries.transaction
 
@@ -43,13 +39,18 @@
 # The following properties define the behavior to use when the node joins the cluster (the usage of the bootstrap
 # synchronizer), per cluster group and per resource.
 # The following values are accepted:
-# disabled: means that the synchronizer is not used, meaning the node or the cluster are not updated at all
-# cluster: if the node is the first one in the cluster, it pushes its local state to the cluster, else it's not the
-#       first node of the cluster, the node will update its local state with the cluster one (meaning that the cluster
-#       is the master)
-# node: in this case, the node is the master, it means that the cluster state will be overwritten by the node state.
+# disabled: means that the synchronizer doesn't sync cluster group and node states
+# cluster: the synchronizer retrieves the state from the cluster group first (pull first), and push the node the state
+#          to the cluster group after (push after)
+# node: the synchronizer push the node state to the cluster group (push first), and pull the state from the cluster group
+#        after (pull after)
+# clusterOnly: the cluster is the "master", the node only retrieves and applies the cluster group state, nothing is
+#              pushed to the cluster group
+# nodeOnly: the node is the "master", the node pushes his state to the cluster group, nothing is pulled from the
+#           cluster group
 #
 default.bundle.sync = cluster
 default.config.sync = cluster
 default.feature.sync = cluster
 default.obr.urls.sync = cluster
+default.balanced.servlet.sync = cluster
diff --git a/assembly/src/main/resources/node.cfg b/assembly/src/main/resources/node.cfg
index 4e389ec..38c841e 100644
--- a/assembly/src/main/resources/node.cfg
+++ b/assembly/src/main/resources/node.cfg
@@ -37,6 +37,8 @@
 # OBR event handler
 handler.org.apache.karaf.cellar.obr.ObrBundleEventHandler = true
 handler.org.apache.karaf.cellar.obr.ObrUrlEventHandler = true
+# HTTP balancer event handler
+handler.org.apache.karaf.cellar.http.balancer.BalancerEventHandler = true
 
 #
 # Excluded config properties from the sync
diff --git a/bundle/NOTICE b/bundle/NOTICE
index addb35c..64cb235 100644
--- a/bundle/NOTICE
+++ b/bundle/NOTICE
@@ -1,5 +1,5 @@
 Apache Karaf Cellar

-Copyright 2011-2014 The Apache Software Foundation

+Copyright 2011-2015 The Apache Software Foundation

 

 I. Used Software

 

diff --git a/bundle/pom.xml b/bundle/pom.xml
index a0a660e..893578a 100644
--- a/bundle/pom.xml
+++ b/bundle/pom.xml
@@ -41,6 +41,14 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.core</artifactId>
             <scope>provided</scope>
@@ -58,12 +66,6 @@
             <scope>provided</scope>
         </dependency>
 
-        <!-- Shell table -->
-        <dependency>
-            <groupId>org.apache.karaf.shell</groupId>
-            <artifactId>org.apache.karaf.shell.table</artifactId>
-        </dependency>
-
         <!-- Logging Dependencies -->
         <dependency>
             <groupId>org.slf4j</groupId>
@@ -75,28 +77,28 @@
     <build>
         <plugins>
             <plugin>
+                <groupId>org.apache.karaf.tooling</groupId>
+                <artifactId>karaf-services-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
                 <configuration>
                     <instructions>
                         <Export-Package>
-                            org.apache.karaf.cellar.bundle*;version="${project.version}"
+                            !org.apache.karaf.cellar.bundle.management.internal,
+                            !org.apache.karaf.cellar.bundle.internal.osgi,
+                            org.apache.karaf.cellar.bundle*
                         </Export-Package>
                         <Import-Package>
-                            org.apache.karaf.cellar.core*;version="${project.version}",
-                            org.apache.felix.service.command,
-                            org.apache.felix.gogo.commands,
-                            org.apache.karaf.shell.console;version="[3,5)",
-                            org.apache.karaf.shell.console.commands;version="[3,5)",
-                            org.apache.karaf.shell.console.completer;version="[3,5)",
-                            org.apache.karaf.shell.commands;version="[3,5)",
-                            org.apache.karaf.features;version="[3,5)",
-                            org.osgi*,
-                            org.slf4j;version="[1.6,2)";resolution:=optional
+                            org.slf4j;version="[1.6,2)";resolution:=optional,
+                            org.apache.karaf.shell*;resolution:=optional,
+                            *
                         </Import-Package>
-                        <DynamicImport-Package>javax.*,org.w3c.*,org.xml.*,*</DynamicImport-Package>
                         <Private-Package>
-                            org.apache.karaf.cellar.bundle.management.internal
+                            org.apache.karaf.cellar.bundle.management.internal,
+                            org.apache.karaf.cellar.bundle.internal.osgi,
+                            org.apache.karaf.util.tracker;-split-package:=merge-first
                         </Private-Package>
                     </instructions>
                 </configuration>
diff --git a/bundle/src/main/java/org/apache/karaf/cellar/bundle/BundleEventHandler.java b/bundle/src/main/java/org/apache/karaf/cellar/bundle/BundleEventHandler.java
index 43a8c51..589fda4 100644
--- a/bundle/src/main/java/org/apache/karaf/cellar/bundle/BundleEventHandler.java
+++ b/bundle/src/main/java/org/apache/karaf/cellar/bundle/BundleEventHandler.java
@@ -20,7 +20,7 @@
 import org.apache.karaf.cellar.core.event.EventHandler;
 import org.apache.karaf.cellar.core.event.EventType;
 import org.apache.karaf.features.Feature;
-import org.osgi.framework.BundleEvent;
+import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleException;
 import org.osgi.service.cm.Configuration;
 import org.slf4j.Logger;
@@ -38,7 +38,7 @@
     public static final String SWITCH_ID = "org.apache.karaf.cellar.bundle.handler";
 
     private final Switch eventSwitch = new BasicSwitch(SWITCH_ID);
-    
+
     /**
      * Handle received bundle cluster events.
      *
@@ -52,7 +52,7 @@
             LOGGER.debug("CELLAR BUNDLE: {} switch is OFF, cluster event is not handled", SWITCH_ID);
             return;
         }
-        
+
         if (groupManager == null) {
         	//in rare cases for example right after installation this happens!
         	LOGGER.error("CELLAR BUNDLE: retrieved event {} while groupManager is not available yet!", event);
@@ -66,33 +66,39 @@
         }
 
         try {
+            // check if it's not a "local" event
+            if (event.getSourceNode() != null && event.getSourceNode().getId().equalsIgnoreCase(clusterManager.getNode().getId())) {
+                LOGGER.trace("CELLAR BUNDLE: cluster event is local (coming from local synchronizer or listener)");
+                return;
+            }
             // check if the pid is marked as local.
             if (isAllowed(event.getSourceGroup(), Constants.CATEGORY, event.getLocation(), EventType.INBOUND)) {
-            	// check the features first
-            	List<Feature> matchingFeatures = retrieveFeature(event.getLocation());
-            	for (Feature feature : matchingFeatures) {
-					if (!isAllowed(event.getSourceGroup(), "features", feature.getName(), EventType.INBOUND)) {
-						LOGGER.trace("CELLAR BUNDLE: bundle {} is contained in feature {} marked BLOCKED INBOUND for cluster group {}", event.getLocation(), feature.getName(), event.getSourceGroup().getName());
-						return;
-					}
-				}
-                if (event.getType() == BundleEvent.INSTALLED) {
+                // check the features first
+                List<Feature> matchingFeatures = retrieveFeature(event.getLocation());
+                for (Feature feature : matchingFeatures) {
+                    if (!isAllowed(event.getSourceGroup(), "features", feature.getName(), EventType.INBOUND)) {
+                        LOGGER.trace("CELLAR BUNDLE: bundle {} is contained in feature {} marked BLOCKED INBOUND for cluster group {}", event.getLocation(), feature.getName(), event.getSourceGroup().getName());
+                        return;
+                    }
+                }
+                if (event.getType() == Bundle.INSTALLED) {
                     installBundleFromLocation(event.getLocation());
                     LOGGER.debug("CELLAR BUNDLE: installing {}/{}", event.getSymbolicName(), event.getVersion());
-                } else if (event.getType() == BundleEvent.UNINSTALLED) {
+                } else if (event.getType() == Bundle.UNINSTALLED) {
                     uninstallBundle(event.getSymbolicName(), event.getVersion());
                     LOGGER.debug("CELLAR BUNDLE: uninstalling {}/{}", event.getSymbolicName(), event.getVersion());
-                } else if (event.getType() == BundleEvent.STARTED) {
+                } else if (event.getType() == Bundle.ACTIVE) {
+                    if (!isInstalled(event.getLocation())) {
+                        installBundleFromLocation(event.getLocation());
+                    }
                     startBundle(event.getSymbolicName(), event.getVersion());
                     LOGGER.debug("CELLAR BUNDLE: starting {}/{}", event.getSymbolicName(), event.getVersion());
-                } else if (event.getType() == BundleEvent.STOPPED) {
+                } else if (event.getType() == Bundle.RESOLVED) {
                     stopBundle(event.getSymbolicName(), event.getVersion());
                     LOGGER.debug("CELLAR BUNDLE: stopping {}/{}", event.getSymbolicName(), event.getVersion());
-                } else if (event.getType() == BundleEvent.UPDATED) {
-                    updateBundle(event.getSymbolicName(), event.getVersion());
-                    LOGGER.debug("CELLAR BUNDLE: updating {}/{}", event.getSymbolicName(), event.getVersion());
                 }
-            } else LOGGER.trace("CELLAR BUNDLE: bundle {} is marked BLOCKED INBOUND for cluster group {}", event.getSymbolicName(), event.getSourceGroup().getName());
+            } else
+                LOGGER.trace("CELLAR BUNDLE: bundle {} is marked BLOCKED INBOUND for cluster group {}", event.getSymbolicName(), event.getSourceGroup().getName());
         } catch (BundleException e) {
             LOGGER.error("CELLAR BUNDLE: failed to install bundle {}/{}.", new Object[]{event.getSymbolicName(), event.getVersion()}, e);
         } catch (Exception e) {
diff --git a/bundle/src/main/java/org/apache/karaf/cellar/bundle/BundleSupport.java b/bundle/src/main/java/org/apache/karaf/cellar/bundle/BundleSupport.java
index 5e08447..b2509f7 100644
--- a/bundle/src/main/java/org/apache/karaf/cellar/bundle/BundleSupport.java
+++ b/bundle/src/main/java/org/apache/karaf/cellar/bundle/BundleSupport.java
@@ -42,6 +42,26 @@
         getBundleContext().installBundle(location);
     }
 
+    public boolean isInstalled(String location) {
+        Bundle[] bundles = getBundleContext().getBundles();
+        for (Bundle bundle : bundles) {
+            if (bundle.getLocation().equals(location)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean isStarted(String location) {
+        Bundle[] bundles = getBundleContext().getBundles();
+        for (Bundle bundle : bundles) {
+            if (bundle.getLocation().equals(location) && (bundle.getState() == Bundle.ACTIVE)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Locally uninstall a bundle.
      *
@@ -53,7 +73,7 @@
         Bundle[] bundles = getBundleContext().getBundles();
         if (bundles != null) {
             for (Bundle bundle : bundles) {
-                if (bundle.getSymbolicName().equals(symbolicName) && bundle.getVersion().toString().equals(version)) {
+                if (bundle.getSymbolicName().equals(symbolicName) && bundle.getHeaders().get("Bundle-Version").toString().equals(version)) {
                     bundle.uninstall();
                 }
             }
@@ -71,7 +91,7 @@
         Bundle[] bundles = getBundleContext().getBundles();
         if (bundles != null) {
             for (Bundle bundle : bundles) {
-                if (bundle.getSymbolicName().equals(symbolicName) && bundle.getVersion().toString().equals(version)) {
+                if (bundle.getSymbolicName().equals(symbolicName) && bundle.getHeaders().get("Bundle-Version").toString().equals(version)) {
                     bundle.start();
                 }
             }
@@ -89,7 +109,7 @@
         Bundle[] bundles = getBundleContext().getBundles();
         if (bundles != null) {
             for (Bundle bundle : bundles) {
-                if (bundle.getSymbolicName().equals(symbolicName) && bundle.getVersion().toString().equals(version)) {
+                if (bundle.getSymbolicName().equals(symbolicName) && bundle.getHeaders().get("Bundle-Version").toString().equals(version)) {
                     bundle.stop();
                 }
             }
@@ -107,7 +127,7 @@
         Bundle[] bundles = getBundleContext().getBundles();
         if (bundles != null) {
             for (Bundle bundle : bundles) {
-                if (bundle.getSymbolicName().equals(symbolicName) && bundle.getVersion().toString().equals(version)) {
+                if (bundle.getSymbolicName().equals(symbolicName) && bundle.getHeaders().get("Bundle-Version").toString().equals(version)) {
                     bundle.update();
                 }
             }
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 b8278e2..aee5a26 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
@@ -21,7 +21,6 @@
 import org.apache.karaf.cellar.core.event.EventType;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleEvent;
 import org.osgi.framework.BundleException;
 import org.osgi.framework.BundleReference;
 import org.osgi.service.cm.Configuration;
@@ -41,7 +40,15 @@
 
     private static final transient Logger LOGGER = LoggerFactory.getLogger(BundleSynchronizer.class);
 
+    private EventProducer eventProducer;
+
+    public void setEventProducer(EventProducer eventProducer) {
+        this.eventProducer = eventProducer;
+    }
+
     public void init() {
+        if (groupManager == null)
+            return;
         Set<Group> groups = groupManager.listLocalGroups();
         if (groups != null && !groups.isEmpty()) {
             for (Group group : groups) {
@@ -62,19 +69,32 @@
     @Override
     public void sync(Group group) {
         String policy = getSyncPolicy(group);
-        if (policy != null && policy.equalsIgnoreCase("cluster")) {
-            LOGGER.debug("CELLAR BUNDLE: sync policy is set as 'cluster' for cluster group " + group.getName());
-            if (clusterManager.listNodesByGroup(group).size() == 1 && clusterManager.listNodesByGroup(group).contains(clusterManager.getNode())) {
-                LOGGER.debug("CELLAR BUNDLE: node is the first and only member of the group, pushing state");
-                push(group);
-            } else {
-                LOGGER.debug("CELLAR BUNDLE: pulling state");
-                pull(group);
-            }
+        if (policy == null) {
+            LOGGER.warn("CELLAR BUNDLE: sync policy is not defined for cluster group {}", group.getName());
         }
-        if (policy != null && policy.equalsIgnoreCase("node")) {
-            LOGGER.debug("CELLAR BUNDLE: sync policy is set as 'node' for cluster group " + group.getName());
+        if (policy.equalsIgnoreCase("cluster")) {
+            LOGGER.debug("CELLAR BUNDLE: sync policy set as 'cluster' for cluster group {}", group.getName());
+            LOGGER.debug("CELLAR BUNDLE: updating node from the cluster (pull first)");
+            pull(group);
+            LOGGER.debug("CELLAR BUNDLE: updating cluster from the local node (push after)");
             push(group);
+        } else if (policy.equalsIgnoreCase("node")) {
+            LOGGER.debug("CELLAR BUNDLE: sync policy set as 'node' for cluster group {}", group.getName());
+            LOGGER.debug("CELLAR BUNDLE: updating cluster from the local node (push first)");
+            push(group);
+            LOGGER.debug("CELLAR BUNDLE: updating node from the cluster (pull after)");
+            pull(group);
+        } else if (policy.equalsIgnoreCase("clusterOnly")) {
+            LOGGER.debug("CELLAR BUNDLE: sync policy set as 'clusterOnly' for cluster group " + group.getName());
+            LOGGER.debug("CELLAR BUNDLE: updating node from the cluster (pull only)");
+            pull(group);
+        } else if (policy.equalsIgnoreCase("nodeOnly")) {
+            LOGGER.debug("CELLAR BUNDLE: sync policy set as 'nodeOnly' for cluster group " + group.getName());
+            LOGGER.debug("CELLAR BUNDLE: updating cluster from the local node (push only)");
+            push(group);
+        } else {
+            LOGGER.debug("CELLAR BUNDLE: sync policy set as 'disabled' for cluster group " + group.getName());
+            LOGGER.debug("CELLAR BUNDLE: no sync");
         }
     }
 
@@ -106,11 +126,24 @@
                             String bundleLocation = state.getLocation();
                             if (isAllowed(group, Constants.CATEGORY, bundleLocation, EventType.INBOUND)) {
                                 try {
-                                    if (state.getStatus() == BundleEvent.INSTALLED) {
-                                        installBundleFromLocation(state.getLocation());
-                                    } else if (state.getStatus() == BundleEvent.STARTED) {
-                                        installBundleFromLocation(state.getLocation());
-                                        startBundle(symbolicName, version);
+                                    if (state.getStatus() == Bundle.INSTALLED) {
+                                        if (!isInstalled(state.getLocation())) {
+                                            LOGGER.debug("CELLAR BUNDLE: installing bundle located {} on node", state.getLocation());
+                                            installBundleFromLocation(state.getLocation());
+                                        } else {
+                                            LOGGER.debug("CELLAR BUNDLE: bundle located {} already installed on node", state.getLocation());
+                                        }
+                                    } else if (state.getStatus() == Bundle.ACTIVE) {
+                                        if (!isInstalled(state.getLocation())) {
+                                            LOGGER.debug("CELLAR BUNDLE: installing bundle located {} on node", state.getLocation());
+                                            installBundleFromLocation(state.getLocation());
+                                        }
+                                        if (!isStarted(state.getLocation())) {
+                                            LOGGER.debug("CELLAR BUNDLE: starting bundle {}/{} on node", symbolicName, version);
+                                            startBundle(symbolicName, version);
+                                        } else {
+                                            LOGGER.debug("CELLAR BUNDLE: bundle located {} already started on node", state.getLocation());
+                                        }
                                     }
                                 } catch (BundleException e) {
                                     LOGGER.error("CELLAR BUNDLE: failed to pull bundle {}", id, e);
@@ -133,6 +166,11 @@
     @Override
     public void push(Group group) {
 
+        if (eventProducer.getSwitch().getStatus().equals(SwitchStatus.OFF)) {
+            LOGGER.warn("CELLAR BUNDLE: cluster event producer is OFF");
+            return;
+        }
+
         if (group != null) {
             String groupName = group.getName();
             LOGGER.debug("CELLAR BUNDLE: pushing bundles to cluster group {}", groupName);
@@ -148,43 +186,49 @@
                 for (Bundle bundle : bundles) {
                     long bundleId = bundle.getBundleId();
                     String symbolicName = bundle.getSymbolicName();
-                    String version = bundle.getVersion().toString();
+                    String version = bundle.getHeaders().get(org.osgi.framework.Constants.BUNDLE_VERSION);
                     String bundleLocation = bundle.getLocation();
                     int status = bundle.getState();
                     String id = symbolicName + "/" + version;
 
                     // check if the pid is marked as local.
                     if (isAllowed(group, Constants.CATEGORY, bundleLocation, EventType.OUTBOUND)) {
-
-                        BundleState bundleState = new BundleState();
-                        // 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) ? symbolicName : name;
-                        // if there is no symbolic name, resort to location.
-                        name = (name == null) ? bundle.getLocation() : name;
-                        bundleState.setId(bundleId);
-                        bundleState.setName(name);
-                        bundleState.setSymbolicName(symbolicName);
-                        bundleState.setVersion(version);
-                        bundleState.setLocation(bundleLocation);
-
-                        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;
-
-                        bundleState.setStatus(status);
-
-                        clusterBundles.put(id, bundleState);
+                        if (!clusterBundles.containsKey(id)) {
+                            LOGGER.debug("CELLAR BUNDLE: deploying bundle {} on the cluster", id);
+                            BundleState bundleState = new BundleState();
+                            // 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) ? symbolicName : name;
+                            // if there is no symbolic name, resort to location.
+                            name = (name == null) ? bundle.getLocation() : name;
+                            bundleState.setId(bundleId);
+                            bundleState.setName(name);
+                            bundleState.setSymbolicName(symbolicName);
+                            bundleState.setVersion(version);
+                            bundleState.setLocation(bundleLocation);
+                            bundleState.setStatus(status);
+                            // update cluster state
+                            clusterBundles.put(id, bundleState);
+                            // send cluster event
+                            ClusterBundleEvent clusterEvent = new ClusterBundleEvent(symbolicName, version, bundleLocation, status);
+                            clusterEvent.setSourceGroup(group);
+                            clusterEvent.setSourceNode(clusterManager.getNode());
+                            eventProducer.produce(clusterEvent);
+                        } else {
+                            BundleState bundleState = clusterBundles.get(id);
+                            if (bundleState.getStatus() != status) {
+                                LOGGER.debug("CELLAR BUNDLE: updating bundle {} on the cluster", id);
+                                // update cluster state
+                                bundleState.setStatus(status);
+                                clusterBundles.put(id, bundleState);
+                                // send cluster event
+                                ClusterBundleEvent clusterEvent = new ClusterBundleEvent(symbolicName, version, bundleLocation, status);
+                                clusterEvent.setSourceGroup(group);
+                                clusterEvent.setSourceNode(clusterManager.getNode());
+                                eventProducer.produce(clusterEvent);
+                            }
+                        }
 
                     } else LOGGER.trace("CELLAR BUNDLE: bundle {} is marked BLOCKED OUTBOUND for cluster group {}", bundleLocation, groupName);
                 }
diff --git a/bundle/src/main/java/org/apache/karaf/cellar/bundle/LocalBundleListener.java b/bundle/src/main/java/org/apache/karaf/cellar/bundle/LocalBundleListener.java
index c5473f9..c418c60 100644
--- a/bundle/src/main/java/org/apache/karaf/cellar/bundle/LocalBundleListener.java
+++ b/bundle/src/main/java/org/apache/karaf/cellar/bundle/LocalBundleListener.java
@@ -48,17 +48,17 @@
     @Override
     public void bundleChanged(BundleEvent event) {
 
-        if (!isEnabled()) {
-            LOGGER.debug("CELLAR BUNDLE: local listener is disabled");
-            return;
-        }
-
         if (event.getBundle().getBundleId() == 0 && (event.getType() == BundleEvent.STOPPING || event.getType() == BundleEvent.STOPPED)) {
             LOGGER.debug("CELLAR BUNDLE: Karaf shutdown detected, removing Cellar LocalBundleListener");
             bundleContext.removeBundleListener(this);
             return;
         }
 
+        if (!isEnabled()) {
+            LOGGER.trace("CELLAR BUNDLE: local listener is disabled");
+            return;
+        }
+
         if (event.getBundle().getBundleId() == 0) {
             return;
         }
@@ -89,7 +89,7 @@
                     String symbolicName = event.getBundle().getSymbolicName();
                     String version = event.getBundle().getHeaders().get(org.osgi.framework.Constants.BUNDLE_VERSION);
                     String bundleLocation = event.getBundle().getLocation();
-                    int type = event.getType();
+                    int status = event.getBundle().getState();
 
                     if (isAllowed(group, Constants.CATEGORY, bundleLocation, EventType.OUTBOUND)) {
 
@@ -99,7 +99,7 @@
                         try {
                             // update bundles in the cluster group
                             Map<String, BundleState> clusterBundles = clusterManager.getMap(Constants.BUNDLE_MAP + Configurations.SEPARATOR + group.getName());
-                            if (type == BundleEvent.UNINSTALLED) {
+                            if (event.getType() == BundleEvent.UNINSTALLED) {
                                 clusterBundles.remove(symbolicName + "/" + version);
                             } else {
                                 BundleState state = clusterBundles.get(symbolicName + "/" + version);
@@ -110,7 +110,7 @@
                                 state.setName(name);
                                 state.setVersion(version);
                                 state.setSymbolicName(symbolicName);
-                                state.setStatus(type);
+                                state.setStatus(status);
                                 state.setLocation(bundleLocation);
                                 clusterBundles.put(symbolicName + "/" + version, state);
                             }
@@ -125,8 +125,9 @@
             				}
                             
                             // broadcast the cluster event
-                            ClusterBundleEvent clusterBundleEvent = new ClusterBundleEvent(symbolicName, version, bundleLocation, type);
+                            ClusterBundleEvent clusterBundleEvent = new ClusterBundleEvent(symbolicName, version, bundleLocation, status);
                             clusterBundleEvent.setSourceGroup(group);
+                            clusterBundleEvent.setSourceNode(clusterManager.getNode());
                             eventProducer.produce(clusterBundleEvent);
                         } catch (Exception e) {
                         	LOGGER.error("CELLAR BUNDLE: failed to create bundle event", e);
diff --git a/bundle/src/main/java/org/apache/karaf/cellar/bundle/internal/osgi/Activator.java b/bundle/src/main/java/org/apache/karaf/cellar/bundle/internal/osgi/Activator.java
new file mode 100644
index 0000000..d9559e5
--- /dev/null
+++ b/bundle/src/main/java/org/apache/karaf/cellar/bundle/internal/osgi/Activator.java
@@ -0,0 +1,147 @@
+/*
+ * 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.bundle.internal.osgi;
+
+import org.apache.karaf.cellar.bundle.BundleEventHandler;
+import org.apache.karaf.cellar.bundle.BundleSynchronizer;
+import org.apache.karaf.cellar.bundle.LocalBundleListener;
+import org.apache.karaf.cellar.bundle.management.internal.CellarBundleMBeanImpl;
+import org.apache.karaf.cellar.core.ClusterManager;
+import org.apache.karaf.cellar.core.GroupManager;
+import org.apache.karaf.cellar.core.Synchronizer;
+import org.apache.karaf.cellar.core.event.EventHandler;
+import org.apache.karaf.cellar.core.event.EventProducer;
+import org.apache.karaf.features.FeaturesService;
+import org.apache.karaf.util.tracker.BaseActivator;
+import org.apache.karaf.util.tracker.annotation.ProvideService;
+import org.apache.karaf.util.tracker.annotation.RequireService;
+import org.apache.karaf.util.tracker.annotation.Services;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Hashtable;
+
+@Services(
+        provides = {
+                @ProvideService(EventHandler.class),
+                @ProvideService(Synchronizer.class)
+        },
+        requires = {
+                @RequireService(ClusterManager.class),
+                @RequireService(GroupManager.class),
+                @RequireService(ConfigurationAdmin.class),
+                @RequireService(EventProducer.class),
+                @RequireService(FeaturesService.class)
+        }
+)
+public class Activator extends BaseActivator {
+
+    private final static Logger LOGGER = LoggerFactory.getLogger(Activator.class);
+
+    private LocalBundleListener localBundleListener;
+    private BundleSynchronizer synchronizer;
+    private BundleEventHandler eventHandler;
+    private ServiceRegistration mbeanRegistration;
+
+    @Override
+    public void doStart() throws Exception {
+
+        ClusterManager clusterManager = getTrackedService(ClusterManager.class);
+        if (clusterManager == null)
+            return;
+        GroupManager groupManager = getTrackedService(GroupManager.class);
+        if (groupManager == null)
+            return;
+        ConfigurationAdmin configurationAdmin = getTrackedService(ConfigurationAdmin.class);
+        if (configurationAdmin == null)
+            return;
+        EventProducer eventProducer = getTrackedService(EventProducer.class);
+        if (eventProducer == null)
+            return;
+        FeaturesService featuresService = getTrackedService(FeaturesService.class);
+        if (featuresService == null)
+            return;
+
+        LOGGER.debug("CELLAR BUNDLE: init even handler");
+        eventHandler = new BundleEventHandler();
+        eventHandler.setConfigurationAdmin(configurationAdmin);
+        eventHandler.setClusterManager(clusterManager);
+        eventHandler.setGroupManager(groupManager);
+        eventHandler.setBundleContext(bundleContext);
+        eventHandler.setFeaturesService(featuresService);
+        eventHandler.init();
+        Hashtable props = new Hashtable();
+        props.put("managed", "true");
+        register(EventHandler.class, eventHandler, props);
+
+        LOGGER.debug("CELLAR BUNDLE: init local listener");
+        localBundleListener = new LocalBundleListener();
+        localBundleListener.setClusterManager(clusterManager);
+        localBundleListener.setGroupManager(groupManager);
+        localBundleListener.setConfigurationAdmin(configurationAdmin);
+        localBundleListener.setEventProducer(eventProducer);
+        localBundleListener.setFeaturesService(featuresService);
+        localBundleListener.setBundleContext(bundleContext);
+        localBundleListener.init();
+
+        LOGGER.debug("CELLAR BUNDLE: init synchronizer");
+        synchronizer = new BundleSynchronizer();
+        synchronizer.setConfigurationAdmin(configurationAdmin);
+        synchronizer.setGroupManager(groupManager);
+        synchronizer.setClusterManager(clusterManager);
+        synchronizer.setBundleContext(bundleContext);
+        synchronizer.setEventProducer(eventProducer);
+        synchronizer.init();
+        props = new Hashtable();
+        props.put("resource", "bundle");
+        register(Synchronizer.class, synchronizer, props);
+
+        LOGGER.debug("CELLAR BUNDLE: register MBean");
+        CellarBundleMBeanImpl mbean = new CellarBundleMBeanImpl();
+        mbean.setClusterManager(clusterManager);
+        mbean.setConfigurationAdmin(configurationAdmin);
+        mbean.setGroupManager(groupManager);
+        mbean.setEventProducer(eventProducer);
+        mbean.setBundleContext(bundleContext);
+        props = new Hashtable();
+        props.put("jmx.objectname", "org.apache.karaf.cellar:type=bundle,name=" + System.getProperty("karaf.name"));
+        mbeanRegistration = bundleContext.registerService(getInterfaceNames(mbean), mbean, props);
+
+    }
+
+    @Override
+    public void doStop() {
+        super.doStop();
+
+        if (mbeanRegistration != null) {
+            mbeanRegistration.unregister();
+            mbeanRegistration = null;
+        }
+        if (synchronizer != null) {
+            synchronizer.destroy();
+            synchronizer = null;
+        }
+        if (localBundleListener != null) {
+            localBundleListener.destroy();
+            localBundleListener = null;
+        }
+        if (eventHandler != null) {
+            eventHandler.destroy();
+            eventHandler = null;
+        }
+    }
+
+}
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 225b99e..f4ccc63 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
@@ -16,16 +16,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.bundle.shell.BundleCommandSupport;
 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;
 
 import javax.management.NotCompliantMBeanException;
@@ -149,24 +146,23 @@
             state.setId(clusterBundles.size());
             state.setLocation(location);
             if (start) {
-                state.setStatus(BundleEvent.STARTED);
+                state.setStatus(Bundle.ACTIVE);
             } else {
-                state.setStatus(BundleEvent.INSTALLED);
+                state.setStatus(Bundle.INSTALLED);
             }
-            clusterBundles.put(name + "/" + version, state);
+            clusterBundles.put(symbolicName + "/" + version, state);
         } finally {
             Thread.currentThread().setContextClassLoader(originalClassLoader);
         }
 
         // broadcast the event
-        ClusterBundleEvent event = new ClusterBundleEvent(name, version, location, BundleEvent.INSTALLED);
+        ClusterBundleEvent event = new ClusterBundleEvent(symbolicName, version, location, Bundle.INSTALLED);
         event.setSourceGroup(group);
-        eventProducer.produce(event);
         if (start) {
-            event = new ClusterBundleEvent(name, version, location, BundleEvent.STARTED);
+            event = new ClusterBundleEvent(symbolicName, version, location, Bundle.ACTIVE);
             event.setSourceGroup(group);
-            eventProducer.produce(event);
         }
+        eventProducer.produce(event);
     }
 
     @Override
@@ -213,7 +209,7 @@
 
                 // broadcast the cluster event
                 String[] split = bundle.split("/");
-                ClusterBundleEvent event = new ClusterBundleEvent(split[0], split[1], location, BundleEvent.UNINSTALLED);
+                ClusterBundleEvent event = new ClusterBundleEvent(split[0], split[1], location, Bundle.UNINSTALLED);
                 event.setSourceGroup(group);
                 eventProducer.produce(event);
             }
@@ -264,12 +260,12 @@
                 }
 
                 // update the cluster state
-                state.setStatus(BundleEvent.STARTED);
+                state.setStatus(Bundle.ACTIVE);
                 clusterBundles.put(bundle, state);
 
                 // broadcast the cluster event
                 String[] split = bundle.split("/");
-                ClusterBundleEvent event = new ClusterBundleEvent(split[0], split[1], location, BundleEvent.STARTED);
+                ClusterBundleEvent event = new ClusterBundleEvent(split[0], split[1], location, Bundle.ACTIVE);
                 event.setSourceGroup(group);
                 eventProducer.produce(event);
             }
@@ -317,12 +313,12 @@
                 }
 
                 // update the cluster state
-                state.setStatus(BundleEvent.STOPPED);
+                state.setStatus(Bundle.RESOLVED);
                 clusterBundles.put(bundle, state);
 
                 // broadcast the cluster event
                 String[] split = bundle.split("/");
-                ClusterBundleEvent event = new ClusterBundleEvent(split[0], split[1], location, BundleEvent.STOPPED);
+                ClusterBundleEvent event = new ClusterBundleEvent(split[0], split[1], location, Bundle.RESOLVED);
                 event.setSourceGroup(group);
                 eventProducer.produce(event);
             }
@@ -369,9 +365,9 @@
     @Override
     public TabularData getBundles(String groupName) throws Exception {
         CompositeType compositeType = new CompositeType("Bundle", "Karaf Cellar bundle",
-                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});
+                new String[]{"id", "name", "symbolic_name", "version", "status", "location", "located", "blocked"},
+                new String[]{"ID of the bundle", "Name of the bundle", "Symbolic 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.LONG, SimpleType.STRING, 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);
@@ -397,25 +393,22 @@
             for (ExtendedBundleState bundle : bundles) {
                 String status;
                 switch (bundle.getStatus()) {
-                    case BundleEvent.INSTALLED:
+                    case Bundle.INSTALLED:
                         status = "Installed";
                         break;
-                    case BundleEvent.RESOLVED:
+                    case Bundle.RESOLVED:
                         status = "Resolved";
                         break;
-                    case BundleEvent.STARTED:
+                    case Bundle.ACTIVE:
                         status = "Active";
                         break;
-                    case BundleEvent.STARTING:
+                    case Bundle.STARTING:
                         status = "Starting";
                         break;
-                    case BundleEvent.STOPPED:
-                        status = "Resolved";
-                        break;
-                    case BundleEvent.STOPPING:
+                    case Bundle.STOPPING:
                         status = "Stopping";
                         break;
-                    case BundleEvent.UNINSTALLED:
+                    case Bundle.UNINSTALLED:
                         status = "Uninstalled";
                         break;
                     default:
@@ -444,10 +437,12 @@
                     blocked = "out";
 
                 CompositeData data = new CompositeDataSupport(compositeType,
-                        new String[]{"id", "name", "version", "status", "location", "located", "blocked"},
-                        new Object[]{bundle.getId(), bundle.getName(), bundle.getVersion(), status, bundle.getLocation(), located, blocked});
+                        new String[]{"id", "name", "symbolic_name", "version", "status", "location", "located", "blocked"},
+                        new Object[]{bundle.getId(), bundle.getName(), bundle.getSymbolicName(), bundle.getVersion(), status, bundle.getLocation(), located, blocked});
                 table.put(data);
             }
+        } catch (Exception e) {
+            e.printStackTrace();
         } finally {
             Thread.currentThread().setContextClassLoader(originalClassLoader);
         }
@@ -580,7 +575,7 @@
             extendedState.setSymbolicName(state.getSymbolicName());
             extendedState.setStatus(state.getStatus());
             extendedState.setLocation(state.getLocation());
-            extendedState.setData(state.getData());
+            // extendedState.setData(state.getData());
             extendedState.setCluster(true);
             extendedState.setLocal(false);
             bundles.put(key, extendedState);
@@ -588,7 +583,7 @@
 
         // retrieve local bundles
         for (Bundle bundle : bundleContext.getBundles()) {
-            String key = bundle.getSymbolicName() + "/" + bundle.getVersion().toString();
+            String key = bundle.getSymbolicName() + "/" + bundle.getHeaders().get("Bundle-Version").toString();
             if (bundles.containsKey(key)) {
                 ExtendedBundleState extendedState = bundles.get(key);
                 extendedState.setLocal(true);
@@ -603,23 +598,10 @@
                 name = (name == null) ? bundle.getLocation() : name;
                 extendedState.setId(bundle.getBundleId());
                 extendedState.setName(name);
-                extendedState.setVersion(bundle.getVersion().toString());
+                extendedState.setVersion(bundle.getHeaders().get(org.osgi.framework.Constants.BUNDLE_VERSION));
                 extendedState.setSymbolicName(bundle.getSymbolicName());
                 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.setStatus(bundle.getState());
                 extendedState.setCluster(false);
                 extendedState.setLocal(true);
                 bundles.put(key, extendedState);
diff --git a/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/BlockCommand.java b/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/BlockCommand.java
index 098d38a..f655c87 100644
--- a/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/BlockCommand.java
+++ b/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/BlockCommand.java
@@ -18,12 +18,14 @@
 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.Command;
-import org.apache.karaf.shell.commands.Option;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.*;
 
 @Command(scope = "cluster", name = "bundle-block", description = "Change the blocking policy for a bundle")
+@Service
 public class BlockCommand extends BundleCommandSupport {
 
     @Option(name = "-in", description = "Update the inbound direction", required = false, multiValued = false)
@@ -48,85 +50,92 @@
 
         List<String> patterns = new ArrayList<String>();
 
-        Map<String, ExtendedBundleState> bundles = gatherBundles();
-        List<String> selectedBundles = selector(bundles);
-        for (String selectedBundle : selectedBundles) {
-            patterns.add(bundles.get(selectedBundle).getLocation());
-        }
+        ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
+        Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
 
-        if (patterns.isEmpty() && ids != null) {
-            for (String id : ids) {
-                patterns.add(id);
+        try {
+            Map<String, ExtendedBundleState> bundles = gatherBundles(false);
+            List<String> selectedBundles = selector(bundles);
+            for (String selectedBundle : selectedBundles) {
+                patterns.add(bundles.get(selectedBundle).getLocation());
             }
-        }
 
-        CellarSupport support = new CellarSupport();
-        support.setClusterManager(clusterManager);
-        support.setGroupManager(groupManager);
-        support.setConfigurationAdmin(configurationAdmin);
-
-        if (!in && !out) {
-            in = true;
-            out = true;
-        }
-        if (!whitelist && !blacklist) {
-            whitelist = true;
-            blacklist = true;
-        }
-
-        if (patterns.isEmpty()) {
-            // display mode
-            if (in) {
-                System.out.println("INBOUND:");
-                if (whitelist) {
-                    System.out.print("\twhitelist: ");
-                    Set<String> list = support.getListEntries(Configurations.WHITELIST, groupName, Constants.CATEGORY, EventType.INBOUND);
-                    System.out.println(list.toString());
-                }
-                if (blacklist) {
-                    System.out.print("\tblacklist: ");
-                    Set<String> list = support.getListEntries(Configurations.BLACKLIST, groupName, Constants.CATEGORY, EventType.INBOUND);
-                    System.out.println(list.toString());
+            if (patterns.isEmpty() && ids != null) {
+                for (String id : ids) {
+                    patterns.add(id);
                 }
             }
-            if (out) {
-                System.out.println("OUTBOUND:");
-                if (whitelist) {
-                    System.out.print("\twhitelist: ");
-                    Set<String> list = support.getListEntries(Configurations.WHITELIST, groupName, Constants.CATEGORY, EventType.OUTBOUND);
-                    System.out.println(list.toString());
-                }
-                if (blacklist) {
-                    System.out.print("\tblacklist: ");
-                    Set<String> list = support.getListEntries(Configurations.BLACKLIST, groupName, Constants.CATEGORY, EventType.OUTBOUND);
-                    System.out.println(list.toString());
-                }
+
+            CellarSupport support = new CellarSupport();
+            support.setClusterManager(clusterManager);
+            support.setGroupManager(groupManager);
+            support.setConfigurationAdmin(configurationAdmin);
+
+            if (!in && !out) {
+                in = true;
+                out = true;
             }
-        } else {
-            // edit mode
-            for (String pattern : patterns) {
-                System.out.println("Updating blocking policy for " + pattern);
+            if (!whitelist && !blacklist) {
+                whitelist = true;
+                blacklist = true;
+            }
+
+            if (patterns.isEmpty()) {
+                // display mode
                 if (in) {
+                    System.out.println("INBOUND:");
                     if (whitelist) {
-                        System.out.println("\tinbound whitelist ...");
-                        support.switchListEntry(Configurations.WHITELIST, groupName, Constants.CATEGORY, EventType.INBOUND, pattern);
+                        System.out.print("\twhitelist: ");
+                        Set<String> list = support.getListEntries(Configurations.WHITELIST, groupName, Constants.CATEGORY, EventType.INBOUND);
+                        System.out.println(list.toString());
                     }
                     if (blacklist) {
-                        System.out.println("\tinbound blacklist ...");
-                        support.switchListEntry(Configurations.BLACKLIST, groupName, Constants.CATEGORY, EventType.INBOUND, pattern);
+                        System.out.print("\tblacklist: ");
+                        Set<String> list = support.getListEntries(Configurations.BLACKLIST, groupName, Constants.CATEGORY, EventType.INBOUND);
+                        System.out.println(list.toString());
                     }
                 }
                 if (out) {
+                    System.out.println("OUTBOUND:");
                     if (whitelist) {
-                        System.out.println("\toutbound whitelist ...");
-                        support.switchListEntry(Configurations.WHITELIST, groupName, Constants.CATEGORY, EventType.OUTBOUND, pattern);
+                        System.out.print("\twhitelist: ");
+                        Set<String> list = support.getListEntries(Configurations.WHITELIST, groupName, Constants.CATEGORY, EventType.OUTBOUND);
+                        System.out.println(list.toString());
                     }
                     if (blacklist) {
-                        System.out.println("\toutbound blacklist ...");
-                        support.switchListEntry(Configurations.BLACKLIST, groupName, Constants.CATEGORY, EventType.OUTBOUND, pattern);
+                        System.out.print("\tblacklist: ");
+                        Set<String> list = support.getListEntries(Configurations.BLACKLIST, groupName, Constants.CATEGORY, EventType.OUTBOUND);
+                        System.out.println(list.toString());
+                    }
+                }
+            } else {
+                // edit mode
+                for (String pattern : patterns) {
+                    System.out.println("Updating blocking policy for " + pattern);
+                    if (in) {
+                        if (whitelist) {
+                            System.out.println("\tinbound whitelist ...");
+                            support.switchListEntry(Configurations.WHITELIST, groupName, Constants.CATEGORY, EventType.INBOUND, pattern);
+                        }
+                        if (blacklist) {
+                            System.out.println("\tinbound blacklist ...");
+                            support.switchListEntry(Configurations.BLACKLIST, groupName, Constants.CATEGORY, EventType.INBOUND, pattern);
+                        }
+                    }
+                    if (out) {
+                        if (whitelist) {
+                            System.out.println("\toutbound whitelist ...");
+                            support.switchListEntry(Configurations.WHITELIST, groupName, Constants.CATEGORY, EventType.OUTBOUND, pattern);
+                        }
+                        if (blacklist) {
+                            System.out.println("\toutbound blacklist ...");
+                            support.switchListEntry(Configurations.BLACKLIST, groupName, Constants.CATEGORY, EventType.OUTBOUND, pattern);
+                        }
                     }
                 }
             }
+        } finally {
+            Thread.currentThread().setContextClassLoader(originalClassLoader);
         }
 
         return null;
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 ad3b0af..06182ac 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
@@ -15,11 +15,15 @@
 
 import org.apache.karaf.cellar.bundle.BundleState;
 import org.apache.karaf.cellar.bundle.Constants;
+import org.apache.karaf.cellar.bundle.shell.completers.AllBundlesNameCompleter;
 import org.apache.karaf.cellar.core.Configurations;
 import org.apache.karaf.cellar.core.shell.CellarCommandSupport;
-import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.cellar.core.shell.completer.AllGroupsCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
 import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleContext;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -31,11 +35,16 @@
 public abstract class BundleCommandSupport extends CellarCommandSupport {
 
     @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
     String groupName;
 
     @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)
+    @Completion(AllBundlesNameCompleter.class)
     List<String> ids;
 
+    @Reference
+    protected BundleContext bundleContext;
+
     protected abstract Object doExecute() throws Exception;
 
     /**
@@ -158,7 +167,7 @@
         }
     }
 
-    protected Map<String, ExtendedBundleState> gatherBundles() {
+    protected Map<String, ExtendedBundleState> gatherBundles(boolean clusterOnly) {
         Map<String, ExtendedBundleState> bundles = new HashMap<String, ExtendedBundleState>();
 
         // retrieve bundles from the cluster
@@ -177,6 +186,9 @@
             bundles.put(key, extendedState);
         }
 
+        if (clusterOnly)
+            return bundles;
+
         // retrieve local bundles
         for (Bundle bundle : bundleContext.getBundles()) {
             String version = (String) bundle.getHeaders().get(org.osgi.framework.Constants.BUNDLE_VERSION);
@@ -195,22 +207,9 @@
                 name = (name == null) ? bundle.getLocation() : name;
                 extendedState.setId(bundle.getBundleId());
                 extendedState.setName(name);
-                extendedState.setVersion(bundle.getVersion().toString());
+                extendedState.setVersion(bundle.getHeaders().get(org.osgi.framework.Constants.BUNDLE_VERSION));
                 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.setStatus(bundle.getState());
                 extendedState.setCluster(false);
                 extendedState.setLocal(true);
                 bundles.put(key, extendedState);
diff --git a/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/InstallBundleCommand.java b/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/InstallBundleCommand.java
index dfbc62b..a347ae5 100644
--- a/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/InstallBundleCommand.java
+++ b/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/InstallBundleCommand.java
@@ -23,10 +23,14 @@
 import org.apache.karaf.cellar.core.event.EventProducer;
 import org.apache.karaf.cellar.core.event.EventType;
 import org.apache.karaf.cellar.core.shell.CellarCommandSupport;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.osgi.framework.BundleEvent;
+import org.apache.karaf.cellar.core.shell.completer.AllGroupsCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.osgi.framework.Bundle;
 
 import java.net.URL;
 import java.util.List;
@@ -35,9 +39,11 @@
 import java.util.jar.Manifest;
 
 @Command(scope = "cluster", name = "bundle-install", description = "Install bundles in a cluster group")
+@Service
 public class InstallBundleCommand extends CellarCommandSupport {
 
     @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
     String groupName;
 
     @Argument(index = 1, name = "urls", description = "Bundle URLs separated by whitespace", required = true, multiValued = true)
@@ -46,6 +52,7 @@
     @Option(name = "-s", aliases = {"--start"}, description = "Start the bundle after installation", required = false, multiValued = false)
     boolean start;
 
+    @Reference
     private EventProducer eventProducer;
 
     @Override
@@ -103,9 +110,9 @@
                     state.setId(clusterBundles.size());
                     state.setLocation(url);
                     if (start) {
-                        state.setStatus(BundleEvent.STARTED);
+                        state.setStatus(Bundle.ACTIVE);
                     } else {
-                        state.setStatus(BundleEvent.INSTALLED);
+                        state.setStatus(Bundle.INSTALLED);
                     }
                     clusterBundles.put(symbolicName + "/" + version, state);
                 } finally {
@@ -113,14 +120,15 @@
                 }
 
                 // broadcast the cluster event
-                ClusterBundleEvent event = new ClusterBundleEvent(symbolicName, version, url, BundleEvent.INSTALLED);
-                event.setSourceGroup(group);
-                eventProducer.produce(event);
+                ClusterBundleEvent event;
                 if (start) {
-                    event = new ClusterBundleEvent(symbolicName, version, url, BundleEvent.STARTED);
+                    event = new ClusterBundleEvent(symbolicName, version, url, Bundle.ACTIVE);
                     event.setSourceGroup(group);
-                    eventProducer.produce(event);
+                } else {
+                    event = new ClusterBundleEvent(symbolicName, version, url, Bundle.INSTALLED);
+                    event.setSourceGroup(group);
                 }
+                eventProducer.produce(event);
             } else {
                 System.err.println("Bundle location " + url + " is blocked outbound for cluster group " + groupName);
             }
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 165d5de..34a6484 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
@@ -18,14 +18,16 @@
 import org.apache.karaf.cellar.core.CellarSupport;
 import org.apache.karaf.cellar.core.Group;
 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;
-import org.osgi.framework.BundleEvent;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.table.ShellTable;
+import org.osgi.framework.Bundle;
 
 import java.util.*;
 
 @Command(scope = "cluster", name = "bundle-list", description = "List the bundles in a cluster group")
+@Service
 public class ListBundleCommand extends BundleCommandSupport {
 
     @Option(name = "-s", aliases = {}, description = "Shows the symbolic name", required = false, multiValued = false)
@@ -61,7 +63,7 @@
         Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
 
         try {
-            Map<String, ExtendedBundleState> allBundles = gatherBundles();
+            Map<String, ExtendedBundleState> allBundles = gatherBundles(false);
             if (allBundles != null && !allBundles.isEmpty()) {
                 System.out.println(String.format("Bundles in cluster group " + groupName));
 
@@ -85,25 +87,22 @@
                 for (ExtendedBundleState bundle : bundles) {
                     String status;
                     switch (bundle.getStatus()) {
-                        case BundleEvent.INSTALLED:
+                        case Bundle.INSTALLED:
                             status = "Installed";
                             break;
-                        case BundleEvent.RESOLVED:
+                        case Bundle.RESOLVED:
                             status = "Resolved";
                             break;
-                        case BundleEvent.STARTED:
+                        case Bundle.ACTIVE:
                             status = "Active";
                             break;
-                        case BundleEvent.STARTING:
+                        case Bundle.STARTING:
                             status = "Starting";
                             break;
-                        case BundleEvent.STOPPED:
-                            status = "Resolved";
-                            break;
-                        case BundleEvent.STOPPING:
+                        case Bundle.STOPPING:
                             status = "Stopping";
                             break;
-                        case BundleEvent.UNINSTALLED:
+                        case Bundle.UNINSTALLED:
                             status = "Uninstalled";
                             break;
                         default:
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 552d753..eba376d 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
@@ -22,15 +22,19 @@
 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.shell.commands.Command;
-import org.osgi.framework.BundleEvent;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.osgi.framework.Bundle;
 
 import java.util.List;
 import java.util.Map;
 
 @Command(scope = "cluster", name = "bundle-start", description = "Start bundles in a cluster group")
+@Service
 public class StartBundleCommand extends BundleCommandSupport {
 
+    @Reference
     private EventProducer eventProducer;
 
     @Override
@@ -55,7 +59,7 @@
         try {
             Map<String, BundleState> clusterBundles = clusterManager.getMap(Constants.BUNDLE_MAP + Configurations.SEPARATOR + groupName);
 
-            List<String> bundles = selector(gatherBundles());
+            List<String> bundles = selector(gatherBundles(true));
 
             for (String bundle : bundles) {
                 BundleState state = clusterBundles.get(bundle);
@@ -74,12 +78,12 @@
                 }
 
                 // update the cluster state
-                state.setStatus(BundleEvent.STARTED);
+                state.setStatus(Bundle.ACTIVE);
                 clusterBundles.put(bundle, state);
 
                 // broadcast the cluster event
                 String[] split = bundle.split("/");
-                ClusterBundleEvent event = new ClusterBundleEvent(split[0], split[1], location, BundleEvent.STARTED);
+                ClusterBundleEvent event = new ClusterBundleEvent(split[0], split[1], location, Bundle.ACTIVE);
                 event.setSourceGroup(group);
                 eventProducer.produce(event);
             }
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 d91aa9a..5fd70f8 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
@@ -22,14 +22,19 @@
 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.shell.commands.Command;
-import org.osgi.framework.BundleEvent;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.osgi.framework.Bundle;
 
 import java.util.List;
 import java.util.Map;
 
 @Command(scope = "cluster", name = "bundle-stop", description = "Stop a bundle in a cluster group")
+@Service
 public class StopBundleCommand extends BundleCommandSupport {
+
+    @Reference
     private EventProducer eventProducer;
 
     @Override
@@ -54,7 +59,7 @@
         try {
             Map<String, BundleState> clusterBundles = clusterManager.getMap(Constants.BUNDLE_MAP + Configurations.SEPARATOR + groupName);
 
-            List<String> bundles = selector(gatherBundles());
+            List<String> bundles = selector(gatherBundles(true));
 
             for (String bundle : bundles) {
                 BundleState state = clusterBundles.get(bundle);
@@ -73,12 +78,12 @@
                 }
 
                 // update the cluster state
-                state.setStatus(BundleEvent.STOPPED);
+                state.setStatus(Bundle.RESOLVED);
                 clusterBundles.put(bundle, state);
 
                 // broadcast the cluster event
                 String[] split = bundle.split("/");
-                ClusterBundleEvent event = new ClusterBundleEvent(split[0], split[1], location, BundleEvent.STOPPED);
+                ClusterBundleEvent event = new ClusterBundleEvent(split[0], split[1], location, Bundle.RESOLVED);
                 event.setSourceGroup(group);
                 eventProducer.produce(event);
             }
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 760a58a..a6616f3 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
@@ -22,15 +22,19 @@
 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.shell.commands.Command;
-import org.osgi.framework.BundleEvent;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.osgi.framework.Bundle;
 
 import java.util.List;
 import java.util.Map;
 
 @Command(scope = "cluster", name = "bundle-uninstall", description = "Uninstall a bundle from a cluster group")
+@Service
 public class UninstallBundleCommand extends BundleCommandSupport {
 
+    @Reference
     private EventProducer eventProducer;
 
     @Override
@@ -55,7 +59,7 @@
         try {
             Map<String, BundleState> clusterBundles = clusterManager.getMap(Constants.BUNDLE_MAP + Configurations.SEPARATOR + groupName);
 
-            List<String> bundles = selector(gatherBundles());
+            List<String> bundles = selector(gatherBundles(true));
 
             for (String bundle : bundles) {
                 BundleState state = clusterBundles.get(bundle);
@@ -78,7 +82,7 @@
 
                 // broadcast the cluster event
                 String[] split = bundle.split("/");
-                ClusterBundleEvent event = new ClusterBundleEvent(split[0], split[1], location, BundleEvent.UNINSTALLED);
+                ClusterBundleEvent event = new ClusterBundleEvent(split[0], split[1], location, Bundle.UNINSTALLED);
                 event.setSourceGroup(group);
                 eventProducer.produce(event);
             }
diff --git a/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/completers/AllBundlesNameCompleter.java b/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/completers/AllBundlesNameCompleter.java
index 2da8af3..1719299 100644
--- a/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/completers/AllBundlesNameCompleter.java
+++ b/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/completers/AllBundlesNameCompleter.java
@@ -13,8 +13,12 @@
  */
 package org.apache.karaf.cellar.bundle.shell.completers;
 
-import org.apache.karaf.shell.console.Completer;
-import org.apache.karaf.shell.console.completer.StringsCompleter;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 
@@ -23,16 +27,18 @@
 /**
  * Completer on all bundle symbolic name.
  */
+@Service
 public class AllBundlesNameCompleter implements Completer {
 
+    @Reference
     private BundleContext bundleContext;
 
-    public int complete(String buffer, int cursor, List<String> candidates) {
+    public int complete(Session session, CommandLine commandLine, List<String> candidates) {
         StringsCompleter delegate = new StringsCompleter();
         for (Bundle bundle : bundleContext.getBundles()) {
             delegate.getStrings().add(bundle.getSymbolicName());
         }
-        return delegate.complete(buffer, cursor, candidates);
+        return delegate.complete(session, commandLine, candidates);
     }
 
     public BundleContext getBundleContext() {
diff --git a/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/completers/AllBundlesVersionCompleter.java b/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/completers/AllBundlesVersionCompleter.java
index 7ffba00..4c6a00c 100644
--- a/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/completers/AllBundlesVersionCompleter.java
+++ b/bundle/src/main/java/org/apache/karaf/cellar/bundle/shell/completers/AllBundlesVersionCompleter.java
@@ -13,8 +13,12 @@
  */
 package org.apache.karaf.cellar.bundle.shell.completers;
 
-import org.apache.karaf.shell.console.Completer;
-import org.apache.karaf.shell.console.completer.StringsCompleter;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 
@@ -23,16 +27,18 @@
 /**
  * Completer on all bundle version..
  */
+@Service
 public class AllBundlesVersionCompleter implements Completer {
 
+    @Reference
     private BundleContext bundleContext;
 
-    public int complete(String buffer, int cursor, List<String> candidates) {
+    public int complete(Session session, CommandLine commandLine, List<String> candidates) {
         StringsCompleter delegate = new StringsCompleter();
         for (Bundle bundle : bundleContext.getBundles()) {
-            delegate.getStrings().add(bundle.getVersion().toString());
+            delegate.getStrings().add(bundle.getHeaders().get("Bundle-Version").toString());
         }
-        return delegate.complete(buffer, cursor, candidates);
+        return delegate.complete(session, commandLine, candidates);
     }
 
     public BundleContext getBundleContext() {
diff --git a/bundle/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/bundle/src/main/resources/OSGI-INF/blueprint/blueprint.xml
deleted file mode 100644
index ec986e7..0000000
--- a/bundle/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ /dev/null
@@ -1,67 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  ~ 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.
-  -->
-<blueprint default-availability="mandatory"
-    xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0
-           http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">
-
-    <!-- Local Bundle Listener -->
-    <bean id="localListener" class="org.apache.karaf.cellar.bundle.LocalBundleListener" init-method="init"
-          destroy-method="destroy">
-        <property name="clusterManager" ref="clusterManager"/>
-        <property name="groupManager" ref="groupManager"/>
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-        <property name="bundleContext" ref="blueprintBundleContext"/>
-        <property name="eventProducer" ref="eventProducer"/>
-        <property name="featuresService" ref="featuresService"/>
-    </bean>
-
-    <!-- Bundle Synchronizer -->
-    <bean id="synchronizer" class="org.apache.karaf.cellar.bundle.BundleSynchronizer"
-          init-method="init" destroy-method="destroy" depends-on="eventHandler">
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-        <property name="groupManager" ref="groupManager"/>
-        <property name="clusterManager" ref="clusterManager"/>
-        <property name="bundleContext" ref="blueprintBundleContext"/>
-    </bean>
-    <service ref="synchronizer" interface="org.apache.karaf.cellar.core.Synchronizer">
-        <service-properties>
-            <entry key="resource" value="bundle"/>
-        </service-properties>
-    </service>
-
-    <!-- Cluster Bundle Event Handler -->
-    <bean id="eventHandler" class="org.apache.karaf.cellar.bundle.BundleEventHandler"
-          init-method="init" destroy-method="destroy">
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-        <property name="clusterManager" ref="clusterManager"/>
-        <property name="bundleContext" ref="blueprintBundleContext"/>
-        <property name="groupManager" ref="groupManager"/>
-        <property name="featuresService" ref="featuresService"/>
-    </bean>
-    <service ref="eventHandler" interface="org.apache.karaf.cellar.core.event.EventHandler">
-        <service-properties>
-            <entry key="managed" value="true"/>
-        </service-properties>
-    </service>
-
-    <!-- Cluster Core Services -->
-    <reference id="clusterManager" interface="org.apache.karaf.cellar.core.ClusterManager"/>
-    <reference id="groupManager" interface="org.apache.karaf.cellar.core.GroupManager" />
-    <reference id="configurationAdmin" interface="org.osgi.service.cm.ConfigurationAdmin"/>
-    <reference id="eventProducer" interface="org.apache.karaf.cellar.core.event.EventProducer"/>
-    <reference id="featuresService" interface="org.apache.karaf.features.FeaturesService"/>
-
-</blueprint>
diff --git a/bundle/src/main/resources/OSGI-INF/blueprint/management.xml b/bundle/src/main/resources/OSGI-INF/blueprint/management.xml
deleted file mode 100644
index 02ceeb7..0000000
--- a/bundle/src/main/resources/OSGI-INF/blueprint/management.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  ~ 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.
-  -->
-<blueprint default-availability="mandatory"
-           xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
-           xmlns:ext="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0">
-
-    <!-- system properties -->
-    <ext:property-placeholder placeholder-prefix="$[" placeholder-suffix="]" />
-
-    <!-- Cellar Bundle MBean -->
-    <bean id="cellarBundleMBean" class="org.apache.karaf.cellar.bundle.management.internal.CellarBundleMBeanImpl">
-        <property name="clusterManager" ref="clusterManager"/>
-        <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>
-            <entry key="jmx.objectname" value="org.apache.karaf.cellar:type=bundle,name=$[karaf.name]"/>
-        </service-properties>
-    </service>
-
-</blueprint>
\ No newline at end of file
diff --git a/bundle/src/main/resources/OSGI-INF/blueprint/shell-bundle.xml b/bundle/src/main/resources/OSGI-INF/blueprint/shell-bundle.xml
deleted file mode 100644
index f8862e8..0000000
--- a/bundle/src/main/resources/OSGI-INF/blueprint/shell-bundle.xml
+++ /dev/null
@@ -1,104 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-   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.
--->
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" default-activation="lazy">
-
-    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
-        <command>
-            <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"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.bundle.shell.InstallBundleCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-                <property name="eventProducer" ref="eventProducer"/>
-                <property name="configurationAdmin" ref="configurationAdmin"/>
-            </action>
-            <completers>
-                <ref component-id="allGroupsCompleter"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.bundle.shell.UninstallBundleCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-                <property name="eventProducer" ref="eventProducer"/>
-                <property name="configurationAdmin" ref="configurationAdmin"/>
-            </action>
-            <completers>
-                <ref component-id="allGroupsCompleter"/>
-                <ref component-id="allBundlesNameCompleter"/>
-                <ref component-id="allBundlesVersionCompleter"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.bundle.shell.StartBundleCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-                <property name="eventProducer" ref="eventProducer"/>
-                <property name="configurationAdmin" ref="configurationAdmin"/>
-            </action>
-            <completers>
-                <ref component-id="allGroupsCompleter"/>
-                <ref component-id="allBundlesNameCompleter"/>
-                <ref component-id="allBundlesVersionCompleter"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.bundle.shell.StopBundleCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-                <property name="eventProducer" ref="eventProducer"/>
-                <property name="configurationAdmin" ref="configurationAdmin"/>
-            </action>
-            <completers>
-                <ref component-id="allGroupsCompleter"/>
-                <ref component-id="allBundlesNameCompleter"/>
-                <ref component-id="allBundlesVersionCompleter"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.bundle.shell.BlockCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-                <property name="configurationAdmin" ref="configurationAdmin"/>
-            </action>
-            <completers>
-                <ref component-id="allGroupsCompleter"/>
-                <ref component-id="allBundlesNameCompleter"/>
-                <ref component-id="allBundlesVersionCompleter"/>
-            </completers>
-        </command>
-    </command-bundle>
-
-    <bean id="allGroupsCompleter" class="org.apache.karaf.cellar.core.shell.completer.AllGroupsCompleter">
-        <property name="groupManager" ref="groupManager"/>
-    </bean>
-
-    <bean id="allBundlesNameCompleter" class="org.apache.karaf.cellar.bundle.shell.completers.AllBundlesNameCompleter">
-        <property name="bundleContext" ref="blueprintBundleContext"/>
-    </bean>
-
-    <bean id="allBundlesVersionCompleter" class="org.apache.karaf.cellar.bundle.shell.completers.AllBundlesVersionCompleter">
-        <property name="bundleContext" ref="blueprintBundleContext"/>
-    </bean>
-
-</blueprint>
\ No newline at end of file
diff --git a/cloud/NOTICE b/cloud/NOTICE
index addb35c..64cb235 100644
--- a/cloud/NOTICE
+++ b/cloud/NOTICE
@@ -1,5 +1,5 @@
 Apache Karaf Cellar

-Copyright 2011-2014 The Apache Software Foundation

+Copyright 2011-2015 The Apache Software Foundation

 

 I. Used Software

 

diff --git a/cloud/pom.xml b/cloud/pom.xml
index 856f0c5..3ad433e 100644
--- a/cloud/pom.xml
+++ b/cloud/pom.xml
@@ -40,6 +40,10 @@
             <groupId>org.apache.karaf.cellar</groupId>
             <artifactId>org.apache.karaf.cellar.core</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.util</artifactId>
+        </dependency>
 
         <!-- JClouds Dependencies -->
         <dependency>
@@ -89,22 +93,27 @@
     <build>
         <plugins>
             <plugin>
+                <groupId>org.apache.karaf.tooling</groupId>
+                <artifactId>karaf-services-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
                 <configuration>
                     <instructions>
                         <Export-Package>
-                            org.apache.karaf.cellar.cloud*;version="${project.version}"
+                            !org.apache.karaf.cellar.cloud.internal.osgi,
+                            org.apache.karaf.cellar.cloud*
                         </Export-Package>
                         <Import-Package>
-                            org.joda.time;version="${joda-time.version}",
-                            org.joda.time.chrono;version="${joda-time.version}",
-                            org.apache.karaf.cellar.core*;version="${project.version}",
-                            org.osgi*,
-                            org.slf4j;version="[1.6,2)";resolution:=optional
+                            org.slf4j;version="[1.6,2)";resolution:=optional,
+                            *
                         </Import-Package>
-                        <DynamicImport-Package>javax.*,org.w3c.*,org.xml.*,org.jclouds.*,*</DynamicImport-Package>
-                        <Bundle-Activator>org.apache.karaf.cellar.cloud.Activator</Bundle-Activator>
+                        <Private-Package>
+                            org.apache.karaf.cellar.cloud.internal.osgi,
+                            org.apache.karaf.util.tracker;-split-package:=merge-first
+                        </Private-Package>
+                        <DynamicImport-Package>org.jclouds.*</DynamicImport-Package>
                     </instructions>
                 </configuration>
             </plugin>
diff --git a/cloud/src/main/java/org/apache/karaf/cellar/cloud/Activator.java b/cloud/src/main/java/org/apache/karaf/cellar/cloud/Activator.java
deleted file mode 100644
index b8f253a..0000000
--- a/cloud/src/main/java/org/apache/karaf/cellar/cloud/Activator.java
+++ /dev/null
@@ -1,46 +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.cloud;
-
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.ServiceRegistration;
-import org.osgi.service.cm.ManagedServiceFactory;
-
-import java.util.Hashtable;
-
-/**
- * Cloud bundle activator.
- */
-public class Activator implements BundleActivator {
-
-    private ServiceRegistration serviceRegistration;
-
-    @Override
-    public void start(BundleContext context) throws Exception {
-        Hashtable<String, Object> properties = new Hashtable<String, Object>();
-        properties.put(Constants.SERVICE_PID, "org.apache.karaf.cellar.cloud");
-        BlobStoreDiscoveryServiceFactory blobStoreDiscoveryServiceFactory = new BlobStoreDiscoveryServiceFactory(context);
-        serviceRegistration = context.registerService(ManagedServiceFactory.class.getName(), blobStoreDiscoveryServiceFactory, properties);
-    }
-
-    @Override
-    public void stop(BundleContext context) throws Exception {
-        if (serviceRegistration != null) {
-            serviceRegistration.unregister();
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/cloud/src/main/java/org/apache/karaf/cellar/cloud/internal/osgi/Activator.java b/cloud/src/main/java/org/apache/karaf/cellar/cloud/internal/osgi/Activator.java
new file mode 100644
index 0000000..cbe0f56
--- /dev/null
+++ b/cloud/src/main/java/org/apache/karaf/cellar/cloud/internal/osgi/Activator.java
@@ -0,0 +1,53 @@
+/*
+ * 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.cloud.internal.osgi;
+
+import org.apache.karaf.cellar.cloud.BlobStoreDiscoveryServiceFactory;
+import org.apache.karaf.util.tracker.BaseActivator;
+import org.apache.karaf.util.tracker.annotation.ProvideService;
+import org.apache.karaf.util.tracker.annotation.Services;
+import org.osgi.framework.Constants;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Hashtable;
+
+/**
+ * Cloud bundle activator.
+ */
+@Services(
+        provides = {
+                @ProvideService(ManagedServiceFactory.class)
+        }
+)
+public class Activator extends BaseActivator {
+
+    private final static Logger LOGGER = LoggerFactory.getLogger(Activator.class);
+
+    @Override
+    public void doStart() throws Exception {
+        LOGGER.debug("CELLAR CLOUD: init discovery service factory");
+        Hashtable<String, Object> properties = new Hashtable<String, Object>();
+        properties.put(Constants.SERVICE_PID, "org.apache.karaf.cellar.cloud");
+        BlobStoreDiscoveryServiceFactory blobStoreDiscoveryServiceFactory = new BlobStoreDiscoveryServiceFactory(bundleContext);
+        register(ManagedServiceFactory.class, blobStoreDiscoveryServiceFactory, properties);
+    }
+
+    @Override
+    public void doStop() {
+        super.doStop();
+    }
+
+}
\ No newline at end of file
diff --git a/config/NOTICE b/config/NOTICE
index addb35c..64cb235 100644
--- a/config/NOTICE
+++ b/config/NOTICE
@@ -1,5 +1,5 @@
 Apache Karaf Cellar

-Copyright 2011-2014 The Apache Software Foundation

+Copyright 2011-2015 The Apache Software Foundation

 

 I. Used Software

 

diff --git a/config/pom.xml b/config/pom.xml
index 591aa1d..0b90d17 100644
--- a/config/pom.xml
+++ b/config/pom.xml
@@ -42,6 +42,14 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.apache.felix</groupId>
             <artifactId>org.apache.felix.utils</artifactId>
         </dependency>
@@ -67,28 +75,29 @@
     <build>
         <plugins>
             <plugin>
+                <groupId>org.apache.karaf.tooling</groupId>
+                <artifactId>karaf-services-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
                 <configuration>
                     <instructions>
                         <Export-Package>
-                            org.apache.karaf.cellar.config*;version="${project.version}"
+                            !org.apache.karaf.cellar.config.internal.osgi,
+                            !org.apache.karaf.cellar.config.management.internal,
+                            org.apache.karaf.cellar.config*
                         </Export-Package>
                         <Import-Package>
-                            org.apache.karaf.cellar.core*;version="${project.version}",
-                            org.apache.felix.service.command,
-                            org.apache.felix.gogo.commands,
-                            org.apache.karaf.shell.console;version="[3,5)",
-                            org.apache.karaf.shell.console.completer;version="[3,5)",
-                            org.apache.karaf.shell.console.commands;version="[3,5)",
-                            org.apache.karaf.shell.commands;version="[3,5)",
-                            org.osgi*,
-                            org.slf4j;version="[1.6,2)";resolution:=optional
+                            org.slf4j;version="[1.6,2)";resolution:=optional,
+                            org.apache.karaf.shell*;resolution:=optional,
+                            *
                         </Import-Package>
-                        <DynamicImport-Package>javax.*,org.w3c.*,org.xml.*,*</DynamicImport-Package>
                         <Private-Package>
+                            org.apache.karaf.cellar.config.internal.osgi,
                             org.apache.karaf.cellar.config.management.internal,
-                            org.apache.felix.utils*;-split-package:=merge-first
+                            org.apache.felix.utils*;-split-package:=merge-first,
+                            org.apache.karaf.util.tracker;-split-package:=merge-first
                         </Private-Package>
                     </instructions>
                 </configuration>
diff --git a/config/src/main/java/org/apache/karaf/cellar/config/ConfigurationEventHandler.java b/config/src/main/java/org/apache/karaf/cellar/config/ConfigurationEventHandler.java
index 9382414..682bc2c 100644
--- a/config/src/main/java/org/apache/karaf/cellar/config/ConfigurationEventHandler.java
+++ b/config/src/main/java/org/apache/karaf/cellar/config/ConfigurationEventHandler.java
@@ -62,6 +62,12 @@
             return;
         }
 
+        // check if it's not a "local" event
+        if (event.getSourceNode() != null && event.getSourceNode().getId().equalsIgnoreCase(clusterManager.getNode().getId())) {
+            LOGGER.trace("CELLAR CONFIG: cluster event is local (coming from local synchronizer or listener)");
+            return;
+        }
+
         Group group = event.getSourceGroup();
         String groupName = group.getName();
 
diff --git a/config/src/main/java/org/apache/karaf/cellar/config/ConfigurationSynchronizer.java b/config/src/main/java/org/apache/karaf/cellar/config/ConfigurationSynchronizer.java
index 307ae19..79b0ade 100644
--- a/config/src/main/java/org/apache/karaf/cellar/config/ConfigurationSynchronizer.java
+++ b/config/src/main/java/org/apache/karaf/cellar/config/ConfigurationSynchronizer.java
@@ -38,11 +38,15 @@
 
     private static final transient Logger LOGGER = LoggerFactory.getLogger(ConfigurationSynchronizer.class);
 
-    public ConfigurationSynchronizer() {
-        // nothing to do
+    private EventProducer eventProducer;
+
+    public void setEventProducer(EventProducer eventProducer) {
+        this.eventProducer = eventProducer;
     }
 
     public void init() {
+        if (groupManager == null)
+            return;
         Set<Group> groups = groupManager.listLocalGroups();
         if (groups != null && !groups.isEmpty()) {
             for (Group group : groups) {
@@ -63,19 +67,32 @@
     @Override
     public void sync(Group group) {
         String policy = getSyncPolicy(group);
-        if (policy != null && policy.equalsIgnoreCase("cluster")) {
-            LOGGER.debug("CELLAR CONFIG: sync policy is set as 'cluster' for cluster group " + group.getName());
-            if (clusterManager.listNodesByGroup(group).size() == 1 && clusterManager.listNodesByGroup(group).contains(clusterManager.getNode())) {
-                LOGGER.debug("CELLAR CONFIG: node is the first and only member of the group, pushing state");
-                push(group);
-            } else {
-                LOGGER.debug("CELLAR CONFIG: pulling state");
-                pull(group);
-            }
+        if (policy == null) {
+            LOGGER.warn("CELLAR CONFIG: sync policy is not defined for cluster group {}", group.getName());
         }
-        if (policy != null && policy.equalsIgnoreCase("node")) {
-            LOGGER.debug("CELLAR CONFIG: sync policy is set as 'node' for cluster group " + group.getName());
+        if (policy.equalsIgnoreCase("cluster")) {
+            LOGGER.debug("CELLAR CONFIG: sync policy set as 'cluster' for cluster group {}", group.getName());
+            LOGGER.debug("CELLAR CONFIG: updating node from the cluster (pull first)");
+            pull(group);
+            LOGGER.debug("CELLAR CONFIG: updating cluster from the local node (push after)");
             push(group);
+        } else if (policy.equalsIgnoreCase("node")) {
+            LOGGER.debug("CELLAR CONFIG: sync policy set as 'node' for cluster group {}", group.getName());
+            LOGGER.debug("CELLAR CONFIG: updating cluster from the local node (push first)");
+            push(group);
+            LOGGER.debug("CELLAR CONFIG: updating node from the cluster (pull after)");
+            pull(group);
+        } else if (policy.equalsIgnoreCase("clusterOnly")) {
+            LOGGER.debug("CELLAR CONFIG: sync policy set as 'clusterOnly' for cluster group " + group.getName());
+            LOGGER.debug("CELLAR CONFIG: updating node from the cluster (pull only)");
+            pull(group);
+        } else if (policy.equalsIgnoreCase("nodeOnly")) {
+            LOGGER.debug("CELLAR CONFIG: sync policy set as 'nodeOnly' for cluster group " + group.getName());
+            LOGGER.debug("CELLAR CONFIG: updating cluster from the local node (push only)");
+            push(group);
+        } else {
+            LOGGER.debug("CELLAR CONFIG: sync policy set as 'disabled' for cluster group " + group.getName());
+            LOGGER.debug("CELLAR CONFIG: no sync");
         }
     }
 
@@ -107,6 +124,7 @@
 
                             localDictionary = filter(localDictionary);
                             if (!equals(clusterDictionary, localDictionary)) {
+                                LOGGER.debug("CELLAR CONFIG: updating configration {} on node", pid);
                                 localConfiguration.update((Dictionary) clusterDictionary);
                                 persistConfiguration(configurationAdmin, pid, clusterDictionary);
                             }
@@ -128,6 +146,11 @@
      */
     public void push(Group group) {
 
+        if (eventProducer.getSwitch().getStatus().equals(SwitchStatus.OFF)) {
+            LOGGER.warn("CELLAR CONFIG: cluster event producer is OFF");
+            return;
+        }
+
         if (group != null) {
             String groupName = group.getName();
             LOGGER.debug("CELLAR CONFIG: pushing configurations to cluster group {}", groupName);
@@ -145,8 +168,28 @@
                         if (isAllowed(group, Constants.CATEGORY, pid, EventType.OUTBOUND)) {
                             Dictionary localDictionary = localConfiguration.getProperties();
                             localDictionary = filter(localDictionary);
-                            // update the configurations in the cluster group
-                            clusterConfigurations.put(pid, dictionaryToProperties(localDictionary));
+                            if (!clusterConfigurations.containsKey(pid)) {
+                                LOGGER.debug("CELLAR CONFIG: creating configuration pid {} on the cluster", pid);
+                                // update cluster configurations
+                                clusterConfigurations.put(pid, dictionaryToProperties(localDictionary));
+                                // send cluster event
+                                ClusterConfigurationEvent event = new ClusterConfigurationEvent(pid);
+                                event.setSourceGroup(group);
+                                event.setSourceNode(clusterManager.getNode());
+                                eventProducer.produce(event);
+                            } else {
+                                Dictionary clusterDictionary = clusterConfigurations.get(pid);
+                                if (!equals(clusterDictionary, localDictionary)) {
+                                    LOGGER.debug("CELLAR CONFIG: updating configuration pid {} on the cluster", pid);
+                                    // update cluster configurations
+                                    clusterConfigurations.put(pid, dictionaryToProperties(localDictionary));
+                                    // send cluster event
+                                    ClusterConfigurationEvent event = new ClusterConfigurationEvent(pid);
+                                    event.setSourceGroup(group);
+                                    event.setSourceNode(clusterManager.getNode());
+                                    eventProducer.produce(event);
+                                }
+                            }
                         } else
                             LOGGER.trace("CELLAR CONFIG: configuration with PID {} is marked BLOCKED OUTBOUND for cluster group {}", pid, groupName);
                     }
diff --git a/config/src/main/java/org/apache/karaf/cellar/config/LocalConfigurationListener.java b/config/src/main/java/org/apache/karaf/cellar/config/LocalConfigurationListener.java
index 8846e70..67fb201 100644
--- a/config/src/main/java/org/apache/karaf/cellar/config/LocalConfigurationListener.java
+++ b/config/src/main/java/org/apache/karaf/cellar/config/LocalConfigurationListener.java
@@ -45,7 +45,7 @@
     public void configurationEvent(ConfigurationEvent event) {
 
         if (!isEnabled()) {
-            LOGGER.debug("CELLAR CONFIG: local listener is disabled");
+            LOGGER.trace("CELLAR CONFIG: local listener is disabled");
             return;
         }
 
@@ -72,7 +72,7 @@
                             if (clusterConfigurations.containsKey(pid)) {
                                 // update the configurations in the cluster group
                                 clusterConfigurations.remove(pid);
-                                // broadcast the cluster event
+                                // send the cluster event
                                 ClusterConfigurationEvent clusterConfigurationEvent = new ClusterConfigurationEvent(pid);
                                 clusterConfigurationEvent.setType(event.getType());
                                 clusterConfigurationEvent.setSourceNode(clusterManager.getNode());
@@ -91,7 +91,7 @@
                             if (!equals(localDictionary, distributedDictionary)) {
                                 // update the configurations in the cluster group
                                 clusterConfigurations.put(pid, dictionaryToProperties(localDictionary));
-                                // broadcast the cluster event
+                                // send the cluster event
                                 ClusterConfigurationEvent clusterConfigurationEvent = new ClusterConfigurationEvent(pid);
                                 clusterConfigurationEvent.setSourceGroup(group);
                                 clusterConfigurationEvent.setSourceNode(clusterManager.getNode());
diff --git a/config/src/main/java/org/apache/karaf/cellar/config/internal/osgi/Activator.java b/config/src/main/java/org/apache/karaf/cellar/config/internal/osgi/Activator.java
new file mode 100644
index 0000000..d52746e
--- /dev/null
+++ b/config/src/main/java/org/apache/karaf/cellar/config/internal/osgi/Activator.java
@@ -0,0 +1,146 @@
+/*
+ * 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.config.internal.osgi;
+
+import org.apache.karaf.cellar.config.ConfigurationEventHandler;
+import org.apache.karaf.cellar.config.ConfigurationSynchronizer;
+import org.apache.karaf.cellar.config.LocalConfigurationListener;
+import org.apache.karaf.cellar.config.management.internal.CellarConfigMBeanImpl;
+import org.apache.karaf.cellar.core.ClusterManager;
+import org.apache.karaf.cellar.core.GroupManager;
+import org.apache.karaf.cellar.core.Synchronizer;
+import org.apache.karaf.cellar.core.event.EventHandler;
+import org.apache.karaf.cellar.core.event.EventProducer;
+import org.apache.karaf.util.tracker.BaseActivator;
+import org.apache.karaf.util.tracker.annotation.Managed;
+import org.apache.karaf.util.tracker.annotation.ProvideService;
+import org.apache.karaf.util.tracker.annotation.RequireService;
+import org.apache.karaf.util.tracker.annotation.Services;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationListener;
+import org.osgi.service.cm.ManagedService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.Hashtable;
+
+@Services(
+        requires = {
+                @RequireService(ClusterManager.class),
+                @RequireService(GroupManager.class),
+                @RequireService(ConfigurationAdmin.class),
+                @RequireService(EventProducer.class)
+        },
+        provides = {
+                @ProvideService(ConfigurationListener.class),
+                @ProvideService(Synchronizer.class),
+                @ProvideService(EventHandler.class)
+        }
+)
+@Managed("org.apache.karaf.shell.config")
+public class Activator extends BaseActivator implements ManagedService {
+
+    private final static Logger LOGGER = LoggerFactory.getLogger(Activator.class);
+
+    private LocalConfigurationListener localConfigurationListener;
+    private ConfigurationSynchronizer configurationSynchronizer;
+    private ConfigurationEventHandler configurationEventHandler;
+    private ServiceRegistration cellarConfigMBeanRegistration;
+
+    @Override
+    public void doStart() throws Exception {
+
+        ClusterManager clusterManager = getTrackedService(ClusterManager.class);
+        if (clusterManager == null)
+            return;
+        GroupManager groupManager = getTrackedService(GroupManager.class);
+        if (groupManager == null)
+            return;
+        ConfigurationAdmin configurationAdmin = getTrackedService(ConfigurationAdmin.class);
+        if (configurationAdmin == null)
+            return;
+        EventProducer eventProducer = getTrackedService(EventProducer.class);
+        if (eventProducer == null)
+            return;
+
+        File storage = new File(getString("storage", System.getProperty("karaf.etc")));
+
+        LOGGER.debug("CELLAR CONFIG: init event handler");
+        configurationEventHandler = new ConfigurationEventHandler();
+        configurationEventHandler.setConfigurationAdmin(configurationAdmin);
+        configurationEventHandler.setGroupManager(groupManager);
+        configurationEventHandler.setClusterManager(clusterManager);
+        configurationEventHandler.setStorage(storage);
+        configurationEventHandler.init();
+        Hashtable props = new Hashtable();
+        props.put("managed", "true");
+        register(EventHandler.class, configurationEventHandler, props);
+
+        LOGGER.debug("CELLAR CONFIG: init local listener");
+        localConfigurationListener = new LocalConfigurationListener();
+        localConfigurationListener.setClusterManager(clusterManager);
+        localConfigurationListener.setGroupManager(groupManager);
+        localConfigurationListener.setConfigurationAdmin(configurationAdmin);
+        localConfigurationListener.setEventProducer(eventProducer);
+        localConfigurationListener.init();
+        register(ConfigurationListener.class, localConfigurationListener);
+
+        LOGGER.debug("CELLAR CONFIG: init synchronizer");
+        configurationSynchronizer = new ConfigurationSynchronizer();
+        configurationSynchronizer.setConfigurationAdmin(configurationAdmin);
+        configurationSynchronizer.setGroupManager(groupManager);
+        configurationSynchronizer.setClusterManager(clusterManager);
+        configurationSynchronizer.setEventProducer(eventProducer);
+        configurationSynchronizer.setStorage(storage);
+        configurationSynchronizer.init();
+        props = new Hashtable();
+        props.put("resource", "config");
+        register(Synchronizer.class, configurationSynchronizer, props);
+
+        LOGGER.debug("CELLAR CONFIG: register MBean");
+        CellarConfigMBeanImpl cellarConfigMBean = new CellarConfigMBeanImpl();
+        cellarConfigMBean.setClusterManager(clusterManager);
+        cellarConfigMBean.setGroupManager(groupManager);
+        cellarConfigMBean.setConfigurationAdmin(configurationAdmin);
+        cellarConfigMBean.setEventProducer(eventProducer);
+        props = new Hashtable();
+        props.put("jmx.objectname", "org.apache.karaf.cellar:type=config,name=" + System.getProperty("karaf.name"));
+        cellarConfigMBeanRegistration = bundleContext.registerService(getInterfaceNames(cellarConfigMBean), cellarConfigMBean, props);
+    }
+
+    @Override
+    public void doStop() {
+        super.doStop();
+
+        if (cellarConfigMBeanRegistration != null) {
+            cellarConfigMBeanRegistration.unregister();
+            cellarConfigMBeanRegistration = null;
+        }
+        if (configurationSynchronizer != null) {
+            configurationSynchronizer.destroy();
+            configurationSynchronizer = null;
+        }
+        if (localConfigurationListener != null) {
+            localConfigurationListener.destroy();
+            localConfigurationListener = null;
+        }
+        if (configurationEventHandler != null) {
+            configurationEventHandler.destroy();
+            configurationEventHandler = null;
+        }
+    }
+
+}
diff --git a/config/src/main/java/org/apache/karaf/cellar/config/shell/BlockCommand.java b/config/src/main/java/org/apache/karaf/cellar/config/shell/BlockCommand.java
index 977025e..4877d10 100644
--- a/config/src/main/java/org/apache/karaf/cellar/config/shell/BlockCommand.java
+++ b/config/src/main/java/org/apache/karaf/cellar/config/shell/BlockCommand.java
@@ -14,24 +14,31 @@
 package org.apache.karaf.cellar.config.shell;
 
 import org.apache.karaf.cellar.config.Constants;
+import org.apache.karaf.cellar.config.shell.completers.ClusterConfigCompleter;
 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.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
+import org.apache.karaf.cellar.core.shell.completer.AllGroupsCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.Set;
 
 @Command(scope = "cluster", name = "config-block", description = "Change the blocking policy for a bundle")
+@Service
 public class BlockCommand extends CellarCommandSupport {
 
     @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
     String groupName;
 
     @Argument(index = 1, name = "pidPattern", description = "The configuration PID pattern", required = false, multiValued = false)
+    @Completion(ClusterConfigCompleter.class)
     String pid;
 
     @Option(name = "-in", description = "Update the inbound direction", required = false, multiValued = false)
diff --git a/config/src/main/java/org/apache/karaf/cellar/config/shell/DeleteCommand.java b/config/src/main/java/org/apache/karaf/cellar/config/shell/DeleteCommand.java
index de66aa0..0ce9994 100644
--- a/config/src/main/java/org/apache/karaf/cellar/config/shell/DeleteCommand.java
+++ b/config/src/main/java/org/apache/karaf/cellar/config/shell/DeleteCommand.java
@@ -15,27 +15,36 @@
 
 import org.apache.karaf.cellar.config.ClusterConfigurationEvent;
 import org.apache.karaf.cellar.config.Constants;
+import org.apache.karaf.cellar.config.shell.completers.ClusterConfigCompleter;
 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;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
 import org.apache.karaf.cellar.core.event.EventType;
+import org.apache.karaf.cellar.core.shell.completer.AllGroupsCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.osgi.service.cm.ConfigurationEvent;
 
 import java.util.Map;
 import java.util.Properties;
 
 @Command(scope = "cluster", name = "config-delete", description = "Delete a configuration from a cluster group")
+@Service
 public class DeleteCommand extends ConfigCommandSupport {
 
     @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
     String groupName;
 
     @Argument(index = 1, name = "pid", description = "The configuration PID", required = true, multiValued = false)
+    @Completion(ClusterConfigCompleter.class)
     String pid;
 
+    @Reference
     private EventProducer eventProducer;
 
     @Override
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 41ba78a..11740cb 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
@@ -18,9 +18,12 @@
 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.apache.karaf.cellar.core.shell.completer.AllGroupsCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.osgi.service.cm.Configuration;
 
 import java.util.Enumeration;
@@ -29,9 +32,11 @@
 import java.util.Properties;
 
 @Command(scope = "cluster", name = "config-list", description = "List the configurations in a cluster group")
+@Service
 public class ListCommand extends ConfigCommandSupport {
 
     @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
     String groupName;
 
     @Argument(index = 1, name = "pid", description = "The configuration PID to look for", required = false, multiValued = false)
diff --git a/config/src/main/java/org/apache/karaf/cellar/config/shell/PropAppendCommand.java b/config/src/main/java/org/apache/karaf/cellar/config/shell/PropAppendCommand.java
index a4c6390..7728840 100644
--- a/config/src/main/java/org/apache/karaf/cellar/config/shell/PropAppendCommand.java
+++ b/config/src/main/java/org/apache/karaf/cellar/config/shell/PropAppendCommand.java
@@ -15,24 +15,32 @@
 
 import org.apache.karaf.cellar.config.ClusterConfigurationEvent;
 import org.apache.karaf.cellar.config.Constants;
+import org.apache.karaf.cellar.config.shell.completers.ClusterConfigCompleter;
 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;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
 import org.apache.karaf.cellar.core.event.EventType;
+import org.apache.karaf.cellar.core.shell.completer.AllGroupsCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.Map;
 import java.util.Properties;
 
 @Command(scope = "cluster", name = "config-property-append", description = "Append to the property value for a configuration PID in a cluster group")
+@Service
 public class PropAppendCommand extends ConfigCommandSupport {
 
     @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
     String groupName;
 
     @Argument(index = 1, name = "pid", description = "The configuration PID", required = true, multiValued = false)
+    @Completion(ClusterConfigCompleter.class)
     String pid;
 
     @Argument(index = 2, name = "key", description = "The property key", required = true, multiValued = false)
@@ -41,6 +49,7 @@
     @Argument(index = 3, name = "value", description = "The property value", required = true, multiValued = false)
     String value;
 
+    @Reference
     private EventProducer eventProducer;
 
     @Override
diff --git a/config/src/main/java/org/apache/karaf/cellar/config/shell/PropDelCommand.java b/config/src/main/java/org/apache/karaf/cellar/config/shell/PropDelCommand.java
index 059a882..daa87f5 100644
--- a/config/src/main/java/org/apache/karaf/cellar/config/shell/PropDelCommand.java
+++ b/config/src/main/java/org/apache/karaf/cellar/config/shell/PropDelCommand.java
@@ -13,31 +13,40 @@
  */
 package org.apache.karaf.cellar.config.shell;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
 import org.apache.karaf.cellar.config.Constants;
 import org.apache.karaf.cellar.config.ClusterConfigurationEvent;
+import org.apache.karaf.cellar.config.shell.completers.ClusterConfigCompleter;
 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;
 import org.apache.karaf.cellar.core.event.EventType;
+import org.apache.karaf.cellar.core.shell.completer.AllGroupsCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.Map;
 import java.util.Properties;
 
 @Command(scope = "cluster", name = "config-property-delete", description = "Delete a property from a configuration in a cluster group")
+@Service
 public class PropDelCommand extends ConfigCommandSupport {
 
     @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
     String groupName;
 
     @Argument(index = 1, name = "pid", description = "The configuration PID", required = true, multiValued = false)
+    @Completion(ClusterConfigCompleter.class)
     String pid;
 
     @Argument(index = 2, name = "key", description = "The property key to delete", required = true, multiValued = false)
     String key;
 
+    @Reference
     private EventProducer eventProducer;
 
     @Override
diff --git a/config/src/main/java/org/apache/karaf/cellar/config/shell/PropExcludedCommand.java b/config/src/main/java/org/apache/karaf/cellar/config/shell/PropExcludedCommand.java
index 1065965..53b08dd 100644
--- a/config/src/main/java/org/apache/karaf/cellar/config/shell/PropExcludedCommand.java
+++ b/config/src/main/java/org/apache/karaf/cellar/config/shell/PropExcludedCommand.java
@@ -15,8 +15,10 @@
 
 import org.apache.karaf.cellar.core.Configurations;
 import org.apache.karaf.cellar.core.shell.CellarCommandSupport;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.osgi.service.cm.Configuration;
 import org.osgi.service.cm.ConfigurationAdmin;
 
@@ -24,11 +26,13 @@
 import java.util.Properties;
 
 @Command(scope = "cluster", name = "config-property-excluded", description = "Display or set the config properties excluded from the cluster sync")
+@Service
 public class PropExcludedCommand extends CellarCommandSupport {
 
     @Argument(index = 0, name = "excluded-properties", description = "A list of comma separated properties excluded from the cluster sync", required = false, multiValued = false)
     String excludedProperties;
 
+    @Reference
     private ConfigurationAdmin configurationAdmin;
 
     @Override
@@ -59,4 +63,5 @@
     public void setConfigurationAdmin(ConfigurationAdmin configurationAdmin) {
         this.configurationAdmin = configurationAdmin;
     }
+
 }
diff --git a/config/src/main/java/org/apache/karaf/cellar/config/shell/PropListCommand.java b/config/src/main/java/org/apache/karaf/cellar/config/shell/PropListCommand.java
index f1bf513..23d99b1 100644
--- a/config/src/main/java/org/apache/karaf/cellar/config/shell/PropListCommand.java
+++ b/config/src/main/java/org/apache/karaf/cellar/config/shell/PropListCommand.java
@@ -14,22 +14,29 @@
 package org.apache.karaf.cellar.config.shell;
 
 import org.apache.karaf.cellar.config.Constants;
+import org.apache.karaf.cellar.config.shell.completers.ClusterConfigCompleter;
 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.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.cellar.core.shell.completer.AllGroupsCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.Map;
 import java.util.Properties;
 
 @Command(scope = "cluster", name = "config-property-list", description = "List the configurations in a cluster group")
+@Service
 public class PropListCommand extends CellarCommandSupport {
 
     @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
     String groupName;
 
     @Argument(index = 1, name = "pid", description = "The configuration PID", required = true, multiValued = false)
+    @Completion(ClusterConfigCompleter.class)
     String pid;
 
     @Override
diff --git a/config/src/main/java/org/apache/karaf/cellar/config/shell/PropSetCommand.java b/config/src/main/java/org/apache/karaf/cellar/config/shell/PropSetCommand.java
index a8da140..ac25f9c 100644
--- a/config/src/main/java/org/apache/karaf/cellar/config/shell/PropSetCommand.java
+++ b/config/src/main/java/org/apache/karaf/cellar/config/shell/PropSetCommand.java
@@ -15,24 +15,32 @@
 
 import org.apache.karaf.cellar.config.ClusterConfigurationEvent;
 import org.apache.karaf.cellar.config.Constants;
+import org.apache.karaf.cellar.config.shell.completers.ClusterConfigCompleter;
 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;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
 import org.apache.karaf.cellar.core.event.EventType;
+import org.apache.karaf.cellar.core.shell.completer.AllGroupsCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.Map;
 import java.util.Properties;
 
 @Command(scope = "cluster", name = "config-property-set", description = "Set a property value for a configuration in a cluster group")
+@Service
 public class PropSetCommand extends ConfigCommandSupport {
 
     @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
     String groupName;
 
     @Argument(index = 1, name = "pid", description = "The configuration PID", required = true, multiValued = false)
+    @Completion(ClusterConfigCompleter.class)
     String pid;
 
     @Argument(index = 2, name = "key", description = "The property key", required = true, multiValued = false)
@@ -41,6 +49,7 @@
     @Argument(index = 3, name = "value", description = "The property value", required = true, multiValued = false)
     String value;
 
+    @Reference
     private EventProducer eventProducer;
 
     @Override
diff --git a/config/src/main/java/org/apache/karaf/cellar/config/shell/completers/ClusterConfigCompleter.java b/config/src/main/java/org/apache/karaf/cellar/config/shell/completers/ClusterConfigCompleter.java
index 6481149..ee549a4 100644
--- a/config/src/main/java/org/apache/karaf/cellar/config/shell/completers/ClusterConfigCompleter.java
+++ b/config/src/main/java/org/apache/karaf/cellar/config/shell/completers/ClusterConfigCompleter.java
@@ -18,8 +18,12 @@
 import org.apache.karaf.cellar.core.Configurations;
 import org.apache.karaf.cellar.core.Group;
 import org.apache.karaf.cellar.core.GroupManager;
-import org.apache.karaf.shell.console.Completer;
-import org.apache.karaf.shell.console.completer.StringsCompleter;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
 
 import java.util.List;
 import java.util.Map;
@@ -28,13 +32,17 @@
 /**
  * Command completer for the configuration from the cluster.
  */
+@Service
 public class ClusterConfigCompleter implements Completer {
 
+    @Reference
     protected ClusterManager clusterManager;
+
+    @Reference
     protected GroupManager groupManager;
 
     @Override
-    public int complete(String buffer, int cursor, List<String> candidates) {
+    public int complete(Session session, CommandLine commandLine, List<String> candidates) {
         StringsCompleter delegate = new StringsCompleter();
         try {
             Map<String, Group> groups = groupManager.listGroups();
@@ -54,7 +62,7 @@
         } catch (Exception e) {
             // nothing to do
         }
-        return delegate.complete(buffer, cursor, candidates);
+        return delegate.complete(session, commandLine, candidates);
     }
 
     public ClusterManager getClusterManager() {
diff --git a/config/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/config/src/main/resources/OSGI-INF/blueprint/blueprint.xml
deleted file mode 100644
index 1baf760..0000000
--- a/config/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ /dev/null
@@ -1,81 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
-   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.
-  -->
-<blueprint default-availability="mandatory"
-           xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
-           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-           xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.0.0"
-           xmlns:ext="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0"
-           xsi:schemaLocation="
-           http://www.osgi.org/xmlns/blueprint/v1.0.0
-           http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
-           http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0
-           http://aries.apache.org/schemas/blueprint-cm/blueprint-cm-1.0.0.xsd
-           http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0
-           http://aries.apache.org/schemas/blueprint-ext/blueprint-ext.xsd
-           ">
-
-    <!-- Local Configuration Listener -->
-    <bean id="localListener" class="org.apache.karaf.cellar.config.LocalConfigurationListener" init-method="init"
-          destroy-method="destroy">
-        <property name="clusterManager" ref="clusterManager"/>
-        <property name="groupManager" ref="groupManager"/>
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-        <property name="eventProducer" ref="eventProducer"/>
-    </bean>
-    <service ref="localListener" interface="org.osgi.service.cm.ConfigurationListener"/>
-
-    <!-- Configuration Synchronizer -->
-    <bean id="synchronizer" class="org.apache.karaf.cellar.config.ConfigurationSynchronizer"
-          init-method="init" destroy-method="destroy" depends-on="eventHandler">
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-        <property name="groupManager" ref="groupManager"/>
-        <property name="clusterManager" ref="clusterManager"/>
-        <property name="storage" value="${storage}"/>
-    </bean>
-    <service ref="synchronizer" interface="org.apache.karaf.cellar.core.Synchronizer">
-        <service-properties>
-            <entry key="resource" value="config"/>
-        </service-properties>
-    </service>
-
-    <!-- Cluster Event Handler -->
-    <bean id="eventHandler" class="org.apache.karaf.cellar.config.ConfigurationEventHandler"
-          init-method="init" destroy-method="destroy">
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-        <property name="groupManager" ref="groupManager"/>
-        <property name="clusterManager" ref="clusterManager"/>
-        <property name="storage" value="${storage}"/>
-    </bean>
-    <service ref="eventHandler" interface="org.apache.karaf.cellar.core.event.EventHandler">
-        <service-properties>
-            <entry key="managed" value="true"/>
-        </service-properties>
-    </service>
-
-    <reference id="clusterManager" interface="org.apache.karaf.cellar.core.ClusterManager"/>
-    <reference id="groupManager" interface="org.apache.karaf.cellar.core.GroupManager"/>
-    <reference id="configurationAdmin" interface="org.osgi.service.cm.ConfigurationAdmin"/>
-    <reference id="eventProducer" interface="org.apache.karaf.cellar.core.event.EventProducer"/>
-
-    <ext:property-placeholder placeholder-prefix="$[" placeholder-suffix="]" />
-
-    <cm:property-placeholder persistent-id="org.apache.karaf.shell.config">
-        <cm:default-properties>
-            <cm:property name="storage" value="$[karaf.base]/etc/"/>
-        </cm:default-properties>
-    </cm:property-placeholder>
-
-</blueprint>
diff --git a/config/src/main/resources/OSGI-INF/blueprint/management.xml b/config/src/main/resources/OSGI-INF/blueprint/management.xml
deleted file mode 100644
index 2888a32..0000000
--- a/config/src/main/resources/OSGI-INF/blueprint/management.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  ~ 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.
-  -->
-<blueprint default-availability="mandatory"
-           xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
-
-    <!-- Cellar Config MBean -->
-    <bean id="cellarConfigMBean" class="org.apache.karaf.cellar.config.management.internal.CellarConfigMBeanImpl">
-        <property name="clusterManager" ref="clusterManager"/>
-        <property name="groupManager" ref="groupManager"/>
-        <property name="eventProducer" ref="eventProducer"/>
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-    </bean>
-    <service ref="cellarConfigMBean" auto-export="interfaces">
-        <service-properties>
-            <entry key="jmx.objectname" value="org.apache.karaf.cellar:type=config,name=$[karaf.name]"/>
-        </service-properties>
-    </service>
-
-</blueprint>
\ No newline at end of file
diff --git a/config/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/config/src/main/resources/OSGI-INF/blueprint/shell-config.xml
deleted file mode 100644
index 82ad3c3..0000000
--- a/config/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ /dev/null
@@ -1,115 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
-   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.
-  -->
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" default-activation="lazy">
-
-    <!-- Command Bundle -->
-    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
-        <command>
-            <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"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.config.shell.PropListCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-            </action>
-            <completers>
-                <ref component-id="allGroupsCompleter"/>
-                <ref component-id="clusterConfigCompleter"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.config.shell.PropSetCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-                <property name="eventProducer" ref="eventProducer"/>
-                <property name="configurationAdmin" ref="configurationAdmin"/>
-            </action>
-            <completers>
-                <ref component-id="allGroupsCompleter"/>
-                <ref component-id="clusterConfigCompleter"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.config.shell.PropAppendCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-                <property name="eventProducer" ref="eventProducer"/>
-                <property name="configurationAdmin" ref="configurationAdmin"/>
-            </action>
-            <completers>
-                <ref component-id="allGroupsCompleter"/>
-                <ref component-id="clusterConfigCompleter"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.config.shell.PropDelCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-                <property name="eventProducer" ref="eventProducer"/>
-                <property name="configurationAdmin" ref="configurationAdmin"/>
-            </action>
-            <completers>
-                <ref component-id="allGroupsCompleter"/>
-                <ref component-id="clusterConfigCompleter"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.config.shell.DeleteCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-                <property name="eventProducer" ref="eventProducer"/>
-                <property name="configurationAdmin" ref="configurationAdmin"/>
-            </action>
-            <completers>
-                <ref component-id="allGroupsCompleter"/>
-                <ref component-id="clusterConfigCompleter"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.config.shell.PropExcludedCommand">
-                <property name="configurationAdmin" ref="configurationAdmin"/>
-            </action>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.config.shell.BlockCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-                <property name="configurationAdmin" ref="configurationAdmin"/>
-            </action>
-            <completers>
-                <ref component-id="allGroupsCompleter"/>
-                <ref component-id="clusterConfigCompleter"/>
-            </completers>
-        </command>
-    </command-bundle>
-
-    <bean id="allGroupsCompleter" class="org.apache.karaf.cellar.core.shell.completer.AllGroupsCompleter">
-        <property name="groupManager" ref="groupManager"/>
-    </bean>
-
-    <bean id="clusterConfigCompleter" class="org.apache.karaf.cellar.config.shell.completers.ClusterConfigCompleter">
-        <property name="groupManager" ref="groupManager"/>
-        <property name="clusterManager" ref="clusterManager"/>
-    </bean>
-
-</blueprint>
diff --git a/core/NOTICE b/core/NOTICE
index addb35c..64cb235 100644
--- a/core/NOTICE
+++ b/core/NOTICE
@@ -1,5 +1,5 @@
 Apache Karaf Cellar

-Copyright 2011-2014 The Apache Software Foundation

+Copyright 2011-2015 The Apache Software Foundation

 

 I. Used Software

 

diff --git a/core/pom.xml b/core/pom.xml
index 94824e7..2ab6198 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -49,13 +49,16 @@
         <!-- Core dependencies -->
         <dependency>
             <groupId>org.apache.karaf.shell</groupId>
-            <artifactId>org.apache.karaf.shell.console</artifactId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.util</artifactId>
         </dependency>
         <dependency>
             <groupId>org.apache.aries.proxy</groupId>
             <artifactId>org.apache.aries.proxy.api</artifactId>
             <version>1.0.1</version>
-            <scope>provided</scope>
         </dependency>
 
         <!-- Logging Dependencies -->
@@ -86,19 +89,24 @@
     <build>
         <plugins>
             <plugin>
+                <groupId>org.apache.karaf.tooling</groupId>
+                <artifactId>karaf-services-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
                 <configuration>
                     <instructions>
                         <Export-Package>
-                            org.apache.karaf.cellar.core*;version="${project.version}"
+                            org.apache.karaf.cellar.core*
                         </Export-Package>
                         <Import-Package>
-                            org.apache.karaf.shell.console*;version="[3,5)",
-                            org.osgi*,
-                            org.slf4j;version="[1.6,2)";resolution:=optional
+                            org.slf4j;version="[1.6,2)";resolution:=optional,
+                            *
                         </Import-Package>
-                        <DynamicImport-Package>javax.*,org.w3c.*,org.xml.*,*</DynamicImport-Package>
+                        <Private-Package>
+                            org.apache.karaf.cellar.core.internal.osgi
+                        </Private-Package>
                     </instructions>
                 </configuration>
             </plugin>
diff --git a/core/src/main/java/org/apache/karaf/cellar/core/command/TimeoutTask.java b/core/src/main/java/org/apache/karaf/cellar/core/command/TimeoutTask.java
index 6546339..01f37dc 100644
--- a/core/src/main/java/org/apache/karaf/cellar/core/command/TimeoutTask.java
+++ b/core/src/main/java/org/apache/karaf/cellar/core/command/TimeoutTask.java
@@ -32,9 +32,9 @@
     @Override
     public void run() {
         // check if command is still pending
-        Boolean pending = store.getPending().containsKey(command);
+        Boolean pending = store.getPending().containsKey(command.getId());
         if (pending) {
-            store.getPending().remove(command);
+            store.getPending().remove(command.getId());
         }
     }
 
diff --git a/core/src/main/java/org/apache/karaf/cellar/core/exception/RemoteServiceInvocationException.java b/core/src/main/java/org/apache/karaf/cellar/core/exception/RemoteServiceInvocationException.java
new file mode 100644
index 0000000..219b514
--- /dev/null
+++ b/core/src/main/java/org/apache/karaf/cellar/core/exception/RemoteServiceInvocationException.java
@@ -0,0 +1,37 @@
+/*
+ * 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.core.exception;
+
+/**
+ * Exception raised when remote service call invocation raises an exception.
+ */
+public class RemoteServiceInvocationException extends Exception {
+
+    public RemoteServiceInvocationException() {
+        // nothing to do
+    }
+
+    public RemoteServiceInvocationException(String message) {
+        super(message);
+    }
+
+    public RemoteServiceInvocationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public RemoteServiceInvocationException(Throwable cause) {
+        super(cause);
+    }
+
+}
diff --git a/core/src/main/java/org/apache/karaf/cellar/core/internal/osgi/Activator.java b/core/src/main/java/org/apache/karaf/cellar/core/internal/osgi/Activator.java
new file mode 100644
index 0000000..0233e93
--- /dev/null
+++ b/core/src/main/java/org/apache/karaf/cellar/core/internal/osgi/Activator.java
@@ -0,0 +1,77 @@
+/*
+ * 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.core.internal.osgi;
+
+import org.apache.karaf.cellar.core.event.EventHandler;
+import org.apache.karaf.cellar.core.event.EventHandlerRegistry;
+import org.apache.karaf.cellar.core.event.EventHandlerServiceRegistry;
+import org.apache.karaf.util.tracker.BaseActivator;
+import org.apache.karaf.util.tracker.annotation.ProvideService;
+import org.apache.karaf.util.tracker.annotation.Services;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Services(
+        provides = { @ProvideService(EventHandlerRegistry.class) }
+)
+public class Activator extends BaseActivator {
+
+    private final static Logger LOGGER = LoggerFactory.getLogger(Activator.class);
+
+    private ServiceTracker<EventHandler, EventHandler> eventHandlerServiceTracker;
+
+    @Override
+    public void doStart() throws Exception {
+
+        LOGGER.debug("CELLAR CORE: register event handler service registry");
+        final EventHandlerServiceRegistry registry = new EventHandlerServiceRegistry();
+        register(EventHandlerRegistry.class, registry);
+
+        LOGGER.debug("CELLAR CORE: starting event handler service tracker");
+        eventHandlerServiceTracker = new ServiceTracker<EventHandler, EventHandler>(bundleContext, EventHandler.class, new ServiceTrackerCustomizer<EventHandler, EventHandler>() {
+            @Override
+            public EventHandler addingService(ServiceReference<EventHandler> serviceReference) {
+                EventHandler eventHandler = bundleContext.getService(serviceReference);
+                registry.bind(eventHandler);
+                return eventHandler;
+            }
+
+            @Override
+            public void modifiedService(ServiceReference<EventHandler> serviceReference, EventHandler eventHandler) {
+                // nothing to do
+            }
+
+            @Override
+            public void removedService(ServiceReference<EventHandler> serviceReference, EventHandler eventHandler) {
+                registry.unbind(eventHandler);
+                bundleContext.ungetService(serviceReference);
+            }
+        });
+        eventHandlerServiceTracker.open();
+    }
+
+    @Override
+    public void doStop() {
+        super.doStop();
+
+        if (eventHandlerServiceTracker != null) {
+            eventHandlerServiceTracker.close();
+            eventHandlerServiceTracker = null;
+        }
+    }
+
+}
diff --git a/core/src/main/java/org/apache/karaf/cellar/core/shell/CellarCommandSupport.java b/core/src/main/java/org/apache/karaf/cellar/core/shell/CellarCommandSupport.java
index 6ff9191..9341334 100644
--- a/core/src/main/java/org/apache/karaf/cellar/core/shell/CellarCommandSupport.java
+++ b/core/src/main/java/org/apache/karaf/cellar/core/shell/CellarCommandSupport.java
@@ -15,16 +15,22 @@
 
 import org.apache.karaf.cellar.core.ClusterManager;
 import org.apache.karaf.cellar.core.GroupManager;
-import org.apache.karaf.shell.console.OsgiCommandSupport;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
 import org.osgi.service.cm.ConfigurationAdmin;
 
 /**
  * Abstract Cellar command.
  */
-public abstract class CellarCommandSupport extends OsgiCommandSupport {
+public abstract class CellarCommandSupport implements Action {
 
+    @Reference
     protected ClusterManager clusterManager;
+
+    @Reference
     protected GroupManager groupManager;
+
+    @Reference
     protected ConfigurationAdmin configurationAdmin;
 
     public ClusterManager getClusterManager() {
@@ -51,4 +57,11 @@
         this.configurationAdmin = configurationAdmin;
     }
 
+    @Override
+    public Object execute() throws Exception {
+        return doExecute();
+    }
+
+    protected abstract Object doExecute() throws Exception;
+
 }
diff --git a/core/src/main/java/org/apache/karaf/cellar/core/shell/completer/AllGroupsCompleter.java b/core/src/main/java/org/apache/karaf/cellar/core/shell/completer/AllGroupsCompleter.java
index b5e776c..d8d4f5b 100644
--- a/core/src/main/java/org/apache/karaf/cellar/core/shell/completer/AllGroupsCompleter.java
+++ b/core/src/main/java/org/apache/karaf/cellar/core/shell/completer/AllGroupsCompleter.java
@@ -14,10 +14,12 @@
 package org.apache.karaf.cellar.core.shell.completer;
 
 import org.apache.karaf.cellar.core.Group;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 /**
  * Completer for all cluster groups.
  */
+@Service
 public class AllGroupsCompleter extends GroupCompleterSupport {
 
     /**
diff --git a/core/src/main/java/org/apache/karaf/cellar/core/shell/completer/AllNodeCompleter.java b/core/src/main/java/org/apache/karaf/cellar/core/shell/completer/AllNodeCompleter.java
index be7f26f..a2b85a5 100644
--- a/core/src/main/java/org/apache/karaf/cellar/core/shell/completer/AllNodeCompleter.java
+++ b/core/src/main/java/org/apache/karaf/cellar/core/shell/completer/AllNodeCompleter.java
@@ -14,10 +14,12 @@
 package org.apache.karaf.cellar.core.shell.completer;
 
 import org.apache.karaf.cellar.core.Node;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 /**
  * A completer which includes all nodes.
  */
+@Service
 public class AllNodeCompleter extends NodeCompleterSupport {
 
     /**
diff --git a/core/src/main/java/org/apache/karaf/cellar/core/shell/completer/GroupCompleterSupport.java b/core/src/main/java/org/apache/karaf/cellar/core/shell/completer/GroupCompleterSupport.java
index bd172ef..058f1e8 100644
--- a/core/src/main/java/org/apache/karaf/cellar/core/shell/completer/GroupCompleterSupport.java
+++ b/core/src/main/java/org/apache/karaf/cellar/core/shell/completer/GroupCompleterSupport.java
@@ -15,8 +15,11 @@
 
 import org.apache.karaf.cellar.core.Group;
 import org.apache.karaf.cellar.core.GroupManager;
-import org.apache.karaf.shell.console.Completer;
-import org.apache.karaf.shell.console.completer.StringsCompleter;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
 
 import java.util.List;
 
@@ -25,6 +28,7 @@
  */
 public abstract class GroupCompleterSupport implements Completer {
 
+    @Reference
     protected GroupManager groupManager;
 
     /**
@@ -36,7 +40,7 @@
     protected abstract boolean acceptsGroup(Group group);
 
     @Override
-    public int complete(String buffer, int cursor, List<String> candidates) {
+    public int complete(Session session, CommandLine commandLine, List<String> candidates) {
         StringsCompleter delegate = new StringsCompleter();
         try {
             for (Group group : groupManager.listAllGroups()) {
@@ -50,7 +54,7 @@
         } catch (Exception e) {
             // ignore
         }
-        return delegate.complete(buffer, cursor, candidates);
+        return delegate.complete(session, commandLine, candidates);
     }
 
     public GroupManager getGroupManager() {
diff --git a/core/src/main/java/org/apache/karaf/cellar/core/shell/completer/LocalGroupsCompleter.java b/core/src/main/java/org/apache/karaf/cellar/core/shell/completer/LocalGroupsCompleter.java
index 16b42d5..61dd223 100644
--- a/core/src/main/java/org/apache/karaf/cellar/core/shell/completer/LocalGroupsCompleter.java
+++ b/core/src/main/java/org/apache/karaf/cellar/core/shell/completer/LocalGroupsCompleter.java
@@ -15,10 +15,12 @@
 
 import org.apache.karaf.cellar.core.Group;
 import org.apache.karaf.cellar.core.Node;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 /**
  * Local cluster groups completer.
  */
+@Service
 public class LocalGroupsCompleter extends GroupCompleterSupport {
 
     /**
diff --git a/core/src/main/java/org/apache/karaf/cellar/core/shell/completer/NodeCompleterSupport.java b/core/src/main/java/org/apache/karaf/cellar/core/shell/completer/NodeCompleterSupport.java
index 815a658..2269315 100644
--- a/core/src/main/java/org/apache/karaf/cellar/core/shell/completer/NodeCompleterSupport.java
+++ b/core/src/main/java/org/apache/karaf/cellar/core/shell/completer/NodeCompleterSupport.java
@@ -15,8 +15,11 @@
 
 import org.apache.karaf.cellar.core.ClusterManager;
 import org.apache.karaf.cellar.core.Node;
-import org.apache.karaf.shell.console.Completer;
-import org.apache.karaf.shell.console.completer.StringsCompleter;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
 
 import java.util.List;
 
@@ -25,10 +28,11 @@
  */
 public abstract class NodeCompleterSupport implements Completer {
 
+    @Reference
     private ClusterManager clusterManager;
 
     @Override
-    public int complete(String buffer, int cursor, List<String> candidates) {
+    public int complete(Session session, CommandLine commandLine, List<String> candidates) {
         StringsCompleter delegate = new StringsCompleter();
         try {
             for (Node node : clusterManager.listNodes()) {
@@ -42,7 +46,7 @@
         } catch (Exception e) {
             // Ignore
         }
-        return delegate.complete(buffer, cursor, candidates);
+        return delegate.complete(session, commandLine, candidates);
     }
 
     protected abstract boolean acceptsNode(Node node);
diff --git a/core/src/main/java/org/apache/karaf/cellar/core/shell/completer/OtherGroupsCompleter.java b/core/src/main/java/org/apache/karaf/cellar/core/shell/completer/OtherGroupsCompleter.java
index 1098bea..dd9c495 100644
--- a/core/src/main/java/org/apache/karaf/cellar/core/shell/completer/OtherGroupsCompleter.java
+++ b/core/src/main/java/org/apache/karaf/cellar/core/shell/completer/OtherGroupsCompleter.java
@@ -15,10 +15,12 @@
 
 import org.apache.karaf.cellar.core.Group;
 import org.apache.karaf.cellar.core.Node;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 /**
  * Other groups completer.
  */
+@Service
 public class OtherGroupsCompleter extends GroupCompleterSupport {
 
     /**
diff --git a/core/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/core/src/main/resources/OSGI-INF/blueprint/blueprint.xml
deleted file mode 100644
index 851da4e..0000000
--- a/core/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-   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.
--->
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
-
-    <!-- Handlers Registry -->
-    <bean id="registry" class="org.apache.karaf.cellar.core.event.EventHandlerServiceRegistry"/>
-    <service ref="registry" interface="org.apache.karaf.cellar.core.event.EventHandlerRegistry"/>
-
-    <reference-list id="eventHandlers" interface="org.apache.karaf.cellar.core.event.EventHandler" availability="optional">
-        <reference-listener bind-method="bind" unbind-method="unbind">
-            <ref component-id="registry"/>
-        </reference-listener>
-    </reference-list>
-
-</blueprint>
diff --git a/dosgi/NOTICE b/dosgi/NOTICE
index addb35c..64cb235 100644
--- a/dosgi/NOTICE
+++ b/dosgi/NOTICE
@@ -1,5 +1,5 @@
 Apache Karaf Cellar

-Copyright 2011-2014 The Apache Software Foundation

+Copyright 2011-2015 The Apache Software Foundation

 

 I. Used Software

 

diff --git a/dosgi/pom.xml b/dosgi/pom.xml
index d39d1d8..71c3531 100644
--- a/dosgi/pom.xml
+++ b/dosgi/pom.xml
@@ -40,8 +40,12 @@
             <artifactId>org.apache.karaf.cellar.core</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.util</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.apache.karaf.shell</groupId>
-            <artifactId>org.apache.karaf.shell.table</artifactId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
         </dependency>
         <dependency>
             <groupId>org.osgi</groupId>
@@ -56,6 +60,7 @@
         <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-lang3</artifactId>
+            <version>3.3.2</version>
         </dependency>
 
         <!-- Logging Dependencies -->
@@ -76,25 +81,29 @@
     <build>
         <plugins>
             <plugin>
+                <groupId>org.apache.karaf.tooling</groupId>
+                <artifactId>karaf-services-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
                 <configuration>
                     <instructions>
                         <Export-Package>
-                            org.apache.karaf.cellar.dosgi*;version="${project.version}"
+                            !org.apache.karaf.cellar.dosgi.internal.osgi,
+                            org.apache.karaf.cellar.dosgi*
                         </Export-Package>
                         <Import-Package>
-                            org.apache.karaf.cellar.core*;version="${project.version}",
-                            org.apache.felix.service.command,
-                            org.apache.felix.gogo.commands,
-                            org.apache.karaf.shell.console;version="[3,5)",
-                            org.apache.karaf.shell.console.commands;version="[3,5)",
-                            org.apache.karaf.shell.console.completer;version="[3,5)",
-                            org.apache.karaf.shell.commands;version="[3,5)",
-                            org.osgi*,
-                            org.slf4j;version="[1.6,2)";resolution:=optional
+                            org.slf4j;version="[1.6,2)";resolution:=optional,
+                            org.apache.karaf.shell*;resolution:=optional,
+                            *
                         </Import-Package>
-                        <DynamicImport-Package>javax.*,org.w3c.*,org.xml.*,*</DynamicImport-Package>
+                        <DynamicImport-Package>javax.*,org.w3c.*,org.xml.*,org.apache.karaf.cellar.*</DynamicImport-Package>
+                        <Private-Package>
+                            org.apache.karaf.cellar.dosgi.internal.osgi,
+                            org.apache.karaf.util.tracker;-split-package:=merge-first,
+                            org.apache.commons.lang3*;-split-package:=merge-first
+                        </Private-Package>
                     </instructions>
                 </configuration>
             </plugin>
diff --git a/dosgi/src/main/java/org/apache/karaf/cellar/dosgi/RemoteServiceCallHandler.java b/dosgi/src/main/java/org/apache/karaf/cellar/dosgi/RemoteServiceCallHandler.java
index 78a38fd..810d4f4 100644
--- a/dosgi/src/main/java/org/apache/karaf/cellar/dosgi/RemoteServiceCallHandler.java
+++ b/dosgi/src/main/java/org/apache/karaf/cellar/dosgi/RemoteServiceCallHandler.java
@@ -22,6 +22,7 @@
 import org.apache.karaf.cellar.core.event.EventHandler;
 import org.apache.karaf.cellar.core.event.EventProducer;
 import org.apache.karaf.cellar.core.event.EventTransportFactory;
+import org.apache.karaf.cellar.core.exception.RemoteServiceInvocationException;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceReference;
@@ -86,21 +87,26 @@
                     }
                 }
 
+                RemoteServiceResult result = new RemoteServiceResult(event.getId());
+                EventProducer producer = eventTransportFactory.getEventProducer(Constants.RESULT_PREFIX + Constants.SEPARATOR + event.getSourceNode().getId() + event.getEndpointId(), false);
                 try {
                     Method method = getMethod(classes, targetService, event);
                     Object obj = method.invoke(targetService, event.getArguments().toArray());
-                    RemoteServiceResult result = new RemoteServiceResult(event.getId());
                     result.setResult(obj);
-
-                    EventProducer producer = eventTransportFactory.getEventProducer(Constants.RESULT_PREFIX + Constants.SEPARATOR + event.getSourceNode().getId() + event.getEndpointId(), false);
                     producer.produce(result);
 
                 } catch (NoSuchMethodException e) {
                     LOGGER.error("CELLAR DOSGI: unable to find remote method for service", e);
+                    result.setResult(new RemoteServiceInvocationException(e));
+                    producer.produce(result);
                 } catch (InvocationTargetException e) {
                     LOGGER.error("CELLAR DOSGI: unable to invoke remote method for service", e);
+                    result.setResult(new RemoteServiceInvocationException(e.getCause()));
+                    producer.produce(result);
                 } catch (IllegalAccessException e) {
                     LOGGER.error("CELLAR DOSGI: unable to access remote method for service", e);
+                    result.setResult(new RemoteServiceInvocationException(e));
+                    producer.produce(result);
                 }
             }
         }
diff --git a/dosgi/src/main/java/org/apache/karaf/cellar/dosgi/RemoteServiceInvocationHandler.java b/dosgi/src/main/java/org/apache/karaf/cellar/dosgi/RemoteServiceInvocationHandler.java
index fc82ed4..79099f8 100644
--- a/dosgi/src/main/java/org/apache/karaf/cellar/dosgi/RemoteServiceInvocationHandler.java
+++ b/dosgi/src/main/java/org/apache/karaf/cellar/dosgi/RemoteServiceInvocationHandler.java
@@ -16,6 +16,7 @@
 import org.apache.karaf.cellar.core.ClusterManager;
 import org.apache.karaf.cellar.core.Node;
 import org.apache.karaf.cellar.core.command.ExecutionContext;
+import org.apache.karaf.cellar.core.exception.RemoteServiceInvocationException;
 
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
@@ -60,6 +61,17 @@
         if(results != null) {
             for(Map.Entry<Node,RemoteServiceResult> entry:results.entrySet()) {
                 RemoteServiceResult result = entry.getValue();
+
+                // an exception being thrown by the remote service call must be raised locally
+                if (result != null && result.getResult() != null && result.getResult() instanceof RemoteServiceInvocationException) {
+                    RemoteServiceInvocationException ute = (RemoteServiceInvocationException) result.getResult();
+                    if (ute.getCause() != null) {
+                        throw ute.getCause();
+                    } else {
+                        throw ute;
+                    }
+                }
+
                 return result.getResult();
             }
         }
diff --git a/dosgi/src/main/java/org/apache/karaf/cellar/dosgi/internal/osgi/Activator.java b/dosgi/src/main/java/org/apache/karaf/cellar/dosgi/internal/osgi/Activator.java
new file mode 100644
index 0000000..b7b360e
--- /dev/null
+++ b/dosgi/src/main/java/org/apache/karaf/cellar/dosgi/internal/osgi/Activator.java
@@ -0,0 +1,139 @@
+/*
+ * 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.dosgi.internal.osgi;
+
+import org.apache.karaf.cellar.core.ClusterManager;
+import org.apache.karaf.cellar.core.command.CommandStore;
+import org.apache.karaf.cellar.core.event.EventHandler;
+import org.apache.karaf.cellar.core.event.EventTransportFactory;
+import org.apache.karaf.cellar.dosgi.*;
+import org.apache.karaf.cellar.dosgi.management.internal.ServiceMBeanImpl;
+import org.apache.karaf.util.tracker.BaseActivator;
+import org.apache.karaf.util.tracker.annotation.ProvideService;
+import org.apache.karaf.util.tracker.annotation.RequireService;
+import org.apache.karaf.util.tracker.annotation.Services;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.hooks.service.ListenerHook;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Hashtable;
+
+@Services(
+        provides = {
+                @ProvideService(ListenerHook.class),
+                @ProvideService(EventHandler.class)
+        },
+        requires = {
+                @RequireService(ClusterManager.class),
+                @RequireService(EventTransportFactory.class),
+                @RequireService(CommandStore.class),
+                @RequireService(ConfigurationAdmin.class)
+        }
+)
+public class Activator extends BaseActivator {
+
+    private final static Logger LOGGER = LoggerFactory.getLogger(Activator.class);
+
+    private ImportServiceListener importServiceListener;
+    private ExportServiceListener exportServiceListener;
+    private RemovedNodeServiceTracker removedNodeServiceTracker;
+    private ServiceRegistration mbeanRegistration;
+
+    @Override
+    public void doStart() throws Exception {
+
+        ClusterManager clusterManager = getTrackedService(ClusterManager.class);
+        if (clusterManager == null)
+            return;
+        EventTransportFactory eventTransportFactory = getTrackedService(EventTransportFactory.class);
+        if (eventTransportFactory == null)
+            return;
+        CommandStore commandStore = getTrackedService(CommandStore.class);
+        if (commandStore == null)
+            return;
+        ConfigurationAdmin configurationAdmin = getTrackedService(ConfigurationAdmin.class);
+        if (configurationAdmin == null)
+            return;
+
+        LOGGER.debug("CELLAR DOSGI: init remote service call handler");
+        RemoteServiceCallHandler remoteServiceCallHandler = new RemoteServiceCallHandler();
+        remoteServiceCallHandler.setEventTransportFactory(eventTransportFactory);
+        remoteServiceCallHandler.setClusterManager(clusterManager);
+        remoteServiceCallHandler.setBundleContext(bundleContext);
+        remoteServiceCallHandler.setConfigurationAdmin(configurationAdmin);
+        Hashtable props = new Hashtable();
+        props.put("managed", "true");
+        register(EventHandler.class, remoteServiceCallHandler, props);
+
+        LOGGER.debug("CELLAR DOSGI: init remote service result handler");
+        RemoteServiceResultHandler remoteServiceResultHandler = new RemoteServiceResultHandler();
+        remoteServiceResultHandler.setCommandStore(commandStore);
+        register(EventHandler.class, remoteServiceResultHandler);
+
+        LOGGER.debug("CELLAR DOSGI: init import service listener");
+        importServiceListener = new ImportServiceListener();
+        importServiceListener.setClusterManager(clusterManager);
+        importServiceListener.setEventTransportFactory(eventTransportFactory);
+        importServiceListener.setCommandStore(commandStore);
+        importServiceListener.setBundleContext(bundleContext);
+        importServiceListener.init();
+        register(ListenerHook.class, importServiceListener);
+
+        LOGGER.debug("CELLAR DOSGI: init export service listener");
+        exportServiceListener = new ExportServiceListener();
+        exportServiceListener.setClusterManager(clusterManager);
+        exportServiceListener.setEventTransportFactory(eventTransportFactory);
+        exportServiceListener.setBundleContext(bundleContext);
+        exportServiceListener.init();
+
+        LOGGER.debug("CELLAR DOSGI: start removed nodes service tracker");
+        removedNodeServiceTracker = new RemovedNodeServiceTracker();
+        removedNodeServiceTracker.setClusterManager(clusterManager);
+        removedNodeServiceTracker.init();
+
+        LOGGER.debug("CELLAR DOSGI: register MBean");
+        ServiceMBeanImpl mbean = new ServiceMBeanImpl();
+        mbean.setClusterManager(clusterManager);
+        props = new Hashtable();
+        props.put("jmx.objectname", "org.apache.karaf.cellar:type=service,name=" + System.getProperty("karaf.name"));
+        mbeanRegistration = bundleContext.registerService(getInterfaceNames(mbean), mbean, props);
+    }
+
+    @Override
+    public void doStop() {
+        super.doStop();
+
+        if (mbeanRegistration != null) {
+            mbeanRegistration.unregister();
+            mbeanRegistration = null;
+        }
+
+        if (removedNodeServiceTracker != null) {
+            removedNodeServiceTracker.destroy();
+            removedNodeServiceTracker = null;
+        }
+
+        if (exportServiceListener != null) {
+            exportServiceListener.destroy();
+            exportServiceListener = null;
+        }
+        if (importServiceListener != null) {
+            importServiceListener.destroy();
+            importServiceListener = null;
+        }
+    }
+
+}
diff --git a/dosgi/src/main/java/org/apache/karaf/cellar/dosgi/internal/osgi/RemovedNodeServiceTracker.java b/dosgi/src/main/java/org/apache/karaf/cellar/dosgi/internal/osgi/RemovedNodeServiceTracker.java
new file mode 100644
index 0000000..1bad57a
--- /dev/null
+++ b/dosgi/src/main/java/org/apache/karaf/cellar/dosgi/internal/osgi/RemovedNodeServiceTracker.java
@@ -0,0 +1,111 @@
+/*
+ * 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.dosgi.internal.osgi;
+
+import org.apache.karaf.cellar.core.ClusterManager;
+import org.apache.karaf.cellar.core.Node;
+import org.apache.karaf.cellar.dosgi.Constants;
+import org.apache.karaf.cellar.dosgi.EndpointDescription;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Listener called when a new service is exported.
+ */
+public class RemovedNodeServiceTracker implements Runnable {
+
+    private static final transient Logger LOGGER = LoggerFactory.getLogger(RemovedNodeServiceTracker.class);
+
+    private ClusterManager clusterManager;
+
+    private Map<String, EndpointDescription> remoteEndpoints;
+
+    private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
+
+    public void init() {
+        remoteEndpoints = clusterManager.getMap(Constants.REMOTE_ENDPOINTS);
+        scheduler.scheduleWithFixedDelay(this, 10, 10, TimeUnit.SECONDS);
+    }
+
+    public void destroy() {
+        scheduler.shutdown();
+    }
+
+    public ClusterManager getClusterManager() {
+        return clusterManager;
+    }
+
+    public void setClusterManager(ClusterManager clusterManager) {
+        this.clusterManager = clusterManager;
+    }
+
+
+    @Override
+    public void run() {
+        LOGGER.trace("CELLAR DOSGI: running the service tracker task");
+        ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
+        try {
+            if (!remoteEndpoints.isEmpty()) {
+                LOGGER.trace("CELLAR DOSGI: found {} remote endpoints", remoteEndpoints.size());
+                Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+                final Set<Node> activeNodes = clusterManager.listNodes();
+
+                // create a clone of remote endpoint to avoid concurrency concerns while iterating it
+                final Set<Map.Entry<String, EndpointDescription>> list = new HashSet<Map.Entry<String, EndpointDescription>>(remoteEndpoints.entrySet());
+
+                for (Map.Entry<String, EndpointDescription> entry : list) {
+                    final EndpointDescription endpointDescription = entry.getValue();
+                    final String key = entry.getKey();
+
+                    // create a clone of nodes to avoid concurrency concerns while iterating it
+                    final Set<Node> nodes = new HashSet<Node>(endpointDescription.getNodes());
+
+                    boolean endpointChanged = false;
+                    for (Node n : nodes) {
+                        if (!activeNodes.contains(n)) {
+                            LOGGER.debug("CELLAR DOSGI: removing node with id {} since it is not active", n.getId());
+                            endpointDescription.getNodes().remove(n);
+                            endpointChanged = true;
+                        }
+                    }
+
+                    if (endpointChanged) {
+                        // if the endpoint is used for export from other nodes too, then update it
+                        if (endpointDescription.getNodes().size() > 0) {
+                            LOGGER.debug("CELLAR DOSGI: updating remote endpoint {}", key);
+                            remoteEndpoints.put(key, endpointDescription);
+                        } else { // remove endpoint permanently
+                            LOGGER.debug("CELLAR DOSGI: removing remote endpoint {}", key);
+                            remoteEndpoints.remove(key);
+                        }
+                    }
+                }
+            } else {
+                LOGGER.trace("CELLAR DOSGI: no remote endpoints found");
+            }
+        } catch (Exception e) {
+            LOGGER.warn("CELLAR DOSGI: failed to run service tracker task", e);
+        } finally {
+            Thread.currentThread().setContextClassLoader(originalClassLoader);
+        }
+    }
+
+}
diff --git a/dosgi/src/main/java/org/apache/karaf/cellar/dosgi/shell/ListDistributedServicesCommand.java b/dosgi/src/main/java/org/apache/karaf/cellar/dosgi/shell/ListDistributedServicesCommand.java
index c3bf6e4..d5ad96b 100644
--- a/dosgi/src/main/java/org/apache/karaf/cellar/dosgi/shell/ListDistributedServicesCommand.java
+++ b/dosgi/src/main/java/org/apache/karaf/cellar/dosgi/shell/ListDistributedServicesCommand.java
@@ -17,13 +17,15 @@
 import org.apache.karaf.cellar.core.shell.CellarCommandSupport;
 import org.apache.karaf.cellar.dosgi.Constants;
 import org.apache.karaf.cellar.dosgi.EndpointDescription;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.table.ShellTable;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.table.ShellTable;
 
 import java.util.Map;
 import java.util.Set;
 
 @Command(scope = "cluster", name = "service-list", description = "List the services available on the cluster")
+@Service
 public class ListDistributedServicesCommand extends CellarCommandSupport {
 
     @Override
diff --git a/dosgi/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/dosgi/src/main/resources/OSGI-INF/blueprint/blueprint.xml
deleted file mode 100644
index 51171fc..0000000
--- a/dosgi/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
-   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.
-  -->
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
-
-    <!-- Import Service Listener -->
-    <bean id="importServiceListener" class="org.apache.karaf.cellar.dosgi.ImportServiceListener" init-method="init" destroy-method="destroy">
-        <property name="clusterManager" ref="clusterManager"/>
-        <property name="commandStore" ref="commandStore"/>
-        <property name="bundleContext" ref="blueprintBundleContext"/>
-        <property name="eventTransportFactory" ref="eventTransportFactory"/>
-    </bean>
-    <service ref="importServiceListener" interface="org.osgi.framework.hooks.service.ListenerHook"/>
-
-    <!-- Export Service Listener -->
-    <bean id="exportServiceListener" class="org.apache.karaf.cellar.dosgi.ExportServiceListener" init-method="init" destroy-method="destroy">
-        <property name="clusterManager" ref="clusterManager"/>
-        <property name="bundleContext" ref="blueprintBundleContext"/>
-        <property name="eventTransportFactory" ref="eventTransportFactory"/>
-    </bean>
-
-    <!-- Remote Service Call Event Handler -->
-    <bean id="remoteServiceCallHandler" class="org.apache.karaf.cellar.dosgi.RemoteServiceCallHandler">
-        <property name="clusterManager" ref="clusterManager"/>
-        <property name="eventTransportFactory" ref="eventTransportFactory"/>
-        <property name="bundleContext" ref="blueprintBundleContext"/>
-        <property name="configurationAdmin" ref="configurationAdmin" />
-    </bean>
-    <service ref="remoteServiceCallHandler" interface="org.apache.karaf.cellar.core.event.EventHandler">
-        <service-properties>
-            <entry key="managed" value="true"/>
-        </service-properties>
-    </service>
-
-    <!-- Remote Service Result Event Handler -->
-    <bean id="remoteServiceResultHandler" class="org.apache.karaf.cellar.dosgi.RemoteServiceResultHandler">
-        <property name="commandStore" ref="commandStore"/>
-    </bean>
-    <service ref="remoteServiceResultHandler" interface="org.apache.karaf.cellar.core.event.EventHandler"/>
-
-    <reference id="clusterManager" interface="org.apache.karaf.cellar.core.ClusterManager"/>
-    <reference id="eventTransportFactory" interface="org.apache.karaf.cellar.core.event.EventTransportFactory"/>
-    <reference id="commandStore" interface="org.apache.karaf.cellar.core.command.CommandStore"/>
-    <reference id="configurationAdmin" interface="org.osgi.service.cm.ConfigurationAdmin"/>
-
-</blueprint>
diff --git a/dosgi/src/main/resources/OSGI-INF/blueprint/management.xml b/dosgi/src/main/resources/OSGI-INF/blueprint/management.xml
deleted file mode 100644
index 3b52c9a..0000000
--- a/dosgi/src/main/resources/OSGI-INF/blueprint/management.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  ~ 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.
-  -->
-<blueprint default-availability="mandatory"
-           xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
-           xmlns:ext="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0">
-
-    <!-- system properties -->
-    <ext:property-placeholder placeholder-prefix="$[" placeholder-suffix="]" />
-
-    <!-- Cellar Service MBean -->
-    <bean id="cellarServiceMBean" class="org.apache.karaf.cellar.dosgi.management.internal.ServiceMBeanImpl">
-        <property name="clusterManager" ref="clusterManager"/>
-    </bean>
-    <service ref="cellarServiceMBean" auto-export="interfaces">
-        <service-properties>
-            <entry key="jmx.objectname" value="org.apache.karaf.cellar:type=service,name=$[karaf.name]"/>
-        </service-properties>
-    </service>
-
-</blueprint>
\ No newline at end of file
diff --git a/dosgi/src/main/resources/OSGI-INF/blueprint/shell-dosgi.xml b/dosgi/src/main/resources/OSGI-INF/blueprint/shell-dosgi.xml
deleted file mode 100644
index c365313..0000000
--- a/dosgi/src/main/resources/OSGI-INF/blueprint/shell-dosgi.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-   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.
--->
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" default-activation="lazy">
-
-    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
-        <command>
-            <action class="org.apache.karaf.cellar.dosgi.shell.ListDistributedServicesCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-            </action>
-        </command>
-    </command-bundle>
-
-</blueprint>
diff --git a/event/NOTICE b/event/NOTICE
index addb35c..64cb235 100644
--- a/event/NOTICE
+++ b/event/NOTICE
@@ -1,5 +1,5 @@
 Apache Karaf Cellar

-Copyright 2011-2014 The Apache Software Foundation

+Copyright 2011-2015 The Apache Software Foundation

 

 I. Used Software

 

diff --git a/event/pom.xml b/event/pom.xml
index 81ff773..3ad8b67 100644
--- a/event/pom.xml
+++ b/event/pom.xml
@@ -40,6 +40,15 @@
             <artifactId>org.apache.karaf.cellar.core</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.compendium</artifactId>
             <scope>provided</scope>
@@ -56,19 +65,26 @@
     <build>
         <plugins>
             <plugin>
+                <groupId>org.apache.karaf.tooling</groupId>
+                <artifactId>karaf-services-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
                 <configuration>
                     <instructions>
                         <Export-Package>
-                            org.apache.karaf.cellar.event*;version="${project.version}"
+                            !org.apache.karaf.cellar.event.internal.osgi,
+                            org.apache.karaf.cellar.event*
                         </Export-Package>
                         <Import-Package>
-                            org.apache.karaf.cellar.core*;version="${project.version}",
-                            org.osgi*,
-                            org.slf4j;version="[1.6,2)";resolution:=optional
+                            org.slf4j;version="[1.6,2)";resolution:=optional,
+                            *
                         </Import-Package>
-                        <DynamicImport-Package>javax.*,org.w3c.*,org.xml.*,*</DynamicImport-Package>
+                        <Private-Package>
+                            org.apache.karaf.cellar.event.internal.osgi,
+                            org.apache.karaf.util.tracker;-split-package:=merge-first
+                        </Private-Package>
                     </instructions>
                 </configuration>
             </plugin>
diff --git a/event/src/main/java/org/apache/karaf/cellar/event/internal/osgi/Activator.java b/event/src/main/java/org/apache/karaf/cellar/event/internal/osgi/Activator.java
new file mode 100644
index 0000000..61445d0
--- /dev/null
+++ b/event/src/main/java/org/apache/karaf/cellar/event/internal/osgi/Activator.java
@@ -0,0 +1,109 @@
+/*
+ * 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.event.internal.osgi;
+
+import org.apache.karaf.cellar.core.ClusterManager;
+import org.apache.karaf.cellar.core.GroupManager;
+import org.apache.karaf.cellar.core.event.EventHandler;
+import org.apache.karaf.cellar.core.event.EventProducer;
+import org.apache.karaf.cellar.event.ClusterEventHandler;
+import org.apache.karaf.cellar.event.LocalEventListener;
+import org.apache.karaf.util.tracker.BaseActivator;
+import org.apache.karaf.util.tracker.annotation.ProvideService;
+import org.apache.karaf.util.tracker.annotation.RequireService;
+import org.apache.karaf.util.tracker.annotation.Services;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.event.EventAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Hashtable;
+
+@Services(
+        provides = {
+                @ProvideService(EventHandler.class),
+                @ProvideService(org.osgi.service.event.EventHandler.class)
+        },
+        requires = {
+                @RequireService(ClusterManager.class),
+                @RequireService(GroupManager.class),
+                @RequireService(ConfigurationAdmin.class),
+                @RequireService(EventAdmin.class),
+                @RequireService(EventProducer.class)
+        }
+)
+public class Activator extends BaseActivator {
+
+    private final static Logger LOGGER = LoggerFactory.getLogger(Activator.class);
+
+    private LocalEventListener localEventListener;
+    private ClusterEventHandler clusterEventHandler;
+
+    @Override
+    public void doStart() throws Exception {
+
+        ClusterManager clusterManager = getTrackedService(ClusterManager.class);
+        if (clusterManager == null)
+            return;
+        GroupManager groupManager = getTrackedService(GroupManager.class);
+        if (groupManager == null)
+            return;
+        ConfigurationAdmin configurationAdmin = getTrackedService(ConfigurationAdmin.class);
+        if (configurationAdmin == null)
+            return;
+        EventAdmin eventAdmin = getTrackedService(EventAdmin.class);
+        if (eventAdmin == null)
+            return;
+        EventProducer eventProducer = getTrackedService(EventProducer.class);
+        if (eventProducer == null)
+            return;
+
+        LOGGER.debug("CELLAR EVENT: init event handler");
+        clusterEventHandler = new ClusterEventHandler();
+        clusterEventHandler.setConfigurationAdmin(configurationAdmin);
+        clusterEventHandler.setGroupManager(groupManager);
+        clusterEventHandler.setClusterManager(clusterManager);
+        clusterEventHandler.setEventAdmin(eventAdmin);
+        clusterEventHandler.init();
+        Hashtable props = new Hashtable();
+        props.put("managed", "true");
+        register(EventHandler.class, clusterEventHandler, props);
+
+        LOGGER.debug("CELLAR EVENT: init local event listener");
+        localEventListener = new LocalEventListener();
+        localEventListener.setClusterManager(clusterManager);
+        localEventListener.setGroupManager(groupManager);
+        localEventListener.setConfigurationAdmin(configurationAdmin);
+        localEventListener.setEventProducer(eventProducer);
+        localEventListener.init();
+        props = new Hashtable();
+        props.put("event.topics", "*");
+        register(org.osgi.service.event.EventHandler.class, localEventListener, props);
+    }
+
+    @Override
+    public void doStop() {
+        super.doStop();
+
+        if (localEventListener != null) {
+            localEventListener.destroy();
+            localEventListener = null;
+        }
+        if (clusterEventHandler != null) {
+            clusterEventHandler.destroy();
+            clusterEventHandler = null;
+        }
+    }
+
+}
diff --git a/event/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/event/src/main/resources/OSGI-INF/blueprint/blueprint.xml
deleted file mode 100644
index 577b34c..0000000
--- a/event/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
-   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.
-  -->
-<blueprint default-availability="mandatory"
-    xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0
-           http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">
-
-    <!-- Local Event Listener -->
-    <bean id="localEventListener" class="org.apache.karaf.cellar.event.LocalEventListener" init-method="init" destroy-method="destroy">
-        <property name="clusterManager" ref="clusterManager"/>
-        <property name="groupManager" ref="groupManager"/>
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-        <property name="eventProducer" ref="eventProducer"/>
-    </bean>
-    <service ref="localEventListener">
-        <interfaces>
-            <value>org.osgi.service.event.EventHandler</value>
-        </interfaces>
-        <service-properties>
-            <entry key="event.topics">
-                <array value-type="java.lang.String">
-                    <value>*</value>
-                </array>
-            </entry>
-        </service-properties>
-    </service>
-
-    <!-- Cluster Event Handler -->
-    <bean id="clusterEventHandler" class="org.apache.karaf.cellar.event.ClusterEventHandler" init-method="init" destroy-method="destroy">
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-        <property name="groupManager" ref="groupManager"/>
-        <property name="clusterManager" ref="clusterManager"/>
-        <property name="eventAdmin" ref="eventAdmin"/>
-    </bean>
-    <service ref="clusterEventHandler" interface="org.apache.karaf.cellar.core.event.EventHandler">
-        <service-properties>
-            <entry key="managed" value="true"/>
-        </service-properties>
-    </service>
-
-    <reference id="clusterManager" interface="org.apache.karaf.cellar.core.ClusterManager"/>
-    <reference id="groupManager" interface="org.apache.karaf.cellar.core.GroupManager"/>
-    <reference id="configurationAdmin" interface="org.osgi.service.cm.ConfigurationAdmin"/>
-    <reference id="eventAdmin" interface="org.osgi.service.event.EventAdmin" />
-    <reference id="eventProducer" interface="org.apache.karaf.cellar.core.event.EventProducer"/>
-
-</blueprint>
\ No newline at end of file
diff --git a/features/NOTICE b/features/NOTICE
index addb35c..64cb235 100644
--- a/features/NOTICE
+++ b/features/NOTICE
@@ -1,5 +1,5 @@
 Apache Karaf Cellar

-Copyright 2011-2014 The Apache Software Foundation

+Copyright 2011-2015 The Apache Software Foundation

 

 I. Used Software

 

diff --git a/features/pom.xml b/features/pom.xml
index 7b701bb..f937a95 100644
--- a/features/pom.xml
+++ b/features/pom.xml
@@ -40,8 +40,12 @@
             <artifactId>org.apache.karaf.cellar.core</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.util</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.apache.karaf.shell</groupId>
-            <artifactId>org.apache.karaf.shell.table</artifactId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
         </dependency>
         <dependency>
             <groupId>org.osgi</groupId>
@@ -92,28 +96,28 @@
     <build>
         <plugins>
             <plugin>
+                <groupId>org.apache.karaf.tooling</groupId>
+                <artifactId>karaf-services-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
                 <configuration>
                     <instructions>
                         <Export-Package>
-                            org.apache.karaf.cellar.features*;version="${project.version}"
+                            !org.apache.karaf.cellar.features.management.internal,
+                            !org.apache.karaf.cellar.features.internal.osgi,
+                            org.apache.karaf.cellar.features*
                         </Export-Package>
                         <Import-Package>
-                            org.apache.karaf.cellar.core*;version="${project.version}",
-                            org.apache.felix.service.command,
-                            org.apache.felix.gogo.commands,
-                            org.apache.karaf.shell.console;version="[3,5)",
-                            org.apache.karaf.shell.console.commands;version="[3,5)",
-                            org.apache.karaf.shell.console.completer;version="[3,5)",
-                            org.apache.karaf.features*;version="[3,5)",
-                            org.apache.karaf.shell.commands;version="[3,5)",
-                            org.osgi*,
-                            org.slf4j;version="[1.6,2)";resolution:=optional
+                            org.slf4j;version="[1.6,2)";resolution:=optional,
+                            org.apache.karaf.shell*;resolution:=optional,
+                            *
                         </Import-Package>
-                        <DynamicImport-Package>javax.*,org.w3c.*,org.xml.*,*</DynamicImport-Package>
                         <Private-Package>
-                            org.apache.karaf.cellar.features.management.internal
+                            org.apache.karaf.cellar.features.management.internal,
+                            org.apache.karaf.cellar.features.internal.osgi,
+                            org.apache.karaf.util.tracker;-split-package:=merge-first
                         </Private-Package>
                     </instructions>
                 </configuration>
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 0329b65..2fead83 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
@@ -72,6 +72,12 @@
             return;
         }
 
+        // check if it's not a "local" event
+        if (event.getSourceNode() != null && event.getSourceNode().getId().equalsIgnoreCase(clusterManager.getNode().getId())) {
+            LOGGER.trace("CELLAR FEATURE: cluster event is local (coming from local synchronizer or listener)");
+            return;
+        }
+
         String name = event.getName();
         String version = event.getVersion();
         if (isAllowed(event.getSourceGroup(), Constants.CATEGORY, name, EventType.INBOUND) || event.getForce()) {
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 51534ff..d5efe38 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
@@ -16,9 +16,13 @@
 import org.apache.karaf.cellar.core.Configurations;
 import org.apache.karaf.cellar.core.Group;
 import org.apache.karaf.cellar.core.Synchronizer;
+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.features.Feature;
+import org.apache.karaf.features.FeatureEvent;
 import org.apache.karaf.features.Repository;
+import org.apache.karaf.features.RepositoryEvent;
 import org.osgi.service.cm.Configuration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -37,8 +41,16 @@
 
     private static final transient Logger LOGGER = LoggerFactory.getLogger(FeaturesSynchronizer.class);
 
+    private EventProducer eventProducer;
+
+    public void setEventProducer(EventProducer eventProducer) {
+        this.eventProducer = eventProducer;
+    }
+
     @Override
     public void init() {
+        if (groupManager == null)
+            return;
         Set<Group> groups = groupManager.listLocalGroups();
         if (groups != null && !groups.isEmpty()) {
             for (Group group : groups) {
@@ -60,19 +72,32 @@
     @Override
     public void sync(Group group) {
         String policy = getSyncPolicy(group);
-        if (policy != null && policy.equalsIgnoreCase("cluster")) {
-            LOGGER.debug("CELLAR FEATURE: sync policy is set as 'cluster' for cluster group " + group.getName());
-            if (clusterManager.listNodesByGroup(group).size() == 1 && clusterManager.listNodesByGroup(group).contains(clusterManager.getNode())) {
-                LOGGER.debug("CELLAR FEATURE: node is the first and only member of the group, pushing state");
-                push(group);
-            } else {
-                LOGGER.debug("CELLAR FEATURE: pulling state");
-                pull(group);
-            }
+        if (policy == null) {
+            LOGGER.warn("CELLAR FEATURE: sync policy is not defined for cluster group {}", group.getName());
         }
-        if (policy != null && policy.equalsIgnoreCase("node")) {
-            LOGGER.debug("CELLAR FEATURE: sync policy is set as 'node' for cluster group " + group.getName());
+        if (policy.equalsIgnoreCase("cluster")) {
+            LOGGER.debug("CELLAR FEATURE: sync policy set as 'cluster' for cluster group {}", group.getName());
+            LOGGER.debug("CELLAR FEATURE: updating node from the cluster (pull first)");
+            pull(group);
+            LOGGER.debug("CELLAR FEATURE: updating cluster from the local node (push after)");
             push(group);
+        } else if (policy.equalsIgnoreCase("node")) {
+            LOGGER.debug("CELLAR FEATURE: sync policy set as 'node' for cluster group {}", group.getName());
+            LOGGER.debug("CELLAR FEATURE: updating cluster from the local node (push first)");
+            push(group);
+            LOGGER.debug("CELLAR FEATURE: updating node from the cluster (pull after)");
+            pull(group);
+        } else if (policy.equalsIgnoreCase("clusterOnly")) {
+            LOGGER.debug("CELLAR FEATURE: sync policy set as 'clusterOnly' for cluster group " + group.getName());
+            LOGGER.debug("CELLAR FEATURE: updating node from the cluster (pull only)");
+            pull(group);
+        } else if (policy.equalsIgnoreCase("nodeOnly")) {
+            LOGGER.debug("CELLAR FEATURE: sync policy set as 'nodeOnly' for cluster group " + group.getName());
+            LOGGER.debug("CELLAR FEATURE: updating cluster from the local node (push only)");
+            push(group);
+        } else {
+            LOGGER.debug("CELLAR FEATURE: sync policy set as 'disabled' for cluster group " + group.getName());
+            LOGGER.debug("CELLAR FEATURE: no sync");
         }
     }
 
@@ -100,7 +125,7 @@
                             if (!isRepositoryRegisteredLocally(url)) {
                                 LOGGER.debug("CELLAR FEATURE: adding repository {}", url);
                                 featuresService.addRepository(new URI(url));
-                            }
+                            } // TODO uninstall local features repositories not on the cluster ?
                         } catch (MalformedURLException e) {
                             LOGGER.error("CELLAR FEATURE: failed to add repository URL {} (malformed)", url, e);
                         } catch (Exception e) {
@@ -134,7 +159,7 @@
                                 } catch (Exception e) {
                                     LOGGER.error("CELLAR FEATURE: failed to install feature {}/{} ", new Object[]{state.getName(), state.getVersion()}, e);
                                 }
-                            }
+                            } // TODO uninstall local features not on the cluster ?
                         } else LOGGER.trace("CELLAR FEATURE: feature {} is marked BLOCKED INBOUND for cluster group {}", name, groupName);
                     }
                 }
@@ -151,6 +176,12 @@
      */
     @Override
     public void push(Group group) {
+
+        if (eventProducer.getSwitch().getStatus().equals(SwitchStatus.OFF)) {
+            LOGGER.warn("CELLAR FEATURE: cluster event producer is OFF");
+            return;
+        }
+
         if (group != null) {
             String groupName = group.getName();
             LOGGER.debug("CELLAR FEATURE: pushing features repositories and features in cluster group {}", groupName);
@@ -177,8 +208,14 @@
                     for (Repository repository : repositoryList) {
                         try {
                             if (!clusterRepositories.containsKey(repository.getURI().toString())) {
-                                clusterRepositories.put(repository.getURI().toString(), repository.getName());
                                 LOGGER.debug("CELLAR FEATURE: pushing repository {} in cluster group {}", repository.getName(), groupName);
+                                // updating cluster state
+                                clusterRepositories.put(repository.getURI().toString(), repository.getName());
+                                // sending cluster event
+                                ClusterRepositoryEvent event = new ClusterRepositoryEvent(repository.getURI().toString(), RepositoryEvent.EventType.RepositoryAdded);
+                                event.setSourceGroup(group);
+                                event.setSourceNode(clusterManager.getNode());
+                                eventProducer.produce(event);
                             } else {
                                 LOGGER.debug("CELLAR FEATURE: repository {} is already in cluster group {}", repository.getName(), groupName);
                             }
@@ -192,12 +229,47 @@
                 if (featuresList != null && featuresList.length > 0) {
                     for (Feature feature : featuresList) {
                         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 FEATURE : pushing feature {}/{} to cluster group {}", feature.getName(), feature.getVersion(), groupName);
+                            boolean installed = featuresService.isInstalled(feature);
+                            String key = feature.getName() + "/" + feature.getVersion();
+                            FeatureState clusterFeature = clusterFeatures.get(key);
+                            if (clusterFeature == null) {
+                                LOGGER.debug("CELLAR FEATURE: adding feature {} to cluster group {}", key, groupName);
+                                // updating cluster state
+                                clusterFeature = new FeatureState();
+                                clusterFeature.setName(feature.getName());
+                                clusterFeature.setVersion(feature.getVersion());
+                                clusterFeature.setInstalled(installed);
+                                clusterFeatures.put(key, clusterFeature);
+                                // sending cluster event
+                                ClusterFeaturesEvent event;
+                                if (installed) {
+                                    event = new ClusterFeaturesEvent(feature.getName(), feature.getVersion(), FeatureEvent.EventType.FeatureInstalled);
+                                } else {
+                                    event = new ClusterFeaturesEvent(feature.getName(), feature.getVersion(), FeatureEvent.EventType.FeatureUninstalled);
+                                }
+                                event.setSourceGroup(group);
+                                event.setSourceNode(clusterManager.getNode());
+                                eventProducer.produce(event);
+
+                            } else {
+                                if (clusterFeature.getInstalled() != installed) {
+                                    // updating cluster state
+                                    clusterFeature.setInstalled(installed);
+                                    clusterFeatures.put(key, clusterFeature);
+                                    // sending cluster event
+                                    ClusterFeaturesEvent event;
+                                    if (installed) {
+                                        event = new ClusterFeaturesEvent(feature.getName(), feature.getVersion(), FeatureEvent.EventType.FeatureInstalled);
+                                    } else {
+                                        event = new ClusterFeaturesEvent(feature.getName(), feature.getVersion(), FeatureEvent.EventType.FeatureUninstalled);
+                                    }
+                                    event.setSourceGroup(group);
+                                    event.setSourceNode(clusterManager.getNode());
+                                    eventProducer.produce(event);
+                                } else {
+                                    LOGGER.debug("CELLAR FEATURE: feature {} already sync on the cluster group {}", key, groupName);
+                                }
+                            }
                         } else {
                             LOGGER.debug("CELLAR FEATURE: feature {} is marked BLOCKED OUTBOUND for cluster group {}", feature.getName(), groupName);
                         }
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 98a7e2e..3bbf34b 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
@@ -57,7 +57,7 @@
     public void featureEvent(FeatureEvent event) {
 
         if (!isEnabled()) {
-            LOGGER.debug("CELLAR FEATURE: local listener is disabled");
+            LOGGER.trace("CELLAR FEATURE: local listener is disabled");
             return;
         }
 
@@ -95,6 +95,7 @@
                         // broadcast the event
                         ClusterFeaturesEvent featureEvent = new ClusterFeaturesEvent(name, version, type);
                         featureEvent.setSourceGroup(group);
+                        featureEvent.setSourceNode(clusterManager.getNode());
                         eventProducer.produce(featureEvent);
                     } else LOGGER.trace("CELLAR FEATURE: feature {} is marked BLOCKED OUTBOUND for cluster group {}", name, group.getName());
                 }
@@ -111,7 +112,7 @@
     public void repositoryEvent(RepositoryEvent event) {
 
         if (!isEnabled()) {
-            LOGGER.debug("CELLAR FEATURE: local listener is disabled");
+            LOGGER.trace("CELLAR FEATURE: local listener is disabled");
             return;
         }
 
@@ -131,6 +132,7 @@
                     for (Group group : groups) {
                         ClusterRepositoryEvent clusterRepositoryEvent = new ClusterRepositoryEvent(event.getRepository().getURI().toString(), event.getType());
                         clusterRepositoryEvent.setSourceGroup(group);
+                        clusterRepositoryEvent.setSourceNode(clusterManager.getNode());
                         RepositoryEvent.EventType type = event.getType();
 
                         Map<String, String> clusterRepositories = clusterManager.getMap(Constants.REPOSITORIES_MAP + Configurations.SEPARATOR + group.getName());
diff --git a/features/src/main/java/org/apache/karaf/cellar/features/internal/osgi/Activator.java b/features/src/main/java/org/apache/karaf/cellar/features/internal/osgi/Activator.java
new file mode 100644
index 0000000..9458356
--- /dev/null
+++ b/features/src/main/java/org/apache/karaf/cellar/features/internal/osgi/Activator.java
@@ -0,0 +1,163 @@
+/*
+ * 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.internal.osgi;
+
+import org.apache.karaf.cellar.core.ClusterManager;
+import org.apache.karaf.cellar.core.GroupManager;
+import org.apache.karaf.cellar.core.Synchronizer;
+import org.apache.karaf.cellar.core.event.EventHandler;
+import org.apache.karaf.cellar.core.event.EventProducer;
+import org.apache.karaf.cellar.features.FeaturesEventHandler;
+import org.apache.karaf.cellar.features.FeaturesSynchronizer;
+import org.apache.karaf.cellar.features.LocalFeaturesListener;
+import org.apache.karaf.cellar.features.RepositoryEventHandler;
+import org.apache.karaf.cellar.features.management.internal.CellarFeaturesMBeanImpl;
+import org.apache.karaf.features.FeaturesListener;
+import org.apache.karaf.features.FeaturesService;
+import org.apache.karaf.util.tracker.BaseActivator;
+import org.apache.karaf.util.tracker.annotation.ProvideService;
+import org.apache.karaf.util.tracker.annotation.RequireService;
+import org.apache.karaf.util.tracker.annotation.Services;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Hashtable;
+
+@Services(
+        provides = {
+                @ProvideService(FeaturesListener.class),
+                @ProvideService(Synchronizer.class),
+                @ProvideService(EventHandler.class)
+        },
+        requires = {
+                @RequireService(ClusterManager.class),
+                @RequireService(GroupManager.class),
+                @RequireService(EventProducer.class),
+                @RequireService(ConfigurationAdmin.class),
+                @RequireService(FeaturesService.class)
+        }
+)
+public class Activator extends BaseActivator {
+
+    private final static Logger LOGGER = LoggerFactory.getLogger(Activator.class);
+
+    private LocalFeaturesListener localFeaturesListener;
+    private FeaturesSynchronizer featuresSynchronizer;
+    private FeaturesEventHandler featuresEventHandler;
+    private RepositoryEventHandler repositoryEventHandler;
+    private ServiceRegistration mbeanRegistration;
+
+    @Override
+    public void doStart() throws Exception {
+
+        ConfigurationAdmin configurationAdmin = getTrackedService(ConfigurationAdmin.class);
+        if (configurationAdmin == null)
+            return;
+        ClusterManager clusterManager = getTrackedService(ClusterManager.class);
+        if (clusterManager == null)
+            return;
+        GroupManager groupManager = getTrackedService(GroupManager.class);
+        if (groupManager == null)
+            return;
+        EventProducer eventProducer = getTrackedService(EventProducer.class);
+        if (eventProducer == null)
+            return;
+        FeaturesService featuresService = getTrackedService(FeaturesService.class);
+        if (featuresService == null)
+            return;
+
+        LOGGER.debug("CELLAR FEATURE: init repository event handler");
+        repositoryEventHandler = new RepositoryEventHandler();
+        repositoryEventHandler.setConfigurationAdmin(configurationAdmin);
+        repositoryEventHandler.setFeaturesService(featuresService);
+        repositoryEventHandler.setClusterManager(clusterManager);
+        repositoryEventHandler.setGroupManager(groupManager);
+        repositoryEventHandler.init();
+        Hashtable props = new Hashtable();
+        props.put("managed", "true");
+        register(new Class[]{ EventHandler.class }, repositoryEventHandler, props);
+
+        LOGGER.debug("CELLAR FEATURE: init features event handler");
+        featuresEventHandler = new FeaturesEventHandler();
+        featuresEventHandler.setFeaturesService(featuresService);
+        featuresEventHandler.setClusterManager(clusterManager);
+        featuresEventHandler.setGroupManager(groupManager);
+        featuresEventHandler.setConfigurationAdmin(configurationAdmin);
+        featuresEventHandler.init();
+        register(new Class[]{ EventHandler.class }, featuresEventHandler, props);
+
+        LOGGER.debug("CELLAR FEATURE: init local features listener");
+        localFeaturesListener = new LocalFeaturesListener();
+        localFeaturesListener.setClusterManager(clusterManager);
+        localFeaturesListener.setGroupManager(groupManager);
+        localFeaturesListener.setEventProducer(eventProducer);
+        localFeaturesListener.setConfigurationAdmin(configurationAdmin);
+        localFeaturesListener.setFeaturesService(featuresService);
+        localFeaturesListener.init();
+        register(FeaturesListener.class, localFeaturesListener);
+
+        LOGGER.debug("CELLAR FEATURE: init features synchronizer");
+        featuresSynchronizer = new FeaturesSynchronizer();
+        featuresSynchronizer.setClusterManager(clusterManager);
+        featuresSynchronizer.setGroupManager(groupManager);
+        featuresSynchronizer.setEventProducer(eventProducer);
+        featuresSynchronizer.setConfigurationAdmin(configurationAdmin);
+        featuresSynchronizer.setFeaturesService(featuresService);
+        featuresSynchronizer.init();
+        props = new Hashtable();
+        props.put("resource", "feature");
+        register(Synchronizer.class, featuresSynchronizer, props);
+
+        LOGGER.debug("CELLAR FEATURE: register MBean");
+        CellarFeaturesMBeanImpl mbean = new CellarFeaturesMBeanImpl();
+        mbean.setClusterManager(clusterManager);
+        mbean.setGroupManager(groupManager);
+        mbean.setConfigurationAdmin(configurationAdmin);
+        mbean.setFeaturesService(featuresService);
+        mbean.setEventProducer(eventProducer);
+        props = new Hashtable();
+        props.put("jmx.objectname", "org.apache.karaf.cellar:type=feature,name=" + System.getProperty("karaf.name"));
+        mbeanRegistration = bundleContext.registerService(getInterfaceNames(mbean), mbean, props);
+
+    }
+
+    @Override
+    public void doStop() {
+        super.doStop();
+
+        if (mbeanRegistration != null) {
+            mbeanRegistration.unregister();
+            mbeanRegistration = null;
+        }
+        if (featuresSynchronizer != null) {
+            featuresSynchronizer.destroy();
+            featuresSynchronizer = null;
+        }
+        if (localFeaturesListener != null) {
+            localFeaturesListener.destroy();
+            localFeaturesListener = null;
+        }
+        if (featuresEventHandler != null) {
+            featuresEventHandler.destroy();
+            featuresEventHandler = null;
+        }
+        if (repositoryEventHandler != null) {
+            repositoryEventHandler.destroy();
+            repositoryEventHandler = null;
+        }
+    }
+
+}
diff --git a/features/src/main/java/org/apache/karaf/cellar/features/management/CellarFeaturesMBean.java b/features/src/main/java/org/apache/karaf/cellar/features/management/CellarFeaturesMBean.java
index 54f6482..faa6db1 100644
--- a/features/src/main/java/org/apache/karaf/cellar/features/management/CellarFeaturesMBean.java
+++ b/features/src/main/java/org/apache/karaf/cellar/features/management/CellarFeaturesMBean.java
@@ -25,39 +25,50 @@
      * Add a features repository in a cluster group.
      *
      * @param group the cluster group name.
-     * @param url the features repository URL.
+     * @param nameOrUrl the features repository name or URL.
      * @throws Exception in case of add failure.
      */
-    void addRepository(String group, String url) throws Exception;
+    void addRepository(String group, String nameOrUrl) throws Exception;
 
     /**
      * Add a features repository in a cluster group.
      *
      * @param group the cluster group name.
-     * @param url the features repository URL.
+     * @param nameOrUrl the features repository name or URL.
+     * @param version the features repository version (when name is provided).
+     * @throws Exception in case of add failure.
+     */
+    void addRepository(String group, String nameOrUrl, String version) throws Exception;
+
+    /**
+     * Add a features repository in a cluster group.
+     *
+     * @param group the cluster group name.
+     * @param nameOrUrl the features repository URL.
+     * @param version the features repository version (when name is provided).
      * @param install true to install all features contained in the repository, false else.
      * @throws Exception in case of add failure.
      */
-    void addRepository(String group, String url, boolean install) throws Exception;
+    void addRepository(String group, String nameOrUrl, String version, boolean install) throws Exception;
 
     /**
      * Remove a features repository from a cluster group.
      *
      * @param group the cluster group name.
-     * @param url the features repository URL.
+     * @param repository the features repository name or URL.
      * @throws Exception in case of remove failure.
      */
-    void removeRepository(String group, String url) throws Exception;
+    void removeRepository(String group, String repository) throws Exception;
 
     /**
      * Remove a features repository from a cluster group, eventually uninstalling all features described in the repository.
      *
      * @param group the cluster group name.
-     * @param url the features repository URL.
+     * @param repository the features repository name or URL.
      * @param uninstall true to uninstall all features described in the repository URL.
      * @throws Exception
      */
-    void removeRepository(String group, String url, boolean uninstall) throws Exception;
+    void removeRepository(String group, String repository, boolean uninstall) throws Exception;
 
     /**
      * Install a feature in a cluster group.
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 4ff1cc3..7af4e04 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
@@ -14,6 +14,7 @@
 package org.apache.karaf.cellar.features.management.internal;
 
 // import org.apache.karaf.cellar.bundle.BundleState;
+
 import org.apache.karaf.cellar.core.*;
 import org.apache.karaf.cellar.core.control.SwitchStatus;
 import org.apache.karaf.cellar.core.event.EventProducer;
@@ -35,6 +36,8 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Implementation of the Cellar Features MBean.
@@ -128,7 +131,7 @@
             // check if the feature exist
             FeatureState feature = null;
             String key = null;
-            for (String k: clusterFeatures.keySet()) {
+            for (String k : clusterFeatures.keySet()) {
                 FeatureState state = clusterFeatures.get(k);
                 key = k;
                 if (version == null) {
@@ -391,12 +394,17 @@
     }
 
     @Override
-    public void addRepository(String groupName, String url) throws Exception {
-        this.addRepository(groupName, url, false);
+    public void addRepository(String groupName, String nameOrUrl) throws Exception {
+        this.addRepository(groupName, nameOrUrl, null, false);
     }
 
     @Override
-    public void addRepository(String groupName, String url, boolean install) throws Exception {
+    public void addRepository(String groupName, String nameOrUrl, String version) throws Exception {
+        this.addRepository(groupName, nameOrUrl, version, false);
+    }
+
+    @Override
+    public void addRepository(String groupName, String nameOrUrl, String version, boolean install) throws Exception {
         // check if the group exists
         Group group = groupManager.findGroupByName(groupName);
         if (group == null) {
@@ -416,10 +424,15 @@
             // get the features in the cluster group
             Map<String, FeatureState> clusterFeatures = clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + groupName);
 
+            URI uri = featuresService.getRepositoryUriFor(nameOrUrl, version);
+            if (uri == null) {
+                uri = new URI(nameOrUrl);
+            }
+
             // check if the URL is already registered
             String name = null;
             for (String repository : clusterRepositories.keySet()) {
-                if (repository.equals(url)) {
+                if (repository.equals(uri)) {
                     name = clusterRepositories.get(repository);
                     break;
                 }
@@ -430,7 +443,7 @@
                 boolean localRegistered = false;
                 // local lookup
                 for (Repository registeredRepository : featuresService.listRepositories()) {
-                    if (registeredRepository.getURI().equals(new URI(url))) {
+                    if (registeredRepository.getURI().equals(uri)) {
                         repository = registeredRepository;
                         break;
                     }
@@ -438,13 +451,13 @@
                 if (repository == null) {
                     // registered locally
                     try {
-                        featuresService.addRepository(new URI(url));
+                        featuresService.addRepository(uri);
                     } catch (Exception e) {
-                        throw new IllegalArgumentException("Features repository URL " + url + " is not valid: " + e.getMessage());
+                        throw new IllegalArgumentException("Features repository URL " + uri + " is not valid: " + e.getMessage());
                     }
                     // get the repository
                     for (Repository registeredRepository : featuresService.listRepositories()) {
-                        if (registeredRepository.getURI().equals(new URI(url))) {
+                        if (registeredRepository.getURI().equals(uri)) {
                             repository = registeredRepository;
                             break;
                         }
@@ -455,7 +468,7 @@
                 }
 
                 // update the cluster group
-                clusterRepositories.put(url, name);
+                clusterRepositories.put(uri.toString(), name);
 
                 for (Feature feature : repository.getFeatures()) {
                     FeatureState state = new FeatureState();
@@ -467,15 +480,15 @@
 
                 // un-register the repository if it's not local registered
                 if (!localRegistered)
-                    featuresService.removeRepository(new URI(url));
+                    featuresService.removeRepository(uri);
 
                 // broadcast the cluster event
-                ClusterRepositoryEvent event = new ClusterRepositoryEvent(url, RepositoryEvent.EventType.RepositoryAdded);
+                ClusterRepositoryEvent event = new ClusterRepositoryEvent(uri.toString(), RepositoryEvent.EventType.RepositoryAdded);
                 event.setInstall(install);
                 event.setSourceGroup(group);
                 eventProducer.produce(event);
             } else {
-                throw new IllegalArgumentException("Features repository URL " + url + " already registered");
+                throw new IllegalArgumentException("Features repository URL " + uri + " already registered");
             }
         } finally {
             Thread.currentThread().setContextClassLoader(originalClassLoader);
@@ -483,12 +496,12 @@
     }
 
     @Override
-    public void removeRepository(String groupName, String url) throws Exception {
-        this.removeRepository(groupName, url, false);
+    public void removeRepository(String groupName, String repository) throws Exception {
+        this.removeRepository(groupName, repository, false);
     }
 
     @Override
-    public void removeRepository(String groupName, String url, boolean uninstall) throws Exception {
+    public void removeRepository(String groupName, String repo, boolean uninstall) throws Exception {
         // check if the group exists
         Group group = groupManager.findGroupByName(groupName);
         if (group == null) {
@@ -508,61 +521,87 @@
         ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
         try {
             Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
-            // looking for the URL in the list
-            String name = null;
-            for (String clusterRepository : clusterRepositories.keySet()) {
-                if (clusterRepository.equals(url)) {
-                    name = clusterRepositories.get(clusterRepository);
-                    break;
+
+            List<String> urls = new ArrayList<String>();
+            Pattern pattern = Pattern.compile(repo);
+            for (String repositoryUrl : clusterRepositories.keySet()) {
+                String repositoryName = clusterRepositories.get(repositoryUrl);
+                if (repositoryName != null && !repositoryName.isEmpty()) {
+                    // repository has name, try regex on the name
+                    Matcher nameMatcher = pattern.matcher(repositoryName);
+                    if (nameMatcher.matches()) {
+                        urls.add(repositoryUrl);
+                    } else {
+                        // the name regex doesn't match, fallback to repository URI regex
+                        Matcher uriMatcher = pattern.matcher(repositoryUrl);
+                        if (uriMatcher.matches()) {
+                            urls.add(repositoryUrl);
+                        }
+                    }
+                } else {
+                    // the repository name is not defined, use repository URI regex
+                    Matcher uriMatcher = pattern.matcher(repositoryUrl);
+                    if (uriMatcher.matches()) {
+                        urls.add(repositoryUrl);
+                    }
                 }
             }
-            if (name == null) {
-                // update the repository temporary locally
-                Repository repository = null;
-                boolean localRegistered = false;
-                // local lookup
-                for (Repository registeredRepository : featuresService.listRepositories()) {
-                    if (registeredRepository.getURI().equals(new URI(url))) {
-                        repository = registeredRepository;
+
+            for (String url : urls) {
+                // looking for the URL in the list
+                String name = null;
+                for (String clusterRepository : clusterRepositories.keySet()) {
+                    if (clusterRepository.equals(url)) {
+                        name = clusterRepositories.get(clusterRepository);
                         break;
                     }
                 }
-                if (repository == null) {
-                    // registered locally
-                    try {
-                        featuresService.addRepository(new URI(url));
-                    } catch (Exception e) {
-                        throw new IllegalArgumentException("Features repository URL " + url + " is not valid: " + e.getMessage());
-                    }
-                    // get the repository
+                if (name == null) {
+                    // update the repository temporary locally
+                    Repository repository = null;
+                    boolean localRegistered = false;
+                    // local lookup
                     for (Repository registeredRepository : featuresService.listRepositories()) {
                         if (registeredRepository.getURI().equals(new URI(url))) {
                             repository = registeredRepository;
                             break;
                         }
                     }
-                } else {
-                    localRegistered = true;
+                    if (repository == null) {
+                        // registered locally
+                        try {
+                            featuresService.addRepository(new URI(url));
+                        } catch (Exception e) {
+                            throw new IllegalArgumentException("Features repository URL " + url + " is not valid: " + e.getMessage());
+                        }
+                        // get the repository
+                        for (Repository registeredRepository : featuresService.listRepositories()) {
+                            if (registeredRepository.getURI().equals(new URI(url))) {
+                                repository = registeredRepository;
+                                break;
+                            }
+                        }
+                    } else {
+                        localRegistered = true;
+                    }
+
+                    // update the cluster group
+                    clusterRepositories.remove(url);
+
+                    for (Feature feature : repository.getFeatures()) {
+                        clusterFeatures.remove(feature.getName() + "/" + feature.getVersion());
+                    }
+
+                    // un-register the repository if it's not local registered
+                    if (!localRegistered)
+                        featuresService.removeRepository(new URI(url));
+
+                    // broadcast a cluster event
+                    ClusterRepositoryEvent event = new ClusterRepositoryEvent(repo, RepositoryEvent.EventType.RepositoryRemoved);
+                    event.setUninstall(uninstall);
+                    event.setSourceGroup(group);
+                    eventProducer.produce(event);
                 }
-
-                // update the cluster group
-                clusterRepositories.remove(url);
-
-                for (Feature feature : repository.getFeatures()) {
-                    clusterFeatures.remove(feature.getName() + "/" + feature.getVersion());
-                }
-
-                // un-register the repository if it's not local registered
-                if (!localRegistered)
-                    featuresService.removeRepository(new URI(url));
-
-                // broadcast a cluster event
-                ClusterRepositoryEvent event = new ClusterRepositoryEvent(url, RepositoryEvent.EventType.RepositoryRemoved);
-                event.setUninstall(uninstall);
-                event.setSourceGroup(group);
-                eventProducer.produce(event);
-            } else {
-                throw new IllegalArgumentException("Features repository URL " + url + " not found in cluster group " + groupName);
             }
         } finally {
             Thread.currentThread().setContextClassLoader(originalClassLoader);
diff --git a/features/src/main/java/org/apache/karaf/cellar/features/shell/BlockCommand.java b/features/src/main/java/org/apache/karaf/cellar/features/shell/BlockCommand.java
index cf9143d..eb246e7 100644
--- a/features/src/main/java/org/apache/karaf/cellar/features/shell/BlockCommand.java
+++ b/features/src/main/java/org/apache/karaf/cellar/features/shell/BlockCommand.java
@@ -18,17 +18,22 @@
 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.core.shell.completer.AllGroupsCompleter;
 import org.apache.karaf.cellar.features.Constants;
-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.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.Set;
 
 @Command(scope = "cluster", name = "feature-block", description = "Change the blocking policy for a feature")
+@Service
 public class BlockCommand extends CellarCommandSupport {
 
     @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
     String groupName;
 
     @Argument(index = 1, name = "featurePattern", description = "The feature pattern to block.", required = false, multiValued = false)
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 9c36e7e..6abd0f7 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
@@ -20,19 +20,25 @@
 import org.apache.karaf.cellar.core.event.EventProducer;
 import org.apache.karaf.cellar.core.event.EventType;
 import org.apache.karaf.cellar.core.shell.CellarCommandSupport;
+import org.apache.karaf.cellar.core.shell.completer.AllGroupsCompleter;
 import org.apache.karaf.cellar.features.ClusterFeaturesEvent;
 import org.apache.karaf.cellar.features.Constants;
 import org.apache.karaf.cellar.features.FeatureState;
 import org.apache.karaf.features.FeatureEvent;
 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;
+import org.apache.karaf.features.command.completers.AllFeatureCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.List;
 import java.util.Map;
 
 @Command(scope = "cluster", name = "feature-install", description = "Install a feature in a cluster group")
+@Service
 public class InstallFeatureCommand extends CellarCommandSupport {
 
     @Option(name = "-c", aliases = {"--no-clean"}, description = "Do not uninstall bundles on failure", required = false, multiValued = false)
@@ -45,12 +51,17 @@
     boolean noStart;
 
     @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
     String groupName;
 
     @Argument(index = 1, name = "features", description = "The name and version of the features to install. A feature id looks like name/version. The version is optional.", required = true, multiValued = true)
+    @Completion(AllFeatureCompleter.class)
     List<String> features;
 
+    @Reference
     private EventProducer eventProducer;
+
+    @Reference
     private FeaturesService featuresService;
 
     @Override
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 f146360..aa67d34 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
@@ -18,21 +18,27 @@
 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.core.shell.completer.AllGroupsCompleter;
 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;
-import org.apache.karaf.shell.table.ShellTable;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.table.ShellTable;
 
 import java.util.*;
 
 @Command(scope = "cluster", name = "feature-list", description = "List the features in a cluster group")
+@Service
 public class ListFeaturesCommand extends CellarCommandSupport {
 
     @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
     String groupName;
 
     @Option(name = "-i", aliases = { "--installed" }, description = "Display only installed features", required = false, multiValued = false)
@@ -53,6 +59,7 @@
     @Option(name = "--blocked", description = "Shows only blocked features", required = false, multiValued = false)
     boolean onlyBlocked;
 
+    @Reference
     private FeaturesService featuresService;
 
     @Override
@@ -73,7 +80,7 @@
             Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
 
             Map<String, ExtendedFeatureState> features = gatherFeatures();
-;
+
             if (features != null && !features.isEmpty()) {
 
                 ShellTable table = new ShellTable();
diff --git a/features/src/main/java/org/apache/karaf/cellar/features/shell/RepoAddCommand.java b/features/src/main/java/org/apache/karaf/cellar/features/shell/RepoAddCommand.java
index 0788087..b00a111 100644
--- a/features/src/main/java/org/apache/karaf/cellar/features/shell/RepoAddCommand.java
+++ b/features/src/main/java/org/apache/karaf/cellar/features/shell/RepoAddCommand.java
@@ -18,6 +18,7 @@
 import org.apache.karaf.cellar.core.control.SwitchStatus;
 import org.apache.karaf.cellar.core.event.EventProducer;
 import org.apache.karaf.cellar.core.shell.CellarCommandSupport;
+import org.apache.karaf.cellar.core.shell.completer.AllGroupsCompleter;
 import org.apache.karaf.cellar.features.Constants;
 import org.apache.karaf.cellar.features.FeatureState;
 import org.apache.karaf.cellar.features.ClusterRepositoryEvent;
@@ -25,27 +26,40 @@
 import org.apache.karaf.features.FeaturesService;
 import org.apache.karaf.features.Repository;
 import org.apache.karaf.features.RepositoryEvent;
-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.features.command.completers.AvailableRepoNameCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.net.URI;
 import java.util.List;
 import java.util.Map;
 
 @Command(scope = "cluster", name = "feature-repo-add", description = "Add a features repository to a cluster group")
+@Service
 public class RepoAddCommand extends CellarCommandSupport {
 
     @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
     String groupName;
 
-    @Argument(index = 1, name = "urls", description = "URLs of the feature repositories separated by whitespaces", required = true, multiValued = true)
-    List<String> urls;
+    @Argument(index = 1, name = "name/url", description = "Shortcut name of the features repository or the full URL", required = true, multiValued = false)
+    @Completion(AvailableRepoNameCompleter.class)
+    String nameOrUrl;
 
-    @Option(name = "-i", aliases = { "--install" }, description = "Install all features contained in the features repository", required = false, multiValued = false)
+    @Argument(index = 2, name = "version", description = "The version of the features repository if using features repository name as first argument. It should be empty if using the URL.", required = false, multiValued = false)
+    String version;
+
+    @Option(name = "-i", aliases = {"--install"}, description = "Install all features contained in the features repository", required = false, multiValued = false)
     boolean install;
 
+    @Reference
     private EventProducer eventProducer;
+
+    @Reference
     private FeaturesService featuresService;
 
     @Override
@@ -71,69 +85,71 @@
             // get the features in the cluster group
             Map<String, FeatureState> clusterFeatures = clusterManager.getMap(Constants.FEATURES_MAP + Configurations.SEPARATOR + groupName);
 
-            for (String url : urls) {
-                // check if the URL is already registered
-                String name = null;
-                for (String repository : clusterRepositories.keySet()) {
-                    if (repository.equals(url)) {
-                        name = clusterRepositories.get(url);
+            URI uri = featuresService.getRepositoryUriFor(nameOrUrl, version);
+            if (uri == null) {
+                uri = new URI(nameOrUrl);
+            }
+            // check if the URL is already registered
+            String name = null;
+            for (String repository : clusterRepositories.keySet()) {
+                if (repository.equals(uri)) {
+                    name = clusterRepositories.get(uri);
+                    break;
+                }
+            }
+            if (name == null) {
+                // update the repository temporary locally
+                Repository repository = null;
+                boolean localRegistered = false;
+                // local lookup
+                for (Repository registeredRepository : featuresService.listRepositories()) {
+                    if (registeredRepository.getURI().equals(uri)) {
+                        repository = registeredRepository;
                         break;
                     }
                 }
-                if (name == null) {
-                    // update the repository temporary locally
-                    Repository repository  = null;
-                    boolean localRegistered = false;
-                    // local lookup
+                if (repository == null) {
+                    // registered locally
+                    try {
+                        featuresService.addRepository(uri);
+                    } catch (Exception e) {
+                        System.err.println("Repository URL " + uri + " is not valid: " + e.getMessage());
+                        return null;
+                    }
+                    // get the repository
                     for (Repository registeredRepository : featuresService.listRepositories()) {
-                        if (registeredRepository.getURI().equals(new URI(url))) {
+                        if (registeredRepository.getURI().equals(uri)) {
                             repository = registeredRepository;
                             break;
                         }
                     }
-                    if (repository == null) {
-                        // registered locally
-                        try {
-                            featuresService.addRepository(new URI(url));
-                        } catch (Exception e) {
-                            System.err.println("Repository URL " + url + " is not valid: " + e.getMessage());
-                            continue;
-                        }
-                        // get the repository
-                        for (Repository registeredRepository : featuresService.listRepositories()) {
-                            if (registeredRepository.getURI().equals(new URI(url))) {
-                                repository = registeredRepository;
-                                break;
-                            }
-                        }
-                    } else {
-                        localRegistered = true;
-                    }
-
-                    // update the features repositories in the cluster group
-                    clusterRepositories.put(url, repository.getName());
-
-                    // update the features in the cluster group
-                    for (Feature feature : repository.getFeatures()) {
-                        FeatureState featureState = new FeatureState();
-                        featureState.setName(feature.getName());
-                        featureState.setVersion(feature.getVersion());
-                        featureState.setInstalled(featuresService.isInstalled(feature));
-                        clusterFeatures.put(feature.getName() + "/" + feature.getVersion(), featureState);
-                    }
-
-                    // un-register the repository if it's not local registered
-                    if (!localRegistered)
-                        featuresService.removeRepository(new URI(url));
-
-                    // broadcast the cluster event
-                    ClusterRepositoryEvent event = new ClusterRepositoryEvent(url, RepositoryEvent.EventType.RepositoryAdded);
-                    event.setInstall(install);
-                    event.setSourceGroup(group);
-                    eventProducer.produce(event);
                 } else {
-                    System.err.println("Features repository URL " + url + " already registered");
+                    localRegistered = true;
                 }
+
+                // update the features repositories in the cluster group
+                clusterRepositories.put(uri.toString(), repository.getName());
+
+                // update the features in the cluster group
+                for (Feature feature : repository.getFeatures()) {
+                    FeatureState featureState = new FeatureState();
+                    featureState.setName(feature.getName());
+                    featureState.setVersion(feature.getVersion());
+                    featureState.setInstalled(featuresService.isInstalled(feature));
+                    clusterFeatures.put(feature.getName() + "/" + feature.getVersion(), featureState);
+                }
+
+                // un-register the repository if it's not local registered
+                if (!localRegistered)
+                    featuresService.removeRepository(uri);
+
+                // broadcast the cluster event
+                ClusterRepositoryEvent event = new ClusterRepositoryEvent(uri.toString(), RepositoryEvent.EventType.RepositoryAdded);
+                event.setInstall(install);
+                event.setSourceGroup(group);
+                eventProducer.produce(event);
+            } else {
+                System.err.println("Features repository URL " + uri + " already registered");
             }
         } finally {
             Thread.currentThread().setContextClassLoader(originalClassLoader);
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 5b6f55b..5794d10 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
@@ -16,21 +16,27 @@
 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.core.shell.completer.AllGroupsCompleter;
 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 org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.table.ShellTable;
 
 import java.util.HashMap;
 import java.util.Map;
 
 @Command(scope = "cluster", name = "feature-repo-list", description = "List the features repositories in a cluster group")
+@Service
 public class RepoListCommand extends CellarCommandSupport {
 
     @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
     String groupName;
 
     @Option(name = "--no-format", description = "Disable table rendered output", required = false, multiValued = false)
@@ -42,6 +48,7 @@
     @Option(name = "--cluster", description = "Shows only features repositories on the cluster", required = false, multiValued = false)
     boolean onlyCluster;
 
+    @Reference
     private FeaturesService featuresService;
 
     @Override
diff --git a/features/src/main/java/org/apache/karaf/cellar/features/shell/RepoRemoveCommand.java b/features/src/main/java/org/apache/karaf/cellar/features/shell/RepoRemoveCommand.java
index 0d3c582..df03c29 100644
--- a/features/src/main/java/org/apache/karaf/cellar/features/shell/RepoRemoveCommand.java
+++ b/features/src/main/java/org/apache/karaf/cellar/features/shell/RepoRemoveCommand.java
@@ -18,6 +18,7 @@
 import org.apache.karaf.cellar.core.control.SwitchStatus;
 import org.apache.karaf.cellar.core.event.EventProducer;
 import org.apache.karaf.cellar.core.shell.CellarCommandSupport;
+import org.apache.karaf.cellar.core.shell.completer.AllGroupsCompleter;
 import org.apache.karaf.cellar.features.Constants;
 import org.apache.karaf.cellar.features.FeatureState;
 import org.apache.karaf.cellar.features.ClusterRepositoryEvent;
@@ -25,27 +26,40 @@
 import org.apache.karaf.features.FeaturesService;
 import org.apache.karaf.features.Repository;
 import org.apache.karaf.features.RepositoryEvent;
-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.features.command.completers.InstalledRepoNameCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.net.URI;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 @Command(scope = "cluster", name = "feature-repo-remove", description = "Remove features repository URLs from a cluster group")
+@Service
 public class RepoRemoveCommand extends CellarCommandSupport {
 
     @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
     String groupName;
 
-    @Argument(index = 1, name = "urls", description = "The features repository URLs separated by whitespaces", required = true, multiValued = true)
-    List<String> urls;
+    @Argument(index = 1, name = "repository", description = "Name or url of the repository to remove", required = true, multiValued = false)
+    @Completion(InstalledRepoNameCompleter.class)
+    String repository;
 
-    @Option(name = "-u", aliases = { "--uninstall-all" }, description = "Uninstall all features contained in the features repositories", required = false, multiValued = false)
+    @Option(name = "-u", aliases = {"--uninstall-all"}, description = "Uninstall all features contained in the features repositories", required = false, multiValued = false)
     boolean uninstall;
 
+    @Reference
     private EventProducer eventProducer;
+
+    @Reference
     private FeaturesService featuresService;
 
     @Override
@@ -71,6 +85,32 @@
         ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
         try {
             Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+
+            List<String> urls = new ArrayList<String>();
+            Pattern pattern = Pattern.compile(repository);
+            for (String repositoryUrl : clusterRepositories.keySet()) {
+                String repositoryName = clusterRepositories.get(repositoryUrl);
+                if (repositoryName != null && !repositoryName.isEmpty()) {
+                    // repository has name, try regex on the name
+                    Matcher nameMatcher = pattern.matcher(repositoryName);
+                    if (nameMatcher.matches()) {
+                        urls.add(repositoryUrl);
+                    } else {
+                        // the name regex doesn't match, fallback to repository URI regex
+                        Matcher uriMatcher = pattern.matcher(repositoryUrl);
+                        if (uriMatcher.matches()) {
+                            urls.add(repositoryUrl);
+                        }
+                    }
+                } else {
+                    // the repository name is not defined, use repository URI regex
+                    Matcher uriMatcher = pattern.matcher(repositoryUrl);
+                    if (uriMatcher.matches()) {
+                        urls.add(repositoryUrl);
+                    }
+                }
+            }
+
             for (String url : urls) {
                 // looking for the URL in the list
                 boolean found = false;
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 2dfff8c..9bc7620 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
@@ -20,29 +20,38 @@
 import org.apache.karaf.cellar.core.event.EventProducer;
 import org.apache.karaf.cellar.core.event.EventType;
 import org.apache.karaf.cellar.core.shell.CellarCommandSupport;
+import org.apache.karaf.cellar.core.shell.completer.AllGroupsCompleter;
 import org.apache.karaf.cellar.features.ClusterFeaturesEvent;
 import org.apache.karaf.cellar.features.Constants;
 import org.apache.karaf.cellar.features.FeatureState;
 import org.apache.karaf.features.FeatureEvent;
-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.features.command.completers.AllFeatureCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.List;
 import java.util.Map;
 
 @Command(scope = "cluster", name = "feature-uninstall", description = "Uninstall a feature from a cluster group")
+@Service
 public class UninstallFeatureCommand extends CellarCommandSupport {
 
     @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
     String groupName;
 
     @Argument(index = 1, name = "features", description = "The name and version of the features to uninstall. A feature id looks like name/version. The version is optional.", required = true, multiValued = true)
+    @Completion(AllFeatureCompleter.class)
     List<String> features;
 
     @Option(name = "-r", aliases = "--no-auto-refresh", description = "Do not automatically refresh bundles", required = false, multiValued = false)
     boolean noRefresh;
 
+    @Reference
     private EventProducer eventProducer;
 
     @Override
diff --git a/features/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/features/src/main/resources/OSGI-INF/blueprint/blueprint.xml
deleted file mode 100644
index 4f30536..0000000
--- a/features/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ /dev/null
@@ -1,75 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-   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.
--->
-<blueprint default-availability="mandatory"
-    xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0
-           http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">
-
-    <!-- Local Features Listener -->
-    <bean id="localListener" class="org.apache.karaf.cellar.features.LocalFeaturesListener" init-method="init"
-          destroy-method="destroy">
-        <property name="clusterManager" ref="clusterManager"/>
-        <property name="groupManager" ref="groupManager"/>
-        <property name="eventProducer" ref="eventProducer"/>
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-        <property name="featuresService" ref="featuresService"/>
-    </bean>
-    <service ref="localListener" interface="org.apache.karaf.features.FeaturesListener"/>
-
-    <!-- Features/Repositories Synchronizer -->
-    <bean id="synchronizer" class="org.apache.karaf.cellar.features.FeaturesSynchronizer"
-          init-method="init" destroy-method="destroy">
-        <property name="clusterManager" ref="clusterManager"/>
-        <property name="groupManager" ref="groupManager"/>
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-        <property name="featuresService" ref="featuresService"/>
-    </bean>
-    <service ref="synchronizer" interface="org.apache.karaf.cellar.core.Synchronizer">
-        <service-properties>
-            <entry key="resource" value="feature"/>
-        </service-properties>
-    </service>
-
-    <!-- Cluster Features Event Handler -->
-    <bean id="featuresEventHandler" class="org.apache.karaf.cellar.features.FeaturesEventHandler"
-          init-method="init" destroy-method="destroy">
-        <property name="featuresService" ref="featuresService"/>
-        <property name="clusterManager" ref="clusterManager"/>
-        <property name="groupManager" ref="groupManager"/>
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-    </bean>
-    <service ref="featuresEventHandler" interface="org.apache.karaf.cellar.core.event.EventHandler">
-        <service-properties>
-            <entry key="managed" value="true"/>
-        </service-properties>
-    </service>
-
-    <!-- Cluster Features Repositories Event Handler -->
-    <bean id="repositoryEventHandler" class="org.apache.karaf.cellar.features.RepositoryEventHandler"
-          init-method="init" destroy-method="destroy">
-        <property name="featuresService" ref="featuresService"/>
-        <property name="clusterManager" ref="clusterManager"/>
-        <property name="groupManager" ref="groupManager"/>
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-    </bean>
-    <service ref="repositoryEventHandler" interface="org.apache.karaf.cellar.core.event.EventHandler"/>
-
-    <reference id="clusterManager" interface="org.apache.karaf.cellar.core.ClusterManager"/>
-    <reference id="groupManager" interface="org.apache.karaf.cellar.core.GroupManager"/>
-    <reference id="eventProducer" interface="org.apache.karaf.cellar.core.event.EventProducer"/>
-    <reference id="featuresService" interface="org.apache.karaf.features.FeaturesService"/>
-    <reference id="configurationAdmin" interface="org.osgi.service.cm.ConfigurationAdmin"/>
-
-</blueprint>
diff --git a/features/src/main/resources/OSGI-INF/blueprint/management.xml b/features/src/main/resources/OSGI-INF/blueprint/management.xml
deleted file mode 100644
index 2795c9a..0000000
--- a/features/src/main/resources/OSGI-INF/blueprint/management.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  ~ 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.
-  -->
-<blueprint default-availability="mandatory"
-           xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
-           xmlns:ext="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0">
-
-    <!-- system properties -->
-    <ext:property-placeholder placeholder-prefix="$[" placeholder-suffix="]" />
-
-    <!-- Cellar Features MBean -->
-    <bean id="cellarFeaturesMBean" class="org.apache.karaf.cellar.features.management.internal.CellarFeaturesMBeanImpl">
-        <property name="clusterManager" ref="clusterManager"/>
-        <property name="eventProducer" ref="eventProducer"/>
-        <property name="groupManager" ref="groupManager"/>
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-        <property name="featuresService" ref="featuresService"/>
-    </bean>
-    <service ref="cellarFeaturesMBean" auto-export="interfaces">
-        <service-properties>
-            <entry key="jmx.objectname" value="org.apache.karaf.cellar:type=feature,name=$[karaf.name]"/>
-        </service-properties>
-    </service>
-
-</blueprint>
\ No newline at end of file
diff --git a/features/src/main/resources/OSGI-INF/blueprint/shell-features.xml b/features/src/main/resources/OSGI-INF/blueprint/shell-features.xml
deleted file mode 100644
index 745fea6..0000000
--- a/features/src/main/resources/OSGI-INF/blueprint/shell-features.xml
+++ /dev/null
@@ -1,110 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-   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.
--->
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" default-activation="lazy">
-
-    <!-- Command Bundle -->
-    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
-        <command>
-            <action class="org.apache.karaf.cellar.features.shell.InstallFeatureCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-                <property name="configurationAdmin" ref="configurationAdmin"/>
-                <property name="featuresService" ref="featuresService"/>
-                <property name="eventProducer" ref="eventProducer"/>
-            </action>
-            <completers>
-                <ref component-id="allGroupsCompleter"/>
-                <!-- <ref component-id="allFeaturesCompleter"/> -->
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.features.shell.UninstallFeatureCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-                <property name="configurationAdmin" ref="configurationAdmin"/>
-                <property name="eventProducer" ref="eventProducer"/>
-            </action>
-            <completers>
-                <ref component-id="allGroupsCompleter"/>
-                <!-- <ref component-id="allFeaturesCompleter"/> -->
-            </completers>
-        </command>
-        <command>
-            <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"/>
-            </completers>
-        </command>
-        <command>
-            <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"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.features.shell.RepoAddCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-                <property name="eventProducer" ref="eventProducer"/>
-                <property name="featuresService" ref="featuresService"/>
-            </action>
-            <completers>
-                <ref component-id="allGroupsCompleter"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.features.shell.RepoRemoveCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-                <property name="eventProducer" ref="eventProducer"/>
-                <property name="featuresService" ref="featuresService"/>
-            </action>
-            <completers>
-                <ref component-id="allGroupsCompleter"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.features.shell.BlockCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-                <property name="configurationAdmin" ref="configurationAdmin"/>
-            </action>
-            <completers>
-                <ref component-id="allGroupsCompleter"/>
-                <!-- <ref component-id="allFeaturesCompleter"/> -->
-            </completers>
-        </command>
-    </command-bundle>
-
-    <bean id="allGroupsCompleter" class="org.apache.karaf.cellar.core.shell.completer.AllGroupsCompleter">
-        <property name="groupManager" ref="groupManager"/>
-    </bean>
-
-    <!--
-    <bean id="allFeaturesCompleter" class="org.apache.karaf.features.command.completers.AllFeatureCompleter">
-        <property name="featuresService" ref="featuresService"/>
-    </bean>
-    -->
-
-</blueprint>
diff --git a/hazelcast/NOTICE b/hazelcast/NOTICE
index addb35c..64cb235 100644
--- a/hazelcast/NOTICE
+++ b/hazelcast/NOTICE
@@ -1,5 +1,5 @@
 Apache Karaf Cellar

-Copyright 2011-2014 The Apache Software Foundation

+Copyright 2011-2015 The Apache Software Foundation

 

 I. Used Software

 

diff --git a/hazelcast/pom.xml b/hazelcast/pom.xml
index ebe6c99..4ffd5a9 100644
--- a/hazelcast/pom.xml
+++ b/hazelcast/pom.xml
@@ -34,17 +34,21 @@
     <name>Apache Karaf :: Cellar :: Hazelcast</name>
 
     <dependencies>
+        <!-- Core Dependencies -->
         <dependency>
             <groupId>com.hazelcast</groupId>
-            <artifactId>hazelcast</artifactId>
+            <artifactId>hazelcast-all</artifactId>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.apache.karaf.cellar</groupId>
             <artifactId>org.apache.karaf.cellar.core</artifactId>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.apache.karaf.cellar</groupId>
             <artifactId>org.apache.karaf.cellar.utils</artifactId>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.osgi</groupId>
@@ -56,6 +60,10 @@
             <artifactId>org.osgi.compendium</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.util</artifactId>
+        </dependency>
 
         <!-- Logging Dependencies -->
         <dependency>
@@ -75,23 +83,23 @@
     <build>
         <plugins>
             <plugin>
+                <groupId>org.apache.karaf.tooling</groupId>
+                <artifactId>karaf-services-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
                 <configuration>
                     <instructions>
                         <Export-Package>
-                            org.apache.karaf.cellar.hazelcast*;version="${project.version}"
+                            org.apache.karaf.cellar.hazelcast*
                         </Export-Package>
                         <Import-Package>
-                            com.hazelcast*,
-                            org.apache.karaf.cellar.core*;version="${project.version}",
-                            org.apache.karaf.cellar.utils.ping;version="${project.version}",
-                            org.apache.karaf.features;version="[3,5)",
-                            org.osgi*,
-                            org.slf4j;version="[1.6,2)";resolution:=optional
+                            org.slf4j;version="[1.6,2)";resolution:=optional,
+                            *
                         </Import-Package>
-                        <DynamicImport-Package>javax.*,org.w3c.*,org.xml.*,*</DynamicImport-Package>
                         <Private-Package>
+                            org.apache.karaf.cellar.hazelcast.internal.osgi,
                             org.apache.karaf.cellar.hazelcast.management.internal
                         </Private-Package>
                     </instructions>
diff --git a/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/HazelcastClusterManager.java b/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/HazelcastClusterManager.java
index b9a9619..9059eb5 100644
--- a/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/HazelcastClusterManager.java
+++ b/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/HazelcastClusterManager.java
@@ -87,7 +87,7 @@
             Set<Member> members = cluster.getMembers();
             if (members != null && !members.isEmpty()) {
                 for (Member member : members) {
-                    HazelcastNode node = new HazelcastNode(member.getInetSocketAddress().getHostName(), member.getInetSocketAddress().getPort());
+                    HazelcastNode node = new HazelcastNode(member.getSocketAddress().getHostString(), member.getSocketAddress().getPort());
                     nodes.add(node);
                 }
             }
@@ -110,7 +110,7 @@
                 Set<Member> members = cluster.getMembers();
                 if (members != null && !members.isEmpty()) {
                     for (Member member : members) {
-                        HazelcastNode node = new HazelcastNode(member.getInetSocketAddress().getHostName(), member.getInetSocketAddress().getPort());
+                        HazelcastNode node = new HazelcastNode(member.getSocketAddress().getHostString(), member.getSocketAddress().getPort());
                         if (ids.contains(node.getId())) {
                             nodes.add(node);
                         }
@@ -135,7 +135,7 @@
                 Set<Member> members = cluster.getMembers();
                 if (members != null && !members.isEmpty()) {
                     for (Member member : members) {
-                        HazelcastNode node = new HazelcastNode(member.getInetSocketAddress().getHostName(), member.getInetSocketAddress().getPort());
+                        HazelcastNode node = new HazelcastNode(member.getSocketAddress().getHostString(), member.getSocketAddress().getPort());
                         if (id.equals(node.getId())) {
                             return node;
                         }
diff --git a/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/HazelcastGroupManager.java b/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/HazelcastGroupManager.java
index 57a5d71..78370dd 100644
--- a/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/HazelcastGroupManager.java
+++ b/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/HazelcastGroupManager.java
@@ -140,7 +140,7 @@
             Cluster cluster = instance.getCluster();
             if (cluster != null) {
                 Member member = cluster.getLocalMember();
-                node = new HazelcastNode(member.getInetSocketAddress().getHostName(), member.getInetSocketAddress().getPort());
+                node = new HazelcastNode(member.getSocketAddress().getHostString(), member.getSocketAddress().getPort());
             }
             return node;
         } finally {
diff --git a/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/HazelcastInstanceAware.java b/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/HazelcastInstanceAware.java
index 87e1288..7c9f14d 100644
--- a/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/HazelcastInstanceAware.java
+++ b/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/HazelcastInstanceAware.java
@@ -42,7 +42,7 @@
         Cluster cluster = instance.getCluster();
         if (cluster != null) {
             Member member = cluster.getLocalMember();
-            return new HazelcastNode(member.getInetSocketAddress().getHostName(), member.getInetSocketAddress().getPort());
+            return new HazelcastNode(member.getSocketAddress().getHostString(), member.getSocketAddress().getPort());
         } else {
             return null;
         }
diff --git a/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/QueueConsumer.java b/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/QueueConsumer.java
index 85c7534..d19461d 100644
--- a/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/QueueConsumer.java
+++ b/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/QueueConsumer.java
@@ -86,26 +86,32 @@
     @Override
     public void run() {
         ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
-        try {
-            while (isConsuming) {
-                if (combinedClassLoader != null) {
-                    Thread.currentThread().setContextClassLoader(combinedClassLoader);
-                } else Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
-                E e = null;
-                try {
-                    e = getQueue().poll(10, TimeUnit.SECONDS);
-                } catch (InterruptedException e1) {
-                    LOGGER.warn("CELLAR HAZELCAST: consume task interrupted");
-                }
+        E e;
+        while (isConsuming) {
+            if (combinedClassLoader != null) {
+                Thread.currentThread().setContextClassLoader(combinedClassLoader);
+            } else Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+            e = null;
+            try {
+                e = getQueue().poll(10, TimeUnit.SECONDS);
+            } catch (InterruptedException e1) {
+                LOGGER.warn("CELLAR HAZELCAST: consume task interrupted");
+            } catch (Exception e2) {
+                // catch everything from Hazelcast to prevent the death of Queue Consumer task
+                LOGGER.warn("CELLAR HAZELCAST: consumer task failed to poll the queue", e2);
+            }
+            
+            try {
                 if (e != null) {
                     consume(e);
                 }
+            } catch (Exception e1) {
+                LOGGER.error("CELLAR HAZELCAST: failed to consume from queue", e1);
             }
-        } catch (Exception ex) {
-            LOGGER.error("CELLAR HAZELCAST: failed to consume from queue", ex);
-        } finally {
-            Thread.currentThread().setContextClassLoader(originalClassLoader);
+
         }
+        
+        Thread.currentThread().setContextClassLoader(originalClassLoader);
     }
 
     /**
diff --git a/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/QueueProducer.java b/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/QueueProducer.java
index 4dcf404..b489444 100644
--- a/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/QueueProducer.java
+++ b/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/QueueProducer.java
@@ -31,6 +31,7 @@
 import com.hazelcast.core.IQueue;
 import org.apache.karaf.cellar.core.Configurations;
 import org.apache.karaf.cellar.core.Node;
+import org.apache.karaf.cellar.core.command.Command;
 import org.apache.karaf.cellar.core.command.Result;
 import org.apache.karaf.cellar.core.control.BasicSwitch;
 import org.apache.karaf.cellar.core.control.Switch;
@@ -71,7 +72,8 @@
     @Override
     public void produce(E event) {
         if (this.getSwitch().getStatus().equals(SwitchStatus.ON) || event.getForce() || event instanceof Result) {
-            event.setSourceNode(node);
+            if (event instanceof Result || event instanceof Command)
+                event.setSourceNode(node);
             try {
                 queue.put(event);
             } catch (InterruptedException e) {
diff --git a/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/TopicProducer.java b/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/TopicProducer.java
index 65f20f1..6ea4fc9 100644
--- a/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/TopicProducer.java
+++ b/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/TopicProducer.java
@@ -17,6 +17,7 @@
 import com.hazelcast.core.ITopic;
 import org.apache.karaf.cellar.core.Configurations;
 import org.apache.karaf.cellar.core.Node;
+import org.apache.karaf.cellar.core.command.Command;
 import org.apache.karaf.cellar.core.command.Result;
 import org.apache.karaf.cellar.core.control.BasicSwitch;
 import org.apache.karaf.cellar.core.control.Switch;
@@ -57,7 +58,8 @@
     @Override
     public void produce(E event) {
         if (this.getSwitch().getStatus().equals(SwitchStatus.ON) || event.getForce() || event instanceof Result) {
-            event.setSourceNode(node);
+            if (event instanceof Result || event instanceof Command)
+                event.setSourceNode(node);
             topic.publish(event);
         } else {
             if (eventSwitch.getStatus().equals(SwitchStatus.OFF)) {
diff --git a/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/factory/HazelcastConfigurationManager.java b/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/factory/HazelcastConfigurationManager.java
index a724962..e1e62e6 100644
--- a/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/factory/HazelcastConfigurationManager.java
+++ b/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/factory/HazelcastConfigurationManager.java
@@ -21,9 +21,12 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.File;
 import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import org.apache.karaf.cellar.core.discovery.DiscoveryService;
 
 /**
  * Hazelcast configuration manager.
@@ -33,9 +36,10 @@
 
     private static final transient Logger LOGGER = LoggerFactory.getLogger(HazelcastServiceFactory.class);
 
-    private String xmlConfigLocation = System.getProperty("karaf.home") + "/etc/hazelcast.xml";
+    private String xmlConfigLocation = System.getProperty("karaf.etc") + File.separator + "hazelcast.xml";
 
     private Set<String> discoveredMemberSet = new LinkedHashSet<String>();
+    private List<DiscoveryService> discoveryServices;
 
     /**
      * Build a Hazelcast {@link com.hazelcast.config.Config}.
@@ -45,7 +49,21 @@
     public Config getHazelcastConfig() {
         System.setProperty("hazelcast.config", xmlConfigLocation);
         Config config = new XmlConfigBuilder().build();
-        if (discoveredMemberSet != null) {
+        if (System.getProperty("hazelcast.instanceName") != null) {
+            config.setInstanceName(System.getProperty("hazelcast.instanceName"));
+        } else {
+            config.setInstanceName("cellar");
+        }
+        
+        if (config.getNetworkConfig().getJoin().getTcpIpConfig().isEnabled() && discoveredMemberSet != null) {
+            if (discoveryServices != null && !discoveryServices.isEmpty()) {
+                for (DiscoveryService service : discoveryServices) {
+                    service.refresh();
+                    Set<String> discovered = service.discoverMembers();
+                    discoveredMemberSet.addAll(discovered);
+                    LOGGER.trace("HAZELCAST STARTUP DISCOVERY: service {} found members {}", service, discovered);
+                }
+            }
             TcpIpConfig tcpIpConfig = config.getNetworkConfig().getJoin().getTcpIpConfig();
             tcpIpConfig.getMembers().addAll(discoveredMemberSet);
         }
@@ -71,5 +89,9 @@
         }
         return updated;
     }
+    
+    public void setDiscoveryServices(List<DiscoveryService> discoveryServices) {
+        this.discoveryServices = discoveryServices;
+    }
 
 }
diff --git a/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/factory/HazelcastServiceFactory.java b/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/factory/HazelcastServiceFactory.java
index 49c98ad..d5c634c 100644
--- a/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/factory/HazelcastServiceFactory.java
+++ b/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/factory/HazelcastServiceFactory.java
@@ -28,7 +28,7 @@
 
     private BundleContext bundleContext;
     private CombinedClassLoader combinedClassLoader;
-    private HazelcastConfigurationManager configurationManager = new HazelcastConfigurationManager();
+    private HazelcastConfigurationManager configurationManager;
 
     private CountDownLatch initializationLatch = new CountDownLatch(1);
     private CountDownLatch instanceLatch = new CountDownLatch(1);
@@ -92,5 +92,9 @@
     public void setCombinedClassLoader(CombinedClassLoader combinedClassLoader) {
         this.combinedClassLoader = combinedClassLoader;
     }
+    
+    public void setConfigurationManager(HazelcastConfigurationManager configurationManager) {
+        this.configurationManager = configurationManager;
+    }
 
 }
diff --git a/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/internal/osgi/Activator.java b/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/internal/osgi/Activator.java
new file mode 100644
index 0000000..e5dc387
--- /dev/null
+++ b/hazelcast/src/main/java/org/apache/karaf/cellar/hazelcast/internal/osgi/Activator.java
@@ -0,0 +1,398 @@
+/*
+ * 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.hazelcast.internal.osgi;
+
+import com.hazelcast.core.HazelcastInstance;
+import org.apache.aries.proxy.ProxyManager;
+import org.apache.karaf.cellar.core.ClusterManager;
+import org.apache.karaf.cellar.core.GroupManager;
+import org.apache.karaf.cellar.core.Node;
+import org.apache.karaf.cellar.core.Synchronizer;
+import org.apache.karaf.cellar.core.command.BasicCommandStore;
+import org.apache.karaf.cellar.core.command.ClusteredExecutionContext;
+import org.apache.karaf.cellar.core.command.CommandStore;
+import org.apache.karaf.cellar.core.command.ExecutionContext;
+import org.apache.karaf.cellar.core.control.*;
+import org.apache.karaf.cellar.core.discovery.DiscoveryService;
+import org.apache.karaf.cellar.core.discovery.DiscoveryTask;
+import org.apache.karaf.cellar.core.event.*;
+import org.apache.karaf.cellar.core.utils.CombinedClassLoader;
+import org.apache.karaf.cellar.hazelcast.*;
+import org.apache.karaf.cellar.hazelcast.factory.HazelcastConfigurationManager;
+import org.apache.karaf.cellar.hazelcast.factory.HazelcastServiceFactory;
+import org.apache.karaf.cellar.hazelcast.management.internal.CellarGroupMBeanImpl;
+import org.apache.karaf.cellar.hazelcast.management.internal.CellarMBeanImpl;
+import org.apache.karaf.cellar.hazelcast.management.internal.CellarNodeMBeanImpl;
+import org.apache.karaf.util.tracker.BaseActivator;
+import org.apache.karaf.util.tracker.annotation.Managed;
+import org.apache.karaf.util.tracker.annotation.ProvideService;
+import org.apache.karaf.util.tracker.annotation.RequireService;
+import org.apache.karaf.util.tracker.annotation.Services;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationListener;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+
+@Services(
+        provides = {
+                @ProvideService(HazelcastInstance.class),
+                @ProvideService(ClusterManager.class),
+                @ProvideService(GroupManager.class),
+                @ProvideService(EventTransportFactory.class),
+                @ProvideService(EventProducer.class),
+                @ProvideService(ExecutionContext.class),
+                @ProvideService(EventHandler.class),
+                @ProvideService(CommandStore.class)
+        },
+        requires = {
+                @RequireService(ConfigurationAdmin.class),
+                @RequireService(ProxyManager.class),
+                @RequireService(EventHandlerRegistry.class)
+        }
+)
+@Managed("org.apache.karaf.cellar.discovery")
+public class Activator extends BaseActivator implements ManagedService {
+
+    private final static Logger LOGGER = LoggerFactory.getLogger(Activator.class);
+
+    private CombinedClassLoader combinedClassLoader;
+    private HazelcastServiceFactory hazelcastServiceFactory;
+    private List<DiscoveryService> discoveryServices = new ArrayList<DiscoveryService>();
+    private List<Synchronizer> synchronizers = new ArrayList<Synchronizer>();
+    private HazelcastInstance hazelcastInstance;
+    private HazelcastGroupManager groupManager;
+    private DiscoveryTask discoveryTask;
+    private CellarExtender extender;
+    private TopicProducer producer;
+    private TopicConsumer consumer;
+    private ServiceTracker<DiscoveryService, DiscoveryService> discoveryServiceTracker;
+    private ServiceTracker<Synchronizer, Synchronizer> synchronizerServiceTracker;
+
+    private volatile ServiceRegistration coreMBeanRegistration;
+    private volatile ServiceRegistration nodeMBeanRegistration;
+    private volatile ServiceRegistration groupMBeanRegistration;
+
+    @Override
+    public void doStart() throws Exception {
+
+        ConfigurationAdmin configurationAdmin = getTrackedService(ConfigurationAdmin.class);
+        if (configurationAdmin == null)
+            return;
+        EventHandlerRegistry eventHandlerRegistry = getTrackedService(EventHandlerRegistry.class);
+        if (eventHandlerRegistry == null)
+            return;
+        ProxyManager proxyManager = getTrackedService(ProxyManager.class);
+        if (proxyManager == null)
+            return;
+
+        LOGGER.debug("CELLAR HAZELCAST: init combined class loader");
+        combinedClassLoader = new CombinedClassLoader();
+        combinedClassLoader.init();
+
+        LOGGER.debug("CELLAR HAZELCAST: start the discovery service tracker");
+        discoveryServiceTracker = new ServiceTracker<DiscoveryService, DiscoveryService>(bundleContext, DiscoveryService.class, new ServiceTrackerCustomizer<DiscoveryService, DiscoveryService>() {
+            @Override
+            public DiscoveryService addingService(ServiceReference<DiscoveryService> serviceReference) {
+                DiscoveryService service = bundleContext.getService(serviceReference);
+                discoveryServices.add(service);
+                return service;
+            }
+
+            @Override
+            public void modifiedService(ServiceReference<DiscoveryService> serviceReference, DiscoveryService discoveryService) {
+                // nothing to do
+            }
+
+            @Override
+            public void removedService(ServiceReference<DiscoveryService> serviceReference, DiscoveryService discoveryService) {
+                discoveryServices.remove(discoveryService);
+                bundleContext.ungetService(serviceReference);
+            }
+        });
+        discoveryServiceTracker.open();
+
+        LOGGER.debug("CELLAR HAZELCAST: start the synchronizer service tracker");
+        synchronizerServiceTracker = new ServiceTracker<Synchronizer, Synchronizer>(bundleContext, Synchronizer.class, new ServiceTrackerCustomizer<Synchronizer, Synchronizer>() {
+            @Override
+            public Synchronizer addingService(ServiceReference<Synchronizer> serviceReference) {
+                Synchronizer service = bundleContext.getService(serviceReference);
+                synchronizers.add(service);
+                return service;
+            }
+
+            @Override
+            public void modifiedService(ServiceReference<Synchronizer> serviceReference, Synchronizer synchronizer) {
+                // nothing to do
+            }
+
+            @Override
+            public void removedService(ServiceReference<Synchronizer> serviceReference, Synchronizer synchronizer) {
+                synchronizers.remove(synchronizer);
+                bundleContext.ungetService(serviceReference);
+            }
+        });
+        synchronizerServiceTracker.open();
+
+        LOGGER.debug("CELLAR HAZELCAST: init dispatcher");
+        EventHandlerRegistryDispatcher dispatcher = new EventHandlerRegistryDispatcher();
+        dispatcher.setHandlerRegistry(eventHandlerRegistry);
+        dispatcher.init();
+
+        LOGGER.debug("CELLAR HAZELCAST: create Hazelcast configuration manager");
+        HazelcastConfigurationManager hazelcastConfigurationManager = new HazelcastConfigurationManager();
+        hazelcastConfigurationManager.setDiscoveryServices(discoveryServices);
+
+        LOGGER.debug("CELLAR HAZELCAST: init Hazelcast service factory");
+        hazelcastServiceFactory = new HazelcastServiceFactory();
+        hazelcastServiceFactory.setCombinedClassLoader(combinedClassLoader);
+        hazelcastServiceFactory.setConfigurationManager(hazelcastConfigurationManager);
+        hazelcastServiceFactory.setBundleContext(bundleContext);
+        hazelcastServiceFactory.init();
+
+        LOGGER.debug("CELLAR HAZELCAST: register Hazelcast instance");
+        hazelcastInstance = hazelcastServiceFactory.getInstance();
+        register(HazelcastInstance.class, hazelcastInstance);
+
+        LOGGER.debug("CELLAR HAZELCAST: init discovery task");
+        discoveryTask = new DiscoveryTask();
+        discoveryTask.setDiscoveryServices(discoveryServices);
+        discoveryTask.setConfigurationAdmin(configurationAdmin);
+        discoveryTask.init();
+
+        LOGGER.debug("CELLAR HAZELCAST: register Hazelcast cluster manager");
+        HazelcastClusterManager clusterManager = new HazelcastClusterManager();
+        clusterManager.setInstance(hazelcastInstance);
+        clusterManager.setConfigurationAdmin(configurationAdmin);
+        clusterManager.setCombinedClassLoader(combinedClassLoader);
+        register(ClusterManager.class, clusterManager);
+
+        LOGGER.debug("CELLAR HAZELCAST: create Hazelcast event transport factory");
+        HazelcastEventTransportFactory eventTransportFactory = new HazelcastEventTransportFactory();
+        eventTransportFactory.setCombinedClassLoader(combinedClassLoader);
+        eventTransportFactory.setConfigurationAdmin(configurationAdmin);
+        eventTransportFactory.setInstance(hazelcastInstance);
+        eventTransportFactory.setDispatcher(dispatcher);
+        register(EventTransportFactory.class, eventTransportFactory);
+
+        LOGGER.debug("CELLAR HAZELCAST: init Hazelcast group manager");
+        groupManager = new HazelcastGroupManager();
+        groupManager.setInstance(hazelcastInstance);
+        groupManager.setCombinedClassLoader(combinedClassLoader);
+        groupManager.setBundleContext(bundleContext);
+        groupManager.setConfigurationAdmin(configurationAdmin);
+        groupManager.setEventTransportFactory(eventTransportFactory);
+        groupManager.init();
+        register(new Class[]{GroupManager.class, ConfigurationListener.class}, groupManager);
+
+        LOGGER.debug("CELLAR HAZELCAST: create Cellar membership listener");
+        CellarMembershipListener membershipListener = new CellarMembershipListener(hazelcastInstance);
+        membershipListener.setSynchronizers(synchronizers);
+        membershipListener.setGroupManager(groupManager);
+
+        LOGGER.debug("CELLAR HAZELCAST: init Cellar extender");
+        extender = new CellarExtender();
+        extender.setCombinedClassLoader(combinedClassLoader);
+        extender.setBundleContext(bundleContext);
+        extender.init();
+
+        Node node = clusterManager.getNode();
+
+        LOGGER.debug("CELLAR HAZELCAST: init topic consumer");
+        consumer = new TopicConsumer();
+        consumer.setInstance(hazelcastInstance);
+        consumer.setDispatcher(dispatcher);
+        consumer.setNode(node);
+        consumer.setConfigurationAdmin(configurationAdmin);
+        consumer.init();
+
+        LOGGER.debug("CELLAR HAZELCAST: init topic producer");
+        producer = new TopicProducer();
+        producer.setInstance(hazelcastInstance);
+        producer.setNode(node);
+        producer.setConfigurationAdmin(configurationAdmin);
+        producer.init();
+        register(EventProducer.class, producer);
+
+        LOGGER.debug("CELLAR HAZELCAST: register basic command store");
+        CommandStore commandStore = new BasicCommandStore();
+        register(CommandStore.class, commandStore);
+
+        LOGGER.debug("CELLAR HAZELCAST: register clustered execution context");
+        ClusteredExecutionContext executionContext = new ClusteredExecutionContext();
+        executionContext.setProducer(producer);
+        executionContext.setCommandStore(commandStore);
+        register(ExecutionContext.class, executionContext);
+
+        LOGGER.debug("CELLAR HAZELCAST: register producer switch command handler");
+        ProducerSwitchCommandHandler producerSwitchCommandHandler = new ProducerSwitchCommandHandler();
+        producerSwitchCommandHandler.setProducer(producer);
+        producerSwitchCommandHandler.setConfigurationAdmin(configurationAdmin);
+        register(EventHandler.class, producerSwitchCommandHandler);
+
+        LOGGER.debug("CELLAR HAZELCAST: register producer switch result handler");
+        ProducerSwitchResultHandler producerSwitchResultHandler = new ProducerSwitchResultHandler();
+        producerSwitchResultHandler.setCommandStore(commandStore);
+        register(EventHandler.class, producerSwitchResultHandler);
+
+        LOGGER.debug("CELLAR HAZELCAST: register consumer switch command handler");
+        ConsumerSwitchCommandHandler consumerSwitchCommandHandler = new ConsumerSwitchCommandHandler();
+        consumerSwitchCommandHandler.setProducer(producer);
+        consumerSwitchCommandHandler.setConsumer(consumer);
+        consumerSwitchCommandHandler.setConfigurationAdmin(configurationAdmin);
+        register(EventHandler.class, consumerSwitchCommandHandler);
+
+        LOGGER.debug("CELLAR HAZELCAST; register consumer switch result handler");
+        ConsumerSwitchResultHandler consumerSwitchResultHandler = new ConsumerSwitchResultHandler();
+        consumerSwitchResultHandler.setCommandStore(commandStore);
+        register(EventHandler.class, consumerSwitchResultHandler);
+
+        LOGGER.debug("CELLAR HAZELCAST: register manage handlers command handler");
+        ManageHandlersCommandHandler manageHandlersCommandHandler = new ManageHandlersCommandHandler();
+        manageHandlersCommandHandler.setConfigurationAdmin(configurationAdmin);
+        manageHandlersCommandHandler.setProducer(producer);
+        manageHandlersCommandHandler.setProxyManager(proxyManager);
+        register(EventHandler.class, manageHandlersCommandHandler);
+
+        LOGGER.debug("CELLAR HAZELCAST: register manage handlers result handler");
+        ManageHandlersResultHandler manageHandlersResultHandler = new ManageHandlersResultHandler();
+        manageHandlersResultHandler.setCommandStore(commandStore);
+        register(EventHandler.class, manageHandlersResultHandler);
+
+        LOGGER.debug("CELLAR HAZELCAST: register manage group command handler");
+        ManageGroupCommandHandler manageGroupCommandHandler = new ManageGroupCommandHandler();
+        manageGroupCommandHandler.setProducer(producer);
+        manageGroupCommandHandler.setClusterManager(clusterManager);
+        manageGroupCommandHandler.setGroupManager(groupManager);
+        register(EventHandler.class, manageGroupCommandHandler);
+
+        LOGGER.debug("CELLAR HAZELCAST: register manage group result handler");
+        ManageGroupResultHandler manageGroupResultHandler = new ManageGroupResultHandler();
+        manageGroupResultHandler.setCommandStore(commandStore);
+        register(EventHandler.class, manageGroupResultHandler);
+
+        LOGGER.debug("CELLAR HAZELCAST: register Cellar Core MBean");
+        CellarMBeanImpl cellarMBean = new CellarMBeanImpl();
+        cellarMBean.setBundleContext(bundleContext);
+        cellarMBean.setClusterManager(clusterManager);
+        cellarMBean.setGroupManager(groupManager);
+        cellarMBean.setExecutionContext(executionContext);
+        Hashtable props = new Hashtable();
+        props.put("jmx.objectname", "org.apache.karaf.cellar:type=core,name=" + System.getProperty("karaf.name"));
+        coreMBeanRegistration = bundleContext.registerService(getInterfaceNames(cellarMBean), cellarMBean, props);
+
+        LOGGER.debug("CELLAR HAZELCAST: register Cellar Node MBean");
+        CellarNodeMBeanImpl cellarNodeMBean = new CellarNodeMBeanImpl();
+        cellarNodeMBean.setClusterManager(clusterManager);
+        cellarNodeMBean.setExecutionContext(executionContext);
+        props = new Hashtable();
+        props.put("jmx.objectname", "org.apache.karaf.cellar:type=node,name=" + System.getProperty("karaf.name"));
+        nodeMBeanRegistration = bundleContext.registerService(getInterfaceNames(cellarNodeMBean), cellarNodeMBean, props);
+
+        LOGGER.debug("CELLAR HAZELCAST: register Cellar Group MBean");
+        CellarGroupMBeanImpl cellarGroupMBean = new CellarGroupMBeanImpl();
+        cellarGroupMBean.setClusterManager(clusterManager);
+        cellarGroupMBean.setExecutionContext(executionContext);
+        cellarGroupMBean.setGroupManager(groupManager);
+        props = new Hashtable();
+        props.put("jmx.objectname", "org.apache.karaf.cellar:type=group,name=" + System.getProperty("karaf.name"));
+        groupMBeanRegistration = bundleContext.registerService(getInterfaceNames(cellarGroupMBean), cellarGroupMBean, props);
+    }
+
+    @Override
+    public void doStop() {
+        super.doStop();
+
+        if (groupMBeanRegistration != null) {
+            groupMBeanRegistration.unregister();
+            groupMBeanRegistration = null;
+        }
+        if (nodeMBeanRegistration != null) {
+            nodeMBeanRegistration.unregister();
+            nodeMBeanRegistration = null;
+        }
+        if (coreMBeanRegistration != null) {
+            coreMBeanRegistration.unregister();
+            coreMBeanRegistration = null;
+        }
+        if (groupManager != null) {
+            try {
+                groupManager.destroy();
+            } catch (Exception e) {
+                LOGGER.trace("Error occured destroying the group manager", e);
+            }
+            groupManager = null;
+        }
+        if (hazelcastServiceFactory != null) {
+            hazelcastServiceFactory.destroy();
+            hazelcastServiceFactory = null;
+        }
+        if (hazelcastInstance != null) {
+            hazelcastInstance.shutdown();
+            hazelcastInstance = null;
+        }
+        if (discoveryTask != null) {
+            discoveryTask.destroy();
+            discoveryTask = null;
+        }
+        if (extender != null) {
+            extender.destroy();
+            extender = null;
+        }
+        if (producer != null) {
+            producer.destroy();
+            producer = null;
+        }
+        if (consumer != null) {
+            consumer.destroy();
+            consumer = null;
+        }
+        if (synchronizerServiceTracker != null) {
+            synchronizerServiceTracker.close();
+            synchronizerServiceTracker = null;
+        }
+        if (discoveryServiceTracker != null) {
+            discoveryServiceTracker.close();
+            discoveryServiceTracker = null;
+        }
+        if (combinedClassLoader != null) {
+            combinedClassLoader.destroy();
+            combinedClassLoader = null;
+        }
+    }
+
+    @Override
+    public void updated(Dictionary config) {
+        if (config == null) {
+            return;
+        }
+        HashMap map = new HashMap();
+        for (Enumeration keys = config.keys(); keys.hasMoreElements();) {
+            Object key = keys.nextElement();
+            map.put(key, config.get(key));
+        }
+        try {
+            hazelcastServiceFactory.update(map);
+        } catch (Exception e) {
+            LOGGER.error("Can't update Hazelcast service factory", e);
+        }
+    }
+
+}
diff --git a/hazelcast/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/hazelcast/src/main/resources/OSGI-INF/blueprint/blueprint.xml
deleted file mode 100644
index 74e571a..0000000
--- a/hazelcast/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ /dev/null
@@ -1,192 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-   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.
--->
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
-        xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.0.0">
-
-    <bean id="hazelcast" class="com.hazelcast.core.Hazelcast" factory-ref="instanceFactory"
-          factory-method="getInstance" destroy-method="shutdown"/>
-    <service ref="hazelcast" interface="com.hazelcast.core.HazelcastInstance"/>
-
-    <bean id="instanceFactory" class="org.apache.karaf.cellar.hazelcast.factory.HazelcastServiceFactory"
-          init-method="init" destroy-method="destroy">
-        <property name="combinedClassLoader" ref="combinedClassLoader"/>
-        <property name="bundleContext" ref="blueprintBundleContext"/>
-        <cm:managed-properties persistent-id="org.apache.karaf.cellar.discovery" update-strategy="component-managed"
-                                  update-method="update"/>
-    </bean>
-
-    <!-- Discovery Task -->
-    <bean id="discoveryTask" class="org.apache.karaf.cellar.core.discovery.DiscoveryTask" init-method="init" destroy-method="destroy">
-        <property name="discoveryServices" ref="discoveryServices"/>
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-    </bean>
-
-    <!-- Members Listener -->
-    <bean id="membershipListener" class="org.apache.karaf.cellar.hazelcast.CellarMembershipListener">
-        <argument index="0" ref="hazelcast"/>
-        <property name="synchronizers" ref="synchronizers"/>
-        <property name="groupManager" ref="groupManager"/>
-    </bean>
-
-    <!-- Cluster Manager -->
-    <bean id="clusterManager" class="org.apache.karaf.cellar.hazelcast.HazelcastClusterManager">
-        <property name="instance" ref="hazelcast"/>
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-        <property name="combinedClassLoader" ref="combinedClassLoader"/>
-    </bean>
-    <service ref="clusterManager" interface="org.apache.karaf.cellar.core.ClusterManager"/>
-
-    <!-- Cluster Group Manager -->
-    <bean id="groupManager" class="org.apache.karaf.cellar.hazelcast.HazelcastGroupManager" init-method="init" destroy-method="destroy">
-        <property name="instance" ref="hazelcast"/>
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-        <property name="eventTransportFactory" ref="eventTransportFactory"/>
-        <property name="combinedClassLoader" ref="combinedClassLoader"/>
-        <property name="bundleContext" ref="blueprintBundleContext"/>
-    </bean>
-    <service ref="groupManager">
-        <interfaces>
-            <value>org.apache.karaf.cellar.core.GroupManager</value>
-            <value>org.osgi.service.cm.ConfigurationListener</value>
-        </interfaces>
-    </service>
-
-    <!-- Cluster Event Transport Factory -->
-    <bean id="eventTransportFactory" class="org.apache.karaf.cellar.hazelcast.HazelcastEventTransportFactory">
-        <property name="dispatcher"  ref="dispatcher"/>
-        <property name="instance" ref="hazelcast"/>
-        <property name="combinedClassLoader" ref="combinedClassLoader"/>
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-    </bean>
-    <service ref="eventTransportFactory" interface="org.apache.karaf.cellar.core.event.EventTransportFactory"/>
-
-    <!-- Cellar Extender -->
-    <bean id="cellarExtender" class="org.apache.karaf.cellar.hazelcast.CellarExtender"  init-method="init" destroy-method="destroy">
-        <property name="combinedClassLoader" ref="combinedClassLoader"/>
-        <property name="bundleContext" ref="blueprintBundleContext"/>
-    </bean>
-
-    <!-- Bundle Combined ClassLoader -->
-    <bean id="combinedClassLoader" class="org.apache.karaf.cellar.core.utils.CombinedClassLoader" init-method="init" destroy-method="destroy"/>
-
-    <!-- Local Node -->
-    <bean id="node" factory-ref="clusterManager" factory-method="getNode"/>
-
-    <!-- Cluster Event Topic -->
-    <bean id="eventTopic" factory-ref="hazelcast" factory-method="getTopic">
-        <argument value="org.apache.karaf.cellar.event.topic"/>
-    </bean>
-
-    <!-- Cluster Event Consumer -->
-    <bean id="consumer" class="org.apache.karaf.cellar.hazelcast.TopicConsumer" init-method="init"
-          destroy-method="destroy">
-        <property name="instance" ref="hazelcast"/>
-        <property name="dispatcher" ref="dispatcher"/>
-        <property name="node" ref="node"/>
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-    </bean>
-
-    <!-- Cluster Event Producer -->
-    <bean id="producer" class="org.apache.karaf.cellar.hazelcast.TopicProducer" init-method="init">
-        <property name="instance" ref="hazelcast"/>
-        <property name="node" ref="node"/>
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-    </bean>
-    <service ref="producer" interface="org.apache.karaf.cellar.core.event.EventProducer"/>
-
-    <!-- Execution Context -->
-    <bean id="executionContext" class="org.apache.karaf.cellar.core.command.ClusteredExecutionContext">
-        <property name="producer" ref="producer"/>
-        <property name="commandStore" ref="commandStore"/>
-    </bean>
-    <service ref="executionContext" interface="org.apache.karaf.cellar.core.command.ExecutionContext"/>
-
-    <!-- Handler For Cluster Producer Switch Command Event -->
-    <bean id="producerSwitchCommandHandler" class="org.apache.karaf.cellar.core.control.ProducerSwitchCommandHandler">
-        <property name="producer" ref="producer"/>
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-    </bean>
-    <service ref="producerSwitchCommandHandler" interface="org.apache.karaf.cellar.core.event.EventHandler"/>
-
-    <!-- Handler For Cluster Producer Switch Result Event -->
-    <bean id="producerSwitchResultHandler" class="org.apache.karaf.cellar.core.control.ProducerSwitchResultHandler">
-        <property name="commandStore" ref="commandStore"/>
-    </bean>
-    <service ref="producerSwitchResultHandler" interface="org.apache.karaf.cellar.core.event.EventHandler"/>
-
-    <!-- Handler For Cluster Consumer Switch Command Event -->
-    <bean id="consumerSwitchCommandHandler" class="org.apache.karaf.cellar.core.control.ConsumerSwitchCommandHandler">
-        <property name="producer" ref="producer"/>
-        <property name="consumer" ref="consumer"/>
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-    </bean>
-    <service ref="consumerSwitchCommandHandler" interface="org.apache.karaf.cellar.core.event.EventHandler"/>
-
-    <!-- Handler For Cluster Consumer Switch Result Event -->
-    <bean id="consumerSwitchResultHandler" class="org.apache.karaf.cellar.core.control.ConsumerSwitchResultHandler">
-        <property name="commandStore" ref="commandStore"/>
-    </bean>
-    <service ref="consumerSwitchResultHandler" interface="org.apache.karaf.cellar.core.event.EventHandler"/>
-
-    <!-- Handler For Cluster Manage Handlers Command Event -->
-    <bean id="manageHandlersCommandHandler" class="org.apache.karaf.cellar.core.control.ManageHandlersCommandHandler">
-        <property name="producer" ref="producer"/>
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-        <property name="proxyManager" ref="proxyManager"/>
-    </bean>
-    <service ref="manageHandlersCommandHandler" interface="org.apache.karaf.cellar.core.event.EventHandler"/>
-
-    <!-- Handler For Cluster Manage Handlers Result Event -->
-    <bean id="manageHandlersResultHandler" class="org.apache.karaf.cellar.core.control.ManageHandlersResultHandler">
-        <property name="commandStore" ref="commandStore"/>
-    </bean>
-    <service ref="manageHandlersResultHandler" interface="org.apache.karaf.cellar.core.event.EventHandler"/>
-
-    <!-- Handler For Cluster Manager Group Command Event -->
-    <bean id="manageGroupCommandHandler" class="org.apache.karaf.cellar.core.control.ManageGroupCommandHandler">
-        <property name="producer" ref="producer"/>
-        <property name="clusterManager" ref="clusterManager"/>
-        <property name="groupManager" ref="groupManager"/>
-    </bean>
-    <service ref="manageGroupCommandHandler" interface="org.apache.karaf.cellar.core.event.EventHandler"/>
-
-    <!-- Handlers For Cluster Manager Group Result Event -->
-    <bean id="manageGroupResultHandler" class="org.apache.karaf.cellar.core.control.ManageGroupResultHandler">
-        <property name="commandStore" ref="commandStore"/>
-    </bean>
-    <service ref="manageGroupResultHandler" interface="org.apache.karaf.cellar.core.event.EventHandler"/>
-
-    <!-- Command Store -->
-    <bean id="commandStore" class="org.apache.karaf.cellar.core.command.BasicCommandStore"/>
-    <service ref="commandStore" interface="org.apache.karaf.cellar.core.command.CommandStore"/>
-
-    <!-- Cluster Event Dispatcher -->
-    <bean id="dispatcher" class="org.apache.karaf.cellar.core.event.EventHandlerRegistryDispatcher" init-method="init">
-        <property name="handlerRegistry" ref="registry"/>
-    </bean>
-
-    <reference id="registry" interface="org.apache.karaf.cellar.core.event.EventHandlerRegistry"/>
-    <reference id="configurationAdmin" interface="org.osgi.service.cm.ConfigurationAdmin"/>
-    <reference id="proxyManager" interface="org.apache.aries.proxy.ProxyManager"/>
-
-    <reference-list id="discoveryServices" availability="optional"
-               interface="org.apache.karaf.cellar.core.discovery.DiscoveryService"/>
-    <reference-list id="groupEventProducers" availability="optional" interface="org.apache.karaf.cellar.core.event.EventProducer"
-          filter="(type = group)"/>
-    <reference-list id="groupEventConsumers" availability="optional" interface="org.apache.karaf.cellar.core.event.EventConsumer"
-          filter="(type = group)"/>
-    <reference-list id="synchronizers" availability="optional" interface="org.apache.karaf.cellar.core.Synchronizer"/>
-
-</blueprint>
diff --git a/hazelcast/src/main/resources/OSGI-INF/blueprint/management.xml b/hazelcast/src/main/resources/OSGI-INF/blueprint/management.xml
deleted file mode 100644
index 14562cf..0000000
--- a/hazelcast/src/main/resources/OSGI-INF/blueprint/management.xml
+++ /dev/null
@@ -1,59 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-   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.
--->
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
-           xmlns:ext="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0">
-
-    <!-- system properties -->
-    <ext:property-placeholder placeholder-prefix="$[" placeholder-suffix="]" />
-
-    <reference id="featuresService" interface="org.apache.karaf.features.FeaturesService"/>
-
-    <!-- Core Cellar MBean -->
-    <bean id="cellarMBean" class="org.apache.karaf.cellar.hazelcast.management.internal.CellarMBeanImpl">
-        <property name="bundleContext" ref="blueprintBundleContext"/>
-        <property name="clusterManager" ref="clusterManager"/>
-        <property name="groupManager" ref="groupManager"/>
-        <property name="executionContext" ref="executionContext"/>
-    </bean>
-    <service ref="cellarMBean" auto-export="interfaces">
-        <service-properties>
-            <entry key="jmx.objectname" value="org.apache.karaf.cellar:type=core,name=$[karaf.name]"/>
-        </service-properties>
-    </service>
-
-    <!-- Cellar Node MBean -->
-    <bean id="cellarNodeMBean" class="org.apache.karaf.cellar.hazelcast.management.internal.CellarNodeMBeanImpl">
-        <property name="clusterManager" ref="clusterManager"/>
-        <property name="executionContext" ref="executionContext"/>
-    </bean>
-    <service ref="cellarNodeMBean" auto-export="interfaces">
-        <service-properties>
-            <entry key="jmx.objectname" value="org.apache.karaf.cellar:type=node,name=$[karaf.name]"/>
-        </service-properties>
-    </service>
-
-    <!-- Cellar Cluster Group MBean -->
-    <bean id="cellarGroupMBean" class="org.apache.karaf.cellar.hazelcast.management.internal.CellarGroupMBeanImpl">
-        <property name="clusterManager" ref="clusterManager"/>
-        <property name="executionContext" ref="executionContext"/>
-        <property name="groupManager" ref="groupManager"/>
-    </bean>
-    <service ref="cellarGroupMBean" auto-export="interfaces">
-        <service-properties>
-            <entry key="jmx.objectname" value="org.apache.karaf.cellar:type=group,name=$[karaf.name]"/>
-        </service-properties>
-    </service>
-
-</blueprint>
\ No newline at end of file
diff --git a/hazelcast/src/test/java/org/apache/karaf/cellar/hazelcast/factory/HazelcastServiceFactoryTest.java b/hazelcast/src/test/java/org/apache/karaf/cellar/hazelcast/factory/HazelcastServiceFactoryTest.java
index 502e3ae..44243d8 100644
--- a/hazelcast/src/test/java/org/apache/karaf/cellar/hazelcast/factory/HazelcastServiceFactoryTest.java
+++ b/hazelcast/src/test/java/org/apache/karaf/cellar/hazelcast/factory/HazelcastServiceFactoryTest.java
@@ -31,8 +31,9 @@
         // Joining a cluster may not work with the default configuration on
         // networks where multicast is disabled. Use a custom hazelcast.xml
         // configuration that disables multicast and enables tcp on 127.0.0.1
-        System.setProperty("karaf.home", "src/test/resources");
+        System.setProperty("karaf.etc", "src/test/resources/etc");
         HazelcastServiceFactory factory = new HazelcastServiceFactory();
+        factory.setConfigurationManager(new HazelcastConfigurationManager());
         factory.init();
         factory.getInstance();
         HazelcastInstance defaultInstance = Hazelcast.newHazelcastInstance(null);
diff --git a/http/balancer/NOTICE b/http/balancer/NOTICE
new file mode 100644
index 0000000..64cb235
--- /dev/null
+++ b/http/balancer/NOTICE
@@ -0,0 +1,39 @@
+Apache Karaf Cellar

+Copyright 2011-2015 The Apache Software Foundation

+

+I. Used Software

+

+This product includes software developed at

+The Apache Software Foundation (http://www.apache.org/).

+Licensed under the Apache License 2.0.

+

+This product uses software developed at

+The OSGi Alliance (http://www.osgi.org/).

+Licensed under the Apache License 2.0.

+

+This product uses software developed at

+Hazelcast (http://www.hazelcast.com/).

+Licensed under the Apache License 2.0.

+

+This product uses software developed at

+OPS4J (http://www.ops4j.org/).

+Licensed under the Apache License 2.0.

+

+This product uses software developed at

+FUSE Source (http://www.fusesource.org/).

+Licensed under the Apache License 2.0.

+

+This product uses software developed at

+JClouds (http://www.jclouds.org/).

+Licensed under the Apache License 2.0.

+

+This product uses software developed at

+SLF4J (http://www.slf4j.org/).

+Licensed under the MIT License.

+

+This product includes software from http://www.json.org.

+Copyright (c) 2002 JSON.org

+

+II. License Summary

+- Apache License 2.0

+- MIT License

diff --git a/http/balancer/pom.xml b/http/balancer/pom.xml
new file mode 100644
index 0000000..b426b1a
--- /dev/null
+++ b/http/balancer/pom.xml
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <!--
+
+        Licensed to the Apache Software Foundation (ASF) under one or more
+        contributor license agreements.  See the NOTICE file distributed with
+        this work for additional information regarding copyright ownership.
+        The ASF licenses this file to You under the Apache License, Version 2.0
+        (the "License"); you may not use this file except in compliance with
+        the License.  You may obtain a copy of the License at
+
+           http://www.apache.org/licenses/LICENSE-2.0
+
+        Unless required by applicable law or agreed to in writing, software
+        distributed under the License is distributed on an "AS IS" BASIS,
+        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+        See the License for the specific language governing permissions and
+        limitations under the License.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.karaf.cellar</groupId>
+        <artifactId>http</artifactId>
+        <version>4.0.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <groupId>org.apache.karaf.cellar.http</groupId>
+    <artifactId>org.apache.karaf.cellar.http.balancer</artifactId>
+    <packaging>bundle</packaging>
+    <name>Apache Karaf :: Cellar :: HTTP :: Balancer</name>
+
+    <properties>
+        <pax.web.version>4.2.0</pax.web.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.ops4j.pax.web</groupId>
+            <artifactId>pax-web-spi</artifactId>
+            <version>${pax.web.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+            <version>3.1.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.5</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.karaf.cellar</groupId>
+            <artifactId>org.apache.karaf.cellar.core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.karaf.tooling</groupId>
+                <artifactId>karaf-services-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <configuration>
+                    <instructions>
+                        <Export-Package>
+                            !org.apache.karaf.cellar.http.balancer.internal.osgi,
+                            org.apache.karaf.cellar.http.balancer*
+                        </Export-Package>
+                        <Import-Package>
+                            org.slf4j;version="[1.6,2)";resolution:=optional,
+                            *
+                        </Import-Package>
+                        <Private-Package>
+                            org.apache.karaf.cellar.http.balancer.internal.osgi,
+                            org.apache.http*,
+                            org.apache.commons.codec*
+                        </Private-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file
diff --git a/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/BalancedServletUtil.java b/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/BalancedServletUtil.java
new file mode 100644
index 0000000..a79a618
--- /dev/null
+++ b/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/BalancedServletUtil.java
@@ -0,0 +1,59 @@
+/*
+ * 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.http.balancer;
+
+import org.apache.karaf.cellar.core.ClusterManager;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Dictionary;
+
+public class BalancedServletUtil {
+
+    private final static Logger LOGGER = LoggerFactory.getLogger(BalancedServletUtil.class);
+
+    private ClusterManager clusterManager;
+    private ConfigurationAdmin configurationAdmin;
+
+    public String constructLocation(String alias) {
+        String httpHost = clusterManager.getNode().getHost();
+        String httpPort = null;
+        try {
+            Configuration configuration = configurationAdmin.getConfiguration("org.ops4j.pax.web", null);
+            if (configuration != null) {
+                Dictionary properties = configuration.getProperties();
+                if (properties != null) {
+                    httpPort = (String) properties.get("org.osgi.service.http.port");
+                }
+            }
+        } catch (Exception e) {
+            LOGGER.warn("CELLAR HTTP BALANCER: can't get HTTP port number from configuration", e);
+        }
+        if (httpPort == null)
+            httpPort = "8181";
+        String location = "http://" + httpHost + ":" + httpPort + alias;
+        return location;
+    }
+
+    public void setClusterManager(ClusterManager clusterManager) {
+        this.clusterManager = clusterManager;
+    }
+
+    public void setConfigurationAdmin(ConfigurationAdmin configurationAdmin) {
+        this.configurationAdmin = configurationAdmin;
+    }
+
+}
diff --git a/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/BalancerEventHandler.java b/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/BalancerEventHandler.java
new file mode 100644
index 0000000..32ca80e
--- /dev/null
+++ b/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/BalancerEventHandler.java
@@ -0,0 +1,139 @@
+/*
+ * 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.http.balancer;
+
+import org.apache.karaf.cellar.core.ClusterManager;
+import org.apache.karaf.cellar.core.Configurations;
+import org.apache.karaf.cellar.core.GroupManager;
+import org.apache.karaf.cellar.core.control.BasicSwitch;
+import org.apache.karaf.cellar.core.control.Switch;
+import org.apache.karaf.cellar.core.control.SwitchStatus;
+import org.apache.karaf.cellar.core.event.EventHandler;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.Servlet;
+import java.util.Hashtable;
+
+public class BalancerEventHandler implements EventHandler<ClusterBalancerEvent> {
+
+    private static final transient Logger LOGGER = LoggerFactory.getLogger(BalancerEventHandler.class);
+
+    public static final String SWITCH_ID = "org.apache.karaf.cellar.event.http.balancer.handler";
+
+    private final Switch eventSwitch = new BasicSwitch(SWITCH_ID);
+
+    private ClusterManager clusterManager;
+    private GroupManager groupManager;
+    private ConfigurationAdmin configurationAdmin;
+    private BundleContext bundleContext;
+    private ProxyServletRegistry proxyRegistry;
+
+    @Override
+    public void handle(ClusterBalancerEvent event) {
+        if (this.getSwitch().getStatus().equals(SwitchStatus.OFF)) {
+            LOGGER.debug("CELLAR HTTP BALANCER: {} switch is OFF, cluster event is not handled", SWITCH_ID);
+            return;
+        }
+
+        if (groupManager == null) {
+            //in rare cases for example right after installation this happens!
+            LOGGER.error("CELLAR HTTP BALANCER: retrieved event {} while groupManager is not available yet!", event);
+            return;
+        }
+
+        // check if the group is local
+        if (!groupManager.isLocalGroup(event.getSourceGroup().getName())) {
+            LOGGER.debug("CELLAR HTTP BALANCER: node is not part of the event cluster group {}", event.getSourceGroup().getName());
+            return;
+        }
+
+        // check if it's not a "local" event
+        if (event.getSourceNode() != null && event.getSourceNode().getId().equalsIgnoreCase(clusterManager.getNode().getId())) {
+            LOGGER.trace("CELLAR HTTP BALANCER: cluster event is local (coming from local synchronizer or listener)");
+            return;
+        }
+
+        String alias = event.getAlias();
+        if (event.getType() == ClusterBalancerEvent.ADDING) {
+            LOGGER.debug("CELLAR HTTP BALANCER: creating proxy servlet for {}", alias);
+            CellarBalancerProxyServlet cellarBalancerProxyServlet = new CellarBalancerProxyServlet();
+            cellarBalancerProxyServlet.setLocations(event.getLocations());
+            try {
+                cellarBalancerProxyServlet.init();
+                Hashtable<String, String> properties = new Hashtable<String, String>();
+                properties.put("alias", alias);
+                properties.put("cellar.http.balancer.proxy", "true");
+                ServiceRegistration registration = bundleContext.registerService(Servlet.class, cellarBalancerProxyServlet, properties);
+                proxyRegistry.register(alias, registration);
+            } catch (Exception e) {
+                LOGGER.error("CELLAR HTTP BALANCER: can't start proxy servlet", e);
+            }
+        } else if (event.getType() == ClusterBalancerEvent.REMOVING) {
+            if (proxyRegistry.contain(alias)) {
+                LOGGER.debug("CELLAR HTTP BALANCER: removing proxy servlet for {}", alias);
+                proxyRegistry.unregister(alias);
+            }
+        }
+    }
+
+    @Override
+    public Class<ClusterBalancerEvent> getType() {
+        return ClusterBalancerEvent.class;
+    }
+
+    @Override
+    public Switch getSwitch() {
+        // load the switch status from the config
+        try {
+            Configuration configuration = configurationAdmin.getConfiguration(Configurations.NODE, null);
+            if (configuration != null) {
+                Boolean status = new Boolean((String) configuration.getProperties().get(Configurations.HANDLER + "." + this.getClass().getName()));
+                if (status) {
+                    eventSwitch.turnOn();
+                } else {
+                    eventSwitch.turnOff();
+                }
+            }
+        } catch (Exception e) {
+            // ignore
+        }
+        return eventSwitch;
+    }
+
+    public void setConfigurationAdmin(ConfigurationAdmin configurationAdmin) {
+        this.configurationAdmin = configurationAdmin;
+    }
+
+    public void setClusterManager(ClusterManager clusterManager) {
+        this.clusterManager = clusterManager;
+    }
+
+    public void setGroupManager(GroupManager groupManager) {
+        this.groupManager = groupManager;
+    }
+
+    public void setBundleContext(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+    }
+
+    public void setProxyRegistry(ProxyServletRegistry proxyRegistry) {
+        this.proxyRegistry = proxyRegistry;
+    }
+
+}
diff --git a/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/CellarBalancerProxyServlet.java b/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/CellarBalancerProxyServlet.java
new file mode 100644
index 0000000..edcf672
--- /dev/null
+++ b/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/CellarBalancerProxyServlet.java
@@ -0,0 +1,552 @@
+/*
+ * 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.http.balancer;
+
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.AbortableHttpRequest;
+import org.apache.http.client.params.ClientPNames;
+import org.apache.http.client.params.CookiePolicy;
+import org.apache.http.client.utils.URIUtils;
+import org.apache.http.entity.InputStreamEntity;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.message.BasicHttpEntityEnclosingRequest;
+import org.apache.http.message.BasicHttpRequest;
+import org.apache.http.message.HeaderGroup;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.Constructor;
+import java.net.HttpCookie;
+import java.net.URI;
+import java.util.*;
+
+/**
+ * An HTTP reverse proxy/gateway servlet. It is designed to be extended for customization
+ * if desired. Most of the work is handled by
+ * <a href="http://hc.apache.org/httpcomponents-client-ga/">Apache HttpClient</a>.
+ * <p>
+ * There are alternatives to a servlet based proxy such as Apache mod_proxy if that is available to you. However
+ * this servlet is easily customizable by Java, secure-able by your web application's security (e.g. spring-security),
+ * portable across servlet engines, and is embeddable into another web application.
+ * </p>
+ * <p>
+ * Inspiration: http://httpd.apache.org/docs/2.0/mod/mod_proxy.html
+ * </p>
+ */
+public class CellarBalancerProxyServlet extends HttpServlet {
+
+    private final static Logger LOGGER = LoggerFactory.getLogger(CellarBalancerProxyServlet.class);
+
+    private List<String> locations;
+
+    protected boolean doForwardIP = true;
+    protected boolean doSendUrlFragment = true;
+
+    private HttpClient proxyClient;
+
+    public void setIPForwarding(boolean IPForwarding) {
+        this.doForwardIP = IPForwarding;
+    }
+
+    public void setLocations(List<String> locations) {
+        this.locations = locations;
+    }
+
+    @Override
+    public String getServletInfo() {
+        return "Apache Karaf Cellar Balancer Proxy Servlet";
+    }
+
+    @Override
+    public void init() throws ServletException {
+        HttpParams hcParams = new BasicHttpParams();
+        hcParams.setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.IGNORE_COOKIES);
+        // hcParams.setParameter(ClientPNames.HANDLE_REDIRECTS, ClientPNames.ALLOW_CIRCULAR_REDIRECTS);
+        proxyClient = createHttpClient(hcParams);
+    }
+
+    /**
+     * Called from {@link #init(javax.servlet.ServletConfig)}. HttpClient offers many opportunities
+     * for customization. By default,
+     * <a href="http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/impl/client/SystemDefaultHttpClient.html">
+     * SystemDefaultHttpClient</a> is used if available, otherwise it falls
+     * back to:
+     * <pre>new DefaultHttpClient(new ThreadSafeClientConnManager(),hcParams)</pre>
+     * SystemDefaultHttpClient uses PoolingClientConnectionManager. In any case, it should be thread-safe.
+     */
+    @SuppressWarnings({"unchecked", "deprecation"})
+    protected HttpClient createHttpClient(HttpParams hcParams) {
+        try {
+            //as of HttpComponents v4.2, this class is better since it uses System
+            // Properties:
+            Class clientClazz = Class.forName("org.apache.http.impl.client.SystemDefaultHttpClient");
+            Constructor constructor = clientClazz.getConstructor(HttpParams.class);
+            return (HttpClient) constructor.newInstance(hcParams);
+        } catch (ClassNotFoundException e) {
+            //no problem; use v4.1 below
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+        //Fallback on using older client:
+        return new DefaultHttpClient(new ThreadSafeClientConnManager(), hcParams);
+    }
+
+    @Override
+    public void destroy() {
+        //As of HttpComponents v4.3, clients implement closeable
+        if (proxyClient instanceof Closeable) {//TODO AutoCloseable in Java 1.6
+            try {
+                ((Closeable) proxyClient).close();
+            } catch (IOException e) {
+                log("While destroying servlet, shutting down HttpClient: " + e, e);
+            }
+        } else {
+            //Older releases require we do this:
+            if (proxyClient != null)
+                proxyClient.getConnectionManager().shutdown();
+        }
+        super.destroy();
+    }
+
+    @Override
+    protected void service(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
+            throws ServletException, IOException {
+
+        String location = locations.get(new Random().nextInt(locations.size()));
+        URI locationUri = URI.create(location);
+        HttpHost host = URIUtils.extractHost(locationUri);
+
+        LOGGER.debug("CELLAR HTTP BALANCER: proxying to");
+        LOGGER.debug("CELLAR HTTP BALANCER:     URI: {}", locationUri);
+        LOGGER.debug("CELLAR HTTP BALANCER:     Host: {}", host);
+
+        // Make the Request
+        //note: we won't transfer the protocol version because I'm not sure it would truly be compatible
+        String method = servletRequest.getMethod();
+        LOGGER.debug("CELLAR HTTP BALANCER:     Method: {}", method);
+        String proxyRequestUri = rewriteUrlFromRequest(servletRequest, location);
+        LOGGER.debug("CELLAR HTTP BALANCER:     Proxy Request URI: {}", proxyRequestUri);
+        HttpRequest proxyRequest;
+        //spec: RFC 2616, sec 4.3: either of these two headers signal that there is a message body.
+        if (servletRequest.getHeader(HttpHeaders.CONTENT_LENGTH) != null ||
+                servletRequest.getHeader(HttpHeaders.TRANSFER_ENCODING) != null) {
+            HttpEntityEnclosingRequest eProxyRequest = new BasicHttpEntityEnclosingRequest(method, proxyRequestUri);
+            // Add the input entity (streamed)
+            //  note: we don't bother ensuring we close the servletInputStream since the container handles it
+            eProxyRequest.setEntity(new InputStreamEntity(servletRequest.getInputStream(), servletRequest.getContentLength()));
+            proxyRequest = eProxyRequest;
+        } else
+            proxyRequest = new BasicHttpRequest(method, proxyRequestUri);
+
+        LOGGER.debug("CELLAR HTTP BALANCER:     copying request headers");
+        copyRequestHeaders(servletRequest, proxyRequest, host);
+
+        LOGGER.debug("CELLAR HTTP BALANCER:     set X-Forwarded header");
+        setXForwardedForHeader(servletRequest, proxyRequest);
+
+        HttpResponse proxyResponse = null;
+        try {
+            // Execute the request
+            LOGGER.debug("CELLAR HTTP BALANCER:     executing proxy request");
+            proxyResponse = proxyClient.execute(host, proxyRequest);
+
+            // Process the response
+            int statusCode = proxyResponse.getStatusLine().getStatusCode();
+            LOGGER.debug("CELLAR HTTP BALANCER:     status code: {}", statusCode);
+
+            // copying response headers to make sure SESSIONID or other Cookie which comes from remote server
+            // will be saved in client when the proxied url was redirected to another one.
+            // see issue [#51](https://github.com/mitre/HTTP-Proxy-Servlet/issues/51)
+            LOGGER.debug("CELLAR HTTP BALANCER:     copying response headers");
+            copyResponseHeaders(proxyResponse, servletRequest, servletResponse);
+
+            if (doResponseRedirectOrNotModifiedLogic(servletRequest, servletResponse, proxyResponse, statusCode, location)) {
+                //the response is already "committed" now without any body to send
+                return;
+            }
+
+            // Pass the response code. This method with the "reason phrase" is deprecated but it's the only way to pass the
+            //  reason along too.
+            //noinspection deprecation
+            LOGGER.debug("CELLAR HTTP BALANCER:     set response status code");
+            servletResponse.setStatus(statusCode, proxyResponse.getStatusLine().getReasonPhrase());
+
+            // Send the content to the client
+            LOGGER.debug("CELLAR HTTP BALANCER:     copying response entity");
+            copyResponseEntity(proxyResponse, servletResponse);
+
+        } catch (Exception e) {
+            //abort request, according to best practice with HttpClient
+            if (proxyRequest instanceof AbortableHttpRequest) {
+                AbortableHttpRequest abortableHttpRequest = (AbortableHttpRequest) proxyRequest;
+                abortableHttpRequest.abort();
+            }
+            if (e instanceof RuntimeException)
+                throw (RuntimeException) e;
+            if (e instanceof ServletException)
+                throw (ServletException) e;
+            //noinspection ConstantConditions
+            if (e instanceof IOException)
+                throw (IOException) e;
+            throw new RuntimeException(e);
+
+        } finally {
+            // make sure the entire entity was consumed, so the connection is released
+            if (proxyResponse != null)
+                consumeQuietly(proxyResponse.getEntity());
+            //Note: Don't need to close servlet outputStream:
+            // http://stackoverflow.com/questions/1159168/should-one-call-close-on-httpservletresponse-getoutputstream-getwriter
+        }
+    }
+
+    protected boolean doResponseRedirectOrNotModifiedLogic(
+            HttpServletRequest servletRequest, HttpServletResponse servletResponse,
+            HttpResponse proxyResponse, int statusCode, String location)
+            throws ServletException, IOException {
+        // Check if the proxy response is a redirect
+        // The following code is adapted from org.tigris.noodle.filters.CheckForRedirect
+        if (statusCode >= HttpServletResponse.SC_MULTIPLE_CHOICES /* 300 */
+                && statusCode < HttpServletResponse.SC_NOT_MODIFIED /* 304 */) {
+            Header locationHeader = proxyResponse.getLastHeader(HttpHeaders.LOCATION);
+            if (locationHeader == null) {
+                throw new ServletException("Received status code: " + statusCode
+                        + " but no " + HttpHeaders.LOCATION + " header was found in the response");
+            }
+            // Modify the redirect to go to this proxy servlet rather that the proxied host
+            String locStr = rewriteUrlFromResponse(servletRequest, locationHeader.getValue(), location);
+
+            servletResponse.sendRedirect(locStr);
+            return true;
+        }
+        // 304 needs special handling.  See:
+        // http://www.ics.uci.edu/pub/ietf/http/rfc1945.html#Code304
+        // We get a 304 whenever passed an 'If-Modified-Since'
+        // header and the data on disk has not changed; server
+        // responds w/ a 304 saying I'm not going to send the
+        // body because the file has not changed.
+        if (statusCode == HttpServletResponse.SC_NOT_MODIFIED) {
+            servletResponse.setIntHeader(HttpHeaders.CONTENT_LENGTH, 0);
+            servletResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+            return true;
+        }
+        return false;
+    }
+
+    protected void closeQuietly(Closeable closeable) {
+        try {
+            closeable.close();
+        } catch (IOException e) {
+            log(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * HttpClient v4.1 doesn't have the
+     * {@link org.apache.http.util.EntityUtils#consumeQuietly(org.apache.http.HttpEntity)} method.
+     */
+    protected void consumeQuietly(HttpEntity entity) {
+        try {
+            EntityUtils.consume(entity);
+        } catch (IOException e) {//ignore
+            log(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * These are the "hop-by-hop" headers that should not be copied.
+     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
+     * I use an HttpClient HeaderGroup class instead of Set<String> because this
+     * approach does case insensitive lookup faster.
+     */
+    protected static final HeaderGroup hopByHopHeaders;
+
+    static {
+        hopByHopHeaders = new HeaderGroup();
+        String[] headers = new String[]{
+                "Connection", "Keep-Alive", "Proxy-Authenticate", "Proxy-Authorization",
+                "TE", "Trailers", "Transfer-Encoding", "Upgrade"};
+        for (String header : headers) {
+            hopByHopHeaders.addHeader(new BasicHeader(header, null));
+        }
+    }
+
+    /**
+     * Copy request headers from the servlet client to the proxy request.
+     */
+    protected void copyRequestHeaders(HttpServletRequest servletRequest, HttpRequest proxyRequest, HttpHost host) {
+        // Get an Enumeration of all of the header names sent by the client
+        Enumeration enumerationOfHeaderNames = servletRequest.getHeaderNames();
+        while (enumerationOfHeaderNames.hasMoreElements()) {
+            String headerName = (String) enumerationOfHeaderNames.nextElement();
+            //Instead the content-length is effectively set via InputStreamEntity
+            if (headerName.equalsIgnoreCase(HttpHeaders.CONTENT_LENGTH))
+                continue;
+            if (hopByHopHeaders.containsHeader(headerName))
+                continue;
+
+            Enumeration headers = servletRequest.getHeaders(headerName);
+            while (headers.hasMoreElements()) {//sometimes more than one value
+                String headerValue = (String) headers.nextElement();
+                // In case the proxy host is running multiple virtual servers,
+                // rewrite the Host header to ensure that we get content from
+                // the correct virtual server
+                if (headerName.equalsIgnoreCase(HttpHeaders.HOST)) {
+                    headerValue = host.getHostName();
+                    if (host.getPort() != -1)
+                        headerValue += ":" + host.getPort();
+                } else if (headerName.equalsIgnoreCase(org.apache.http.cookie.SM.COOKIE)) {
+                    headerValue = getRealCookie(headerValue);
+                }
+                proxyRequest.addHeader(headerName, headerValue);
+            }
+        }
+    }
+
+    private void setXForwardedForHeader(HttpServletRequest servletRequest,
+                                        HttpRequest proxyRequest) {
+        String headerName = "X-Forwarded-For";
+        if (doForwardIP) {
+            String newHeader = servletRequest.getRemoteAddr();
+            String existingHeader = servletRequest.getHeader(headerName);
+            if (existingHeader != null) {
+                newHeader = existingHeader + ", " + newHeader;
+            }
+            proxyRequest.setHeader(headerName, newHeader);
+        }
+    }
+
+    /**
+     * Copy proxied response headers back to the servlet client.
+     */
+    protected void copyResponseHeaders(HttpResponse proxyResponse, HttpServletRequest servletRequest,
+                                       HttpServletResponse servletResponse) {
+        for (Header header : proxyResponse.getAllHeaders()) {
+            if (hopByHopHeaders.containsHeader(header.getName()))
+                continue;
+            if (header.getName().equalsIgnoreCase(org.apache.http.cookie.SM.SET_COOKIE) ||
+                    header.getName().equalsIgnoreCase(org.apache.http.cookie.SM.SET_COOKIE2)) {
+                copyProxyCookie(servletRequest, servletResponse, header);
+            } else {
+                servletResponse.addHeader(header.getName(), header.getValue());
+            }
+        }
+    }
+
+    /**
+     * Copy cookie from the proxy to the servlet client.
+     * Replaces cookie path to local path and renames cookie to avoid collisions.
+     */
+    protected void copyProxyCookie(HttpServletRequest servletRequest,
+                                   HttpServletResponse servletResponse, Header header) {
+        List<HttpCookie> cookies = HttpCookie.parse(header.getValue());
+        String path = servletRequest.getContextPath(); // path starts with / or is empty string
+        path += servletRequest.getServletPath(); // servlet path starts with / or is empty string
+
+        for (HttpCookie cookie : cookies) {
+            //set cookie name prefixed w/ a proxy value so it won't collide w/ other cookies
+            String proxyCookieName = getCookieNamePrefix() + cookie.getName();
+            Cookie servletCookie = new Cookie(proxyCookieName, cookie.getValue());
+            servletCookie.setComment(cookie.getComment());
+            servletCookie.setMaxAge((int) cookie.getMaxAge());
+            servletCookie.setPath(path); //set to the path of the proxy servlet
+            // don't set cookie domain
+            servletCookie.setSecure(cookie.getSecure());
+            servletCookie.setVersion(cookie.getVersion());
+            servletResponse.addCookie(servletCookie);
+        }
+    }
+
+    /**
+     * Take any client cookies that were originally from the proxy and prepare them to send to the
+     * proxy.  This relies on cookie headers being set correctly according to RFC 6265 Sec 5.4.
+     * This also blocks any local cookies from being sent to the proxy.
+     */
+    protected String getRealCookie(String cookieValue) {
+        StringBuilder escapedCookie = new StringBuilder();
+        String cookies[] = cookieValue.split("; ");
+        for (String cookie : cookies) {
+            String cookieSplit[] = cookie.split("=");
+            if (cookieSplit.length == 2) {
+                String cookieName = cookieSplit[0];
+                if (cookieName.startsWith(getCookieNamePrefix())) {
+                    cookieName = cookieName.substring(getCookieNamePrefix().length());
+                    if (escapedCookie.length() > 0) {
+                        escapedCookie.append("; ");
+                    }
+                    escapedCookie.append(cookieName).append("=").append(cookieSplit[1]);
+                }
+            }
+
+            cookieValue = escapedCookie.toString();
+        }
+        return cookieValue;
+    }
+
+    /**
+     * The string prefixing rewritten cookies.
+     */
+    protected String getCookieNamePrefix() {
+        return "!Proxy!" + getServletConfig().getServletName();
+    }
+
+    /**
+     * Copy response body data (the entity) from the proxy to the servlet client.
+     */
+    protected void copyResponseEntity(HttpResponse proxyResponse, HttpServletResponse servletResponse) throws IOException {
+        HttpEntity entity = proxyResponse.getEntity();
+        if (entity != null) {
+            OutputStream servletOutputStream = servletResponse.getOutputStream();
+            entity.writeTo(servletOutputStream);
+        }
+    }
+
+    /**
+     * Reads the request URI from {@code servletRequest} and rewrites it, considering targetUri.
+     * It's used to make the new request.
+     */
+    protected String rewriteUrlFromRequest(HttpServletRequest servletRequest, String location) {
+        StringBuilder uri = new StringBuilder(500);
+        uri.append(location);
+        // Handle the path given to the servlet
+        if (servletRequest.getPathInfo() != null) {//ex: /my/path.html
+            uri.append(encodeUriQuery(servletRequest.getPathInfo()));
+        }
+        // Handle the query string & fragment
+        String queryString = servletRequest.getQueryString();//ex:(following '?'): name=value&foo=bar#fragment
+        String fragment = null;
+        //split off fragment from queryString, updating queryString if found
+        if (queryString != null) {
+            int fragIdx = queryString.indexOf('#');
+            if (fragIdx >= 0) {
+                fragment = queryString.substring(fragIdx + 1);
+                queryString = queryString.substring(0, fragIdx);
+            }
+        }
+
+        queryString = rewriteQueryStringFromRequest(servletRequest, queryString);
+        if (queryString != null && queryString.length() > 0) {
+            uri.append('?');
+            uri.append(encodeUriQuery(queryString));
+        }
+
+        if (doSendUrlFragment && fragment != null) {
+            uri.append('#');
+            uri.append(encodeUriQuery(fragment));
+        }
+        return uri.toString();
+    }
+
+    protected String rewriteQueryStringFromRequest(HttpServletRequest servletRequest, String queryString) {
+        return queryString;
+    }
+
+    /**
+     * For a redirect response from the target server, this translates {@code theUrl} to redirect to
+     * and translates it to one the original client can use.
+     */
+    protected String rewriteUrlFromResponse(HttpServletRequest servletRequest, String theUrl, String location) {
+        //TODO document example paths
+        if (theUrl.startsWith(location)) {
+            String curUrl = servletRequest.getRequestURL().toString();//no query
+            String pathInfo = servletRequest.getPathInfo();
+            if (pathInfo != null) {
+                assert curUrl.endsWith(pathInfo);
+                curUrl = curUrl.substring(0, curUrl.length() - pathInfo.length());//take pathInfo off
+            }
+            theUrl = curUrl + theUrl.substring(location.length());
+        }
+        return theUrl;
+    }
+
+    /**
+     * Encodes characters in the query or fragment part of the URI.
+     * <p/>
+     * <p>Unfortunately, an incoming URI sometimes has characters disallowed by the spec.  HttpClient
+     * insists that the outgoing proxied request has a valid URI because it uses Java's {@link URI}.
+     * To be more forgiving, we must escape the problematic characters.  See the URI class for the
+     * spec.
+     *
+     * @param in example: name=value&foo=bar#fragment
+     */
+    protected static CharSequence encodeUriQuery(CharSequence in) {
+        //Note that I can't simply use URI.java to encode because it will escape pre-existing escaped things.
+        StringBuilder outBuf = null;
+        Formatter formatter = null;
+        for (int i = 0; i < in.length(); i++) {
+            char c = in.charAt(i);
+            boolean escape = true;
+            if (c < 128) {
+                if (asciiQueryChars.get((int) c)) {
+                    escape = false;
+                }
+            } else if (!Character.isISOControl(c) && !Character.isSpaceChar(c)) {//not-ascii
+                escape = false;
+            }
+            if (!escape) {
+                if (outBuf != null)
+                    outBuf.append(c);
+            } else {
+                //escape
+                if (outBuf == null) {
+                    outBuf = new StringBuilder(in.length() + 5 * 3);
+                    outBuf.append(in, 0, i);
+                    formatter = new Formatter(outBuf);
+                }
+                //leading %, 0 padded, width 2, capital hex
+                formatter.format("%%%02X", (int) c);//TODO
+            }
+        }
+        return outBuf != null ? outBuf : in;
+    }
+
+    protected static final BitSet asciiQueryChars;
+
+    static {
+        char[] c_unreserved = "_-!.~'()*".toCharArray();//plus alphanum
+        char[] c_punct = ",;:$&+=".toCharArray();
+        char[] c_reserved = "?/[]@".toCharArray();//plus punct
+
+        asciiQueryChars = new BitSet(128);
+        for (char c = 'a'; c <= 'z'; c++) asciiQueryChars.set((int) c);
+        for (char c = 'A'; c <= 'Z'; c++) asciiQueryChars.set((int) c);
+        for (char c = '0'; c <= '9'; c++) asciiQueryChars.set((int) c);
+        for (char c : c_unreserved) asciiQueryChars.set((int) c);
+        for (char c : c_punct) asciiQueryChars.set((int) c);
+        for (char c : c_reserved) asciiQueryChars.set((int) c);
+
+        asciiQueryChars.set((int) '%');//leave existing percent escapes in place
+    }
+
+}
diff --git a/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/ClusterBalancerEvent.java b/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/ClusterBalancerEvent.java
new file mode 100644
index 0000000..4713a09
--- /dev/null
+++ b/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/ClusterBalancerEvent.java
@@ -0,0 +1,60 @@
+/*
+ * 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.http.balancer;
+
+import org.apache.karaf.cellar.core.event.Event;
+
+import java.util.List;
+
+public class ClusterBalancerEvent extends Event {
+
+    private String alias;
+    private int type;
+    private List<String> locations;
+
+    public static int ADDING = 0;
+    public static int REMOVING = 1;
+
+    public ClusterBalancerEvent(String alias, int type, List<String> locations) {
+        super(alias);
+        this.alias = alias;
+        this.type = type;
+        this.locations = locations;
+    }
+
+    public String getAlias() {
+        return alias;
+    }
+
+    public void setAlias(String alias) {
+        this.alias = alias;
+    }
+
+    public int getType() {
+        return type;
+    }
+
+    public void setType(int type) {
+        this.type = type;
+    }
+
+    public List<String> getLocations() {
+        return locations;
+    }
+
+    public void setLocations(List<String> locations) {
+        this.locations = locations;
+    }
+
+}
diff --git a/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/Constants.java b/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/Constants.java
new file mode 100644
index 0000000..4b6d565
--- /dev/null
+++ b/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/Constants.java
@@ -0,0 +1,21 @@
+/*
+ * 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.http.balancer;
+
+public class Constants {
+
+    public static String BALANCER_MAP = "org.apache.karaf.cellar.http.balancer";
+    public static String CATEGORY = "balanced.servlet";
+
+}
diff --git a/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/LocalServletListener.java b/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/LocalServletListener.java
new file mode 100644
index 0000000..04e7331
--- /dev/null
+++ b/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/LocalServletListener.java
@@ -0,0 +1,129 @@
+/*
+ * 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.http.balancer;
+
+import org.apache.karaf.cellar.core.ClusterManager;
+import org.apache.karaf.cellar.core.Configurations;
+import org.apache.karaf.cellar.core.Group;
+import org.apache.karaf.cellar.core.GroupManager;
+import org.apache.karaf.cellar.core.control.SwitchStatus;
+import org.apache.karaf.cellar.core.event.EventProducer;
+import org.ops4j.pax.web.service.spi.ServletEvent;
+import org.ops4j.pax.web.service.spi.ServletListener;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.Servlet;
+import java.util.*;
+
+/**
+ * Listen local node servlet event, in order to update the cluster servlet map
+ * and send a cluster event to the other nodes
+ */
+public class LocalServletListener implements ServletListener {
+
+    private final static Logger LOGGER = LoggerFactory.getLogger(LocalServletListener.class);
+
+    private ClusterManager clusterManager;
+    private GroupManager groupManager;
+    private ConfigurationAdmin configurationAdmin;
+    private EventProducer eventProducer;
+
+    @Override
+    public synchronized void servletEvent(ServletEvent servletEvent) {
+
+        if (eventProducer.getSwitch().getStatus().equals(SwitchStatus.OFF)) {
+            LOGGER.warn("CELLAR HTTP BALANCER: cluster event producer is OFF");
+            return;
+        }
+
+        Servlet servlet = servletEvent.getServlet();
+        if (servlet != null && servlet.getClass().getName().equals(CellarBalancerProxyServlet.class.getName())) {
+            LOGGER.trace("CELLAR HTTP BALANCER: ignoring CellarBalancerProxyServlet servlet event");
+            return;
+        }
+
+        Set<Group> localGroups = groupManager.listLocalGroups();
+
+        BalancedServletUtil util = new BalancedServletUtil();
+        util.setClusterManager(clusterManager);
+        util.setConfigurationAdmin(configurationAdmin);
+        String alias = servletEvent.getAlias();
+        String location = util.constructLocation(alias);
+
+        for (Group group : localGroups) {
+            Map<String, List<String>> clusterServlets = clusterManager.getMap(Constants.BALANCER_MAP + Configurations.SEPARATOR + group.getName());
+
+            if (servletEvent.getType() == ServletEvent.DEPLOYED) {
+                // update the cluster servlets
+                List<String> locations = clusterServlets.get(alias);
+                if (locations == null) {
+                    locations = new ArrayList<String>();
+                }
+
+                if (!locations.contains(location)) {
+                    LOGGER.debug("CELLAR HTTP BALANCER: adding location {} to servlet {} on cluster", location, alias);
+                    locations.add(location);
+                    clusterServlets.put(alias, locations);
+                    // send cluster event
+                    ClusterBalancerEvent event = new ClusterBalancerEvent(alias, ClusterBalancerEvent.ADDING, locations);
+                    event.setSourceGroup(group);
+                    event.setSourceNode(clusterManager.getNode());
+                    eventProducer.produce(event);
+                } else {
+                    LOGGER.debug("CELLAR HTTP BALANCER: location {} already defined for servlet {} on cluster", location, alias);
+                }
+            } else if (servletEvent.getType() == ServletEvent.UNDEPLOYED) {
+                List<String> locations = clusterServlets.get(alias);
+                if (locations == null)
+                    locations = new ArrayList<String>();
+                if (locations.contains(location)) {
+                    LOGGER.debug("CELLAR HTTP BALANCER: removing location {} for servlet {} on cluster", location, alias);
+                    locations.remove(location);
+                    // update cluster state
+                    clusterServlets.put(alias, locations);
+                    // send cluster event
+                    ClusterBalancerEvent event = new ClusterBalancerEvent(alias, ClusterBalancerEvent.REMOVING, locations);
+                    event.setSourceGroup(group);
+                    event.setSourceNode(clusterManager.getNode());
+                    eventProducer.produce(event);
+                }
+                if (locations.isEmpty()) {
+                    LOGGER.debug("CELLAR HTTP BALANCER: destroying servlet {} from cluster", alias);
+                    // update the cluster servlets
+                    clusterServlets.remove(alias);
+                }
+            }
+        }
+
+    }
+
+    public void setClusterManager(ClusterManager clusterManager) {
+        this.clusterManager = clusterManager;
+    }
+
+    public void setGroupManager(GroupManager groupManager) {
+        this.groupManager = groupManager;
+    }
+
+    public void setConfigurationAdmin(ConfigurationAdmin configurationAdmin) {
+        this.configurationAdmin = configurationAdmin;
+    }
+
+    public void setEventProducer(EventProducer eventProducer) {
+        this.eventProducer = eventProducer;
+    }
+
+}
diff --git a/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/ProxyServletRegistry.java b/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/ProxyServletRegistry.java
new file mode 100644
index 0000000..db0e373
--- /dev/null
+++ b/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/ProxyServletRegistry.java
@@ -0,0 +1,51 @@
+/*
+ * 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.http.balancer;
+
+import org.osgi.framework.ServiceRegistration;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ProxyServletRegistry {
+
+    private Map<String, ServiceRegistration> proxyRegistrations;
+
+    public void register(String alias, ServiceRegistration registration) {
+        proxyRegistrations.put(alias, registration);
+    }
+
+    public void unregister(String alias) {
+        ServiceRegistration registration = proxyRegistrations.remove(alias);
+        if (registration != null) {
+            registration.unregister();
+        }
+    }
+
+    public boolean contain(String alias) {
+        return proxyRegistrations.containsKey(alias);
+    }
+
+    public void init() {
+        proxyRegistrations = new HashMap<String, ServiceRegistration>();
+    }
+
+    public void destroy() {
+        for (ServiceRegistration registration : proxyRegistrations.values()) {
+            registration.unregister();
+        }
+        proxyRegistrations = new HashMap<String, ServiceRegistration>();
+    }
+
+}
diff --git a/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/ServletSynchronizer.java b/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/ServletSynchronizer.java
new file mode 100644
index 0000000..d3714a0
--- /dev/null
+++ b/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/ServletSynchronizer.java
@@ -0,0 +1,206 @@
+/*
+ * 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.http.balancer;
+
+import org.apache.karaf.cellar.core.*;
+import org.apache.karaf.cellar.core.control.SwitchStatus;
+import org.apache.karaf.cellar.core.event.EventProducer;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.Servlet;
+import java.io.IOException;
+import java.util.*;
+
+public class ServletSynchronizer implements Synchronizer {
+
+    private final static Logger LOGGER = LoggerFactory.getLogger(ServletSynchronizer.class);
+
+    private ClusterManager clusterManager;
+    private GroupManager groupManager;
+    private ConfigurationAdmin configurationAdmin;
+    private ProxyServletRegistry proxyRegistry;
+    private BundleContext bundleContext;
+    private EventProducer eventProducer;
+
+    public void init() {
+        if (groupManager == null)
+            return;
+        Set<Group> groups = groupManager.listLocalGroups();
+        if (groups != null && !groups.isEmpty()) {
+            for (Group group : groups) {
+                sync(group);
+            }
+        }
+    }
+
+    @Override
+    public void sync(Group group) {
+        String policy = getSyncPolicy(group);
+        if (policy == null) {
+            LOGGER.warn("CELLAR HTTP BALANCER: sync policy is not defined for cluster group {}", group.getName());
+        }
+        if (policy.equalsIgnoreCase("cluster")) {
+            LOGGER.debug("CELLAR HTTP BALANCER: sync policy set as 'cluster' for cluster group {}", group.getName());
+            LOGGER.debug("CELLAR HTTP BALANCER: updating node from the cluster (pull first)");
+            pull(group);
+            LOGGER.debug("CELLAR HTTP BALANCER: updating cluster from the local node (push after)");
+            push(group);
+        } else if (policy.equalsIgnoreCase("node")) {
+            LOGGER.debug("CELLAR HTTP BALANCER: sync policy set as 'node' for cluster group {}", group.getName());
+            LOGGER.debug("CELLAR HTTP BALANCER: updating cluster from the local node (push first)");
+            push(group);
+            LOGGER.debug("CELLAR HTTP BALANCER: updating node from the cluster (pull after)");
+            pull(group);
+        } else if (policy.equalsIgnoreCase("clusterOnly")) {
+            LOGGER.debug("CELLAR HTTP BALANCER: sync policy set as 'clusterOnly' for cluster group " + group.getName());
+            LOGGER.debug("CELLAR HTTP BALANCER: updating node from the cluster (pull only)");
+            pull(group);
+        } else if (policy.equalsIgnoreCase("nodeOnly")) {
+            LOGGER.debug("CELLAR HTTP BALANCER: sync policy set as 'nodeOnly' for cluster group " + group.getName());
+            LOGGER.debug("CELLAR HTTP BALANCER: updating cluster from the local node (push only)");
+            push(group);
+        } else {
+            LOGGER.debug("CELLAR HTTP BALANCER: sync policy set as 'disabled' for cluster group " + group.getName());
+            LOGGER.debug("CELLAR HTTP BALANCER: no sync");
+        }
+    }
+
+    @Override
+    public void pull(Group group) {
+        Map<String, List<String>> clusterServlets = clusterManager.getMap(Constants.BALANCER_MAP + Configurations.SEPARATOR + group.getName());
+        for (String alias : clusterServlets.keySet()) {
+            try {
+                // add a proxy servlet only if the alias is not present locally
+                Collection<ServiceReference<Servlet>> references = bundleContext.getServiceReferences(Servlet.class, "(alias=" + alias + ")");
+                if (references.isEmpty()) {
+                    LOGGER.debug("CELLAR HTTP BALANCER: create proxy servlet for {}", alias);
+                    CellarBalancerProxyServlet proxyServlet = new CellarBalancerProxyServlet();
+                    proxyServlet.setLocations(clusterServlets.get(alias));
+                    proxyServlet.init();
+                    Hashtable<String, String> properties = new Hashtable<String, String>();
+                    properties.put("alias", alias);
+                    properties.put("cellar.http.balancer.proxy", "true");
+                    ServiceRegistration registration = bundleContext.registerService(Servlet.class, proxyServlet, properties);
+                    proxyRegistry.register(alias, registration);
+                }
+            } catch (Exception e) {
+                LOGGER.warn("CELLAR HTTP BALANCER: can't create proxy servlet for {}", alias, e);
+            }
+        }
+    }
+
+    @Override
+    public void push(Group group) {
+
+        if (eventProducer.getSwitch().getStatus().equals(SwitchStatus.OFF)) {
+            LOGGER.warn("CELLAR HTTP BALANCER: cluster event producer is OFF");
+            return;
+        }
+
+        Map<String, List<String>> clusterServlets = clusterManager.getMap(Constants.BALANCER_MAP + Configurations.SEPARATOR + group.getName());
+        BalancedServletUtil util = new BalancedServletUtil();
+        util.setClusterManager(clusterManager);
+        util.setConfigurationAdmin(configurationAdmin);
+        try {
+            Collection<ServiceReference<Servlet>> references = bundleContext.getServiceReferences(Servlet.class, null);
+            for (ServiceReference<Servlet> reference : references) {
+                if (reference.getProperty("alias") != null) {
+                    String alias = (String) reference.getProperty("alias");
+                    String location = util.constructLocation(alias);
+                    Servlet servlet = bundleContext.getService(reference);
+                    if (servlet != null) {
+                        if (!(servlet instanceof CellarBalancerProxyServlet)) {
+                            // update the cluster servlets
+                            List<String> locations = clusterServlets.get(alias);
+                            if (locations == null) {
+                                locations = new ArrayList<String>();
+                            }
+
+                            if (!locations.contains(location)) {
+                                LOGGER.debug("CELLAR HTTP BALANCER: adding location {} to servlet {} on cluster", location, alias);
+                                locations.add(location);
+                                clusterServlets.put(alias, locations);
+                                // send cluster event
+                                ClusterBalancerEvent event = new ClusterBalancerEvent(alias, ClusterBalancerEvent.ADDING, locations);
+                                event.setSourceGroup(group);
+                                event.setSourceNode(clusterManager.getNode());
+                                eventProducer.produce(event);
+                            } else {
+                                LOGGER.debug("CELLAR HTTP BALANCER: location {} already defined for servlet {} on cluster", location, alias);
+                            }
+                        }
+                    }
+                } else {
+                    LOGGER.warn("CELLAR HTTP BALANCER: alias property is not defined");
+                }
+            }
+        } catch (Exception e) {
+            LOGGER.warn("CELLAR HTTP BALANCER: can't push servlet on cluster", e);
+        }
+    }
+
+    /**
+     * Get the balanced servlet sync policy for the given cluster group.
+     *
+     * @param group the cluster group.
+     * @return the current features sync policy for the given cluster group.
+     */
+    @Override
+    public String getSyncPolicy(Group group) {
+        String groupName = group.getName();
+        try {
+            Configuration configuration = configurationAdmin.getConfiguration(Configurations.GROUP, null);
+            Dictionary<String, Object> properties = configuration.getProperties();
+            if (properties != null) {
+                String propertyKey = groupName + Configurations.SEPARATOR + Constants.CATEGORY + Configurations.SEPARATOR + Configurations.SYNC;
+                return properties.get(propertyKey).toString();
+            }
+        } catch (IOException e) {
+            LOGGER.error("CELLAR FEATURE: error while retrieving the sync policy", e);
+        }
+
+        return "disabled";
+    }
+
+    public void setClusterManager(ClusterManager clusterManager) {
+        this.clusterManager = clusterManager;
+    }
+
+    public void setGroupManager(GroupManager groupManager) {
+        this.groupManager = groupManager;
+    }
+
+    public void setConfigurationAdmin(ConfigurationAdmin configurationAdmin) {
+        this.configurationAdmin = configurationAdmin;
+    }
+
+    public void setProxyRegistry(ProxyServletRegistry proxyRegistry) {
+        this.proxyRegistry = proxyRegistry;
+    }
+
+    public void setBundleContext(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+    }
+
+    public void setEventProducer(EventProducer eventProducer) {
+        this.eventProducer = eventProducer;
+    }
+
+}
diff --git a/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/command/ListClusterServlets.java b/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/command/ListClusterServlets.java
new file mode 100644
index 0000000..34c5c80
--- /dev/null
+++ b/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/command/ListClusterServlets.java
@@ -0,0 +1,67 @@
+/*
+ * 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.http.balancer.command;
+
+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.core.shell.completer.AllGroupsCompleter;
+import org.apache.karaf.cellar.http.balancer.Constants;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.table.ShellTable;
+
+import java.util.List;
+import java.util.Map;
+
+@Command(scope = "cluster", name = "http-list", description = "List the HTTP servlets on the cluster")
+@Service
+public class ListClusterServlets extends CellarCommandSupport {
+
+    @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
+    private String groupName;
+
+    @Override
+    public Object doExecute() throws Exception {
+
+        Group group = groupManager.findGroupByName(groupName);
+        if (group == null) {
+            System.err.println("Cluster group " + groupName + " doesn't exist");
+            return null;
+        }
+
+        Map<String, List<String>> clusterServlets = clusterManager.getMap(Constants.BALANCER_MAP + Configurations.SEPARATOR + groupName);
+
+        ShellTable table = new ShellTable();
+        table.column("Alias");
+        table.column("Locations");
+
+        for (String alias : clusterServlets.keySet()) {
+            List<String> locations = clusterServlets.get(alias);
+            StringBuilder builder = new StringBuilder();
+            for (String location : locations) {
+                builder.append(location).append(" ");
+            }
+            table.addRow().addContent(alias, builder.toString());
+        }
+
+        table.print(System.out);
+
+        return null;
+    }
+
+}
diff --git a/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/internal/osgi/Activator.java b/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/internal/osgi/Activator.java
new file mode 100644
index 0000000..219e284
--- /dev/null
+++ b/http/balancer/src/main/java/org/apache/karaf/cellar/http/balancer/internal/osgi/Activator.java
@@ -0,0 +1,121 @@
+/*
+ * 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.http.balancer.internal.osgi;
+
+import org.apache.karaf.cellar.core.ClusterManager;
+import org.apache.karaf.cellar.core.GroupManager;
+import org.apache.karaf.cellar.core.Synchronizer;
+import org.apache.karaf.cellar.core.event.EventHandler;
+import org.apache.karaf.cellar.core.event.EventProducer;
+import org.apache.karaf.cellar.http.balancer.BalancerEventHandler;
+import org.apache.karaf.cellar.http.balancer.LocalServletListener;
+import org.apache.karaf.cellar.http.balancer.ProxyServletRegistry;
+import org.apache.karaf.cellar.http.balancer.ServletSynchronizer;
+import org.apache.karaf.util.tracker.BaseActivator;
+import org.apache.karaf.util.tracker.annotation.ProvideService;
+import org.apache.karaf.util.tracker.annotation.RequireService;
+import org.apache.karaf.util.tracker.annotation.Services;
+import org.ops4j.pax.web.service.spi.ServletListener;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Hashtable;
+
+@Services(
+        provides = {
+                @ProvideService(ServletListener.class),
+                @ProvideService(EventHandler.class),
+                @ProvideService(Synchronizer.class)
+        },
+        requires = {
+                @RequireService(ClusterManager.class),
+                @RequireService(GroupManager.class),
+                @RequireService(ConfigurationAdmin.class),
+                @RequireService(EventProducer.class)
+        }
+)
+public class Activator extends BaseActivator {
+
+    private final static Logger LOGGER = LoggerFactory.getLogger(Activator.class);
+
+    private ProxyServletRegistry proxyRegistry;
+
+    @Override
+    public void doStart() throws Exception {
+        ClusterManager clusterManager = getTrackedService(ClusterManager.class);
+        if (clusterManager == null) {
+            return;
+        }
+        GroupManager groupManager = getTrackedService(GroupManager.class);
+        if (groupManager == null) {
+            return;
+        }
+        ConfigurationAdmin configurationAdmin = getTrackedService(ConfigurationAdmin.class);
+        if (configurationAdmin == null) {
+            return;
+        }
+        EventProducer eventProducer = getTrackedService(EventProducer.class);
+        if (eventProducer == null) {
+            return;
+        }
+
+        LOGGER.debug("CELLAR HTTP BALANCER: starting proxy registry");
+        proxyRegistry = new ProxyServletRegistry();
+        proxyRegistry.init();
+
+        LOGGER.debug("CELLAR HTTP BALANCER: starting balancer event handler");
+        BalancerEventHandler balancerEventHandler = new BalancerEventHandler();
+        balancerEventHandler.setClusterManager(clusterManager);
+        balancerEventHandler.setBundleContext(bundleContext);
+        balancerEventHandler.setConfigurationAdmin(configurationAdmin);
+        balancerEventHandler.setGroupManager(groupManager);
+        balancerEventHandler.setProxyRegistry(proxyRegistry);
+        Hashtable props = new Hashtable();
+        props.put("managed", "true");
+        register(EventHandler.class, balancerEventHandler, props);
+
+        LOGGER.debug("CELLAR HTTP BALANCER: starting servlet synchronizer");
+        ServletSynchronizer synchronizer = new ServletSynchronizer();
+        synchronizer.setEventProducer(eventProducer);
+        synchronizer.setClusterManager(clusterManager);
+        synchronizer.setConfigurationAdmin(configurationAdmin);
+        synchronizer.setProxyRegistry(proxyRegistry);
+        synchronizer.setGroupManager(groupManager);
+        synchronizer.setBundleContext(bundleContext);
+        synchronizer.init();
+        props = new Hashtable();
+        props.put("resource", "balanced.servlet");
+        register(Synchronizer.class, synchronizer, props);
+
+        LOGGER.debug("CELLAR HTTP BALANCER: starting local servlet listener");
+        LocalServletListener servletListener = new LocalServletListener();
+        servletListener.setClusterManager(clusterManager);
+        servletListener.setGroupManager(groupManager);
+        servletListener.setConfigurationAdmin(configurationAdmin);
+        servletListener.setEventProducer(eventProducer);
+        register(ServletListener.class, servletListener);
+    }
+
+    @Override
+    public void doStop() {
+        super.doStop();
+
+        if (proxyRegistry != null) {
+            proxyRegistry.destroy();
+            proxyRegistry = null;
+        }
+    }
+
+}
diff --git a/http/pom.xml b/http/pom.xml
new file mode 100644
index 0000000..ee59c29
--- /dev/null
+++ b/http/pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <!--
+
+        Licensed to the Apache Software Foundation (ASF) under one or more
+        contributor license agreements.  See the NOTICE file distributed with
+        this work for additional information regarding copyright ownership.
+        The ASF licenses this file to You under the Apache License, Version 2.0
+        (the "License"); you may not use this file except in compliance with
+        the License.  You may obtain a copy of the License at
+
+           http://www.apache.org/licenses/LICENSE-2.0
+
+        Unless required by applicable law or agreed to in writing, software
+        distributed under the License is distributed on an "AS IS" BASIS,
+        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+        See the License for the specific language governing permissions and
+        limitations under the License.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.karaf</groupId>
+        <artifactId>cellar</artifactId>
+        <version>4.0.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <groupId>org.apache.karaf.cellar</groupId>
+    <artifactId>http</artifactId>
+    <packaging>pom</packaging>
+    <name>Apache Karaf :: Cellar :: HTTP</name>
+
+    <modules>
+        <module>balancer</module>
+    </modules>
+
+</project>
\ No newline at end of file
diff --git a/itests/pom.xml b/itests/pom.xml
index 5b50599..8ee1062 100644
--- a/itests/pom.xml
+++ b/itests/pom.xml
@@ -40,6 +40,10 @@
             <version>${karaf.version}</version>
             <type>tar.gz</type>
         </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
+        </dependency>
 
         <dependency>
             <groupId>org.apache.karaf.cellar</groupId>
@@ -64,26 +68,14 @@
         <!-- pax exam -->
         <dependency>
             <groupId>org.ops4j.pax.exam</groupId>
-            <artifactId>pax-exam</artifactId>
-            <version>4.3.0</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.ops4j.pax.exam</groupId>
             <artifactId>pax-exam-junit4</artifactId>
-            <version>4.3.0</version>
+            <version>4.5.0</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.ops4j.pax.exam</groupId>
             <artifactId>pax-exam-container-karaf</artifactId>
-            <version>4.3.0</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.ops4j.pax.exam</groupId>
-            <artifactId>pax-exam-inject</artifactId>
-            <version>4.3.0</version>
+            <version>4.5.0</version>
             <scope>test</scope>
         </dependency>
         <dependency>
diff --git a/itests/src/test/java/org/apache/karaf/cellar/itests/CellarTestSupport.java b/itests/src/test/java/org/apache/karaf/cellar/itests/CellarTestSupport.java
index 9c7d0fe..918860a 100644
--- a/itests/src/test/java/org/apache/karaf/cellar/itests/CellarTestSupport.java
+++ b/itests/src/test/java/org/apache/karaf/cellar/itests/CellarTestSupport.java
@@ -22,21 +22,21 @@
 import java.io.PrintStream;
 import java.net.DatagramSocket;
 import java.net.ServerSocket;
+import java.security.Principal;
+import java.security.PrivilegedExceptionAction;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Dictionary;
 import java.util.Enumeration;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.FutureTask;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.*;
+import java.util.concurrent.TimeoutException;
 
 import javax.inject.Inject;
+import javax.security.auth.Subject;
 
-import org.apache.felix.service.command.CommandProcessor;
-import org.apache.felix.service.command.CommandSession;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.console.SessionFactory;
 import org.ops4j.pax.exam.*;
 import org.ops4j.pax.exam.karaf.options.*;
 import org.osgi.framework.Bundle;
@@ -66,6 +66,10 @@
     @Inject
     protected BundleContext bundleContext;
 
+    @Inject
+    protected SessionFactory sessionFactory;
+
+
     /**
      * @param probe
      * @return
@@ -94,7 +98,9 @@
         String propsetCmd = membersBuilder.toString();
         String updateCmd = "config:update";
 
-        executeCommands(editCmd, propsetCmd, updateCmd);
+        executeCommand(editCmd);
+        executeCommand(propsetCmd);
+        executeCommand(updateCmd);
     }
 
     /**
@@ -192,98 +198,103 @@
         return options;
     }
 
-    /**
-     * Executes a shell command and returns output as a String.
-     * Commands have a default timeout of 10 seconds.
-     *
-     * @param command
-     * @return
-     */
-    protected String executeCommand(final String command) {
-        return executeCommand(command, COMMAND_TIMEOUT, false);
+    protected String executeCommand(final String command, Principal ... principals) {
+        return executeCommand(command, COMMAND_TIMEOUT, false, principals);
     }
 
-    /**
-     * Executes a shell command and returns output as a String.
-     * Commands have a default timeout of 10 seconds.
-     *
-     * @param command The command to execute.
-     * @param timeout The amount of time in millis to wait for the command to execute.
-     * @param silent  Specifies if the command should be displayed in the screen.
-     * @return
-     */
-    protected String executeCommand(final String command, final Long timeout, final Boolean silent) {
+
+    protected String executeCommand(final String command, final Long timeout, final Boolean silent, final Principal... principals) {
+        waitForCommandService(command);
+
         String response;
         final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
         final PrintStream printStream = new PrintStream(byteArrayOutputStream);
-        final CommandProcessor commandProcessor = getOsgiService(CommandProcessor.class);
-        final CommandSession commandSession = commandProcessor.createSession(System.in, printStream, System.err);
-        FutureTask<String> commandFuture = new FutureTask<String>(
-                new Callable<String>() {
-                    public String call() {
-                        try {
-                            if (!silent) {
-                                System.err.println(command);
-                            }
-                            commandSession.execute(command);
-                        } catch (Exception e) {
-                            e.printStackTrace(System.err);
-                        }
-                        printStream.flush();
-                        return byteArrayOutputStream.toString();
+        final SessionFactory sessionFactory = getOsgiService(SessionFactory.class);
+        final Session session = sessionFactory.create(System.in, printStream, System.err);
+
+        final Callable<String> commandCallable = new Callable<String>() {
+            @Override
+            public String call() throws Exception {
+                try {
+                    if (!silent) {
+                        System.err.println(command);
                     }
-                });
+                    session.execute(command);
+                } catch (Exception e) {
+                    throw new RuntimeException(e.getMessage(), e);
+                }
+                printStream.flush();
+                return byteArrayOutputStream.toString();
+            }
+        };
+
+        FutureTask<String> commandFuture;
+        if (principals.length == 0) {
+            commandFuture = new FutureTask<String>(commandCallable);
+        } else {
+            // If principals are defined, run the command callable via Subject.doAs()
+            commandFuture = new FutureTask<String>(new Callable<String>() {
+                @Override
+                public String call() throws Exception {
+                    Subject subject = new Subject();
+                    subject.getPrincipals().addAll(Arrays.asList(principals));
+                    return Subject.doAs(subject, new PrivilegedExceptionAction<String>() {
+                        @Override
+                        public String run() throws Exception {
+                            return commandCallable.call();
+                        }
+                    });
+                }
+            });
+        }
 
         try {
             executor.submit(commandFuture);
             response = commandFuture.get(timeout, TimeUnit.MILLISECONDS);
-        } catch (Exception e) {
+        } catch (TimeoutException e) {
             e.printStackTrace(System.err);
             response = "SHELL COMMAND TIMED OUT: ";
+        } catch (ExecutionException e) {
+            Throwable cause = e.getCause().getCause();
+            throw new RuntimeException(cause.getMessage(), cause);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e.getMessage(), e);
         }
-
         return response;
     }
 
-    /**
-     * Executes multiple commands inside a Single Session.
-     * Commands have a default timeout of 10 seconds.
-     *
-     * @param commands
-     * @return
-     */
-    protected String executeCommands(final String... commands) {
-        String response;
-        final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-        final PrintStream printStream = new PrintStream(byteArrayOutputStream);
-        final CommandProcessor commandProcessor = getOsgiService(CommandProcessor.class);
-        final CommandSession commandSession = commandProcessor.createSession(System.in, printStream, System.err);
-        FutureTask<String> commandFuture = new FutureTask<String>(
-                new Callable<String>() {
-                    public String call() {
-                        try {
-                            for (String command : commands) {
-                                System.err.println(command);
-                                commandSession.execute(command);
-                            }
-                        } catch (Exception e) {
-                            e.printStackTrace(System.err);
-                        }
-                        return byteArrayOutputStream.toString();
-                    }
-                });
+    private void waitForCommandService(String command) {
+        // the commands are represented by services. Due to the asynchronous nature of services they may not be
+        // immediately available. This code waits the services to be available, in their secured form. It
+        // means that the code waits for the command service to appear with the roles defined.
 
+        if (command == null || command.length() == 0) {
+            return;
+        }
+
+        int spaceIdx = command.indexOf(' ');
+        if (spaceIdx > 0) {
+            command = command.substring(0, spaceIdx);
+        }
+        int colonIndx = command.indexOf(':');
+        String scope = (colonIndx > 0) ? command.substring(0, colonIndx) : "*";
+        String name  = (colonIndx > 0) ? command.substring(colonIndx + 1) : command;
         try {
-            executor.submit(commandFuture);
-            response = commandFuture.get(COMMAND_TIMEOUT, TimeUnit.MILLISECONDS);
+            long start = System.currentTimeMillis();
+            long cur   = start;
+            while (cur - start < SERVICE_TIMEOUT) {
+                if (sessionFactory.getRegistry().getCommand(scope, name) != null) {
+                    return;
+                }
+                Thread.sleep(100);
+                cur = System.currentTimeMillis();
+            }
         } catch (Exception e) {
-            e.printStackTrace(System.err);
-            response = "SHELL COMMAND TIMED OUT: ";
+            throw new RuntimeException(e);
         }
-
-        return response;
     }
 
+
     protected Bundle getInstalledBundle(String symbolicName) {
         for (Bundle b : bundleContext.getBundles()) {
             if (b.getSymbolicName().equals(symbolicName)) {
diff --git a/kubernetes/NOTICE b/kubernetes/NOTICE
index addb35c..64cb235 100644
--- a/kubernetes/NOTICE
+++ b/kubernetes/NOTICE
@@ -1,5 +1,5 @@
 Apache Karaf Cellar

-Copyright 2011-2014 The Apache Software Foundation

+Copyright 2011-2015 The Apache Software Foundation

 

 I. Used Software

 

diff --git a/kubernetes/pom.xml b/kubernetes/pom.xml
index 9cc8e68..c290f78 100644
--- a/kubernetes/pom.xml
+++ b/kubernetes/pom.xml
@@ -44,11 +44,15 @@
         <!-- Fabric8 Kubernetes API Dependencies -->
         <dependency>
             <groupId>io.fabric8</groupId>
-            <artifactId>kubernetes-api</artifactId>
+            <artifactId>kubernetes-client</artifactId>
         </dependency>
 
         <!-- OSGi -->
         <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.util</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.core</artifactId>
             <scope>provided</scope>
@@ -64,21 +68,27 @@
     <build>
         <plugins>
             <plugin>
+                <groupId>org.apache.karaf.tooling</groupId>
+                <artifactId>karaf-services-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
                 <configuration>
                     <instructions>
                         <Export-Package>
-                            org.apache.karaf.cellar.kubernetes.*;version="${project.version}"
+                            !org.apache.karaf.cellar.kubernetes.internal.osgi,
+                            org.apache.karaf.cellar.kubernetes.*
                         </Export-Package>
                         <Import-Package>
-                            io.fabric8.kubernetes.api;version="${fabric8.version}",
-                            org.apache.karaf.cellar.core*;version="${project.version}",
-                            org.osgi*,
-                            org.slf4j;version="[1.6,2)";resolution:=optional
+                            org.slf4j;version="[1.6,2)";resolution:=optional,
+                            *
                         </Import-Package>
-                        <DynamicImport-Package>javax.*,org.w3c.*,org.xml.*,io.fabric8.*,*</DynamicImport-Package>
-                        <Bundle-Activator>org.apache.karaf.cellar.kubernetes.Activator</Bundle-Activator>
+                        <DynamicImport-Package>io.fabric8.*</DynamicImport-Package>
+                        <Private-Package>
+                            org.apache.karaf.cellar.kubernetes.internal.osgi,
+                            org.apache.karaf.util.tracker;-split-package:=merge-first
+                        </Private-Package>
                     </instructions>
                 </configuration>
             </plugin>
diff --git a/kubernetes/src/main/java/org/apache/karaf/cellar/kubernetes/Activator.java b/kubernetes/src/main/java/org/apache/karaf/cellar/kubernetes/Activator.java
deleted file mode 100644
index c4dfbc2..0000000
--- a/kubernetes/src/main/java/org/apache/karaf/cellar/kubernetes/Activator.java
+++ /dev/null
@@ -1,46 +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.kubernetes;
-
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.ServiceRegistration;
-import org.osgi.service.cm.ManagedServiceFactory;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Hashtable;
-
-public class Activator implements BundleActivator {
-
-    private ServiceRegistration serviceRegistration;
-
-    private final static Logger LOGGER = LoggerFactory.getLogger(Activator.class);
-
-    public void start(BundleContext bundleContext) throws Exception {
-        Hashtable<String, Object> properties = new Hashtable<String, Object>();
-        properties.put(Constants.SERVICE_PID, "org.apache.karaf.cellar.kubernetes");
-        KubernetesDiscoveryServiceFactory kubernetesDiscoveryServiceFactory = new KubernetesDiscoveryServiceFactory(bundleContext);
-        serviceRegistration = bundleContext.registerService(ManagedServiceFactory.class.getName(), kubernetesDiscoveryServiceFactory, properties);
-    }
-
-    public void stop(BundleContext bundleContext) throws Exception {
-        if (serviceRegistration != null) {
-            serviceRegistration.unregister();
-            serviceRegistration = null;
-        }
-    }
-
-}
diff --git a/kubernetes/src/main/java/org/apache/karaf/cellar/kubernetes/KubernetesDiscoveryService.java b/kubernetes/src/main/java/org/apache/karaf/cellar/kubernetes/KubernetesDiscoveryService.java
index a42e2bf..2873da2 100644
--- a/kubernetes/src/main/java/org/apache/karaf/cellar/kubernetes/KubernetesDiscoveryService.java
+++ b/kubernetes/src/main/java/org/apache/karaf/cellar/kubernetes/KubernetesDiscoveryService.java
@@ -13,10 +13,13 @@
  */
 package org.apache.karaf.cellar.kubernetes;
 
-import io.fabric8.kubernetes.api.KubernetesClient;
-import io.fabric8.kubernetes.api.KubernetesFactory;
+import io.fabric8.kubernetes.client.Config;
+import io.fabric8.kubernetes.client.ConfigBuilder;
+import io.fabric8.kubernetes.client.DefaultKubernetesClient;
+import io.fabric8.kubernetes.client.KubernetesClient;
 import io.fabric8.kubernetes.api.model.Pod;
 import io.fabric8.kubernetes.api.model.PodList;
+
 import org.apache.karaf.cellar.core.discovery.DiscoveryService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -47,7 +50,8 @@
         try {
             String kubernetesUrl = "http://" + kubernetesHost + ":" + kubernetesPort;
             LOGGER.debug("CELLAR KUBERNETES: query API at {} ...", kubernetesUrl);
-            kubernetesClient = new KubernetesClient(new KubernetesFactory(kubernetesUrl));
+            Config config = new ConfigBuilder().withMasterUrl(kubernetesUrl).build();
+            kubernetesClient = new DefaultKubernetesClient(config);
             LOGGER.debug("CELLAR KUBERNETES: discovery service initialized");
         } catch (Exception e) {
             LOGGER.error("CELLAR KUBERNETES: can't init discovery service", e);
@@ -67,11 +71,11 @@
         LOGGER.debug("CELLAR KUBERNETES: query pods with labeled with [{}={}]", kubernetesPodLabelKey, kubernetesPodLabelValue);
         Set<String> members = new HashSet<String>();
         try {
-            PodList podList = kubernetesClient.getPods();
+            PodList podList = kubernetesClient.pods().list();
             for (Pod pod : podList.getItems()) {
-                String value = pod.getLabels().get(kubernetesPodLabelKey);
+                String value = pod.getMetadata().getLabels().get(kubernetesPodLabelKey);
                 if (value != null && !value.isEmpty()) {
-                    members.add(pod.getCurrentState().getPodIP());
+                    members.add(pod.getStatus().getPodIP());
                 }
             }
         } catch (Exception e) {
diff --git a/kubernetes/src/main/java/org/apache/karaf/cellar/kubernetes/internal/osgi/Activator.java b/kubernetes/src/main/java/org/apache/karaf/cellar/kubernetes/internal/osgi/Activator.java
new file mode 100644
index 0000000..1317a0b
--- /dev/null
+++ b/kubernetes/src/main/java/org/apache/karaf/cellar/kubernetes/internal/osgi/Activator.java
@@ -0,0 +1,50 @@
+/*
+ * 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.kubernetes.internal.osgi;
+
+import org.apache.karaf.cellar.kubernetes.KubernetesDiscoveryServiceFactory;
+import org.apache.karaf.util.tracker.BaseActivator;
+import org.apache.karaf.util.tracker.annotation.ProvideService;
+import org.apache.karaf.util.tracker.annotation.Services;
+import org.osgi.framework.Constants;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Hashtable;
+
+@Services(
+        provides = {
+                @ProvideService(ManagedServiceFactory.class)
+        }
+)
+public class Activator extends BaseActivator {
+
+    private final static Logger LOGGER = LoggerFactory.getLogger(Activator.class);
+
+    @Override
+    public void doStart() throws Exception {
+        LOGGER.debug("CELLAR KUBERNETES: init discovery service factory");
+        Hashtable props = new Hashtable();
+        props.put(Constants.SERVICE_PID, "org.apache.karaf.cellar.kubernetes");
+        KubernetesDiscoveryServiceFactory factory = new KubernetesDiscoveryServiceFactory(bundleContext);
+        register(ManagedServiceFactory.class, factory, props);
+    }
+
+    @Override
+    public void doStop() {
+        super.doStop();
+    }
+
+}
diff --git a/manual/NOTICE b/manual/NOTICE
index addb35c..64cb235 100644
--- a/manual/NOTICE
+++ b/manual/NOTICE
@@ -1,5 +1,5 @@
 Apache Karaf Cellar

-Copyright 2011-2014 The Apache Software Foundation

+Copyright 2011-2015 The Apache Software Foundation

 

 I. Used Software

 

diff --git a/manual/src/main/webapp/manual.conf b/manual/src/main/webapp/manual.conf
index a910a60..3e3959d 100644
--- a/manual/src/main/webapp/manual.conf
+++ b/manual/src/main/webapp/manual.conf
@@ -36,6 +36,9 @@
 {include:user-guide/groups.conf}
 {include:user-guide/obr.conf}
 {include:user-guide/event.conf}
+{include:user-guide/http-balancer.conf}
+{include:user-guide/http-session.conf}
+{include:user-guide/transport.conf}
 {include:user-guide/cloud.conf}
 
 h1. Architecture Guide
diff --git a/manual/src/main/webapp/user-guide/http-balancer.conf b/manual/src/main/webapp/user-guide/http-balancer.conf
new file mode 100644
index 0000000..5673b40
--- /dev/null
+++ b/manual/src/main/webapp/user-guide/http-balancer.conf
@@ -0,0 +1,131 @@
+h1. HTTP Balancer
+
+Apache Karaf Cellar is able to expose servlets local to a node on the cluster.
+It means that a client (browser) can use any node in the cluster, proxying the requests to the node actually
+hosting the servlets.
+
+h2. Enable HTTP Balancer
+
+To enable Cellar HTTP Balancer, you have to first install the http and http-whiteboard features:
+
+{code}
+karaf@root()> feature:install http
+karaf@root()> feature:install http-whiteboard
+{code}
+
+Now, we install the cellar-http-balancer feature, actually providing the balancer:
+
+{code}
+karaf@root()> feature:install cellar-http-balancer
+{code}
+
+Of course, you can use Cellar to spread the installation of the cellar-http-balancer feature on all nodes in the
+cluster group:
+
+{code}
+karaf@root()> cluster:feature-install default cellar-http-balancer
+{code}
+
+It's done: the Cellar HTTP Balancer is now enabled. It will expose proxy servlets on nodes.
+
+h2. Balancer in action
+
+To illustrate Cellar HTTP Balancer in action, you need at least a cluster with two nodes.
+
+On node1, we enable the Cellar HTTP Balancer:
+
+{code}
+karaf@node1()> feature:install http
+karaf@node1()> feature:install http-whiteboard
+karaf@node1()> feature:repo-add cellar 4.0.0
+karaf@node1()> feature:install cellar
+karaf@node1()> cluster:feature-install default cellar-http-balancer
+{code}
+
+Now, we install the webconsole on node1:
+
+{code}
+karaf@node1()> feature:install webconsole
+{code}
+
+We can see the "local" servlets provided by the webconsole feature using the http:list command:
+
+{code}
+karaf@node1()> http:list
+ID  | Servlet          | Servlet-Name    | State       | Alias               | Url
+------------------------------------------------------------------------------------------------------
+101 | KarafOsgiManager | ServletModel-2  | Undeployed  | /system/console     | [/system/console/*]
+103 | GogoPlugin       | ServletModel-7  | Deployed    | /gogo               | [/gogo/*]
+102 | FeaturesPlugin   | ServletModel-6  | Deployed    | /features           | [/features/*]
+101 | ResourceServlet  | /res            | Deployed    | /system/console/res | [/system/console/res/*]
+101 | KarafOsgiManager | ServletModel-11 | Deployed    | /system/console     | [/system/console/*]
+105 | InstancePlugin   | ServletModel-9  | Deployed    | /instance           | [/instance/*]
+{code}
+
+You can access to the webconsole using a browser on http://localhost:8181/system/console.
+
+We can see that Cellar HTTP Balancer exposed the servlets to the cluster, using the cluster:http-list command:
+
+{code}
+karaf@node1()> cluster:http-list default
+Alias               | Locations
+-----------------------------------------------------------------
+/system/console/res | http://172.17.42.1:8181/system/console/res
+/gogo               | http://172.17.42.1:8181/gogo
+/instance           | http://172.17.42.1:8181/instance
+/system/console     | http://172.17.42.1:8181/system/console
+/features           | http://172.17.42.1:8181/features
+{code}
+
+
+On another node (node2), we install http, http-whiteboard and cellar features:
+
+{code}
+karaf@node1()> feature:install http
+karaf@node1()> feature:install http-whiteboard
+karaf@node1()> feature:repo-add cellar 4.0.0
+karaf@node1()> feature:install cellar
+{code}
+
+WARNING: if you run the nodes on a single machine, you have to provision etc/org.ops4j.pax.web.cfg configuration file
+containing the org.osgi.service.http.port property with a port number different to 8181.
+For this example, I use the following etc/org.ops4j.pax.web.cfg file:
+
+{code}
+org.osgi.service.http.port=8041
+{code}
+
+On node1, as we installed the cellar-http-balancer using cluster:feature-install command, it's automatically installed
+when node2 joins the default cluster group.
+
+We can see the HTTP endpoints available on the cluster using the cluster:http-list command:
+
+{code}
+karaf@node2()> cluster:http-list default
+Alias               | Locations
+-----------------------------------------------------------------
+/system/console/res | http://172.17.42.1:8181/system/console/res
+/gogo               | http://172.17.42.1:8181/gogo
+/instance           | http://172.17.42.1:8181/instance
+/system/console     | http://172.17.42.1:8181/system/console
+/features           | http://172.17.42.1:8181/features
+{code}
+
+If we take a look on the HTTP endpoints locally available on node2 (using http:list command), we can see the proxies
+created by Cellar HTTP Balancer:
+
+{code}
+karaf@node2()> http:list
+ID  | Servlet                    | Servlet-Name   | State       | Alias               | Url
+---------------------------------------------------------------------------------------------------------------
+100 | CellarBalancerProxyServlet | ServletModel-3 | Deployed    | /gogo               | [/gogo/*]
+100 | CellarBalancerProxyServlet | ServletModel-2 | Deployed    | /system/console/res | [/system/console/res/*]
+100 | CellarBalancerProxyServlet | ServletModel-6 | Deployed    | /features           | [/features/*]
+100 | CellarBalancerProxyServlet | ServletModel-5 | Deployed    | /system/console     | [/system/console/*]
+100 | CellarBalancerProxyServlet | ServletModel-4 | Deployed    | /instance           | [/instance/*]
+{code}
+
+You can use a browser on http://localhost:8041/system/console: you will actually use the webconsole from node1, as
+Cellar HTTP Balancer proxies from node2 to node1.
+
+Cellar HTTP Balancer randomly chooses one endpoint providing the HTTP endpoint.
\ No newline at end of file
diff --git a/manual/src/main/webapp/user-guide/http-session.conf b/manual/src/main/webapp/user-guide/http-session.conf
new file mode 100644
index 0000000..b87a069
--- /dev/null
+++ b/manual/src/main/webapp/user-guide/http-session.conf
@@ -0,0 +1,77 @@
+h1. HTTP Session Replication
+
+Apache Karaf Cellar supports replication of the HTTP sessions on the cluster.
+
+It means that the same web application deployed on multiple nodes in the cluster will share the same HTTP sessions
+pool, allowing clients to transparently connect to any node, without loosing any session state.
+
+h2. Enable Cluster HTTP Session Replication
+
+In order to be able to be stored on the cluster, all HTTP Sessions used in your web application have to implement
+Serializable interface. Any non-serializable attribute has to be flagged as transient.
+
+You have to enable a specific filter in your application to enable the replication. See next chapter for details.
+
+At runtime level, you just have to install http, http-whiteboard, and cellar features:
+
+{code}
+karaf@root()> feature:install http
+karaf@root()> feature:install http-whiteboard
+karaf@root()> feature:repo-add cellar 4.0.0
+karaf@root()> feature:install cellar
+{code}
+
+h2. Web Application Session Replication
+
+In order to use HTTP session replication on the cluster, you just have to add a filter in your web application.
+
+Basically, the WEB-INF/web.xml file of your web application should look like this:
+
+{code}
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+	version="3.0">
+
+	<filter>
+		<filter-name>hazelcast-filter</filter-name>
+		<filter-class>com.hazelcast.web.WebFilter</filter-class>
+	    <!--
+	        Name of the distributed map storing
+	        your web session objects
+	    -->
+		<init-param>
+			<param-name>map-name</param-name>
+			<param-value>my-sessions</param-value>
+		</init-param>
+	    <!-- How is your load-balancer configured? stick-session means all requests of
+	    	a session is routed to the node where the session is first created. This is
+	    	excellent for performance. If sticky-session is set to false, when a session
+	    	 is updated on a node, entry for this session on all other nodes is invalidated.
+	    	 You have to know how your load-balancer is configured before setting this
+	    	 parameter. Default is true. -->
+		<init-param>
+			<param-name>sticky-session</param-name>
+			<param-value>false</param-value>
+		</init-param>
+	    <!--
+	        Are you debugging? Default is false.
+	    -->
+		<init-param>
+			<param-name>debug</param-name>
+			<param-value>false</param-value>
+		</init-param>
+	</filter>
+	<filter-mapping>
+		<filter-name>hazelcast-filter</filter-name>
+		<url-pattern>/*</url-pattern>
+		<dispatcher>FORWARD</dispatcher>
+		<dispatcher>INCLUDE</dispatcher>
+		<dispatcher>REQUEST</dispatcher>
+	</filter-mapping>
+	<listener>
+		<listener-class>com.hazelcast.web.SessionListener</listener-class>
+	</listener>
+
+</web-app>
+{code}
\ No newline at end of file
diff --git a/manual/src/main/webapp/user-guide/index.conf b/manual/src/main/webapp/user-guide/index.conf
index e568b51..19de361 100644
--- a/manual/src/main/webapp/user-guide/index.conf
+++ b/manual/src/main/webapp/user-guide/index.conf
@@ -8,5 +8,7 @@
 * [Groups in Karaf Cellar|/user-guide/groups]
 * [OBR in Karaf Cellar|/user-guide/obr]
 * [OSGi Event broadcast with Karaf Cellar|/user-guide/event]
+* [HTTP Balancer|/user-guide/http-balancer]
+* [HTTP Session Replication|/user-guide/http-session]
 * [DOSGi and Transport|/user-guide/transport]
 * [Discovery Services|/user-guide/cloud]
diff --git a/obr/NOTICE b/obr/NOTICE
index addb35c..64cb235 100644
--- a/obr/NOTICE
+++ b/obr/NOTICE
@@ -1,5 +1,5 @@
 Apache Karaf Cellar

-Copyright 2011-2014 The Apache Software Foundation

+Copyright 2011-2015 The Apache Software Foundation

 

 I. Used Software

 

diff --git a/obr/pom.xml b/obr/pom.xml
index 43e36ea..0fbbdae 100644
--- a/obr/pom.xml
+++ b/obr/pom.xml
@@ -40,8 +40,12 @@
             <artifactId>org.apache.karaf.cellar.core</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.util</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.apache.karaf.shell</groupId>
-            <artifactId>org.apache.karaf.shell.table</artifactId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
         </dependency>
         <dependency>
             <groupId>org.osgi</groupId>
@@ -64,33 +68,31 @@
     <build>
         <plugins>
             <plugin>
+                <groupId>org.apache.karaf.tooling</groupId>
+                <artifactId>karaf-services-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
                 <configuration>
                     <instructions>
                         <Export-Package>
-                            org.apache.karaf.cellar.obr*;version="${project.version}"
+                            !org.apache.karaf.cellar.obr.management.internal,
+                            !org.apache.karaf.cellar.obr.internal.osgi,
+                            org.apache.karaf.cellar.obr*
                         </Export-Package>
                         <Import-Package>
-                            org.apache.karaf.cellar.core*;version=${project.version},
-                            org.apache.felix.service.command,
-                            org.apache.felix.gogo.commands,
-                            org.apache.karaf.shell.console;version="[3,5)",
-                            org.apache.karaf.shell.console.commands;version="[3,5)",
-                            org.apache.karaf.shell.console.completer;version="[3,5)",
-                            org.apache.karaf.shell.commands;version="[3,5)",
-                            org.apache.karaf.management;version="[3,5)",
-                            javax.management*,
-                            org.osgi*,
-                            org.slf4j;version="[1.6,2)";resolution:=optional
+                            org.slf4j;version="[1.6,2)";resolution:=optional,
+                            org.apache.karaf.shell*;resolution:=optional,
+                            *
                         </Import-Package>
                         <DynamicImport-Package>
-                            org.apache.felix.bundlerepository,
-                            org.osgi.service.obr,
-                            *
+                            org.apache.felix.bundlerepository, org.osgi.service.obr
                         </DynamicImport-Package>
                         <Private-Package>
-                            org.apache.karaf.cellar.obr.management.internal
+                            org.apache.karaf.cellar.obr.management.internal,
+                            org.apache.karaf.cellar.obr.internal.osgi,
+                            org.apache.karaf.util.tracker;-split-package:=merge-first
                         </Private-Package>
                     </instructions>
                 </configuration>
diff --git a/obr/src/main/java/org/apache/karaf/cellar/obr/ObrBundleEventHandler.java b/obr/src/main/java/org/apache/karaf/cellar/obr/ObrBundleEventHandler.java
index f821cd5..02cbf1a 100644
--- a/obr/src/main/java/org/apache/karaf/cellar/obr/ObrBundleEventHandler.java
+++ b/obr/src/main/java/org/apache/karaf/cellar/obr/ObrBundleEventHandler.java
@@ -131,6 +131,12 @@
             return;
         }
 
+        // check if it's not a "local" event
+        if (event.getSourceNode() != null && event.getSourceNode().getId().equalsIgnoreCase(clusterManager.getNode().getId())) {
+            LOGGER.trace("CELLAR OBR: cluster event is local (coming from local synchronizer or listener)");
+            return;
+        }
+
         String bundleId = event.getBundleId();
         boolean deployOptional = event.getDeployOptional();
         boolean start = event.getStart();
diff --git a/obr/src/main/java/org/apache/karaf/cellar/obr/ObrUrlEventHandler.java b/obr/src/main/java/org/apache/karaf/cellar/obr/ObrUrlEventHandler.java
index ed27ace..6f435aa 100644
--- a/obr/src/main/java/org/apache/karaf/cellar/obr/ObrUrlEventHandler.java
+++ b/obr/src/main/java/org/apache/karaf/cellar/obr/ObrUrlEventHandler.java
@@ -70,6 +70,12 @@
             return;
         }
 
+        // check if it's not a "local" event
+        if (event.getSourceNode() != null && event.getSourceNode().getId().equalsIgnoreCase(clusterManager.getNode().getId())) {
+            LOGGER.trace("CELLAR OBR: cluster event is local (coming from local synchronizer or listener)");
+            return;
+        }
+
         String url = event.getUrl();
         try {
             if (isAllowed(event.getSourceGroup(), Constants.URLS_CONFIG_CATEGORY, url, EventType.INBOUND) || event.getForce()) {
diff --git a/obr/src/main/java/org/apache/karaf/cellar/obr/ObrUrlSynchronizer.java b/obr/src/main/java/org/apache/karaf/cellar/obr/ObrUrlSynchronizer.java
index 73be384..d5f7d7d 100644
--- a/obr/src/main/java/org/apache/karaf/cellar/obr/ObrUrlSynchronizer.java
+++ b/obr/src/main/java/org/apache/karaf/cellar/obr/ObrUrlSynchronizer.java
@@ -18,6 +18,7 @@
 import org.apache.karaf.cellar.core.Configurations;
 import org.apache.karaf.cellar.core.Group;
 import org.apache.karaf.cellar.core.Synchronizer;
+import org.apache.karaf.cellar.core.event.EventProducer;
 import org.apache.karaf.cellar.core.event.EventType;
 import org.osgi.service.cm.Configuration;
 import org.slf4j.Logger;
@@ -35,8 +36,16 @@
 
     private static final transient Logger LOGGER = LoggerFactory.getLogger(ObrUrlSynchronizer.class);
 
+    private EventProducer eventProducer;
+
+    public void setEventProducer(EventProducer eventProducer) {
+        this.eventProducer = eventProducer;
+    }
+
     @Override
     public void init() {
+        if (groupManager == null)
+            return;
         Set<Group> groups = groupManager.listLocalGroups();
         if (groups != null && !groups.isEmpty()) {
             for (Group group : groups) {
@@ -58,19 +67,32 @@
     @Override
     public void sync(Group group) {
         String policy = getSyncPolicy(group);
-        if (policy != null && policy.equalsIgnoreCase("cluster")) {
-            LOGGER.debug("CELLAR OBR: sync policy is set as 'cluster' for cluster group " + group.getName());
-            if (clusterManager.listNodesByGroup(group).size() == 1 && clusterManager.listNodesByGroup(group).contains(clusterManager.getNode())) {
-                LOGGER.debug("CELLAR OBR: node is the first and only member of the group, pushing state");
-                push(group);
-            } else {
-                LOGGER.debug("CELLAR OBR: pulling state");
-                pull(group);
-            }
+        if (policy == null) {
+            LOGGER.warn("CELLAR OBR: sync policy is not defined for cluster group {}", group.getName());
         }
-        if (policy != null && policy.equalsIgnoreCase("node")) {
-            LOGGER.debug("CELLAR OBR: sync policy is set as 'node' for cluster group " + group.getName());
+        if (policy.equalsIgnoreCase("cluster")) {
+            LOGGER.debug("CELLAR OBR: sync policy set as 'cluster' for cluster group {}", group.getName());
+            LOGGER.debug("CELLAR OBR: updating node from the cluster (pull first)");
+            pull(group);
+            LOGGER.debug("CELLAR OBR: updating cluster from the local node (push after)");
             push(group);
+        } else if (policy.equalsIgnoreCase("node")) {
+            LOGGER.debug("CELLAR OBR: sync policy set as 'node' for cluster group {}", group.getName());
+            LOGGER.debug("CELLAR OBR: updating cluster from the local node (push first)");
+            push(group);
+            LOGGER.debug("CELLAR OBR: updating node from the cluster (pull after)");
+            pull(group);
+        } else if (policy.equalsIgnoreCase("clusterOnly")) {
+            LOGGER.debug("CELLAR OBR: sync policy set as 'clusterOnly' for cluster group " + group.getName());
+            LOGGER.debug("CELLAR OBR: updating node from the cluster (pull only)");
+            pull(group);
+        } else if (policy.equalsIgnoreCase("nodeOnly")) {
+            LOGGER.debug("CELLAR OBR: sync policy set as 'nodeOnly' for cluster group " + group.getName());
+            LOGGER.debug("CELLAR OBR: updating cluster from the local node (push only)");
+            push(group);
+        } else {
+            LOGGER.debug("CELLAR OBR: sync policy set as 'disabled' for cluster group " + group.getName());
+            LOGGER.debug("CELLAR OBR: no sync");
         }
     }
 
@@ -120,11 +142,19 @@
                 Repository[] repositories = obrService.listRepositories();
                 for (Repository repository : repositories) {
                     if (isAllowed(group, Constants.URLS_CONFIG_CATEGORY, repository.getURI().toString(), EventType.OUTBOUND)) {
+                        LOGGER.debug("CELLAR OBR: adding repository {} to the cluster", repository.getURI().toString());
+                        // update cluster state
                         clusterUrls.add(repository.getURI().toString());
+                        // send cluster event
+                        ClusterObrUrlEvent urlEvent = new ClusterObrUrlEvent(repository.getURI().toString(), Constants.URL_ADD_EVENT_TYPE);
+                        urlEvent.setSourceGroup(group);
+                        urlEvent.setSourceNode(clusterManager.getNode());
+                        eventProducer.produce(urlEvent);
                         // update OBR bundles in the cluster group
                         Set<ObrBundleInfo> clusterBundles = clusterManager.getSet(Constants.BUNDLES_DISTRIBUTED_SET_NAME + Configurations.SEPARATOR + groupName);
                         Resource[] resources = repository.getResources();
                         for (Resource resource : resources) {
+                            LOGGER.debug("CELLAR OBR: adding bundle {} to the cluster", resource.getPresentationName());
                             ObrBundleInfo info = new ObrBundleInfo(resource.getPresentationName(), resource.getSymbolicName(), resource.getVersion().toString());
                             clusterBundles.add(info);
                         }
diff --git a/obr/src/main/java/org/apache/karaf/cellar/obr/internal/osgi/Activator.java b/obr/src/main/java/org/apache/karaf/cellar/obr/internal/osgi/Activator.java
new file mode 100644
index 0000000..6ec3381
--- /dev/null
+++ b/obr/src/main/java/org/apache/karaf/cellar/obr/internal/osgi/Activator.java
@@ -0,0 +1,143 @@
+/*
+ * 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.obr.internal.osgi;
+
+import org.apache.felix.bundlerepository.RepositoryAdmin;
+import org.apache.karaf.cellar.core.ClusterManager;
+import org.apache.karaf.cellar.core.GroupManager;
+import org.apache.karaf.cellar.core.Synchronizer;
+import org.apache.karaf.cellar.core.event.EventHandler;
+import org.apache.karaf.cellar.core.event.EventProducer;
+import org.apache.karaf.cellar.obr.ObrBundleEventHandler;
+import org.apache.karaf.cellar.obr.ObrUrlEventHandler;
+import org.apache.karaf.cellar.obr.ObrUrlSynchronizer;
+import org.apache.karaf.cellar.obr.management.internal.CellarOBRMBeanImpl;
+import org.apache.karaf.util.tracker.BaseActivator;
+import org.apache.karaf.util.tracker.annotation.ProvideService;
+import org.apache.karaf.util.tracker.annotation.RequireService;
+import org.apache.karaf.util.tracker.annotation.Services;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Hashtable;
+
+@Services(
+        provides = {
+                @ProvideService(EventHandler.class),
+                @ProvideService(Synchronizer.class)
+        },
+        requires = {
+                @RequireService(RepositoryAdmin.class),
+                @RequireService(ClusterManager.class),
+                @RequireService(GroupManager.class),
+                @RequireService(ConfigurationAdmin.class),
+                @RequireService(EventProducer.class)
+        }
+)
+public class Activator extends BaseActivator {
+
+    private final static Logger LOGGER = LoggerFactory.getLogger(Activator.class);
+
+    private ObrUrlEventHandler urlEventHandler;
+    private ObrBundleEventHandler bundleEventHandler;
+    private ObrUrlSynchronizer urlSynchronizer;
+    private ServiceRegistration mbeanRegistration;
+
+    @Override
+    public void doStart() throws Exception {
+
+        RepositoryAdmin repositoryAdmin = getTrackedService(RepositoryAdmin.class);
+        if (repositoryAdmin == null)
+            return;
+        ClusterManager clusterManager = getTrackedService(ClusterManager.class);
+        if (clusterManager == null)
+            return;
+        GroupManager groupManager = getTrackedService(GroupManager.class);
+        if (groupManager == null)
+            return;
+        ConfigurationAdmin configurationAdmin = getTrackedService(ConfigurationAdmin.class);
+        if (configurationAdmin == null)
+            return;
+        EventProducer eventProducer = getTrackedService(EventProducer.class);
+        if (eventProducer == null)
+            return;
+
+        LOGGER.debug("CELLAR OBR: init URL event handler");
+        urlEventHandler = new ObrUrlEventHandler();
+        urlEventHandler.setClusterManager(clusterManager);
+        urlEventHandler.setGroupManager(groupManager);
+        urlEventHandler.setConfigurationAdmin(configurationAdmin);
+        urlEventHandler.setObrService(repositoryAdmin);
+        urlEventHandler.init();
+        Hashtable props = new Hashtable();
+        props.put("managed", "true");
+        register(EventHandler.class, urlEventHandler, props);
+
+        LOGGER.debug("CELLAR OBR: init bundle event handler");
+        bundleEventHandler = new ObrBundleEventHandler();
+        bundleEventHandler.setObrService(repositoryAdmin);
+        bundleEventHandler.setClusterManager(clusterManager);
+        bundleEventHandler.setGroupManager(groupManager);
+        bundleEventHandler.setConfigurationAdmin(configurationAdmin);
+        bundleEventHandler.init();
+        register(EventHandler.class, bundleEventHandler, props);
+
+        LOGGER.debug("CELLAR OBR: init URL synchronizer");
+        urlSynchronizer = new ObrUrlSynchronizer();
+        urlSynchronizer.setObrService(repositoryAdmin);
+        urlSynchronizer.setClusterManager(clusterManager);
+        urlSynchronizer.setGroupManager(groupManager);
+        urlSynchronizer.setEventProducer(eventProducer);
+        urlSynchronizer.setConfigurationAdmin(configurationAdmin);
+        urlSynchronizer.init();
+        props = new Hashtable();
+        props.put("resource", "obr.urls");
+        register(Synchronizer.class, urlSynchronizer, props);
+
+        LOGGER.debug("CELLAR OBR: register MBean");
+        CellarOBRMBeanImpl mbean = new CellarOBRMBeanImpl();
+        mbean.setClusterManager(clusterManager);
+        mbean.setGroupManager(groupManager);
+        mbean.setConfigurationAdmin(configurationAdmin);
+        mbean.setEventProducer(eventProducer);
+        props = new Hashtable();
+        props.put("jmx.objectname", "org.apache.karaf.cellar:type=obr,name=" + System.getProperty("karaf.name"));
+        mbeanRegistration = bundleContext.registerService(getInterfaceNames(mbean), mbean, props);
+    }
+
+    @Override
+    public void doStop() {
+        super.doStop();
+
+        if (mbeanRegistration != null) {
+            mbeanRegistration.unregister();
+            mbeanRegistration = null;
+        }
+        if (urlSynchronizer != null) {
+            urlSynchronizer.destroy();
+            urlEventHandler = null;
+        }
+        if (bundleEventHandler != null) {
+            bundleEventHandler.destroy();
+            bundleEventHandler = null;
+        }
+        if (urlEventHandler != null) {
+            urlEventHandler.destroy();
+            urlEventHandler = null;
+        }
+    }
+
+}
diff --git a/obr/src/main/java/org/apache/karaf/cellar/obr/shell/ObrAddUrlCommand.java b/obr/src/main/java/org/apache/karaf/cellar/obr/shell/ObrAddUrlCommand.java
index d8fade4..46e7b61 100644
--- a/obr/src/main/java/org/apache/karaf/cellar/obr/shell/ObrAddUrlCommand.java
+++ b/obr/src/main/java/org/apache/karaf/cellar/obr/shell/ObrAddUrlCommand.java
@@ -20,23 +20,30 @@
 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.core.shell.completer.AllGroupsCompleter;
 import org.apache.karaf.cellar.obr.ClusterObrUrlEvent;
 import org.apache.karaf.cellar.obr.Constants;
 import org.apache.karaf.cellar.obr.ObrBundleInfo;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.Set;
 
 @Command(scope = "cluster", name = "obr-add-url", description = "Add an OBR URL in a cluster group")
+@Service
 public class ObrAddUrlCommand extends ObrCommandSupport {
 
     @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
     String groupName;
 
     @Argument(index = 1, name = "url", description = "The OBR URL.", required = true, multiValued = false)
     String url;
 
+    @Reference
     private EventProducer eventProducer;
 
     @Override
diff --git a/obr/src/main/java/org/apache/karaf/cellar/obr/shell/ObrCommandSupport.java b/obr/src/main/java/org/apache/karaf/cellar/obr/shell/ObrCommandSupport.java
index 5c75ad8..e17903e 100644
--- a/obr/src/main/java/org/apache/karaf/cellar/obr/shell/ObrCommandSupport.java
+++ b/obr/src/main/java/org/apache/karaf/cellar/obr/shell/ObrCommandSupport.java
@@ -18,12 +18,14 @@
 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.shell.api.action.lifecycle.Reference;
 
 /**
  * Generic cluster OBR shell command support.
  */
 public abstract class ObrCommandSupport extends CellarCommandSupport {
 
+    @Reference
     protected RepositoryAdmin obrService;
 
     public RepositoryAdmin getObrService() {
@@ -54,5 +56,4 @@
     @Override
     public abstract Object doExecute() throws Exception;
 
-
 }
diff --git a/obr/src/main/java/org/apache/karaf/cellar/obr/shell/ObrDeployCommand.java b/obr/src/main/java/org/apache/karaf/cellar/obr/shell/ObrDeployCommand.java
index 7c39b66..0f5de1a 100644
--- a/obr/src/main/java/org/apache/karaf/cellar/obr/shell/ObrDeployCommand.java
+++ b/obr/src/main/java/org/apache/karaf/cellar/obr/shell/ObrDeployCommand.java
@@ -17,16 +17,22 @@
 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.core.shell.completer.AllGroupsCompleter;
 import org.apache.karaf.cellar.obr.ClusterObrBundleEvent;
 import org.apache.karaf.cellar.obr.Constants;
-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.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 @Command(scope = "cluster", name = "obr-deploy", description = "Deploy an OBR bundle in a cluster group")
+@Service
 public class ObrDeployCommand extends ObrCommandSupport {
 
     @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
     String groupName;
 
     @Argument(index = 1, name="bundleId", description = "The bundle ID (symbolicname,version in the OBR) to deploy", required = true, multiValued = false)
@@ -38,6 +44,7 @@
     @Option(name = "-d", aliases = { "--deployOptional" }, description = "Deploy optional bundles", required = false, multiValued = false)
     boolean deployOptional = false;
 
+    @Reference
     private EventProducer eventProducer;
 
     @Override
diff --git a/obr/src/main/java/org/apache/karaf/cellar/obr/shell/ObrListCommand.java b/obr/src/main/java/org/apache/karaf/cellar/obr/shell/ObrListCommand.java
index 848ade1..8b1941b 100644
--- a/obr/src/main/java/org/apache/karaf/cellar/obr/shell/ObrListCommand.java
+++ b/obr/src/main/java/org/apache/karaf/cellar/obr/shell/ObrListCommand.java
@@ -16,19 +16,24 @@
 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.core.shell.completer.AllGroupsCompleter;
 import org.apache.karaf.cellar.obr.Constants;
 import org.apache.karaf.cellar.obr.ObrBundleInfo;
-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 org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.table.ShellTable;
 
 import java.util.Set;
 
 @Command(scope = "cluster", name = "obr-list", description = "List the OBR bundles in a cluster group")
+@Service
 public class ObrListCommand extends CellarCommandSupport {
 
     @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
     String groupName;
 
     @Option(name = "--no-format", description = "Disable table rendered output", required = false, multiValued = false)
diff --git a/obr/src/main/java/org/apache/karaf/cellar/obr/shell/ObrListUrlCommand.java b/obr/src/main/java/org/apache/karaf/cellar/obr/shell/ObrListUrlCommand.java
index b5c4031..e846e0f 100644
--- a/obr/src/main/java/org/apache/karaf/cellar/obr/shell/ObrListUrlCommand.java
+++ b/obr/src/main/java/org/apache/karaf/cellar/obr/shell/ObrListUrlCommand.java
@@ -16,16 +16,21 @@
 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.core.shell.completer.AllGroupsCompleter;
 import org.apache.karaf.cellar.obr.Constants;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.Set;
 
 @Command(scope = "cluster", name = "obr-list-url", description = "List the OBR URLs in a cluster group")
+@Service
 public class ObrListUrlCommand extends CellarCommandSupport {
 
     @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
     String groupName;
 
     @Override
diff --git a/obr/src/main/java/org/apache/karaf/cellar/obr/shell/ObrRemoveUrlCommand.java b/obr/src/main/java/org/apache/karaf/cellar/obr/shell/ObrRemoveUrlCommand.java
index c7a2fe9..1c4e919 100644
--- a/obr/src/main/java/org/apache/karaf/cellar/obr/shell/ObrRemoveUrlCommand.java
+++ b/obr/src/main/java/org/apache/karaf/cellar/obr/shell/ObrRemoveUrlCommand.java
@@ -20,23 +20,30 @@
 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.core.shell.completer.AllGroupsCompleter;
 import org.apache.karaf.cellar.obr.ClusterObrUrlEvent;
 import org.apache.karaf.cellar.obr.Constants;
 import org.apache.karaf.cellar.obr.ObrBundleInfo;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.Set;
 
 @Command(scope = "cluster", name = "obr-remove-url", description = "Remove a repository URL from the distributed OBR service.")
+@Service
 public class ObrRemoveUrlCommand extends ObrCommandSupport {
 
     @Argument(index = 0, name = "group", description = "The cluster group name.", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
     String groupName;
 
     @Argument(index = 1, name = "url", description = "The repository URL to remove from the OBR service.", required = true, multiValued = false)
     String url;
 
+    @Reference
     private EventProducer eventProducer;
 
     public Object doExecute() throws Exception {
diff --git a/obr/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/obr/src/main/resources/OSGI-INF/blueprint/blueprint.xml
deleted file mode 100644
index e3a5881..0000000
--- a/obr/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-   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.
--->
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
-
-    <!-- Cluster OBR Bundle Event Handler -->
-    <bean id="obrBundleEventHandler" class="org.apache.karaf.cellar.obr.ObrBundleEventHandler"
-            init-method="init" destroy-method="destroy">
-        <property name="obrService" ref="repositoryAdmin"/>
-        <property name="clusterManager" ref="clusterManager"/>
-        <property name="groupManager" ref="groupManager"/>
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-    </bean>
-    <service ref="obrBundleEventHandler" interface="org.apache.karaf.cellar.core.event.EventHandler">
-        <service-properties>
-            <entry key="managed" value="true"/>
-        </service-properties>
-    </service>
-
-    <!-- OBR URLs Synchronizer -->
-    <bean id="obrUrlSynchronizer" class="org.apache.karaf.cellar.obr.ObrUrlSynchronizer"
-            init-method="init" destroy-method="destroy">
-        <property name="obrService" ref="repositoryAdmin"/>
-        <property name="clusterManager" ref="clusterManager"/>
-        <property name="groupManager" ref="groupManager"/>
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-    </bean>
-    <service ref="obrUrlSynchronizer" interface="org.apache.karaf.cellar.core.Synchronizer">
-        <service-properties>
-            <entry key="resource" value="obr.urls"/>
-        </service-properties>
-    </service>
-
-    <!-- Cluster OBR URL Event Handler -->
-    <bean id="obrUrlEventHandler" class="org.apache.karaf.cellar.obr.ObrUrlEventHandler"
-          init-method="init" destroy-method="destroy">
-        <property name="obrService" ref="repositoryAdmin"/>
-        <property name="clusterManager" ref="clusterManager"/>
-        <property name="groupManager" ref="groupManager"/>
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-    </bean>
-    <service ref="obrUrlEventHandler" interface="org.apache.karaf.cellar.core.event.EventHandler">
-        <service-properties>
-            <entry key="managed" value="true"/>
-        </service-properties>
-    </service>
-
-    <reference id="repositoryAdmin" interface="org.apache.felix.bundlerepository.RepositoryAdmin"/>
-    <reference id="clusterManager" interface="org.apache.karaf.cellar.core.ClusterManager"/>
-    <reference id="groupManager" interface="org.apache.karaf.cellar.core.GroupManager"/>
-    <reference id="configurationAdmin" interface="org.osgi.service.cm.ConfigurationAdmin"/>
-    <reference id="eventProducer" interface="org.apache.karaf.cellar.core.event.EventProducer"/>
-
-</blueprint>
\ No newline at end of file
diff --git a/obr/src/main/resources/OSGI-INF/blueprint/management.xml b/obr/src/main/resources/OSGI-INF/blueprint/management.xml
deleted file mode 100644
index eb1370e..0000000
--- a/obr/src/main/resources/OSGI-INF/blueprint/management.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-   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.
--->
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
-
-    <!-- Cellar OBR MBean -->
-    <bean id="cellarOBRMBean" class="org.apache.karaf.cellar.obr.management.internal.CellarOBRMBeanImpl">
-        <property name="clusterManager" ref="clusterManager"/>
-        <property name="groupManager" ref="groupManager"/>
-        <property name="eventProducer" ref="eventProducer"/>
-        <property name="configurationAdmin" ref="configurationAdmin"/>
-    </bean>
-    <service ref="cellarOBRMBean" auto-export="interfaces">
-        <service-properties>
-            <entry key="jmx.objectname" value="org.apache.karaf.cellar:type=obr,name=${karaf.name}"/>
-        </service-properties>
-    </service>
-
-</blueprint>
\ No newline at end of file
diff --git a/obr/src/main/resources/OSGI-INF/blueprint/shell-commands.xml b/obr/src/main/resources/OSGI-INF/blueprint/shell-commands.xml
deleted file mode 100644
index ac8fe4c..0000000
--- a/obr/src/main/resources/OSGI-INF/blueprint/shell-commands.xml
+++ /dev/null
@@ -1,82 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-   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.
--->
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" default-activation="lazy">
-
-    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
-        <command>
-            <action class="org.apache.karaf.cellar.obr.shell.ObrListCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-            </action>
-            <completers>
-                <ref component-id="allGroupsCompleter"/>
-                <null/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.obr.shell.ObrDeployCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-                <property name="eventProducer" ref="eventProducer"/>
-                <property name="configurationAdmin" ref="configurationAdmin"/>
-            </action>
-            <completers>
-                <ref component-id="allGroupsCompleter"/>
-                <null/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.obr.shell.ObrListUrlCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-            </action>
-            <completers>
-                <ref component-id="allGroupsCompleter"/>
-                <null/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.obr.shell.ObrAddUrlCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-                <property name="eventProducer" ref="eventProducer"/>
-                <property name="obrService" ref="repositoryAdmin"/>
-                <property name="configurationAdmin" ref="configurationAdmin"/>
-            </action>
-            <completers>
-                <ref component-id="allGroupsCompleter"/>
-                <null/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.obr.shell.ObrRemoveUrlCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-                <property name="eventProducer" ref="eventProducer"/>
-                <property name="obrService" ref="repositoryAdmin"/>
-                <property name="configurationAdmin" ref="configurationAdmin"/>
-            </action>
-            <completers>
-                <ref component-id="allGroupsCompleter"/>
-                <null/>
-            </completers>
-        </command>
-    </command-bundle>
-
-    <bean id="allGroupsCompleter" class="org.apache.karaf.cellar.core.shell.completer.AllGroupsCompleter">
-        <property name="groupManager" ref="groupManager"/>
-    </bean>
-
-</blueprint>
diff --git a/pom.xml b/pom.xml
index 28e794f..c1f0b11 100644
--- a/pom.xml
+++ b/pom.xml
@@ -36,19 +36,20 @@
 
     <properties>
         <easymock.version>3.2</easymock.version>
-        <fabric8.version>2.0.20</fabric8.version>
-        <felix.bundlerepository.version>2.0.2</felix.bundlerepository.version>
-        <felix.utils.version>1.6.0</felix.utils.version>
-        <felix.webconsole.version>4.2.2</felix.webconsole.version>
-        <hazelcast.version>3.3.4</hazelcast.version>
+        <fabric8.version>2.2.23</fabric8.version>
+        <fabric8.kubernetes-client.version>1.3.22</fabric8.kubernetes-client.version>
+        <felix.bundlerepository.version>2.0.4</felix.bundlerepository.version>
+        <felix.utils.version>1.8.0</felix.utils.version>
+        <felix.webconsole.version>4.2.8</felix.webconsole.version>
+        <hazelcast.version>3.5.1</hazelcast.version>
         <jclouds.version>1.8.1</jclouds.version>
         <joda-time.version>2.5</joda-time.version>
         <junit.version>4.11</junit.version>
-        <karaf.version>4.0.0.M1</karaf.version>
-        <osgi.version>5.0.0</osgi.version>
+        <karaf.version>4.0.0</karaf.version>
+        <osgi.version>6.0.0</osgi.version>
         <osgi.compendium.version>5.0.0</osgi.compendium.version>
         <slf4j.version>1.7.7</slf4j.version>
-        <apache.commons.lang3.version>3.3.2</apache.commons.lang3.version>
+        <bnd.version.policy>[$(version;==;$(@)),$(version;+;$(@)))</bnd.version.policy>
     </properties>
 
     <modules>
@@ -62,6 +63,7 @@
         <module>shell</module>
         <module>hazelcast</module>
         <module>utils</module>
+        <module>http</module>
         <module>cloud</module>
         <module>kubernetes</module>
         <module>webconsole</module>
@@ -207,30 +209,31 @@
             <!-- Hazelcast -->
             <dependency>
                 <groupId>com.hazelcast</groupId>
-                <artifactId>hazelcast</artifactId>
+                <artifactId>hazelcast-all</artifactId>
                 <version>${hazelcast.version}</version>
             </dependency>
 
             <!-- Karaf -->
             <dependency>
                 <groupId>org.apache.karaf.features</groupId>
-                <artifactId>org.apache.karaf.features.command</artifactId>
-                <version>${karaf.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>org.apache.karaf.features</groupId>
                 <artifactId>org.apache.karaf.features.core</artifactId>
                 <version>${karaf.version}</version>
             </dependency>
             <dependency>
-                <groupId>org.apache.karaf.shell</groupId>
-                <artifactId>org.apache.karaf.shell.console</artifactId>
+                <groupId>org.apache.karaf.features</groupId>
+                <artifactId>org.apache.karaf.features.command</artifactId>
+                <version>${karaf.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.karaf</groupId>
+                <artifactId>org.apache.karaf.util</artifactId>
                 <version>${karaf.version}</version>
             </dependency>
             <dependency>
                 <groupId>org.apache.karaf.shell</groupId>
-                <artifactId>org.apache.karaf.shell.table</artifactId>
+                <artifactId>org.apache.karaf.shell.core</artifactId>
                 <version>${karaf.version}</version>
+                <optional>true</optional>
             </dependency>
 
             <dependency>
@@ -300,8 +303,8 @@
             <!-- Kubernetes -->
             <dependency>
                 <groupId>io.fabric8</groupId>
-                <artifactId>kubernetes-api</artifactId>
-                <version>${fabric8.version}</version>
+                <artifactId>kubernetes-client</artifactId>
+                <version>${fabric8.kubernetes-client.version}</version>
             </dependency>
 
             <!-- Testing -->
@@ -373,9 +376,34 @@
                     </configuration>
                 </plugin>
                 <plugin>
+                    <groupId>org.apache.karaf.tooling</groupId>
+                    <artifactId>karaf-services-maven-plugin</artifactId>
+                    <version>${karaf.version}</version>
+                    <executions>
+                        <execution>
+                            <id>service-metadata-generate</id>
+                            <phase>process-classes</phase>
+                            <goals>
+                                <goal>service-metadata-generate</goal>
+                            </goals>
+                        </execution>
+                    </executions>
+                </plugin>
+                <plugin>
+                    <groupId>org.apache.karaf.tooling</groupId>
+                    <artifactId>karaf-maven-plugin</artifactId>
+                    <version>${karaf.version}</version>
+                </plugin>
+                <plugin>
                     <groupId>org.apache.felix</groupId>
                     <artifactId>maven-bundle-plugin</artifactId>
-                    <version>2.5.3</version>
+                    <version>2.5.4</version>
+                    <extensions>true</extensions>
+                    <configuration>
+                        <instructions>
+                            <_versionpolicy>${bnd.version.policy}</_versionpolicy>
+                        </instructions>
+                    </configuration>
                 </plugin>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
@@ -498,32 +526,6 @@
                     </execution>
                 </executions>
             </plugin>
-            <plugin>
-                <groupId>org.apache.felix</groupId>
-                <artifactId>maven-bundle-plugin</artifactId>
-                <extensions>true</extensions>
-                <configuration>
-                    <instructions>
-                        <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
-                        <Export-Package>${project.artifactId}*;version=${project.version}</Export-Package>
-                    </instructions>
-                    <supportedProjectTypes>
-                        <supportedProjectType>jar</supportedProjectType>
-                        <supportedProjectType>war</supportedProjectType>
-                        <supportedProjectType>bundle</supportedProjectType>
-                    </supportedProjectTypes>
-                    <unpackBundle>true</unpackBundle>
-                </configuration>
-                <executions>
-                    <execution>
-                        <id>bundle-manifest</id>
-                        <phase>process-classes</phase>
-                        <goals>
-                            <goal>manifest</goal>
-                        </goals>
-                    </execution>
-                </executions>
-            </plugin>
             <!-- generate dependencies versions -->
             <plugin>
                 <groupId>org.apache.servicemix.tooling</groupId>
diff --git a/samples/camel-hazelcast-app/NOTICE b/samples/camel-hazelcast-app/NOTICE
index addb35c..64cb235 100644
--- a/samples/camel-hazelcast-app/NOTICE
+++ b/samples/camel-hazelcast-app/NOTICE
@@ -1,5 +1,5 @@
 Apache Karaf Cellar

-Copyright 2011-2014 The Apache Software Foundation

+Copyright 2011-2015 The Apache Software Foundation

 

 I. Used Software

 

diff --git a/samples/dosgi-greeter/NOTICE b/samples/dosgi-greeter/NOTICE
index addb35c..64cb235 100644
--- a/samples/dosgi-greeter/NOTICE
+++ b/samples/dosgi-greeter/NOTICE
@@ -1,5 +1,5 @@
 Apache Karaf Cellar

-Copyright 2011-2014 The Apache Software Foundation

+Copyright 2011-2015 The Apache Software Foundation

 

 I. Used Software

 

diff --git a/samples/dosgi-greeter/README b/samples/dosgi-greeter/README
index 24dbc80..e5d59f3 100644
--- a/samples/dosgi-greeter/README
+++ b/samples/dosgi-greeter/README
@@ -3,33 +3,33 @@
 Use Case
 --------
 A service bundle expose an OSGi service on the cluster using Cellar DOSGi.
-On the other hand, a client bundle provides a shell command to call the service bundle.
+On the other hand, a client bundle provides a shell command to call the service.
 The call is remote using DOSGi.
 
 Installation
 -------------
 To show the remote service call, your Cellar cluster should contain at least two nodes.
 
-NodeA
------
-On NodeA, we install the API bundle (providing the OSGi service description), and the service bundle (providing the
-actual service implementation):
+Install cellar and cellar-dosgi on the cluster:
 
-karaf@root()> bundle:install -s mvn:org.apache.karaf.cellar.samples.dosgi.greeter/org.apache.karaf.cellar.samples.dosgi.greeter.api/3.0.0
-karaf@root()> bundle:install -s mvn:org.apache.karaf.cellar.samples.dosgi.greeter/org.apache.karaf.cellar.samples.dosgi.greeter.service/3.0.0
+karaf@root()> feature:repo-add cellar 4.0.0
+karaf@root()> feature:install cellar
+karaf@root()> feature:install cellar-dosgi
 
-We can see the service exposed on the cluster using the cluster:service-list command.
+Install the api bundle on the cluster:
 
-NodeB
------
-On NodeB, we install the API bundle (to get the description of the OSGi service), and the client bundle (providing a shell
-command to call the service):
+karaf@root()> cluster:bundle-install -s default mvn:org.apache.karaf.cellar.samples.dosgi.greeter/org.apache.karaf.cellar.samples.dosgi.greeter.api/4.0.0
 
-karaf@root()> bundle:install -s mvn:org.apache.karaf.cellar.samples.dosgi.greeter/org.apache.karaf.cellar.samples.dosgi.greeter.api/3.0.0
-karaf@root()> bundle:install -s mvn:org.apache.karaf.cellar.samples.dosgi.greeter/org.apache.karaf.cellar.samples.dosgi.greeter.client/3.0.0
+Install the service bundle only on one node (node1):
+
+karaf@root()> bundle:install -s mvn:org.apache.karaf.cellar.samples.dosgi.greeter/org.apache.karaf.cellar.samples.dosgi.greeter.service/4.0.0
+
+On other nodes (node2), install the client bundle:
+
+karaf@root()> bundle:install -s mvn:org.apache.karaf.cellar.samples.dosgi.greeter/org.apache.karaf.cellar.samples.dosgi.greeter.client/4.0.0
 
 The client bundle provides a new shell command that we can use to call the OSGi service (remotely):
 
-karaf@root> dosgi-greeter:greet Hello 2
+karaf@root()> dosgi-greeter:greet Hello 2
 Hello.Hello from node 192.168.1.101:5702 count 0.
 Hello.Hello from node 192.168.1.101:5702 count 1.
diff --git a/samples/dosgi-greeter/api/src/main/java/org/apache/karaf/cellar/samples/dosgi/greeter/api/Greeter.java b/samples/dosgi-greeter/api/src/main/java/org/apache/karaf/cellar/samples/dosgi/greeter/api/Greeter.java
index 928bf34..d888a7d 100644
--- a/samples/dosgi-greeter/api/src/main/java/org/apache/karaf/cellar/samples/dosgi/greeter/api/Greeter.java
+++ b/samples/dosgi-greeter/api/src/main/java/org/apache/karaf/cellar/samples/dosgi/greeter/api/Greeter.java
@@ -22,6 +22,6 @@
      * Returns a greet message.
      * @return
      */
-    public GreetResponse greet(Greet greet);
+    GreetResponse greet(Greet greet);
 
 }
diff --git a/samples/dosgi-greeter/client/pom.xml b/samples/dosgi-greeter/client/pom.xml
index 26fa213..30f6a8b 100644
--- a/samples/dosgi-greeter/client/pom.xml
+++ b/samples/dosgi-greeter/client/pom.xml
@@ -43,24 +43,27 @@
             <artifactId>org.apache.karaf.cellar.samples.dosgi.greeter.api</artifactId>
             <version>${project.version}</version>
         </dependency>
+
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
         <plugins>
             <plugin>
+                <groupId>org.apache.karaf.tooling</groupId>
+                <artifactId>karaf-services-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
                 <configuration>
                     <instructions>
                         <Export-Package>
-                            org.apache.karaf.cellar.samples.dosgi.greeter.client*;version="${project.version}"
+                            org.apache.karaf.cellar.samples.dosgi.greeter.client*
                         </Export-Package>
-                        <Import-Package>
-                            org.apache.karaf.cellar.samples.dosgi.greeter.api,
-                            org.apache.karaf.shell.commands;version="[3,5)",
-                            org.apache.karaf.shell.console;version="[3,5)",
-                            org.osgi*
-                        </Import-Package>
                     </instructions>
                 </configuration>
             </plugin>
diff --git a/samples/dosgi-greeter/client/src/main/java/org/apache/karaf/cellar/samples/dosgi/greeter/client/GreetCommand.java b/samples/dosgi-greeter/client/src/main/java/org/apache/karaf/cellar/samples/dosgi/greeter/client/GreetCommand.java
index cb0b2f1..5d33061 100644
--- a/samples/dosgi-greeter/client/src/main/java/org/apache/karaf/cellar/samples/dosgi/greeter/client/GreetCommand.java
+++ b/samples/dosgi-greeter/client/src/main/java/org/apache/karaf/cellar/samples/dosgi/greeter/client/GreetCommand.java
@@ -14,12 +14,15 @@
 package org.apache.karaf.cellar.samples.dosgi.greeter.client;
 
 import org.apache.karaf.cellar.samples.dosgi.greeter.api.Greeter;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.console.OsgiCommandSupport;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 @Command(scope = "dosgi-greeter", name = "greet", description = "Starts the greet client")
-public class GreetCommand extends OsgiCommandSupport {
+@Service
+public class GreetCommand implements Action {
 
     @Argument(index = 0, name = "greetMessage", description = "The message that will be sent as the greeting.", required = true, multiValued = false)
     String greetMessage;
@@ -27,9 +30,10 @@
     @Argument(index = 1, name = "iterations", description = "The number of greet iterations to perform", required = false, multiValued = false)
     Integer iterations = 10;
 
+    @Reference
     private Greeter greeter;
 
-    protected Object doExecute() throws Exception {
+    public Object execute() throws Exception {
         GreeterClient greeterClient = new GreeterClient(greeter, greetMessage,iterations);
         greeterClient.start();
         return null;
diff --git a/samples/dosgi-greeter/client/src/main/resources/OSGI-INF/blueprint/shell-greeter.xml b/samples/dosgi-greeter/client/src/main/resources/OSGI-INF/blueprint/shell-greeter.xml
deleted file mode 100644
index 4db1842..0000000
--- a/samples/dosgi-greeter/client/src/main/resources/OSGI-INF/blueprint/shell-greeter.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
-   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.
--->
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" default-activation="eager">
-
-    <reference id="greeter" interface="org.apache.karaf.cellar.samples.dosgi.greeter.api.Greeter"/>
-
-    <!-- Command Bundle -->
-    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
-        <command>
-            <action class="org.apache.karaf.cellar.samples.dosgi.greeter.client.GreetCommand">
-                <property name="greeter" ref="greeter"/>
-            </action>
-        </command>
-    </command-bundle>
-
-</blueprint>
diff --git a/samples/dosgi-greeter/service/pom.xml b/samples/dosgi-greeter/service/pom.xml
index 2557645..bd1d351 100644
--- a/samples/dosgi-greeter/service/pom.xml
+++ b/samples/dosgi-greeter/service/pom.xml
@@ -39,23 +39,35 @@
             <artifactId>org.apache.karaf.cellar.samples.dosgi.greeter.api</artifactId>
             <version>${project.version}</version>
         </dependency>
+
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.cellar</groupId>
+            <artifactId>org.apache.karaf.cellar.core</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
         <plugins>
             <plugin>
+                <groupId>org.apache.karaf.tooling</groupId>
+                <artifactId>karaf-services-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
                 <configuration>
                     <instructions>
                         <Export-Package>
-                            org.apache.karaf.cellar.samples.dosgi.greeter.service*;version="${project.version}"
+                            org.apache.karaf.cellar.samples.dosgi.greeter.service*
                         </Export-Package>
-                        <Import-Package>
-                            org.apache.karaf.cellar.samples.dosgi.greeter.api,
-                            org.apache.karaf.cellar.core,
-                            org.osgi*
-                        </Import-Package>
                     </instructions>
                 </configuration>
             </plugin>
diff --git a/samples/dosgi-greeter/service/src/main/java/org/apache/karaf/cellar/samples/dosgi/greeter/service/Activator.java b/samples/dosgi-greeter/service/src/main/java/org/apache/karaf/cellar/samples/dosgi/greeter/service/Activator.java
new file mode 100644
index 0000000..4a3c46c
--- /dev/null
+++ b/samples/dosgi-greeter/service/src/main/java/org/apache/karaf/cellar/samples/dosgi/greeter/service/Activator.java
@@ -0,0 +1,50 @@
+/*
+ * 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.samples.dosgi.greeter.service;
+
+import org.apache.karaf.cellar.core.ClusterManager;
+import org.apache.karaf.cellar.samples.dosgi.greeter.api.Greeter;
+import org.apache.karaf.util.tracker.BaseActivator;
+import org.apache.karaf.util.tracker.annotation.ProvideService;
+import org.apache.karaf.util.tracker.annotation.RequireService;
+import org.apache.karaf.util.tracker.annotation.Services;
+
+import java.util.Hashtable;
+
+@Services(
+        provides = {
+                @ProvideService(Greeter.class)
+        },
+        requires = {
+                @RequireService(ClusterManager.class)
+        }
+)
+public class Activator extends BaseActivator {
+
+        @Override
+        public void doStart() throws Exception {
+
+                ClusterManager clusterManager = getTrackedService(ClusterManager.class);
+                if (clusterManager == null)
+                        return;
+
+                String nodeId = clusterManager.getNode().getId();
+                GreeterImpl greeter = new GreeterImpl(nodeId);
+                Hashtable props = new Hashtable();
+                props.put("service.exported.interfaces", "*");
+                register(Greeter.class, greeter, props);
+
+        }
+
+}
diff --git a/samples/dosgi-greeter/service/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/samples/dosgi-greeter/service/src/main/resources/OSGI-INF/blueprint/blueprint.xml
deleted file mode 100644
index 7c2a916..0000000
--- a/samples/dosgi-greeter/service/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
-   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.
-  -->
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
-
-    <!-- Greeter Implementation -->
-    <bean id="greeterImpl" class="org.apache.karaf.cellar.samples.dosgi.greeter.service.GreeterImpl">
-        <!-- We want the greeter to display the origin of the greet, so we use the nodeId -->
-        <argument ref="nodeId"/>
-    </bean>
-
-    <!-- The current Node -->
-    <bean id="node" factory-ref="clusterManager" factory-method="getNode"/>
-    <!-- The id of the current node -->
-    <bean id="nodeId" factory-ref="node" factory-method="getId"/>
-
-    <!-- OSGi Services  & References -->
-    <service ref="greeterImpl" interface="org.apache.karaf.cellar.samples.dosgi.greeter.api.Greeter">
-        <service-properties>
-            <!-- This property turn the Greeter service as a "cluster aware" service -->
-            <entry key="service.exported.interfaces" value="*"/>
-        </service-properties>
-    </service>
-
-    <reference id="clusterManager" interface="org.apache.karaf.cellar.core.ClusterManager"/>
-
-</blueprint>
diff --git a/samples/hazelcast-app/NOTICE b/samples/hazelcast-app/NOTICE
index addb35c..64cb235 100644
--- a/samples/hazelcast-app/NOTICE
+++ b/samples/hazelcast-app/NOTICE
@@ -1,5 +1,5 @@
 Apache Karaf Cellar

-Copyright 2011-2014 The Apache Software Foundation

+Copyright 2011-2015 The Apache Software Foundation

 

 I. Used Software

 

diff --git a/samples/hazelcast-app/pom.xml b/samples/hazelcast-app/pom.xml
index 39718cc..47d2ab1 100644
--- a/samples/hazelcast-app/pom.xml
+++ b/samples/hazelcast-app/pom.xml
@@ -42,7 +42,7 @@
 
         <dependency>
             <groupId>com.hazelcast</groupId>
-            <artifactId>hazelcast</artifactId>
+            <artifactId>hazelcast-all</artifactId>
         </dependency>
 
         <dependency>
@@ -59,7 +59,7 @@
                 <configuration>
                     <instructions>
                         <Export-Package>
-                            org.apache.karaf.cellar.samples.hazelcast*;version="${project.version}"
+                            org.apache.karaf.cellar.samples.hazelcast*
                         </Export-Package>
                         <Import-Package>
                             com.hazelcast.core,
diff --git a/samples/http-session-replication/NOTICE b/samples/http-session-replication/NOTICE
new file mode 100644
index 0000000..64cb235
--- /dev/null
+++ b/samples/http-session-replication/NOTICE
@@ -0,0 +1,39 @@
+Apache Karaf Cellar

+Copyright 2011-2015 The Apache Software Foundation

+

+I. Used Software

+

+This product includes software developed at

+The Apache Software Foundation (http://www.apache.org/).

+Licensed under the Apache License 2.0.

+

+This product uses software developed at

+The OSGi Alliance (http://www.osgi.org/).

+Licensed under the Apache License 2.0.

+

+This product uses software developed at

+Hazelcast (http://www.hazelcast.com/).

+Licensed under the Apache License 2.0.

+

+This product uses software developed at

+OPS4J (http://www.ops4j.org/).

+Licensed under the Apache License 2.0.

+

+This product uses software developed at

+FUSE Source (http://www.fusesource.org/).

+Licensed under the Apache License 2.0.

+

+This product uses software developed at

+JClouds (http://www.jclouds.org/).

+Licensed under the Apache License 2.0.

+

+This product uses software developed at

+SLF4J (http://www.slf4j.org/).

+Licensed under the MIT License.

+

+This product includes software from http://www.json.org.

+Copyright (c) 2002 JSON.org

+

+II. License Summary

+- Apache License 2.0

+- MIT License

diff --git a/samples/http-session-replication/pom.xml b/samples/http-session-replication/pom.xml
new file mode 100644
index 0000000..6c42940
--- /dev/null
+++ b/samples/http-session-replication/pom.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <!--
+
+        Licensed to the Apache Software Foundation (ASF) under one or more
+        contributor license agreements.  See the NOTICE file distributed with
+        this work for additional information regarding copyright ownership.
+        The ASF licenses this file to You under the Apache License, Version 2.0
+        (the "License"); you may not use this file except in compliance with
+        the License.  You may obtain a copy of the License at
+
+           http://www.apache.org/licenses/LICENSE-2.0
+
+        Unless required by applicable law or agreed to in writing, software
+        distributed under the License is distributed on an "AS IS" BASIS,
+        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+        See the License for the specific language governing permissions and
+        limitations under the License.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.karaf.cellar</groupId>
+        <artifactId>samples</artifactId>
+        <version>4.0.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <groupId>org.apache.karaf.cellar.samples</groupId>
+    <artifactId>http-session-replication</artifactId>
+    <name>Apache Karaf :: Cellar :: Samples :: HTTP Session Replication</name>
+    <packaging>bundle</packaging>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <configuration>
+                    <instructions>
+                        <_wab>src/main/webapp</_wab>
+                        <Web-ContextPath>cellar-session-sample</Web-ContextPath>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file
diff --git a/samples/http-session-replication/src/main/webapp/WEB-INF/web.xml b/samples/http-session-replication/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..0657f5d
--- /dev/null
+++ b/samples/http-session-replication/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+         version="3.0">
+
+    <filter>
+        <filter-name>hazelcast-filter</filter-name>
+        <filter-class>com.hazelcast.web.WebFilter</filter-class>
+        <!--
+            Name of the distributed map storing
+            your web session objects
+        -->
+        <init-param>
+            <param-name>map-name</param-name>
+            <param-value>my-sessions</param-value>
+        </init-param>
+        <!-- How is your load-balancer configured? stick-session means all requests of
+            a session is routed to the node where the session is first created. This is
+            excellent for performance. If sticky-session is set to false, when a session
+             is updated on a node, entry for this session on all other nodes is invalidated.
+             You have to know how your load-balancer is configured before setting this
+             parameter. Default is true. -->
+        <init-param>
+            <param-name>sticky-session</param-name>
+            <param-value>false</param-value>
+        </init-param>
+        <!--
+            Are you debugging? Default is false.
+        -->
+        <init-param>
+            <param-name>debug</param-name>
+            <param-value>false</param-value>
+        </init-param>
+    </filter>
+    <filter-mapping>
+        <filter-name>hazelcast-filter</filter-name>
+        <url-pattern>/*</url-pattern>
+        <dispatcher>FORWARD</dispatcher>
+        <dispatcher>INCLUDE</dispatcher>
+        <dispatcher>REQUEST</dispatcher>
+    </filter-mapping>
+    <listener>
+        <listener-class>com.hazelcast.web.SessionListener</listener-class>
+    </listener>
+
+</web-app>
diff --git a/samples/pom.xml b/samples/pom.xml
index 84aefe3..0f32356 100644
--- a/samples/pom.xml
+++ b/samples/pom.xml
@@ -35,6 +35,7 @@
 
     <modules>
         <module>dosgi-greeter</module>
+        <module>http-session-replication</module>
         <module>hazelcast-app</module>
         <module>camel-hazelcast-app</module>
     </modules>
diff --git a/shell/NOTICE b/shell/NOTICE
index addb35c..64cb235 100644
--- a/shell/NOTICE
+++ b/shell/NOTICE
@@ -1,5 +1,5 @@
 Apache Karaf Cellar

-Copyright 2011-2014 The Apache Software Foundation

+Copyright 2011-2015 The Apache Software Foundation

 

 I. Used Software

 

diff --git a/shell/pom.xml b/shell/pom.xml
index ce1d023..0f5b96c 100644
--- a/shell/pom.xml
+++ b/shell/pom.xml
@@ -46,6 +46,14 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.apache.karaf.cellar</groupId>
             <artifactId>org.apache.karaf.cellar.core</artifactId>
         </dependency>
@@ -61,10 +69,6 @@
             <groupId>org.apache.karaf.cellar</groupId>
             <artifactId>org.apache.karaf.cellar.utils</artifactId>
         </dependency>
-        <dependency>
-            <groupId>org.apache.karaf.shell</groupId>
-            <artifactId>org.apache.karaf.shell.table</artifactId>
-        </dependency>
 
         <!-- Logging Dependencies -->
         <dependency>
@@ -77,25 +81,21 @@
     <build>
         <plugins>
             <plugin>
+                <groupId>org.apache.karaf.tooling</groupId>
+                <artifactId>karaf-services-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
                 <configuration>
                     <instructions>
                         <Export-Package>
-                            org.apache.karaf.cellar.shell*;version="${project.version}"
+                            org.apache.karaf.cellar.shell*
                         </Export-Package>
                         <Import-Package>
-                            org.apache.karaf.cellar.core*;version="${project.version}",
-                            org.apache.karaf.cellar*;version="${project.version}",
-                            org.apache.felix.service.command,
-                            org.apache.felix.gogo.commands,
-                            org.apache.karaf.shell.console;version="[3,5)",
-                            org.apache.karaf.shell.console.commands;version="[3,5)",
-                            org.apache.karaf.shell.console.completer;version="[3,5)",
-                            org.apache.karaf.shell.commands;version="[3,5)",
-                            org.osgi*
+                            org.slf4j;version="[1.6,2)";resolution:=optional,
+                            *
                         </Import-Package>
-                        <DynamicImport-Package>javax.*,org.w3c.*,org.xml.*,*</DynamicImport-Package>
                     </instructions>
                 </configuration>
             </plugin>
diff --git a/shell/src/main/java/org/apache/karaf/cellar/shell/ClusterCommandSupport.java b/shell/src/main/java/org/apache/karaf/cellar/shell/ClusterCommandSupport.java
index 3ce20b4..f814503 100644
--- a/shell/src/main/java/org/apache/karaf/cellar/shell/ClusterCommandSupport.java
+++ b/shell/src/main/java/org/apache/karaf/cellar/shell/ClusterCommandSupport.java
@@ -15,12 +15,14 @@
 
 import org.apache.karaf.cellar.core.command.ExecutionContext;
 import org.apache.karaf.cellar.core.shell.CellarCommandSupport;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
 
 /**
  * Abstract cluster shell command.
  */
 public abstract class ClusterCommandSupport extends CellarCommandSupport {
 
+    @Reference
     protected ExecutionContext executionContext;
 
     public ExecutionContext getExecutionContext() {
diff --git a/shell/src/main/java/org/apache/karaf/cellar/shell/NodePingCommand.java b/shell/src/main/java/org/apache/karaf/cellar/shell/NodePingCommand.java
index f26b52f..ebd3875 100644
--- a/shell/src/main/java/org/apache/karaf/cellar/shell/NodePingCommand.java
+++ b/shell/src/main/java/org/apache/karaf/cellar/shell/NodePingCommand.java
@@ -14,19 +14,24 @@
 package org.apache.karaf.cellar.shell;
 
 import org.apache.karaf.cellar.core.Node;
+import org.apache.karaf.cellar.core.shell.completer.AllNodeCompleter;
 import org.apache.karaf.cellar.utils.ping.Ping;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.Arrays;
 import java.util.HashSet;
 
 @Command(scope = "cluster", name = "node-ping", description = "Ping a cluster node")
+@Service
 public class NodePingCommand extends ClusterCommandSupport {
 
     private static Long TIMEOUT = 5000L;
 
     @Argument(index = 0, name = "node", description = "The ID of the node to ping", required = true, multiValued = false)
+    @Completion(AllNodeCompleter.class)
     String nodeId;
 
     @Argument(index = 1, name = "iterations", description = "The number of iterations to perform", required = false, multiValued = false)
diff --git a/shell/src/main/java/org/apache/karaf/cellar/shell/NodesListCommand.java b/shell/src/main/java/org/apache/karaf/cellar/shell/NodesListCommand.java
index d8559f8..44ee352 100644
--- a/shell/src/main/java/org/apache/karaf/cellar/shell/NodesListCommand.java
+++ b/shell/src/main/java/org/apache/karaf/cellar/shell/NodesListCommand.java
@@ -14,15 +14,16 @@
 package org.apache.karaf.cellar.shell;
 
 import org.apache.karaf.cellar.core.Node;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.table.ShellTable;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.table.ShellTable;
 
 import java.util.Set;
 
 @Command(scope = "cluster", name = "node-list", description = "List the nodes in the cluster")
+@Service
 public class NodesListCommand extends ClusterCommandSupport {
 
-
     @Override
     protected Object doExecute() throws Exception {
         Set<Node> nodes = clusterManager.listNodes();
diff --git a/shell/src/main/java/org/apache/karaf/cellar/shell/SyncCommand.java b/shell/src/main/java/org/apache/karaf/cellar/shell/SyncCommand.java
index d6cd94c..aded14d 100644
--- a/shell/src/main/java/org/apache/karaf/cellar/shell/SyncCommand.java
+++ b/shell/src/main/java/org/apache/karaf/cellar/shell/SyncCommand.java
@@ -16,9 +16,12 @@
 import org.apache.karaf.cellar.core.Configurations;
 import org.apache.karaf.cellar.core.Group;
 import org.apache.karaf.cellar.core.Synchronizer;
-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.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
 import org.osgi.service.cm.Configuration;
 import org.osgi.service.cm.ConfigurationAdmin;
@@ -29,6 +32,7 @@
 import java.util.Set;
 
 @Command(scope = "cluster", name = "sync", description = "Manipulate the synchronizers")
+@Service
 public class SyncCommand extends ClusterCommandSupport {
 
     @Option(name = "-g", aliases = { "--group" }, description = "The cluster group name", required = false, multiValued = false)
@@ -49,8 +53,12 @@
     @Argument(name = "policy", description = "The definition of the sync policy for the given cluster resource", required = false, multiValued = false)
     private String policy;
 
+    @Reference
     private ConfigurationAdmin configurationAdmin;
 
+    @Reference
+    private BundleContext bundleContext;
+
     @Override
     protected Object doExecute() throws Exception {
         boolean allResources = false;
diff --git a/shell/src/main/java/org/apache/karaf/cellar/shell/consumer/ConsumerStartCommand.java b/shell/src/main/java/org/apache/karaf/cellar/shell/consumer/ConsumerStartCommand.java
index 666613e..6a719e1 100644
--- a/shell/src/main/java/org/apache/karaf/cellar/shell/consumer/ConsumerStartCommand.java
+++ b/shell/src/main/java/org/apache/karaf/cellar/shell/consumer/ConsumerStartCommand.java
@@ -14,15 +14,20 @@
 package org.apache.karaf.cellar.shell.consumer;
 
 import org.apache.karaf.cellar.core.control.SwitchStatus;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.cellar.core.shell.completer.AllNodeCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.List;
 
 @Command(scope = "cluster", name = "consumer-start", description = "Start a cluster event consumer")
+@Service
 public class ConsumerStartCommand extends ConsumerSupport {
 
     @Argument(index = 0, name = "node", description = "The node(s) ID", required = false, multiValued = true)
+    @Completion(AllNodeCompleter.class)
     List<String> nodes;
 
     @Override
diff --git a/shell/src/main/java/org/apache/karaf/cellar/shell/consumer/ConsumerStatusCommand.java b/shell/src/main/java/org/apache/karaf/cellar/shell/consumer/ConsumerStatusCommand.java
index a3cd144..b436870 100644
--- a/shell/src/main/java/org/apache/karaf/cellar/shell/consumer/ConsumerStatusCommand.java
+++ b/shell/src/main/java/org/apache/karaf/cellar/shell/consumer/ConsumerStatusCommand.java
@@ -13,15 +13,20 @@
  */
 package org.apache.karaf.cellar.shell.consumer;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.cellar.core.shell.completer.AllNodeCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.List;
 
 @Command(scope = "cluster", name = "consumer-status", description = "Status of a cluster event consumer")
+@Service
 public class ConsumerStatusCommand extends ConsumerSupport {
 
     @Argument(index = 0, name = "node", description = "The node(s) ID", required = false, multiValued = true)
+    @Completion(AllNodeCompleter.class)
     List<String> nodes;
 
     @Override
diff --git a/shell/src/main/java/org/apache/karaf/cellar/shell/consumer/ConsumerStopCommand.java b/shell/src/main/java/org/apache/karaf/cellar/shell/consumer/ConsumerStopCommand.java
index 92ef7c0..992e5e0 100644
--- a/shell/src/main/java/org/apache/karaf/cellar/shell/consumer/ConsumerStopCommand.java
+++ b/shell/src/main/java/org/apache/karaf/cellar/shell/consumer/ConsumerStopCommand.java
@@ -14,15 +14,20 @@
 package org.apache.karaf.cellar.shell.consumer;
 
 import org.apache.karaf.cellar.core.control.SwitchStatus;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.cellar.core.shell.completer.AllNodeCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.List;
 
 @Command(scope = "cluster", name = "consumer-stop", description = "Stop a cluster event consumer")
+@Service
 public class ConsumerStopCommand extends ConsumerSupport {
 
     @Argument(index = 0, name = "node", description = "The node(s) ID.", required = false, multiValued = true)
+    @Completion(AllNodeCompleter.class)
     List<String> nodes;
 
     @Override
diff --git a/shell/src/main/java/org/apache/karaf/cellar/shell/consumer/ConsumerSupport.java b/shell/src/main/java/org/apache/karaf/cellar/shell/consumer/ConsumerSupport.java
index daa80b0..4863491 100644
--- a/shell/src/main/java/org/apache/karaf/cellar/shell/consumer/ConsumerSupport.java
+++ b/shell/src/main/java/org/apache/karaf/cellar/shell/consumer/ConsumerSupport.java
@@ -18,7 +18,7 @@
 import org.apache.karaf.cellar.core.control.ConsumerSwitchResult;
 import org.apache.karaf.cellar.core.control.SwitchStatus;
 import org.apache.karaf.cellar.shell.ClusterCommandSupport;
-import org.apache.karaf.shell.table.ShellTable;
+import org.apache.karaf.shell.support.table.ShellTable;
 
 import java.util.HashSet;
 import java.util.List;
diff --git a/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupCreateCommand.java b/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupCreateCommand.java
index 1f50a5a..d6b5089 100644
--- a/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupCreateCommand.java
+++ b/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupCreateCommand.java
@@ -14,10 +14,12 @@
 package org.apache.karaf.cellar.shell.group;
 
 import org.apache.karaf.cellar.core.Group;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 @Command(scope = "cluster", name = "group-create", description = "Create a cluster group")
+@Service
 public class GroupCreateCommand extends GroupSupport {
 
     @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
diff --git a/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupDeleteCommand.java b/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupDeleteCommand.java
index b587969..cb3f756 100644
--- a/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupDeleteCommand.java
+++ b/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupDeleteCommand.java
@@ -14,13 +14,18 @@
 package org.apache.karaf.cellar.shell.group;
 
 import org.apache.karaf.cellar.core.Group;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.cellar.core.shell.completer.AllGroupsCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 @Command(scope = "cluster", name = "group-delete", description = "Delete a cluster group")
+@Service
 public class GroupDeleteCommand extends GroupSupport {
 
     @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
     String groupName;
 
     @Override
diff --git a/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupJoinCommand.java b/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupJoinCommand.java
index c04dfff..f1dab58 100644
--- a/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupJoinCommand.java
+++ b/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupJoinCommand.java
@@ -15,18 +15,25 @@
 
 import org.apache.karaf.cellar.core.Group;
 import org.apache.karaf.cellar.core.control.ManageGroupAction;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.cellar.core.shell.completer.AllGroupsCompleter;
+import org.apache.karaf.cellar.core.shell.completer.AllNodeCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.List;
 
 @Command(scope = "cluster", name = "group-join", description = "Join node(s) to a cluster group")
+@Service
 public class GroupJoinCommand extends GroupSupport {
 
     @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
     String groupName;
 
-    @Argument(index = 1, name = "node", description = "The node(s) ID", required = true, multiValued = true)
+    @Argument(index = 1, name = "node", description = "The node(s) ID", required = false, multiValued = true)
+    @Completion(AllNodeCompleter.class)
     List<String> nodes;
 
     @Override
diff --git a/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupListCommand.java b/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupListCommand.java
index 6feb809..d346fb6 100644
--- a/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupListCommand.java
+++ b/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupListCommand.java
@@ -14,15 +14,20 @@
 package org.apache.karaf.cellar.shell.group;
 
 import org.apache.karaf.cellar.core.control.ManageGroupAction;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.cellar.core.shell.completer.AllNodeCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.List;
 
 @Command(scope = "cluster", name = "group-list", description = "List the cluster groups")
+@Service
 public class GroupListCommand extends GroupSupport {
 
     @Argument(index = 0, name = "node", description = "The node(s) ID", required = false, multiValued = true)
+    @Completion(AllNodeCompleter.class)
     List<String> nodes;
 
     @Override
diff --git a/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupPickCommand.java b/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupPickCommand.java
index 186cad3..7dff28e 100644
--- a/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupPickCommand.java
+++ b/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupPickCommand.java
@@ -16,8 +16,12 @@
 import org.apache.karaf.cellar.core.Group;
 import org.apache.karaf.cellar.core.Node;
 import org.apache.karaf.cellar.core.control.ManageGroupAction;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.cellar.core.shell.completer.AllGroupsCompleter;
+import org.apache.karaf.cellar.core.shell.completer.LocalGroupsCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.ArrayList;
 import java.util.LinkedList;
@@ -25,12 +29,15 @@
 import java.util.Set;
 
 @Command(scope = "cluster", name = "group-pick", description = "Picks a number of nodes from one cluster group and moves them into another")
+@Service
 public class GroupPickCommand extends GroupSupport {
 
     @Argument(index = 0, name = "sourceGroupName", description = "The source cluster group name", required = true, multiValued = false)
+    @Completion(LocalGroupsCompleter.class)
     String sourceGroupName;
 
     @Argument(index = 1, name = "targetGroupName", description = "The destination cluster group name", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
     String targetGroupName;
 
     @Argument(index = 2, name = "count", description = "The number of nodes to transfer", required = false, multiValued = false)
diff --git a/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupQuitCommand.java b/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupQuitCommand.java
index 2272624..4bd3cb3 100644
--- a/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupQuitCommand.java
+++ b/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupQuitCommand.java
@@ -15,18 +15,25 @@
 
 import org.apache.karaf.cellar.core.Group;
 import org.apache.karaf.cellar.core.control.ManageGroupAction;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.cellar.core.shell.completer.AllGroupsCompleter;
+import org.apache.karaf.cellar.core.shell.completer.AllNodeCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.List;
 
 @Command(scope = "cluster", name = "group-quit", description = "Quit node(s) from a cluster group")
+@Service
 public class GroupQuitCommand extends GroupSupport {
 
     @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
     String groupName;
 
     @Argument(index = 1, name = "node", description = "The node(s) ID", required = false, multiValued = true)
+    @Completion(AllNodeCompleter.class)
     List<String> nodes;
 
     @Override
diff --git a/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupSetCommand.java b/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupSetCommand.java
index 1dbc9d0..e7362b8 100644
--- a/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupSetCommand.java
+++ b/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupSetCommand.java
@@ -15,18 +15,25 @@
 
 import org.apache.karaf.cellar.core.Group;
 import org.apache.karaf.cellar.core.control.ManageGroupAction;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.cellar.core.shell.completer.AllGroupsCompleter;
+import org.apache.karaf.cellar.core.shell.completer.AllNodeCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.List;
 
 @Command(scope = "cluster", name = "group-set", description = "Set the target nodes to a cluster group")
+@Service
 public class GroupSetCommand extends GroupSupport {
 
     @Argument(index = 0, name = "group", description = "The cluster group name", required = true, multiValued = false)
+    @Completion(AllGroupsCompleter.class)
     String groupName;
 
     @Argument(index = 1, name = "node", description = "The node(s) ID", required = false, multiValued = true)
+    @Completion(AllNodeCompleter.class)
     List<String> nodes;
 
     @Override
diff --git a/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupSupport.java b/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupSupport.java
index 3f1605a..3bd4d49 100644
--- a/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupSupport.java
+++ b/shell/src/main/java/org/apache/karaf/cellar/shell/group/GroupSupport.java
@@ -19,7 +19,7 @@
 import org.apache.karaf.cellar.core.control.ManageGroupCommand;
 import org.apache.karaf.cellar.core.control.ManageGroupResult;
 import org.apache.karaf.cellar.shell.ClusterCommandSupport;
-import org.apache.karaf.shell.table.ShellTable;
+import org.apache.karaf.shell.support.table.ShellTable;
 
 import java.util.Collection;
 import java.util.HashSet;
diff --git a/shell/src/main/java/org/apache/karaf/cellar/shell/handler/HandlersStartCommand.java b/shell/src/main/java/org/apache/karaf/cellar/shell/handler/HandlersStartCommand.java
index 3603418..6618402 100644
--- a/shell/src/main/java/org/apache/karaf/cellar/shell/handler/HandlersStartCommand.java
+++ b/shell/src/main/java/org/apache/karaf/cellar/shell/handler/HandlersStartCommand.java
@@ -13,18 +13,23 @@
  */
 package org.apache.karaf.cellar.shell.handler;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.cellar.core.shell.completer.AllNodeCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.List;
 
 @Command(scope = "cluster", name = "handler-start", description = "Start a cluster event handler")
+@Service
 public class HandlersStartCommand extends HandlersSupport {
 
     @Argument(index = 0, name = "handler", description = "The cluster event handler ID", required = true, multiValued = false)
     String handler;
 
     @Argument(index = 1, name = "node", description = "The node(s) ID", required = false, multiValued = true)
+    @Completion(AllNodeCompleter.class)
     List<String> nodes;
 
     @Override
diff --git a/shell/src/main/java/org/apache/karaf/cellar/shell/handler/HandlersStatusCommand.java b/shell/src/main/java/org/apache/karaf/cellar/shell/handler/HandlersStatusCommand.java
index d08c7f0..7717df6 100644
--- a/shell/src/main/java/org/apache/karaf/cellar/shell/handler/HandlersStatusCommand.java
+++ b/shell/src/main/java/org/apache/karaf/cellar/shell/handler/HandlersStatusCommand.java
@@ -13,18 +13,23 @@
  */
 package org.apache.karaf.cellar.shell.handler;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.cellar.core.shell.completer.AllNodeCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.List;
 
 @Command(scope = "cluster", name = "handler-status", description = "Status of a cluster event handler")
+@Service
 public class HandlersStatusCommand extends HandlersSupport {
 
     @Argument(index = 0, name = "handler", description = "The cluster event handler", required = false, multiValued = false)
     String handler;
 
     @Argument(index = 1, name = "node", description = "The node(s) ID", required = false, multiValued = true)
+    @Completion(AllNodeCompleter.class)
     List<String> nodes;
 
     @Override
diff --git a/shell/src/main/java/org/apache/karaf/cellar/shell/handler/HandlersStopCommand.java b/shell/src/main/java/org/apache/karaf/cellar/shell/handler/HandlersStopCommand.java
index d77e294..68b13f6 100644
--- a/shell/src/main/java/org/apache/karaf/cellar/shell/handler/HandlersStopCommand.java
+++ b/shell/src/main/java/org/apache/karaf/cellar/shell/handler/HandlersStopCommand.java
@@ -13,18 +13,23 @@
  */
 package org.apache.karaf.cellar.shell.handler;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.cellar.core.shell.completer.AllNodeCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.List;
 
 @Command(scope = "cluster", name = "handler-stop", description = "Stop a cluster event handler")
+@Service
 public class HandlersStopCommand extends HandlersSupport {
 
     @Argument(index = 0, name = "handler", description = "The event handler", required = true, multiValued = false)
     String handler;
 
     @Argument(index = 1, name = "node", description = "The node(s) ID", required = false, multiValued = true)
+    @Completion(AllNodeCompleter.class)
     List<String> nodes;
 
     @Override
diff --git a/shell/src/main/java/org/apache/karaf/cellar/shell/handler/HandlersSupport.java b/shell/src/main/java/org/apache/karaf/cellar/shell/handler/HandlersSupport.java
index e848dbd..b8dcd84 100644
--- a/shell/src/main/java/org/apache/karaf/cellar/shell/handler/HandlersSupport.java
+++ b/shell/src/main/java/org/apache/karaf/cellar/shell/handler/HandlersSupport.java
@@ -17,7 +17,7 @@
 import org.apache.karaf.cellar.core.control.ManageHandlersCommand;
 import org.apache.karaf.cellar.core.control.ManageHandlersResult;
 import org.apache.karaf.cellar.shell.ClusterCommandSupport;
-import org.apache.karaf.shell.table.ShellTable;
+import org.apache.karaf.shell.support.table.ShellTable;
 
 import java.util.HashSet;
 import java.util.List;
diff --git a/shell/src/main/java/org/apache/karaf/cellar/shell/producer/ProducerStartCommand.java b/shell/src/main/java/org/apache/karaf/cellar/shell/producer/ProducerStartCommand.java
index 5c5f520..6ea606c 100644
--- a/shell/src/main/java/org/apache/karaf/cellar/shell/producer/ProducerStartCommand.java
+++ b/shell/src/main/java/org/apache/karaf/cellar/shell/producer/ProducerStartCommand.java
@@ -14,15 +14,20 @@
 package org.apache.karaf.cellar.shell.producer;
 
 import org.apache.karaf.cellar.core.control.SwitchStatus;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.cellar.core.shell.completer.AllNodeCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.List;
 
 @Command(scope = "cluster", name = "producer-start", description = "Start a cluster event producer")
+@Service
 public class ProducerStartCommand extends ProducerSupport {
 
     @Argument(index = 0, name = "node", description = "The node(s) ID", required = false, multiValued = true)
+    @Completion(AllNodeCompleter.class)
     List<String> nodes;
 
     @Override
diff --git a/shell/src/main/java/org/apache/karaf/cellar/shell/producer/ProducerStatusCommand.java b/shell/src/main/java/org/apache/karaf/cellar/shell/producer/ProducerStatusCommand.java
index 1dd2310..eb87744 100644
--- a/shell/src/main/java/org/apache/karaf/cellar/shell/producer/ProducerStatusCommand.java
+++ b/shell/src/main/java/org/apache/karaf/cellar/shell/producer/ProducerStatusCommand.java
@@ -13,15 +13,20 @@
  */
 package org.apache.karaf.cellar.shell.producer;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.cellar.core.shell.completer.AllNodeCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.List;
 
 @Command(scope = "cluster", name = "producer-status", description = "Status of a cluster event producer")
+@Service
 public class ProducerStatusCommand extends ProducerSupport {
 
     @Argument(index = 0, name = "node", description = "The node(s) ID", required = false, multiValued = true)
+    @Completion(AllNodeCompleter.class)
     List<String> nodes;
 
     @Override
diff --git a/shell/src/main/java/org/apache/karaf/cellar/shell/producer/ProducerStopCommand.java b/shell/src/main/java/org/apache/karaf/cellar/shell/producer/ProducerStopCommand.java
index e79ad5e..bb1291e 100644
--- a/shell/src/main/java/org/apache/karaf/cellar/shell/producer/ProducerStopCommand.java
+++ b/shell/src/main/java/org/apache/karaf/cellar/shell/producer/ProducerStopCommand.java
@@ -14,15 +14,20 @@
 package org.apache.karaf.cellar.shell.producer;
 
 import org.apache.karaf.cellar.core.control.SwitchStatus;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.cellar.core.shell.completer.AllNodeCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.util.List;
 
 @Command(scope = "cluster", name = "producer-stop", description = "Stop a cluster event producer")
+@Service
 public class ProducerStopCommand extends ProducerSupport {
 
     @Argument(index = 0, name = "node", description = "The node(s) ID", required = false, multiValued = true)
+    @Completion(AllNodeCompleter.class)
     List<String> nodes;
 
     @Override
diff --git a/shell/src/main/java/org/apache/karaf/cellar/shell/producer/ProducerSupport.java b/shell/src/main/java/org/apache/karaf/cellar/shell/producer/ProducerSupport.java
index a37bf96..89d2a30 100644
--- a/shell/src/main/java/org/apache/karaf/cellar/shell/producer/ProducerSupport.java
+++ b/shell/src/main/java/org/apache/karaf/cellar/shell/producer/ProducerSupport.java
@@ -18,7 +18,7 @@
 import org.apache.karaf.cellar.core.control.ProducerSwitchResult;
 import org.apache.karaf.cellar.core.control.SwitchStatus;
 import org.apache.karaf.cellar.shell.ClusterCommandSupport;
-import org.apache.karaf.shell.table.ShellTable;
+import org.apache.karaf.shell.support.table.ShellTable;
 
 import java.util.HashSet;
 import java.util.List;
diff --git a/shell/src/main/resources/OSGI-INF/blueprint/shell-cluster.xml b/shell/src/main/resources/OSGI-INF/blueprint/shell-cluster.xml
deleted file mode 100644
index b5b2991..0000000
--- a/shell/src/main/resources/OSGI-INF/blueprint/shell-cluster.xml
+++ /dev/null
@@ -1,208 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
-   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.
--->
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" default-activation="lazy">
-
-    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
-        <command>
-            <action class="org.apache.karaf.cellar.shell.NodesListCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-            </action>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.shell.NodePingCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="executionContext" ref="executionContext"/>
-            </action>
-            <completers>
-                <ref component-id="allNodesCompleter"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.shell.SyncCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-                <property name="configurationAdmin" ref="configurationAdmin"/>
-            </action>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.shell.consumer.ConsumerStartCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="executionContext" ref="executionContext"/>
-            </action>
-            <completers>
-                <ref component-id="allNodesCompleter"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.shell.consumer.ConsumerStopCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="executionContext" ref="executionContext"/>
-            </action>
-            <completers>
-                <ref component-id="allNodesCompleter"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.shell.consumer.ConsumerStatusCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="executionContext" ref="executionContext"/>
-            </action>
-            <completers>
-                <ref component-id="allNodesCompleter"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.shell.producer.ProducerStartCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="executionContext" ref="executionContext"/>
-            </action>
-            <completers>
-                <ref component-id="allNodesCompleter"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.shell.producer.ProducerStopCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="executionContext" ref="executionContext"/>
-            </action>
-            <completers>
-                <ref component-id="allNodesCompleter"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.shell.producer.ProducerStatusCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="executionContext" ref="executionContext"/>
-            </action>
-            <completers>
-                <ref component-id="allNodesCompleter"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.shell.handler.HandlersStartCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="executionContext" ref="executionContext"/>
-            </action>
-            <completers>
-                <ref component-id="allNodesCompleter"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.shell.handler.HandlersStopCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="executionContext" ref="executionContext"/>
-            </action>
-            <completers>
-                <ref component-id="allNodesCompleter"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.shell.handler.HandlersStatusCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="executionContext" ref="executionContext"/>
-            </action>
-            <completers>
-                <ref component-id="allNodesCompleter"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.shell.group.GroupJoinCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-                <property name="executionContext" ref="executionContext"/>
-            </action>
-            <completers>
-                <ref component-id="allGroupsCompleter"/>
-                <ref component-id="allNodesCompleter"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.shell.group.GroupQuitCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-                <property name="executionContext" ref="executionContext"/>
-            </action>
-            <completers>
-                <ref component-id="localGroupCompleter"/>
-                <ref component-id="allNodesCompleter"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.shell.group.GroupSetCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-                <property name="executionContext" ref="executionContext"/>
-            </action>
-            <completers>
-                <ref component-id="allGroupsCompleter"/>
-                <ref component-id="allNodesCompleter"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.shell.group.GroupPickCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-                <property name="executionContext" ref="executionContext"/>
-            </action>
-            <completers>
-                <ref component-id="allGroupsCompleter"/>
-            </completers>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.shell.group.GroupListCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-                <property name="executionContext" ref="executionContext"/>
-            </action>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.shell.group.GroupCreateCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-                <property name="executionContext" ref="executionContext"/>
-            </action>
-        </command>
-        <command>
-            <action class="org.apache.karaf.cellar.shell.group.GroupDeleteCommand">
-                <property name="clusterManager" ref="clusterManager"/>
-                <property name="groupManager" ref="groupManager"/>
-                <property name="executionContext" ref="executionContext"/>
-            </action>
-            <completers>
-                <ref component-id="allGroupsCompleter"/>
-            </completers>
-        </command>
-    </command-bundle>
-
-    <reference id="clusterManager" interface="org.apache.karaf.cellar.core.ClusterManager" availability="optional"/>
-    <reference id="groupManager" interface="org.apache.karaf.cellar.core.GroupManager" availability="optional"/>
-    <reference id="executionContext" interface="org.apache.karaf.cellar.core.command.ExecutionContext" availability="optional"/>
-    <reference id="configurationAdmin" interface="org.osgi.service.cm.ConfigurationAdmin"/>
-
-    <bean id="allNodesCompleter" class="org.apache.karaf.cellar.core.shell.completer.AllNodeCompleter">
-        <property name="clusterManager" ref="clusterManager"/>
-    </bean>
-    <bean id="allGroupsCompleter" class="org.apache.karaf.cellar.core.shell.completer.AllGroupsCompleter">
-        <property name="groupManager" ref="groupManager"/>
-    </bean>
-    <bean id="localGroupCompleter" class="org.apache.karaf.cellar.core.shell.completer.LocalGroupsCompleter">
-        <property name="groupManager" ref="groupManager"/>
-    </bean>
-    <bean id="otherGroupCompleter" class="org.apache.karaf.cellar.core.shell.completer.OtherGroupsCompleter">
-        <property name="groupManager" ref="groupManager"/>
-    </bean>
-
-</blueprint>
diff --git a/utils/NOTICE b/utils/NOTICE
index addb35c..64cb235 100644
--- a/utils/NOTICE
+++ b/utils/NOTICE
@@ -1,5 +1,5 @@
 Apache Karaf Cellar

-Copyright 2011-2014 The Apache Software Foundation

+Copyright 2011-2015 The Apache Software Foundation

 

 I. Used Software

 

diff --git a/utils/pom.xml b/utils/pom.xml
index fc37aff..0ecbd6c 100644
--- a/utils/pom.xml
+++ b/utils/pom.xml
@@ -38,12 +38,22 @@
         <dependency>
             <groupId>org.apache.karaf.cellar</groupId>
             <artifactId>org.apache.karaf.cellar.core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.compendium</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.util</artifactId>
+        </dependency>
 
         <!-- Logging Dependencies -->
         <dependency>
@@ -56,18 +66,24 @@
     <build>
         <plugins>
             <plugin>
+                <groupId>org.apache.karaf.tooling</groupId>
+                <artifactId>karaf-services-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
                 <configuration>
                     <instructions>
                         <Export-Package>
-                            org.apache.karaf.cellar.utils*;version="${project.version}"
+                            org.apache.karaf.cellar.utils*
                         </Export-Package>
                         <Import-Package>
-                            org.apache.karaf.cellar.core*;version="${project.version}",
-                            org.osgi*
+                            org.slf4j;version="[1.6,2)";resolution:=optional,
+                            *
                         </Import-Package>
-                        <DynamicImport-Package>javax.*,org.w3c.*,org.xml.*,*</DynamicImport-Package>
+                        <Private-Package>
+                            org.apache.karaf.cellar.utils.ping.internal
+                        </Private-Package>
                     </instructions>
                 </configuration>
             </plugin>
diff --git a/utils/src/main/java/org/apache/karaf/cellar/utils/ping/internal/osgi/Activator.java b/utils/src/main/java/org/apache/karaf/cellar/utils/ping/internal/osgi/Activator.java
new file mode 100644
index 0000000..c700022
--- /dev/null
+++ b/utils/src/main/java/org/apache/karaf/cellar/utils/ping/internal/osgi/Activator.java
@@ -0,0 +1,68 @@
+/*
+ * 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.utils.ping.internal.osgi;
+
+import org.apache.karaf.cellar.core.ClusterManager;
+import org.apache.karaf.cellar.core.command.CommandStore;
+import org.apache.karaf.cellar.core.event.EventHandler;
+import org.apache.karaf.cellar.core.event.EventProducer;
+import org.apache.karaf.cellar.utils.ping.PingHandler;
+import org.apache.karaf.cellar.utils.ping.PongHandler;
+import org.apache.karaf.util.tracker.BaseActivator;
+import org.apache.karaf.util.tracker.annotation.ProvideService;
+import org.apache.karaf.util.tracker.annotation.RequireService;
+import org.apache.karaf.util.tracker.annotation.Services;
+import org.osgi.service.cm.ConfigurationAdmin;
+
+@Services(
+        requires = { @RequireService(ClusterManager.class), @RequireService(CommandStore.class), @RequireService(EventProducer.class), @RequireService(ConfigurationAdmin.class)},
+        provides = { @ProvideService(EventHandler.class)}
+)
+public class Activator extends BaseActivator {
+
+    @Override
+    public void doStart() throws Exception {
+        // retrieving services
+        ConfigurationAdmin configurationAdmin = getTrackedService(ConfigurationAdmin.class);
+        if (configurationAdmin == null)
+            return;
+        ClusterManager clusterManager = getTrackedService(ClusterManager.class);
+        if (clusterManager == null)
+            return;
+        EventProducer eventProducer = getTrackedService(EventProducer.class);
+        if (eventProducer == null)
+            return;
+        CommandStore commandStore = getTrackedService(CommandStore.class);
+        if (commandStore == null)
+            return;
+
+        // registering ping event handler
+        PingHandler pingHandler = new PingHandler();
+        pingHandler.setClusterManager(clusterManager);
+        pingHandler.setConfigurationAdmin(configurationAdmin);
+        pingHandler.setProducer(eventProducer);
+        register(EventHandler.class, pingHandler);
+
+        // registering pong event handler
+        PongHandler pongHandler = new PongHandler();
+        pongHandler.setCommandStore(commandStore);
+        register(EventHandler.class, pongHandler);
+    }
+
+    @Override
+    public void doStop() {
+        super.doStop();
+    }
+
+}
diff --git a/utils/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/utils/src/main/resources/OSGI-INF/blueprint/blueprint.xml
deleted file mode 100644
index 85cfb16..0000000
--- a/utils/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
-   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.
--->
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
-
-    <!-- Handler for the cluster ping event -->
-    <bean id="pingHandler" class="org.apache.karaf.cellar.utils.ping.PingHandler">
-        <property name="producer" ref="producer"/>
-    </bean>
-    <service ref="pingHandler" interface="org.apache.karaf.cellar.core.event.EventHandler"/>
-
-    <!-- Handler for the cluster pong event -->
-    <bean id="pongHandler" class="org.apache.karaf.cellar.utils.ping.PongHandler">
-        <property name="commandStore" ref="commandStore"/>
-    </bean>
-    <service ref="pongHandler" interface="org.apache.karaf.cellar.core.event.EventHandler"/>
-
-    <reference id="clusterManager" interface="org.apache.karaf.cellar.core.ClusterManager" availability="optional"/>
-    <reference id="commandStore" interface="org.apache.karaf.cellar.core.command.CommandStore" availability="optional"/>
-    <reference id="producer" interface="org.apache.karaf.cellar.core.event.EventProducer" filter="(!(type = group))" availability="optional"/>
-    <reference id="configurationAdmin" interface="org.osgi.service.cm.ConfigurationAdmin"/>
-
-</blueprint>
diff --git a/webconsole/NOTICE b/webconsole/NOTICE
index addb35c..64cb235 100644
--- a/webconsole/NOTICE
+++ b/webconsole/NOTICE
@@ -1,5 +1,5 @@
 Apache Karaf Cellar

-Copyright 2011-2014 The Apache Software Foundation

+Copyright 2011-2015 The Apache Software Foundation

 

 I. Used Software

 

diff --git a/webconsole/pom.xml b/webconsole/pom.xml
index 3268e59..02a229d 100644
--- a/webconsole/pom.xml
+++ b/webconsole/pom.xml
@@ -40,6 +40,10 @@
         </dependency>
 
         <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.util</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.core</artifactId>
             <scope>provided</scope>
@@ -96,22 +100,29 @@
         </resources>
         <plugins>
             <plugin>
+                <groupId>org.apache.karaf.tooling</groupId>
+                <artifactId>karaf-services-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
                 <configuration>
                     <instructions>
                         <Export-Package>
-                            org.apache.karaf.cellar.webconsole*;version="${project.version}"
+                            !org.apache.karaf.cellar.webconsole.internal.osgi,
+                            org.apache.karaf.cellar.webconsole*
                         </Export-Package>
                         <Import-Package>
-                            org.apache.karaf.cellar.core*;version="${project.version}",
-                            org.apache.felix.webconsole;version="[3,5)",
-                            org.osgi.framework,
-                            org.osgi.service.blueprint,
-                            org.slf4j;version="[1.6,2)";resolution:=optional
+                            org.slf4j;version="[1.6,2)";resolution:=optional,
+                            javax.servlet*;version="[3,4)",
+                            org.apache.felix.webconsole*;version="[3,5)",
+                            *
                         </Import-Package>
-                        <DynamicImport-Package>javax.*,org.w3c.*,org.xml.*,*</DynamicImport-Package>
                         <Embed-Dependency>json</Embed-Dependency>
+                        <Private-Package>
+                            org.apache.karaf.cellar.webconsole.internal.osgi,
+                            org.apache.karaf.util.tracker;-split-package:=merge-first
+                        </Private-Package>
                     </instructions>
                 </configuration>
             </plugin>
diff --git a/webconsole/src/main/java/org/apache/karaf/cellar/webconsole/internal/osgi/Activator.java b/webconsole/src/main/java/org/apache/karaf/cellar/webconsole/internal/osgi/Activator.java
new file mode 100644
index 0000000..fd49155
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/karaf/cellar/webconsole/internal/osgi/Activator.java
@@ -0,0 +1,76 @@
+/*
+ * 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.webconsole.internal.osgi;
+
+import org.apache.karaf.cellar.core.ClusterManager;
+import org.apache.karaf.cellar.core.GroupManager;
+import org.apache.karaf.cellar.webconsole.CellarPlugin;
+import org.apache.karaf.util.tracker.BaseActivator;
+import org.apache.karaf.util.tracker.annotation.ProvideService;
+import org.apache.karaf.util.tracker.annotation.RequireService;
+import org.apache.karaf.util.tracker.annotation.Services;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.Servlet;
+import java.util.Hashtable;
+
+@Services(
+        provides = {
+                @ProvideService(Servlet.class)
+        },
+        requires = {
+                @RequireService(ClusterManager.class),
+                @RequireService(GroupManager.class)
+        }
+)
+public class Activator extends BaseActivator {
+
+    private final static Logger LOGGER = LoggerFactory.getLogger(Activator.class);
+
+    private CellarPlugin plugin;
+
+    @Override
+    public void doStart() throws Exception {
+
+        ClusterManager clusterManager = getTrackedService(ClusterManager.class);
+        if (clusterManager == null)
+            return;
+        GroupManager groupManager = getTrackedService(GroupManager.class);
+        if (groupManager == null)
+            return;
+
+        LOGGER.debug("CELLAR WEBCONSOLE: init plugin");
+        plugin = new CellarPlugin();
+        plugin.setClusterManager(clusterManager);
+        plugin.setGroupManager(groupManager);
+        plugin.setBundleContext(bundleContext);
+        plugin.start();
+        Hashtable props = new Hashtable();
+        props.put("felix.webconsole.label", "cellar");
+        register(Servlet.class, plugin, props);
+
+    }
+
+    @Override
+    public void doStop() {
+        super.doStop();
+
+        if (plugin != null) {
+            plugin.stop();
+            plugin = null;
+        }
+    }
+
+}
diff --git a/webconsole/src/main/resources/OSGI-INF/blueprint/webconsole-cellar.xml b/webconsole/src/main/resources/OSGI-INF/blueprint/webconsole-cellar.xml
deleted file mode 100644
index 6c60241..0000000
--- a/webconsole/src/main/resources/OSGI-INF/blueprint/webconsole-cellar.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
-    Licensed to the Apache Software Foundation (ASF) under one or more
-    contributor license agreements.  See the NOTICE file distributed with
-    this work for additional information regarding copyright ownership.
-    The ASF licenses this file to You under the Apache License, Version 2.0
-    (the "License"); you may not use this file except in compliance with
-    the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
-
--->
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" default-activation="lazy">
-
-    <reference id="groupManager" interface="org.apache.karaf.cellar.core.GroupManager"/>
-    <reference id="clusterManager" interface="org.apache.karaf.cellar.core.ClusterManager"/>
-
-    <bean id="cellarPlugin" class="org.apache.karaf.cellar.webconsole.CellarPlugin" init-method="start"
-          destroy-method="stop">
-        <property name="bundleContext" ref="blueprintBundleContext"/>
-        <property name="groupManager" ref="groupManager"/>
-        <property name="clusterManager" ref="clusterManager"/>
-    </bean>
-    <service ref="cellarPlugin" interface="javax.servlet.Servlet">
-        <service-properties>
-            <entry key="felix.webconsole.label" value="cellar"/>
-        </service-properties>
-    </service>
-
-</blueprint>