You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by jb...@apache.org on 2019/10/18 14:18:11 UTC

[karaf-cave] branch master updated: Complete refactoring of Cave

This is an automated email from the ASF dual-hosted git repository.

jbonofre pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/karaf-cave.git


The following commit(s) were added to refs/heads/master by this push:
     new 63859a1  Complete refactoring of Cave
     new bbe77f1  Merge pull request #27 from jbonofre/REFACTORING
63859a1 is described below

commit 63859a1954347ad4c427fffaadfdc0d2a9f3b8cb
Author: Jean-Baptiste Onofré <jb...@apache.org>
AuthorDate: Mon Sep 23 19:36:19 2019 +0200

    Complete refactoring of Cave
---
 assembly/pom.xml                                   |    9 +-
 assembly/src/main/resources/features.xml           |  101 +-
 assembly/src/main/resources/filesystem.cfg         |   23 -
 deployer/api/pom.xml                               |    4 +-
 .../karaf/cave/deployer/{api => }/Bundle.java      |    2 +-
 .../karaf/cave/deployer/{api => }/Config.java      |    2 +-
 .../karaf/cave/deployer/{api => }/Connection.java  |    2 +-
 .../{api/Deployer.java => DeployerService.java}    |    4 +-
 .../karaf/cave/deployer/{api => }/Feature.java     |    2 +-
 .../deployer/{api => }/FeaturesRepository.java     |    2 +-
 deployer/command/pom.xml                           |   66 --
 deployer/management/pom.xml                        |   77 --
 .../deployer/management/internal/Activator.java    |   53 -
 deployer/pom.xml                                   |    4 +-
 deployer/service/pom.xml                           |  120 +-
 .../{impl => }/ConsoleRepositoryListener.java      |    2 +-
 .../{impl => }/ConsoleTransferListener.java        |    2 +-
 .../DeployerImpl.java => DeployerServiceImpl.java} |   48 +-
 .../service}/command/AssembleFeatureCommand.java   |    6 +-
 .../service}/command/BundleInstallCommand.java     |    8 +-
 .../service}/command/BundleListCommand.java        |   10 +-
 .../service}/command/BundleStartCommand.java       |    8 +-
 .../service}/command/BundleStopCommand.java        |    8 +-
 .../service}/command/BundleUninstallCommand.java   |    8 +-
 .../command/ClusterFeatureInstallCommand.java      |    8 +-
 .../ClusterFeatureRepositoryAddCommand.java        |    8 +-
 .../ClusterFeatureRepositoryRemoveCommand.java     |    8 +-
 .../command/ClusterFeatureUninstallCommand.java    |    8 +-
 .../service}/command/ClusterGroupListCommand.java  |    8 +-
 .../service}/command/ClusterNodeListCommand.java   |    8 +-
 .../service}/command/ConfigCreateCommand.java      |    8 +-
 .../service}/command/ConfigDeleteCommand.java      |    8 +-
 .../command/ConfigFactoryCreateCommand.java        |    8 +-
 .../service}/command/ConfigListCommand.java        |    8 +-
 .../command/ConfigPropertyAppendCommand.java       |    8 +-
 .../command/ConfigPropertyDeleteCommand.java       |    8 +-
 .../command/ConfigPropertyListCommand.java         |    8 +-
 .../service}/command/ConfigPropertySetCommand.java |    8 +-
 .../service}/command/ConnectionDeleteCommand.java  |    8 +-
 .../service}/command/ConnectionListCommand.java    |   12 +-
 .../command/ConnectionRegisterCommand.java         |    8 +-
 .../deployer/service}/command/DownloadCommand.java |    6 +-
 .../deployer/service}/command/ExplodeCommand.java  |    6 +-
 .../deployer/service}/command/ExtractCommand.java  |    6 +-
 .../service}/command/FeatureInstallCommand.java    |    8 +-
 .../command/FeatureInstalledListCommand.java       |    8 +-
 .../service}/command/FeatureListCommand.java       |   10 +-
 .../service}/command/FeatureUninstallCommand.java  |    8 +-
 .../command/FeaturesRepositoryAddCommand.java      |    8 +-
 .../command/FeaturesRepositoryListCommand.java     |   10 +-
 .../command/FeaturesRepositoryProvideCommand.java  |    8 +-
 .../command/FeaturesRepositoryRemoveCommand.java   |    8 +-
 .../service}/command/KarInstallCommand.java        |    8 +-
 .../deployer/service}/command/KarListCommand.java  |    8 +-
 .../service}/command/KarUninstallCommand.java      |    8 +-
 .../deployer/service}/command/UploadCommand.java   |    6 +-
 .../command/completers/ConnectionCompleter.java    |    8 +-
 .../cave/deployer/service/impl/Activator.java      |   40 -
 .../service/management/DeployerMBean.java}         |    4 +-
 .../service/management/DeployerMBeanService.java}  |   30 +-
 .../deployer/service/rest/DeployerRestApi.java     |  253 ++++
 .../deployer/service/rest/DeployerRestService.java |   69 ++
 .../deployer/service/rest/DeployerRestServlet.java |   42 +-
 .../service/{impl => }/DeployerImplTest.java       |   17 +-
 {server => gateway}/api/pom.xml                    |   20 +-
 .../karaf/cave/gateway/FeaturesGatewayService.java |   25 +-
 {server => gateway}/pom.xml                        |   14 +-
 {server/maven => gateway/service}/pom.xml          |  107 +-
 .../service/FeaturesGatewayServiceImpl.java        |  132 +++
 .../command/FeaturesGatewayListCommand.java        |   12 +-
 .../command/FeaturesGatewayRegisterCommand.java    |   22 +-
 .../command/FeaturesGatewayRemoveCommand.java      |   23 +-
 .../completers/FeaturesRepositoryUrlCompleter.java |   12 +-
 .../service/http/FeaturesGatewayServlet.java       |   54 +
 .../service/management/FeaturesGatewayMBean.java   |   11 +-
 .../management/FeaturesGatewayMBeanService.java    |   48 +
 .../service/rest/FeaturesGatewayRestApi.java       |   60 +
 .../service/rest/FeaturesGatewayRestService.java   |   68 ++
 .../service/rest/FeaturesGatewayRestServlet.java   |   41 +-
 .../service/FeaturesGatewayServiceImplTest.java    |   55 +
 itest/pom.xml                                      |  133 +++
 .../karaf/cave/itest/repository/AllInTest.java     |   69 ++
 .../karaf/cave/itest/repository/DeployerTest.java  |   78 ++
 .../cave/itest/repository/FeaturesGatewayTest.java |   66 ++
 .../cave/itest/repository/RepositoryTest.java      |  155 +++
 .../test/resources/etc/org.ops4j.pax.logging.cfg   |   69 ++
 manual/pom.xml                                     |    2 +-
 manual/src/main/asciidoc/index.adoc                |   20 +-
 manual/src/main/asciidoc/overview.adoc             |   10 +
 .../asciidoc/user-guide/administrate-cave.adoc     |   54 -
 .../main/asciidoc/user-guide/cave-repository.adoc  |  103 --
 manual/src/main/asciidoc/user-guide/deployer.adoc  |  432 ++++++-
 .../main/asciidoc/user-guide/features-gateway.adoc |  125 +-
 .../src/main/asciidoc/user-guide/http-wrapper.adoc |   49 -
 .../src/main/asciidoc/user-guide/installation.adoc |   55 -
 .../main/asciidoc/user-guide/maven-wrapper.adoc    |   54 -
 .../asciidoc/user-guide/populate-repository.adoc   |   81 --
 .../main/asciidoc/user-guide/proxy-repository.adoc |   56 -
 .../src/main/asciidoc/user-guide/repository.adoc   |  583 ++++++++++
 pom.xml                                            |   78 +-
 {server/command => repository/api}/pom.xml         |   33 +-
 .../apache/karaf/cave/repository/Repository.java   |  238 ++++
 .../karaf/cave/repository/RepositoryService.java   |  220 ++++
 {deployer => repository}/pom.xml                   |    8 +-
 {deployer => repository}/service/pom.xml           |  232 ++--
 .../repository/service/RepositoryServiceImpl.java  |  989 ++++++++++++++++
 .../service/bundlerepository/BaseClause.java       |  109 ++
 .../service/bundlerepository/BaseRepository.java   |  105 ++
 .../service/bundlerepository/BundleRepository.java |   20 +-
 .../service/bundlerepository/CapabilityImpl.java   |   83 ++
 .../service/bundlerepository/CapabilitySet.java    |  463 ++++++++
 .../service/bundlerepository/RequirementImpl.java  |   81 ++
 .../service/bundlerepository/ResourceBuilder.java  | 1203 ++++++++++++++++++++
 .../service/bundlerepository/ResourceImpl.java     |  111 ++
 .../service/bundlerepository/ResourceUtils.java    |  162 +++
 .../service/bundlerepository/SimpleFilter.java     |  573 ++++++++++
 .../service/bundlerepository/StaxParser.java       |  447 ++++++++
 .../service/bundlerepository/UrlLoader.java        |  105 ++
 .../service/bundlerepository/XmlRepository.java    |  183 +++
 .../command/RepositoryAddArtifactCommand.java      |   30 +-
 .../service/command/RepositoryCopyCommand.java     |   36 +-
 .../service/command/RepositoryCreateCommand.java   |   73 ++
 .../command/RepositoryDeleteArtifactCommand.java   |   31 +-
 .../service/command/RepositoryInfoCommand.java     |   61 +
 .../service/command/RepositoryListCommand.java     |   28 +-
 .../service/command/RepositoryLocationCommand.java |   33 +-
 .../service/command/RepositoryProxyCommand.java    |   63 +
 .../service/command/RepositoryPurgeCommand.java    |   27 +-
 .../service/command/RepositoryRemoveCommand.java   |   31 +-
 .../service/command/RepositoryScheduleCommand.java |   58 +
 .../service/command/RepositorySecurityCommand.java |   64 ++
 .../RepositoryUpdateBundleDescriptorCommand.java   |   27 +-
 .../service/command/RepositoryUrlCommand.java      |   33 +-
 .../completers/RepositoryNameCompleter.java        |   22 +-
 .../service/management/RepositoryMBean.java        |   36 +
 .../service/management/RepositoryMBeanService.java |  131 +++
 .../service/maven}/ConsoleRepositoryListener.java  |    4 +-
 .../service/maven}/ConsoleTransferListener.java    |    4 +-
 .../repository/service}/maven/DefaultFuture.java   |    4 +-
 .../repository/service}/maven/FutureListener.java  |    4 +-
 .../maven/InvalidMavenArtifactRequest.java         |    4 +-
 .../repository/service/maven/MavenServlet.java     |   13 +-
 .../repository/service}/maven/ThreadFactory.java   |    4 +-
 .../repository/service/rest/RepositoryRestApi.java |  110 ++
 .../service/rest/RepositoryRestService.java        |   68 ++
 .../service/rest/RepositoryRestServlet.java        |   42 +-
 .../service/scheduler/RepositoryJob.java           |   72 ++
 .../service/RepositoryServiceImplTest.java         |  279 +++++
 rest/pom.xml                                       |  121 --
 .../java/org/apache/karaf/cave/rest/Activator.java |   67 --
 .../org/apache/karaf/cave/rest/DeployerRest.java   |  527 ---------
 .../org/apache/karaf/cave/rest/RepositoryRest.java |  175 ---
 .../karaf/cave/deployer/rest/DeployerRestTest.java |   60 -
 rest/src/test/resources/test.kar                   |  Bin 2895 -> 0 bytes
 server/api/NOTICE                                  |   29 -
 .../server/api/CaveMavenRepositoryListener.java    |   28 -
 .../karaf/cave/server/api/CaveRepository.java      |  166 ---
 .../cave/server/api/CaveRepositoryService.java     |   95 --
 server/command/NOTICE                              |   29 -
 .../command/CaveRepositoryCommandSupport.java      |   42 -
 .../cave/server/command/GatewayRemoveCommand.java  |   45 -
 .../server/command/RepositoryCreateCommand.java    |   73 --
 .../server/command/RepositoryDestroyCommand.java   |   51 -
 .../server/command/RepositoryInstallCommand.java   |   45 -
 .../server/command/RepositoryPopulateCommand.java  |   65 --
 .../server/command/RepositoryProxyCommand.java     |   66 --
 .../server/command/RepositoryUninstallCommand.java |   45 -
 .../server/command/RepositoryUpdateCommand.java    |   47 -
 .../command/RepositoryUploadArtifactCommand.java   |   59 -
 server/http/NOTICE                                 |   29 -
 server/http/pom.xml                                |   88 --
 .../apache/karaf/cave/server/http/Activator.java   |   82 --
 .../karaf/cave/server/http/CaveHttpServlet.java    |  257 -----
 server/management/NOTICE                           |   29 -
 server/management/pom.xml                          |   76 --
 .../server/management/CaveRepositoryMBean.java     |   34 -
 .../cave/server/management/internal/Activator.java |   67 --
 .../management/internal/CaveGatewayMBeanImpl.java  |   49 -
 .../internal/CaveRepositoryMBeanImpl.java          |  167 ---
 .../apache/karaf/cave/server/maven/Activator.java  |  107 --
 .../maven/CaveMavenRepositoryListenerImpl.java     |   60 -
 .../cave/server/maven/MavenProxyServletTest.java   |  743 ------------
 server/storage/NOTICE                              |   29 -
 server/storage/pom.xml                             |  115 --
 .../karaf/cave/server/storage/Activator.java       |   51 -
 .../server/storage/CaveFeatureGatewayImpl.java     |   96 --
 .../cave/server/storage/CaveRepositoryImpl.java    |  699 ------------
 .../server/storage/CaveRepositoryServiceImpl.java  |  329 ------
 .../apache/karaf/cave/server/storage/Utils.java    |  160 ---
 .../server/storage/CaveFeatureGatewayImplTest.java |   80 --
 .../server/storage/CaveRepositoryImplTest.java     |   95 --
 server/storage/src/test/resources/log4j.xml        |   35 -
 192 files changed, 9231 insertions(+), 7104 deletions(-)

diff --git a/assembly/pom.xml b/assembly/pom.xml
index 7706070..9385e03 100644
--- a/assembly/pom.xml
+++ b/assembly/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.karaf</groupId>
         <artifactId>cave</artifactId>
-        <version>4.1.3-SNAPSHOT</version>
+        <version>4.2.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 
@@ -85,7 +85,7 @@
                                 <feature>framework</feature>
                             </framework>
                             <features>
-                                <feature>!cave-rest</feature>
+                                <feature>!*</feature>
                             </features>
                             <verifyTransitive>false</verifyTransitive>
                         </configuration>
@@ -130,11 +130,6 @@
                                     <type>xml</type>
                                     <classifier>features</classifier>
                                 </artifact>
-                                <artifact>
-                                    <file>target/classes/filesystem.cfg</file>
-                                    <type>cfg</type>
-                                    <classifier>filesystem</classifier>
-                                </artifact>
                             </artifacts>
                         </configuration>
                     </execution>
diff --git a/assembly/src/main/resources/features.xml b/assembly/src/main/resources/features.xml
index 18983b6..bc847b2 100644
--- a/assembly/src/main/resources/features.xml
+++ b/assembly/src/main/resources/features.xml
@@ -16,95 +16,50 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 -->
-<features name="karaf-cave-${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">
+<features name="karaf-cave-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://karaf.apache.org/xmlns/features/v1.4.0 http://karaf.apache.org/xmlns/features/v1.4.0">
 
     <repository>mvn:org.apache.cxf.karaf/apache-cxf/${cxf.version}/xml/features</repository>
 
-    <feature name="cave" version="${project.version}">
-        <feature prerequisite="true">cave-server</feature>
-        <feature prerequisite="true">cave-deployer</feature>
-        <feature>cave-rest</feature>
-    </feature>
-
-    <feature name="cave-server" version="${project.version}">
-        <feature>cave-server-storage</feature>
-        <feature>cave-server-http</feature>
-        <feature>cave-server-maven</feature>
-    </feature>
-
-    <feature name="cave-server-storage" version="${project.version}">
-        <configfile finalname="/etc/org.apache.karaf.cave.server.storage.cfg">mvn:org.apache.karaf.cave/apache-karaf-cave/${project.version}/cfg/filesystem</configfile>
-        <bundle>mvn:org.jsoup/jsoup/${jsoup.version}</bundle>
-        <bundle>mvn:commons-codec/commons-codec/${commons-codec.version}</bundle>
-        <bundle>mvn:org.apache.karaf.cave.server/org.apache.karaf.cave.server.api/${project.version}</bundle>
-        <bundle>mvn:org.apache.karaf.cave.server/org.apache.karaf.cave.server.storage/${project.version}</bundle>
-        <conditional>
-            <condition>management</condition>
-            <bundle>mvn:org.apache.karaf.cave.server/org.apache.karaf.cave.server.management/${project.version}</bundle>
-        </conditional>
-        <conditional>
-            <condition>shell</condition>
-            <bundle>mvn:org.apache.karaf.cave.server/org.apache.karaf.cave.server.command/${project.version}</bundle>
-        </conditional>
-    </feature>
-
-    <feature name="cave-server-http" version="${project.version}">
+    <feature name="cave-common" version="${project.version}">
         <feature prerequisite="true">http</feature>
-        <requirement>osgi.service;effective:=active;filter:=(objectClass=org.osgi.service.http.HttpService)</requirement>
-        <feature>cave-server-storage</feature>
-        <bundle>mvn:org.apache.karaf.cave.server/org.apache.karaf.cave.server.http/${project.version}</bundle>
+        <feature prerequisite="true">scr</feature>
+        <feature version="${cxf.version}">cxf-jaxrs</feature>
+        <bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-core/${jackson.version}</bundle>
+        <bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-annotations/${jackson.version}</bundle>
+        <bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-databind/${jackson.version}</bundle>
+        <bundle dependency="true">mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-base/${jackson.version}</bundle>
+        <bundle dependency="true">mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-json-provider/${jackson.version}</bundle>
     </feature>
 
-    <feature name="cave-server-maven" version="${project.version}">
-        <feature prerequisite="true">http</feature>
-        <requirement>osgi.service;effective:=active;filter:=(objectClass=org.osgi.service.http.HttpService)</requirement>
-        <bundle>mvn:org.apache.karaf.cave.server/org.apache.karaf.cave.server.maven/${project.version}</bundle>
+    <feature name="cave-repository-api" version="${project.version}">
+        <bundle>mvn:org.apache.karaf.cave.repository/org.apache.karaf.cave.repository.api/${project.version}</bundle>
     </feature>
 
-    <feature name="cave-deployer" version="${project.version}">
-        <feature>cave-deployer-service</feature>
-        <feature>cave-deployer-command</feature>
-        <feature>cave-deployer-management</feature>
+    <feature name="cave-repository" version="${project.version}">
+        <feature prerequisite="true">cave-common</feature>
+        <feature version="[4.2,4.3)">cave-repository-api</feature>
+        <feature prerequisite="true">scheduler</feature>
+        <bundle>mvn:org.apache.karaf.cave.repository/org.apache.karaf.cave.repository.service/${project.version}</bundle>
     </feature>
 
-    <feature name="cave-deployer-service" version="${project.version}">
-        <bundle>mvn:org.apache.karaf.cave.deployer/org.apache.karaf.cave.deployer.api/${project.version}</bundle>
-        <bundle dependency="true">mvn:org.apache.httpcomponents/httpcore-osgi/4.2.5</bundle>
-        <bundle dependency="true">mvn:org.apache.httpcomponents/httpclient-osgi/4.2.5</bundle>
-        <bundle dependency="true">mvn:com.google.inject/guice/3.0</bundle>
-        <bundle dependency="true">mvn:com.google.guava/guava/18.0</bundle>
-        <bundle>mvn:org.apache.karaf.cave.deployer/org.apache.karaf.cave.deployer.service/${project.version}</bundle>
-        <config name="org.apache.karaf.cave.deployer">
-            # Karaf Cave Deployer connection storage
-        </config>
+    <feature name="cave-features-gateway-api" version="${project.version}">
+        <bundle>mvn:org.apache.karaf.cave.gateway/org.apache.karaf.cave.gateway.api/${project.version}</bundle>
     </feature>
 
-    <feature name="cave-deployer-command" version="${project.version}">
-        <feature version="${project.version}">cave-deployer-service</feature>
-        <conditional>
-            <condition>shell</condition>
-            <bundle>mvn:org.apache.karaf.cave.deployer/org.apache.karaf.cave.deployer.command/${project.version}</bundle>
-        </conditional>
+    <feature name="cave-features-gateway" version="${project.version}">
+        <feature prerequisite="true">cave-common</feature>
+        <feature version="[4.2,4.3)">cave-features-gateway-api</feature>
+        <bundle>mvn:org.apache.karaf.cave.gateway/org.apache.karaf.cave.gateway.service/${project.version}</bundle>
     </feature>
 
-    <feature name="cave-deployer-management" version="${project.version}">
-        <feature version="${project.version}">cave-deployer-service</feature>
-        <conditional>
-            <condition>management</condition>
-            <bundle>mvn:org.apache.karaf.cave.deployer/org.apache.karaf.cave.deployer.management/${project.version}</bundle>
-        </conditional>
+    <feature name="cave-deployer-api" version="${project.version}">
+        <bundle>mvn:org.apache.karaf.cave.deployer/org.apache.karaf.cave.deployer.api/${project.version}</bundle>
     </feature>
 
-    <feature name="cave-rest" version="${project.version}">
-        <feature prerequisite="true">http</feature>
-        <requirement>osgi.service;effective:=active;filter:=(objectClass=org.osgi.service.http.HttpService)</requirement>
-        <feature version="${cxf.version}">cxf-jaxrs</feature>
-        <bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-core/2.4.6</bundle>
-        <bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-annotations/2.4.6</bundle>
-        <bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-databind/2.4.6</bundle>
-        <bundle dependency="true">mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-base/2.4.6</bundle>
-        <bundle dependency="true">mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-json-provider/2.4.6</bundle>
-        <bundle>mvn:org.apache.karaf.cave/org.apache.karaf.cave.rest/${project.version}</bundle>
+    <feature name="cave-deployer" version="${project.version}">
+        <feature prerequisite="true">cave-common</feature>
+        <feature version="[4.2,4.3)">cave-deployer-api</feature>
+        <bundle>mvn:org.apache.karaf.cave.deployer/org.apache.karaf.cave.deployer.service/${project.version}</bundle>
     </feature>
 
     <feature name="cave-documentation" description="Documentation of Karaf Cave sub-project in HTML" version="${project.version}">
diff --git a/assembly/src/main/resources/filesystem.cfg b/assembly/src/main/resources/filesystem.cfg
deleted file mode 100644
index e314f4b..0000000
--- a/assembly/src/main/resources/filesystem.cfg
+++ /dev/null
@@ -1,23 +0,0 @@
-################################################################################
-#
-#    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.
-#
-################################################################################
-
-#
-# Storage location where Apache Karaf Cave create repositories by default
-#
-cave.storage.location=${karaf.data}/cave
\ No newline at end of file
diff --git a/deployer/api/pom.xml b/deployer/api/pom.xml
index 5d8ceee..7aeb258 100644
--- a/deployer/api/pom.xml
+++ b/deployer/api/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.karaf.cave</groupId>
         <artifactId>org.apache.karaf.cave.deployer</artifactId>
-        <version>4.1.3-SNAPSHOT</version>
+        <version>4.2.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 
@@ -43,7 +43,7 @@
                 <configuration>
                     <instructions>
                         <Export-Package>
-                            org.apache.karaf.cave.deployer.api
+                            org.apache.karaf.cave.deployer
                         </Export-Package>
                     </instructions>
                 </configuration>
diff --git a/deployer/api/src/main/java/org/apache/karaf/cave/deployer/api/Bundle.java b/deployer/api/src/main/java/org/apache/karaf/cave/deployer/Bundle.java
similarity index 97%
rename from deployer/api/src/main/java/org/apache/karaf/cave/deployer/api/Bundle.java
rename to deployer/api/src/main/java/org/apache/karaf/cave/deployer/Bundle.java
index fbdb4c2..ce2b3a8 100644
--- a/deployer/api/src/main/java/org/apache/karaf/cave/deployer/api/Bundle.java
+++ b/deployer/api/src/main/java/org/apache/karaf/cave/deployer/Bundle.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.api;
+package org.apache.karaf.cave.deployer;
 
 /**
  * Simplified wrapper representing a bundle.
diff --git a/deployer/api/src/main/java/org/apache/karaf/cave/deployer/api/Config.java b/deployer/api/src/main/java/org/apache/karaf/cave/deployer/Config.java
similarity index 96%
rename from deployer/api/src/main/java/org/apache/karaf/cave/deployer/api/Config.java
rename to deployer/api/src/main/java/org/apache/karaf/cave/deployer/Config.java
index ea7cff8..0405eb5 100644
--- a/deployer/api/src/main/java/org/apache/karaf/cave/deployer/api/Config.java
+++ b/deployer/api/src/main/java/org/apache/karaf/cave/deployer/Config.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.api;
+package org.apache.karaf.cave.deployer;
 
 import java.util.HashMap;
 import java.util.Map;
diff --git a/deployer/api/src/main/java/org/apache/karaf/cave/deployer/api/Connection.java b/deployer/api/src/main/java/org/apache/karaf/cave/deployer/Connection.java
similarity index 97%
rename from deployer/api/src/main/java/org/apache/karaf/cave/deployer/api/Connection.java
rename to deployer/api/src/main/java/org/apache/karaf/cave/deployer/Connection.java
index 03797be..bbd573b 100644
--- a/deployer/api/src/main/java/org/apache/karaf/cave/deployer/api/Connection.java
+++ b/deployer/api/src/main/java/org/apache/karaf/cave/deployer/Connection.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.api;
+package org.apache.karaf.cave.deployer;
 
 /**
  * Simple wrapper describing a connection to a Karaf instance.
diff --git a/deployer/api/src/main/java/org/apache/karaf/cave/deployer/api/Deployer.java b/deployer/api/src/main/java/org/apache/karaf/cave/deployer/DeployerService.java
similarity index 99%
rename from deployer/api/src/main/java/org/apache/karaf/cave/deployer/api/Deployer.java
rename to deployer/api/src/main/java/org/apache/karaf/cave/deployer/DeployerService.java
index a2f5014..a2ffdbb 100644
--- a/deployer/api/src/main/java/org/apache/karaf/cave/deployer/api/Deployer.java
+++ b/deployer/api/src/main/java/org/apache/karaf/cave/deployer/DeployerService.java
@@ -14,12 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.api;
+package org.apache.karaf.cave.deployer;
 
 import java.util.List;
 import java.util.Map;
 
-public interface Deployer {
+public interface DeployerService {
 
     /**
      * Register a connection in the Deployer service.
diff --git a/deployer/api/src/main/java/org/apache/karaf/cave/deployer/api/Feature.java b/deployer/api/src/main/java/org/apache/karaf/cave/deployer/Feature.java
similarity index 96%
rename from deployer/api/src/main/java/org/apache/karaf/cave/deployer/api/Feature.java
rename to deployer/api/src/main/java/org/apache/karaf/cave/deployer/Feature.java
index da1c034..8f3c0e7 100644
--- a/deployer/api/src/main/java/org/apache/karaf/cave/deployer/api/Feature.java
+++ b/deployer/api/src/main/java/org/apache/karaf/cave/deployer/Feature.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.api;
+package org.apache.karaf.cave.deployer;
 
 /**
  * Simplified wrapper representing a feature.
diff --git a/deployer/api/src/main/java/org/apache/karaf/cave/deployer/api/FeaturesRepository.java b/deployer/api/src/main/java/org/apache/karaf/cave/deployer/FeaturesRepository.java
similarity index 96%
rename from deployer/api/src/main/java/org/apache/karaf/cave/deployer/api/FeaturesRepository.java
rename to deployer/api/src/main/java/org/apache/karaf/cave/deployer/FeaturesRepository.java
index 1bf704f..fde8a7d 100644
--- a/deployer/api/src/main/java/org/apache/karaf/cave/deployer/api/FeaturesRepository.java
+++ b/deployer/api/src/main/java/org/apache/karaf/cave/deployer/FeaturesRepository.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.api;
+package org.apache.karaf.cave.deployer;
 
 /**
  * Simplified wrapper representing a features repository.
diff --git a/deployer/command/pom.xml b/deployer/command/pom.xml
deleted file mode 100644
index 20fad4d..0000000
--- a/deployer/command/pom.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?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.cave</groupId>
-        <artifactId>org.apache.karaf.cave.deployer</artifactId>
-        <version>4.1.3-SNAPSHOT</version>
-        <relativePath>../pom.xml</relativePath>
-    </parent>
-
-    <groupId>org.apache.karaf.cave.deployer</groupId>
-    <artifactId>org.apache.karaf.cave.deployer.command</artifactId>
-    <name>Apache Karaf :: Cave :: Deployer :: Command</name>
-    <packaging>bundle</packaging>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.apache.karaf.shell</groupId>
-            <artifactId>org.apache.karaf.shell.core</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.karaf.cave.deployer</groupId>
-            <artifactId>org.apache.karaf.cave.deployer.api</artifactId>
-            <version>${project.version}</version>
-        </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>
-                        <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
-                    </instructions>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
-
-</project>
\ No newline at end of file
diff --git a/deployer/management/pom.xml b/deployer/management/pom.xml
deleted file mode 100644
index 0546786..0000000
--- a/deployer/management/pom.xml
+++ /dev/null
@@ -1,77 +0,0 @@
-<?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.cave</groupId>
-        <artifactId>org.apache.karaf.cave.deployer</artifactId>
-        <version>4.1.3-SNAPSHOT</version>
-        <relativePath>../pom.xml</relativePath>
-    </parent>
-
-    <groupId>org.apache.karaf.cave.deployer</groupId>
-    <artifactId>org.apache.karaf.cave.deployer.management</artifactId>
-    <packaging>bundle</packaging>
-    <name>Apache Karaf :: Cave :: Deployer :: Management</name>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.apache.karaf.cave.deployer</groupId>
-            <artifactId>org.apache.karaf.cave.deployer.api</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.karaf</groupId>
-            <artifactId>org.apache.karaf.util</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.osgi</groupId>
-            <artifactId>osgi.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>
-                        <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
-                        <Export-Package>
-                            org.apache.karaf.cave.deployer.management
-                        </Export-Package>
-                        <Private-Package>
-                            org.apache.karaf.cave.server.deployer.internal,
-                            org.apache.karaf.util.tracker*
-                        </Private-Package>
-                    </instructions>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
-
-</project>
\ No newline at end of file
diff --git a/deployer/management/src/main/java/org/apache/karaf/cave/deployer/management/internal/Activator.java b/deployer/management/src/main/java/org/apache/karaf/cave/deployer/management/internal/Activator.java
deleted file mode 100644
index 4daf8a0..0000000
--- a/deployer/management/src/main/java/org/apache/karaf/cave/deployer/management/internal/Activator.java
+++ /dev/null
@@ -1,53 +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.cave.deployer.management.internal;
-
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.util.tracker.BaseActivator;
-import org.apache.karaf.util.tracker.annotation.RequireService;
-import org.apache.karaf.util.tracker.annotation.Services;
-import org.osgi.framework.ServiceRegistration;
-
-import java.util.Hashtable;
-
-@Services(
-        requires = {
-                @RequireService(Deployer.class)
-        }
-)
-public class Activator extends BaseActivator {
-
-    private volatile ServiceRegistration mbeanRegistration;
-
-    @Override
-    protected void doStart() throws Exception {
-        Deployer deployer = getTrackedService(Deployer.class);
-        CaveDeployerMBeanImpl mbean = new CaveDeployerMBeanImpl();
-        mbean.setDeployer(deployer);
-
-        Hashtable<String, Object> props = new Hashtable<>();
-        props.put("jmx.objectname", "org.apache.karaf.cave:type=deployer,name=" + System.getProperty("karaf.name"));
-        mbeanRegistration = this.bundleContext.registerService(getInterfaceNames(mbean), mbean, props);
-    }
-
-    @Override
-    protected void doStop() {
-        if (mbeanRegistration != null) {
-            mbeanRegistration.unregister();
-            mbeanRegistration = null;
-        }
-        super.doStop();
-    }
-
-}
diff --git a/deployer/pom.xml b/deployer/pom.xml
index 5d23b7e..7e002b1 100644
--- a/deployer/pom.xml
+++ b/deployer/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.karaf</groupId>
         <artifactId>cave</artifactId>
-        <version>4.1.3-SNAPSHOT</version>
+        <version>4.2.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 
@@ -36,8 +36,6 @@
     <modules>
         <module>api</module>
         <module>service</module>
-        <module>command</module>
-        <module>management</module>
     </modules>
 
 </project>
\ No newline at end of file
diff --git a/deployer/service/pom.xml b/deployer/service/pom.xml
index 6009585..4c8bbb2 100644
--- a/deployer/service/pom.xml
+++ b/deployer/service/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.karaf.cave</groupId>
         <artifactId>org.apache.karaf.cave.deployer</artifactId>
-        <version>4.1.3-SNAPSHOT</version>
+        <version>4.2.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 
@@ -252,6 +252,10 @@
             <artifactId>org.apache.karaf.features.core</artifactId>
             <version>${karaf.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
+        </dependency>
 
         <!-- Logging -->
         <dependency>
@@ -278,7 +282,18 @@
             <version>1.7.25</version>
             <scope>test</scope>
         </dependency>
-
+        <dependency>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-frontend-jaxrs</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${servlet.spec.groupId}</groupId>
+            <artifactId>${servlet.spec.artifactId}</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.jaxrs</groupId>
+            <artifactId>jackson-jaxrs-json-provider</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
@@ -290,82 +305,51 @@
             <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
-                <version>2.5.4</version>
                 <extensions>true</extensions>
                 <inherited>true</inherited>
                 <configuration>
                     <instructions>
                         <Import-Package>
-                            org.apache.karaf.cave.deployer.api,
-                            org.slf4j;resolution:=optional,
-                            org.junit;resolution:=optional,
-                            org.testng*;resolution:=optional,
-                            junit.framework.*;resolution:=optional,
-                            org.apache.maven.model.*;resolution:=optional,
-                            org.apache.maven.artifact.*;resolution:=optional,
-                            org.apache.maven.cli.*;resolution:=optional,
-                            org.apache.maven.settings.merge.*;resolution:=optional,
-                            org.apache.maven.wagon.events.*;resolution:=optional,
-                            org.apache.commons.cli.*;resolution:=optional,
-                            org.apache.tools.ant.*;resolution:=optional,
-                            org.codehaus.plexus.component.repository.exception.*,
-                            org.codehaus.plexus.component.annotations.*;resolution:=optional,
-                            org.codehaus.plexus.util.*;resolution:=optional,
-                            org.sonatype.plexus.components.cipher.*;resolution:=optional,
-                            org.sonatype.plexus.components.sec.dispatcher.*;resolution:=optional,
-                            hudson.maven.*;resolution:=optional,
-                            org.eclipse.aether.util.repository.layout.*;resolution:=optional,
-                            org.apache.maven.wagon.*;resolution:=optional,
+                            !org.apache.http*,
+                            !org.eclipse.aether*,
+                            !org.sonatype*,
+                            !net.spy.memcached,
+                            !org.apache.commons.codec*,
                             ch.qos.logback*;resolution:=optional,
+                            junit.framework;resolution:=optional,
+                            org.junit;resolution:=optional,
+                            org.slf4j.impl;resolution:=optional,
+                            org.testng.annotations;resolution:=optional,
+                            javax.xml.bind*;version="[2,3)",
+                            net.sf.ehcache;resolution:=optional,
+                            com.fasterxml.jackson*;version="[2.8,3)",
                             *
                         </Import-Package>
                         <Private-Package>
-                            org.apache.karaf.cave.deployer.service.impl,
-                            org.apache.karaf.features.internal.model,
-                            org.apache.felix.utils.version,
-                            org.apache.felix.utils.properties,
-                            org.apache.karaf.util
+                            org.apache.karaf.cave.repository.service*,
+                            org.apache.karaf.util*,
+                            org.apache.maven*,
+                            org.eclipse.aether*,
+                            com.google*,
+                            javax.inject,
+                            org.apache.commons.cli,
+                            org.apache.http*,
+                            org.codehaus.plexus*,
+                            org.codehaus.classworlds,
+                            org.eclipse.sisu*,
+                            javax.enterprise.inject,
+                            javax.enterprise.context,
+                            javax.enterprise.util,
+                            org.sonatype*,
+                            net.spy.memcached,
+                            org.apache.commons.codec*,
+                            org.apache.felix.utils.*,
+                            org.apache.felix.resolver,
+                            org.apache.felix.resolver.util,
+                            org.apache.felix.resolver.reason,
+                            org.apache.karaf.features.internal.model
                         </Private-Package>
-                        <Embed-Dependency>
-                            sisu-guice,
-                            sisu-guava,
-                            sisu-inject-bean,
-                            javax.interceptor-api,
-                            javax.el-api,
-                            cdi-api,
-                            aether-transport-http,
-                            aether-transport-file,
-                            aether-connector-basic,
-                            wagon-provider-api,
-                            aether-util,
-                            aether-spi,
-                            aether-impl,
-                            aether-api,
-                            aether-connector-wagon,
-                            maven-aether-provider,
-                            maven-model,
-                            maven-compat,
-                            ant,
-                            maven-settings-builder,
-                            maven-settings,
-                            org.eclipse.sisu.plexus,
-                            org.eclipse.sisu.inject,
-                            plexus-component-annotations,
-                            plexus-sec-dispatcher,
-                            plexus-cipher,
-                            commons-cli,
-                            maven-embedder,
-                            plexus-classworlds,
-                            maven-model-builder,
-                            maven-artifact,
-                            maven-repository-metadata,
-                            maven-core,
-                            maven-plugin-api,
-                            plexus-utils,
-                            plexus-interpolation
-                        </Embed-Dependency>
-                        <Embed-Directory>jars</Embed-Directory>
-                        <Embed-Transitive>true</Embed-Transitive>
+                        <_dsannotations>*</_dsannotations>
                     </instructions>
                 </configuration>
             </plugin>
diff --git a/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/impl/ConsoleRepositoryListener.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/ConsoleRepositoryListener.java
similarity index 98%
copy from deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/impl/ConsoleRepositoryListener.java
copy to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/ConsoleRepositoryListener.java
index 7db240c..873fe44 100644
--- a/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/impl/ConsoleRepositoryListener.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/ConsoleRepositoryListener.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.service.impl;
+package org.apache.karaf.cave.deployer.service;
 
 import org.eclipse.aether.AbstractRepositoryListener;
 import org.eclipse.aether.RepositoryEvent;
diff --git a/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/impl/ConsoleTransferListener.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/ConsoleTransferListener.java
similarity index 98%
copy from deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/impl/ConsoleTransferListener.java
copy to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/ConsoleTransferListener.java
index 3b8c37d..d1d092f 100644
--- a/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/impl/ConsoleTransferListener.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/ConsoleTransferListener.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.service.impl;
+package org.apache.karaf.cave.deployer.service;
 
 import org.eclipse.aether.transfer.AbstractTransferListener;
 import org.eclipse.aether.transfer.TransferEvent;
diff --git a/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/impl/DeployerImpl.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/DeployerServiceImpl.java
similarity index 97%
rename from deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/impl/DeployerImpl.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/DeployerServiceImpl.java
index 46d9fc0..7dbd846 100644
--- a/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/impl/DeployerImpl.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/DeployerServiceImpl.java
@@ -14,12 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.service.impl;
+package org.apache.karaf.cave.deployer.service;
 
 import com.google.common.io.Files;
-import org.apache.karaf.cave.deployer.api.Connection;
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.api.FeaturesRepository;
+import org.apache.karaf.cave.deployer.Connection;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.FeaturesRepository;
 import org.apache.karaf.features.internal.model.*;
 import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
 import org.eclipse.aether.DefaultRepositorySystemSession;
@@ -39,6 +39,8 @@ import org.eclipse.aether.transport.file.FileTransporterFactory;
 import org.eclipse.aether.transport.http.HttpTransporterFactory;
 import org.osgi.service.cm.Configuration;
 import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.w3c.dom.Document;
@@ -65,20 +67,18 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.zip.ZipEntry;
 
-public class DeployerImpl implements Deployer {
+@Component(name = "org.apache.karaf.cave.deployer", immediate = true, service = DeployerService.class)
+public class DeployerServiceImpl implements DeployerService {
 
-    private final static Logger LOGGER  = LoggerFactory.getLogger(DeployerImpl.class);
+    @Reference
+    private ConfigurationAdmin configurationAdmin;
+
+    private final static Logger LOGGER  = LoggerFactory.getLogger(DeployerServiceImpl.class);
 
     private final static Pattern mvnPattern = Pattern.compile("mvn:([^/ ]+)/([^/ ]+)/([^/ ]*)(/([^/ ]+)(/([^/ ]+))?)?");
 
     private final static String CONFIG_PID = "org.apache.karaf.cave.deployer";
 
-    private ConfigurationAdmin configurationAdmin;
-
-    public void setConfigurationAdmin(ConfigurationAdmin configurationAdmin) {
-        this.configurationAdmin = configurationAdmin;
-    }
-
     @Override
     public void registerConnection(Connection connection) throws Exception {
         Configuration configuration = configurationAdmin.getConfiguration(CONFIG_PID);
@@ -425,7 +425,7 @@ public class DeployerImpl implements Deployer {
                                 List<String> featuresRepositoryUrls,
                                 List<String> features,
                                 List<String> bundles,
-                                List<org.apache.karaf.cave.deployer.api.Config> configs) throws Exception {
+                                List<org.apache.karaf.cave.deployer.Config> configs) throws Exception {
         Features featuresModel = new Features();
         featuresModel.setName(feature);
         // add features repository
@@ -456,7 +456,7 @@ public class DeployerImpl implements Deployer {
         }
         // add config
         if (configs != null) {
-            for (org.apache.karaf.cave.deployer.api.Config config : configs) {
+            for (org.apache.karaf.cave.deployer.Config config : configs) {
                 Config modelConfig = new Config();
                 modelConfig.setName(config.getPid());
                 StringBuilder builder = new StringBuilder();
@@ -603,7 +603,7 @@ public class DeployerImpl implements Deployer {
     }
 
     @Override
-    public List<org.apache.karaf.cave.deployer.api.Bundle> bundles(String connectionName) throws Exception {
+    public List<org.apache.karaf.cave.deployer.Bundle> bundles(String connectionName) throws Exception {
         Connection connection = getConnection(connectionName);
         JMXConnector jmxConnector = connect(connection.getJmxUrl(),
                 connection.getKarafName(),
@@ -613,7 +613,7 @@ public class DeployerImpl implements Deployer {
             MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection();
             ObjectName name = new ObjectName("org.apache.karaf:type=bundle,name=" + connection.getKarafName());
             TabularData tabularData = (TabularData) mBeanServerConnection.getAttribute(name, "Bundles");
-            List<org.apache.karaf.cave.deployer.api.Bundle> result = new ArrayList<org.apache.karaf.cave.deployer.api.Bundle>();
+            List<org.apache.karaf.cave.deployer.Bundle> result = new ArrayList<org.apache.karaf.cave.deployer.Bundle>();
             for (Object value : tabularData.values()) {
                 CompositeData compositeData = (CompositeData) value;
                 Long bundleId = (Long) compositeData.get("ID");
@@ -621,7 +621,7 @@ public class DeployerImpl implements Deployer {
                 String bundleVersion = (String) compositeData.get("Version");
                 String bundleState = (String) compositeData.get("State");
                 Integer bundleStartLevel = (Integer) compositeData.get("Start Level");
-                org.apache.karaf.cave.deployer.api.Bundle bundle = new org.apache.karaf.cave.deployer.api.Bundle();
+                org.apache.karaf.cave.deployer.Bundle bundle = new org.apache.karaf.cave.deployer.Bundle();
                 bundle.setId(bundleId.toString());
                 bundle.setName(bundleName);
                 bundle.setVersion(bundleVersion);
@@ -637,11 +637,11 @@ public class DeployerImpl implements Deployer {
         }
     }
 
-    public List<org.apache.karaf.cave.deployer.api.Feature> providedFeatures(String featureRepositoryUrl) throws Exception {
+    public List<org.apache.karaf.cave.deployer.Feature> providedFeatures(String featureRepositoryUrl) throws Exception {
         Features features = JaxbUtil.unmarshal(featureRepositoryUrl, true);
-        List<org.apache.karaf.cave.deployer.api.Feature> wrappedFeatures = new ArrayList<>();
+        List<org.apache.karaf.cave.deployer.Feature> wrappedFeatures = new ArrayList<>();
         for (Feature feature : features.getFeature()) {
-            org.apache.karaf.cave.deployer.api.Feature wrappedFeature = new org.apache.karaf.cave.deployer.api.Feature();
+            org.apache.karaf.cave.deployer.Feature wrappedFeature = new org.apache.karaf.cave.deployer.Feature();
             wrappedFeature.setName(feature.getName());
             wrappedFeature.setVersion(feature.getVersion());
             wrappedFeatures.add(wrappedFeature);
@@ -751,7 +751,7 @@ public class DeployerImpl implements Deployer {
     }
 
     @Override
-    public List<org.apache.karaf.cave.deployer.api.Feature> features(String connectionName) throws Exception {
+    public List<org.apache.karaf.cave.deployer.Feature> features(String connectionName) throws Exception {
         Connection connection = getConnection(connectionName);
         JMXConnector jmxConnector = connect(connection.getJmxUrl(),
                 connection.getKarafName(),
@@ -761,13 +761,13 @@ public class DeployerImpl implements Deployer {
             MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection();
             ObjectName name = new ObjectName("org.apache.karaf:type=feature,name=" + connection.getKarafName());
             TabularData tabularData = (TabularData) mBeanServerConnection.getAttribute(name, "Features");
-            List<org.apache.karaf.cave.deployer.api.Feature> result = new ArrayList<>();
+            List<org.apache.karaf.cave.deployer.Feature> result = new ArrayList<>();
             for (Object value : tabularData.values()) {
                 CompositeData compositeData = (CompositeData) value;
                 String featureName = (String) compositeData.get("Name");
                 String featureVersion = (String) compositeData.get("Version");
                 boolean featureInstalled = (Boolean) compositeData.get("Installed");
-                org.apache.karaf.cave.deployer.api.Feature feature = new org.apache.karaf.cave.deployer.api.Feature();
+                org.apache.karaf.cave.deployer.Feature feature = new org.apache.karaf.cave.deployer.Feature();
                 feature.setName(featureName);
                 feature.setVersion(featureVersion);
                 if (featureInstalled)
@@ -1003,7 +1003,7 @@ public class DeployerImpl implements Deployer {
     }
 
     @Override
-    public void updateConfig(org.apache.karaf.cave.deployer.api.Config config, String connectionName) throws Exception {
+    public void updateConfig(org.apache.karaf.cave.deployer.Config config, String connectionName) throws Exception {
         Connection connection = getConnection(connectionName);
         JMXConnector jmxConnector = connect(connection.getJmxUrl(),
                 connection.getKarafName(),
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/AssembleFeatureCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/AssembleFeatureCommand.java
similarity index 95%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/AssembleFeatureCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/AssembleFeatureCommand.java
index 0badc22..b6618ae 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/AssembleFeatureCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/AssembleFeatureCommand.java
@@ -14,9 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.DeployerService;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -31,7 +31,7 @@ import java.util.List;
 public class AssembleFeatureCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Option(name = "-g", aliases = "--groupId", description = "Maven groupId", required = true, multiValued = false)
     String groupId;
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleInstallCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/BundleInstallCommand.java
similarity index 88%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleInstallCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/BundleInstallCommand.java
index 3e3616c..2e6a6a0 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleInstallCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/BundleInstallCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -30,7 +30,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class BundleInstallCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleListCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/BundleListCommand.java
similarity index 88%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleListCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/BundleListCommand.java
index 5d9e38b..da64c75 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleListCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/BundleListCommand.java
@@ -14,11 +14,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Bundle;
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.Bundle;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -34,7 +34,7 @@ import java.util.List;
 public class BundleListCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleStartCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/BundleStartCommand.java
similarity index 88%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleStartCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/BundleStartCommand.java
index 2ea9e7f..53e085f 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleStartCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/BundleStartCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -30,7 +30,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class BundleStartCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleStopCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/BundleStopCommand.java
similarity index 88%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleStopCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/BundleStopCommand.java
index 1368266..631fcfe 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleStopCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/BundleStopCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -30,7 +30,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class BundleStopCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleUninstallCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/BundleUninstallCommand.java
similarity index 88%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleUninstallCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/BundleUninstallCommand.java
index 4ee4a1b..9a2d814 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleUninstallCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/BundleUninstallCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -30,7 +30,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class BundleUninstallCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureInstallCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ClusterFeatureInstallCommand.java
similarity index 89%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureInstallCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ClusterFeatureInstallCommand.java
index 4fc92df..c7462a3 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureInstallCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ClusterFeatureInstallCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -30,7 +30,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class ClusterFeatureInstallCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureRepositoryAddCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ClusterFeatureRepositoryAddCommand.java
similarity index 89%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureRepositoryAddCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ClusterFeatureRepositoryAddCommand.java
index 7feaff6..4cc5bd9 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureRepositoryAddCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ClusterFeatureRepositoryAddCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -30,7 +30,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class ClusterFeatureRepositoryAddCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureRepositoryRemoveCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ClusterFeatureRepositoryRemoveCommand.java
similarity index 89%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureRepositoryRemoveCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ClusterFeatureRepositoryRemoveCommand.java
index a5f6059..46e8c54 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureRepositoryRemoveCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ClusterFeatureRepositoryRemoveCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -30,7 +30,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class ClusterFeatureRepositoryRemoveCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureUninstallCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ClusterFeatureUninstallCommand.java
similarity index 89%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureUninstallCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ClusterFeatureUninstallCommand.java
index 2e44ad4..7998673 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureUninstallCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ClusterFeatureUninstallCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -30,7 +30,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class ClusterFeatureUninstallCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterGroupListCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ClusterGroupListCommand.java
similarity index 90%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterGroupListCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ClusterGroupListCommand.java
index df00ffc..7fdf573 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterGroupListCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ClusterGroupListCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -34,7 +34,7 @@ import java.util.Map;
 public class ClusterGroupListCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterNodeListCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ClusterNodeListCommand.java
similarity index 88%
copy from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterNodeListCommand.java
copy to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ClusterNodeListCommand.java
index 78a6bbb..c98701b 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterNodeListCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ClusterNodeListCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -32,7 +32,7 @@ import java.util.List;
 public class ClusterNodeListCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigCreateCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConfigCreateCommand.java
similarity index 88%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigCreateCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConfigCreateCommand.java
index 86e895b..7d8523f 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigCreateCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConfigCreateCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -30,7 +30,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class ConfigCreateCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigDeleteCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConfigDeleteCommand.java
similarity index 88%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigDeleteCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConfigDeleteCommand.java
index a9c8704..ee47571 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigDeleteCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConfigDeleteCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -30,7 +30,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class ConfigDeleteCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigFactoryCreateCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConfigFactoryCreateCommand.java
similarity index 90%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigFactoryCreateCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConfigFactoryCreateCommand.java
index af2ac5c..bc6df2d 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigFactoryCreateCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConfigFactoryCreateCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -30,7 +30,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class ConfigFactoryCreateCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigListCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConfigListCommand.java
similarity index 88%
copy from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigListCommand.java
copy to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConfigListCommand.java
index 1284ebe..2987c59 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigListCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConfigListCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -32,7 +32,7 @@ import java.util.List;
 public class ConfigListCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertyAppendCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConfigPropertyAppendCommand.java
similarity index 90%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertyAppendCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConfigPropertyAppendCommand.java
index ad575c8..51e02e8 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertyAppendCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConfigPropertyAppendCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -30,7 +30,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class ConfigPropertyAppendCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertyDeleteCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConfigPropertyDeleteCommand.java
similarity index 89%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertyDeleteCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConfigPropertyDeleteCommand.java
index e7f084f..33b921a 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertyDeleteCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConfigPropertyDeleteCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -30,7 +30,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class ConfigPropertyDeleteCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertyListCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConfigPropertyListCommand.java
similarity index 89%
copy from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertyListCommand.java
copy to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConfigPropertyListCommand.java
index e4f5ed0..14d8983 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertyListCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConfigPropertyListCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -32,7 +32,7 @@ import java.util.Map;
 public class ConfigPropertyListCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertySetCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConfigPropertySetCommand.java
similarity index 90%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertySetCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConfigPropertySetCommand.java
index 3e72cdc..95484ce 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertySetCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConfigPropertySetCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -30,7 +30,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class ConfigPropertySetCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConnectionDeleteCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConnectionDeleteCommand.java
similarity index 87%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConnectionDeleteCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConnectionDeleteCommand.java
index 9c1b481..b559093 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConnectionDeleteCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConnectionDeleteCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -30,7 +30,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class ConnectionDeleteCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection name", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConnectionListCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConnectionListCommand.java
similarity index 84%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConnectionListCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConnectionListCommand.java
index 2c110a7..ec628e2 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConnectionListCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConnectionListCommand.java
@@ -14,24 +14,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Connection;
-import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.Connection;
+import org.apache.karaf.cave.deployer.DeployerService;
 import org.apache.karaf.shell.api.action.Action;
 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.apache.karaf.shell.support.table.ShellTable;
 
-import java.util.Collections;
-
 @Service
-@Command(scope = "cave", name = "deployer-connection", description = "List of registered connections in the deployer service")
+@Command(scope = "cave", name = "deployer-connection-list", description = "List of registered connections in the deployer service")
 public class ConnectionListCommand implements Action {
 
     @Reference
-    Deployer deployer;
+    DeployerService deployer;
 
     @Override
     public Object execute() throws Exception {
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConnectionRegisterCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConnectionRegisterCommand.java
similarity index 92%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConnectionRegisterCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConnectionRegisterCommand.java
index 6907ee8..03d8d6d 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConnectionRegisterCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ConnectionRegisterCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Connection;
-import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.Connection;
+import org.apache.karaf.cave.deployer.DeployerService;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -29,7 +29,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class ConnectionRegisterCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "name", description = "Name of the connection", required = true, multiValued = false)
     String name;
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/DownloadCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/DownloadCommand.java
similarity index 91%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/DownloadCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/DownloadCommand.java
index 7dc48d5..863b598 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/DownloadCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/DownloadCommand.java
@@ -14,9 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.DeployerService;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -28,7 +28,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class DownloadCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "artifact", description = "The artifact URL", required = true, multiValued = false)
     String artifact;
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ExplodeCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ExplodeCommand.java
similarity index 92%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ExplodeCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ExplodeCommand.java
index 5dd9a7c..18ebef9 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ExplodeCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ExplodeCommand.java
@@ -14,9 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.DeployerService;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -28,7 +28,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class ExplodeCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "url", description = "The location of the file", required = true, multiValued = false)
     String url;
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ExtractCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ExtractCommand.java
similarity index 92%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ExtractCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ExtractCommand.java
index 2bb921f..8b0a3aa 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ExtractCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/ExtractCommand.java
@@ -14,9 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.DeployerService;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -28,7 +28,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class ExtractCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "url", description = "The location of the file", required = true, multiValued = false)
     String url;
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureInstallCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/FeatureInstallCommand.java
similarity index 88%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureInstallCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/FeatureInstallCommand.java
index fe5d9be..56b89dc 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureInstallCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/FeatureInstallCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -30,7 +30,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class FeatureInstallCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureInstalledListCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/FeatureInstalledListCommand.java
similarity index 88%
copy from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureInstalledListCommand.java
copy to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/FeatureInstalledListCommand.java
index 4030a3a..2e8cd03 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureInstalledListCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/FeatureInstalledListCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -32,7 +32,7 @@ import java.util.List;
 public class FeatureInstalledListCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureListCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/FeatureListCommand.java
similarity index 88%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureListCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/FeatureListCommand.java
index 1d1c181..4563bd0 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureListCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/FeatureListCommand.java
@@ -14,11 +14,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.api.Feature;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.Feature;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -34,7 +34,7 @@ import java.util.List;
 public class FeatureListCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureUninstallCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/FeatureUninstallCommand.java
similarity index 88%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureUninstallCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/FeatureUninstallCommand.java
index b8c5a0b..395588b 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureUninstallCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/FeatureUninstallCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -30,7 +30,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class FeatureUninstallCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeaturesRepositoryAddCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/FeaturesRepositoryAddCommand.java
similarity index 88%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeaturesRepositoryAddCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/FeaturesRepositoryAddCommand.java
index 0e6b7a4..f5279f2 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeaturesRepositoryAddCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/FeaturesRepositoryAddCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -30,7 +30,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class FeaturesRepositoryAddCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeaturesRepositoryListCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/FeaturesRepositoryListCommand.java
similarity index 87%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeaturesRepositoryListCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/FeaturesRepositoryListCommand.java
index 0b24564..5802e89 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeaturesRepositoryListCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/FeaturesRepositoryListCommand.java
@@ -14,11 +14,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.api.FeaturesRepository;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.FeaturesRepository;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -34,7 +34,7 @@ import java.util.List;
 public class FeaturesRepositoryListCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeaturesRepositoryProvideCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/FeaturesRepositoryProvideCommand.java
similarity index 91%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeaturesRepositoryProvideCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/FeaturesRepositoryProvideCommand.java
index 4b133e8..b16b1e2 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeaturesRepositoryProvideCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/FeaturesRepositoryProvideCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.api.Feature;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.Feature;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -32,7 +32,7 @@ import java.util.List;
 public class FeaturesRepositoryProvideCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "featuresRepositoryUrl", description = "The location", required = true, multiValued = false)
     String featuresRepositoryUrl;
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeaturesRepositoryRemoveCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/FeaturesRepositoryRemoveCommand.java
similarity index 88%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeaturesRepositoryRemoveCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/FeaturesRepositoryRemoveCommand.java
index 235540e..f4bab87 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeaturesRepositoryRemoveCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/FeaturesRepositoryRemoveCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -30,7 +30,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class FeaturesRepositoryRemoveCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/KarInstallCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/KarInstallCommand.java
similarity index 88%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/KarInstallCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/KarInstallCommand.java
index fd1ee6d..a768654 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/KarInstallCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/KarInstallCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -30,7 +30,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class KarInstallCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/KarListCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/KarListCommand.java
similarity index 88%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/KarListCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/KarListCommand.java
index a488d08..d3640c8 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/KarListCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/KarListCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -32,7 +32,7 @@ import java.util.List;
 public class KarListCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/KarUninstallCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/KarUninstallCommand.java
similarity index 88%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/KarUninstallCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/KarUninstallCommand.java
index 5ba1525..423a6e1 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/KarUninstallCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/KarUninstallCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.service.command.completers.ConnectionCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -30,7 +30,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class KarUninstallCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
     @Completion(ConnectionCompleter.class)
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/UploadCommand.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/UploadCommand.java
similarity index 93%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/UploadCommand.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/UploadCommand.java
index da98973..7920f27 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/UploadCommand.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/UploadCommand.java
@@ -14,9 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.deployer.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.DeployerService;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -29,7 +29,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class UploadCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Option(name = "-g", aliases = "--groupId", description = "Maven groupId", required = true, multiValued = false)
     String groupId;
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/completers/ConnectionCompleter.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/completers/ConnectionCompleter.java
similarity index 89%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/completers/ConnectionCompleter.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/completers/ConnectionCompleter.java
index 1a072e1..849f306 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/completers/ConnectionCompleter.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/command/completers/ConnectionCompleter.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command.completers;
+package org.apache.karaf.cave.deployer.service.command.completers;
 
-import org.apache.karaf.cave.deployer.api.Connection;
-import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.Connection;
+import org.apache.karaf.cave.deployer.DeployerService;
 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;
@@ -31,7 +31,7 @@ import java.util.List;
 public class ConnectionCompleter implements Completer {
 
     @Reference
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Override
     public int complete(Session session, CommandLine commandLine, List<String> list) {
diff --git a/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/impl/Activator.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/impl/Activator.java
deleted file mode 100644
index 732e811..0000000
--- a/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/impl/Activator.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.karaf.cave.deployer.service.impl;
-
-import org.apache.karaf.cave.deployer.api.Deployer;
-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(ConfigurationAdmin.class) },
-        provides = { @ProvideService(Deployer.class) }
-)
-public class Activator extends BaseActivator {
-
-    @Override
-    public void doStart() {
-        ConfigurationAdmin configurationAdmin = getTrackedService(ConfigurationAdmin.class);
-        DeployerImpl deployer = new DeployerImpl();
-        deployer.setConfigurationAdmin(configurationAdmin);
-        register(Deployer.class, deployer);
-    }
-
-}
diff --git a/deployer/management/src/main/java/org/apache/karaf/cave/deployer/management/CaveDeployerMBean.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/management/DeployerMBean.java
similarity index 97%
rename from deployer/management/src/main/java/org/apache/karaf/cave/deployer/management/CaveDeployerMBean.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/management/DeployerMBean.java
index ef9d37e..5e37d03 100644
--- a/deployer/management/src/main/java/org/apache/karaf/cave/deployer/management/CaveDeployerMBean.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/management/DeployerMBean.java
@@ -11,13 +11,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.management;
+package org.apache.karaf.cave.deployer.service.management;
 
 import javax.management.openmbean.TabularData;
 import java.util.List;
 import java.util.Map;
 
-public interface CaveDeployerMBean {
+public interface DeployerMBean {
 
     void registerConnection(String name, String jmxUrl, String karafName, String user, String password) throws Exception;
     void deleteConnection(String name) throws Exception;
diff --git a/deployer/management/src/main/java/org/apache/karaf/cave/deployer/management/internal/CaveDeployerMBeanImpl.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/management/DeployerMBeanService.java
similarity index 94%
rename from deployer/management/src/main/java/org/apache/karaf/cave/deployer/management/internal/CaveDeployerMBeanImpl.java
rename to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/management/DeployerMBeanService.java
index 3f4db03..569e060 100644
--- a/deployer/management/src/main/java/org/apache/karaf/cave/deployer/management/internal/CaveDeployerMBeanImpl.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/management/DeployerMBeanService.java
@@ -11,10 +11,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.management.internal;
+package org.apache.karaf.cave.deployer.service.management;
 
-import org.apache.karaf.cave.deployer.api.*;
-import org.apache.karaf.cave.deployer.management.CaveDeployerMBean;
+import org.apache.karaf.cave.deployer.Bundle;
+import org.apache.karaf.cave.deployer.Connection;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.Feature;
+import org.apache.karaf.cave.deployer.FeaturesRepository;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
 
 import javax.management.NotCompliantMBeanException;
 import javax.management.StandardMBean;
@@ -22,23 +27,16 @@ import javax.management.openmbean.*;
 import java.util.List;
 import java.util.Map;
 
-public class CaveDeployerMBeanImpl extends StandardMBean implements CaveDeployerMBean {
+@Component(name = "org.apache.karaf.cave.deployer.management", immediate = true, property = { "jmx.objectname=org.apache.karaf.cave:type=deployer" })
+public class DeployerMBeanService extends StandardMBean implements DeployerMBean {
 
-    private Deployer deployer;
+    @Reference
+    private DeployerService deployer;
 
-    public CaveDeployerMBeanImpl() throws NotCompliantMBeanException {
-        super(CaveDeployerMBean.class);
+    public DeployerMBeanService() throws NotCompliantMBeanException {
+        super(DeployerMBean.class);
     }
 
-    public Deployer getDeployer() {
-        return deployer;
-    }
-
-    public void setDeployer(Deployer deployer) {
-        this.deployer = deployer;
-    }
-
-
     @Override
     public void registerConnection(String name, String jmxUrl, String karafName, String user, String password) throws Exception {
         Connection connection = new Connection();
diff --git a/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/rest/DeployerRestApi.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/rest/DeployerRestApi.java
new file mode 100644
index 0000000..f909438
--- /dev/null
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/rest/DeployerRestApi.java
@@ -0,0 +1,253 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.cave.deployer.service.rest;
+
+import org.apache.karaf.cave.deployer.Bundle;
+import org.apache.karaf.cave.deployer.Connection;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.apache.karaf.cave.deployer.Feature;
+import org.apache.karaf.cave.deployer.FeaturesRepository;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import java.util.List;
+import java.util.Map;
+
+@Path("/")
+public class DeployerRestApi {
+
+    private DeployerService deployerService;
+
+    public DeployerRestApi(DeployerService deployerService) {
+        this.deployerService = deployerService;
+    }
+
+    @GET
+    @Path("/connections")
+    @Produces("application/json")
+    List<Connection> getConnections() throws Exception {
+        return deployerService.connections();
+    }
+
+    @POST
+    @Path("/connections")
+    @Consumes("application/json")
+    public void addConnection(Connection connection) throws Exception {
+        deployerService.registerConnection(connection);
+    }
+
+    @DELETE
+    @Path("/connections/{connection}")
+    public void removeConnection(@PathParam(value = "connection") String connection) throws Exception {
+        deployerService.deleteConnection(connection);
+    }
+
+    @POST
+    @Path("/explode")
+    @Produces("application/json")
+    public List<String> explode(@HeaderParam(value = "url") String url, @HeaderParam(value = "repository") String repository) throws Exception {
+        return deployerService.explode(url, repository);
+    }
+
+    @POST
+    @Path("/extract")
+    public void extract(@HeaderParam(value = "url") String url, @HeaderParam(value = "directory") String directory) throws Exception {
+        deployerService.extract(url, directory);
+    }
+
+    @POST
+    @Path("/download")
+    public void download(@HeaderParam(value = "artifact") String artifact, @HeaderParam(value = "directory") String directory) throws Exception {
+        deployerService.download(artifact, directory);
+    }
+
+    @POST
+    @Path("/upload")
+    public void upload(@HeaderParam(value = "groupId") String groupId, @HeaderParam(value = "artifactId") String artifactId, @HeaderParam(value = "version") String version, @HeaderParam(value = "artifactUrl") String artifactUrl, @HeaderParam(value = "repositoryUrl") String repositoryUrl) throws Exception {
+        deployerService.upload(groupId, artifactId, version, artifactUrl, repositoryUrl);
+    }
+
+    @POST
+    @Path("/connections/{connection}/bundles")
+    public void installBundle(@HeaderParam(value = "artifactUrl") String artifactUrl, @PathParam(value = "connection") String connection) throws Exception {
+        deployerService.installBundle(artifactUrl, connection);
+    }
+
+    @DELETE
+    @Path("/connections/{connection}/bundles/{id}")
+    public void uninstallBundle(@PathParam(value = "id") String id, @PathParam(value = "connection") String connection) throws Exception {
+        deployerService.uninstallBundle(id, connection);
+    }
+
+    @POST
+    @Path("/connections/{connection}/bundles/{id}/start")
+    public void startBundle(@PathParam(value = "id") String id, @PathParam(value = "connection") String connection) throws Exception {
+        deployerService.startBundle(id, connection);
+    }
+
+    @POST
+    @Path("/connections/{connection}/bundles/{id}/stop")
+    public void stopBundle(@PathParam(value = "id") String id, @PathParam(value = "connection") String connection) throws Exception {
+        deployerService.stopBundle(id, connection);
+    }
+
+    @GET
+    @Path("/connections/{connection}/bundles")
+    @Produces("application/json")
+    public List<Bundle> listBundles(@PathParam(value = "connection") String connection) throws Exception {
+        return deployerService.bundles(connection);
+    }
+
+    @POST
+    @Path("/connections/{connection}/kars")
+    public void installKar(@HeaderParam(value = "artifactUrl") String artifactUrl, @PathParam(value = "connection") String connection) throws Exception {
+        deployerService.installKar(artifactUrl, connection);
+    }
+
+    @DELETE
+    @Path("/connections/{connection}/kars/{id}")
+    public void uninstallKar(@PathParam(value = "id") String id, @PathParam(value = "connection") String connection) throws Exception {
+        deployerService.uninstallKar(id, connection);
+    }
+
+    @GET
+    @Path("/connections/{connection}/kars")
+    public List<String> listKars(@PathParam(value = "connection") String connection) throws Exception {
+        return deployerService.kars(connection);
+    }
+
+    @GET
+    @Path("/features/repository")
+    public List<Feature> providedFeatures(@HeaderParam(value = "featuresRepositoryUrl") String featuresRepositoryUrl) throws Exception {
+        return deployerService.providedFeatures(featuresRepositoryUrl);
+    }
+
+    @GET
+    @Path("/connections/{connection}/features/repositories")
+    @Produces("application/json")
+    public List<FeaturesRepository> listFeaturesRepositories(@PathParam(value = "connection") String connection) throws Exception {
+        return deployerService.featuresRepositories(connection);
+    }
+
+    @POST
+    @Path("/connections/{connection}/features/repositories")
+    public void addFeaturesRepository(@HeaderParam(value = "artifactUrl") String artifactUrl, @PathParam(value = "connection") String connection) throws Exception {
+        deployerService.addFeaturesRepository(artifactUrl, connection);
+    }
+
+    @DELETE
+    @Path("/connections/{connection}/features/repositories")
+    public void removeFeaturesRepository(@HeaderParam(value = "artifactUrl") String artifactUrl, @PathParam(value = "connection") String connection) throws Exception {
+        deployerService.removeFeaturesRepository(artifactUrl, connection);
+    }
+
+    @GET
+    @Path("/connections/{connection}/features")
+    @Produces("application/json")
+    public List<Feature> listFeatures(@PathParam(value = "connection") String connection) throws Exception {
+        return deployerService.features(connection);
+    }
+
+    @POST
+    @Path("/connections/{connection}/features/{feature}")
+    public void installFeature(@PathParam(value = "feature") String feature, @PathParam(value = "connection") String connection) throws Exception {
+        deployerService.installFeature(feature, connection);
+    }
+
+    @DELETE
+    @Path("/connections/{connection}/features/{feature}")
+    public void uninstallFeature(@PathParam(value = "feature") String feature, @PathParam(value = "connection") String connection) throws Exception {
+        deployerService.uninstallFeature(feature, connection);
+    }
+
+    @GET
+    @Path("/connections/{connection}/configurations")
+    @Produces("application/json")
+    public List<String> getConfigs(@PathParam(value = "connection") String connection) throws Exception {
+        return deployerService.configs(connection);
+    }
+
+    @POST
+    @Path("/connections/{connection}/configurations/{pid}")
+    public void createconfig(@PathParam(value = "pid") String pid, @PathParam(value = "connection") String connection) throws Exception {
+        deployerService.createConfig(pid, connection);
+    }
+
+    @POST
+    @Path("/connections/{connection}/configurations/factories/{factoryPid}")
+    public void createConfigFactory(@PathParam(value = "factoryPid") String pid, @PathParam(value = "connection") String connection) throws Exception {
+        deployerService.createConfigurationFactory(pid, connection);
+    }
+
+    @GET
+    @Path("/connections/{connection}/configurations/{pid}/properties")
+    @Produces("application/json")
+    public Map<String, String> getConfigProperties(@PathParam(value = "pid")String pid, @PathParam(value = "connection") String connection) throws Exception {
+        return deployerService.configProperties(pid, connection);
+    }
+
+    @DELETE
+    @Path("/connections/{connection}/configurations/{pid}")
+    public void deleteConfig(@PathParam(value = "pid") String pid, @PathParam(value = "connection") String connection) throws Exception {
+        deployerService.deleteConfig(pid, connection);
+    }
+
+    @GET
+    @Path("/connections/{connection}/cluster/nodes")
+    @Produces("application/json")
+    public List<String> listClusterNodes(@PathParam(value = "connection") String connection) throws Exception {
+        return deployerService.clusterNodes(connection);
+    }
+
+    @GET
+    @Path("/connections/{connection}/cluster/groups")
+    @Produces("application/json")
+    public Map<String, List<String>> listClusterGroups(@PathParam(value = "connection") String connection) throws Exception {
+        return deployerService.clusterGroups(connection);
+    }
+
+    @POST
+    @Path("/connections/{connection}/cluster/groups/{group}/features/repositories")
+    public void clusterAddFeaturesRepository(@PathParam(value = "connection") String connection, @PathParam(value = "group") String clusterGroup, @HeaderParam(value = "url") String repositoryUrl) throws Exception {
+        deployerService.clusterAddFeaturesRepository(repositoryUrl, clusterGroup, connection);
+    }
+
+    @DELETE
+    @Path("/connections/{connection}/cluster/groups/{group}/features/repositories")
+    public void clusterRemoveFeaturesRepository(@PathParam(value = "connection") String connection, @PathParam(value = "group") String clusterGroup, @HeaderParam(value = "url") String repositoryUrl) throws Exception {
+        deployerService.clusterRemoveFeaturesRepository(repositoryUrl, clusterGroup, connection);
+    }
+
+    @POST
+    @Path("/connections/{connection}/cluster/groups/{group}/features/{feature}")
+    public void clusterInstallFeature(@PathParam(value = "connection") String connection, @PathParam(value = "group") String clusterGroup, @PathParam(value = "feature") String feature) throws Exception {
+        deployerService.clusterFeatureInstall(feature, clusterGroup, connection);
+    }
+
+    @DELETE
+    @Path("/connections/{connection}/cluster/groups/{group}/features/{feature}")
+    public void clusterUninstallFeature(@PathParam(value = "connection") String connection, @PathParam(value = "group") String clusterGroup, @PathParam(value = "feature") String feature) throws Exception {
+        deployerService.clusterFeatureUninstall(feature, clusterGroup, connection);
+    }
+
+}
diff --git a/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/rest/DeployerRestService.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/rest/DeployerRestService.java
new file mode 100644
index 0000000..0fe5ec0
--- /dev/null
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/rest/DeployerRestService.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.cave.deployer.service.rest;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.bus.extension.ExtensionManagerBus;
+import org.apache.cxf.transport.http.DestinationRegistry;
+import org.apache.cxf.transport.http.DestinationRegistryImpl;
+import org.apache.cxf.transport.http.HTTPTransportFactory;
+import org.apache.karaf.cave.deployer.DeployerService;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.http.HttpService;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Component(name = "org.apache.karaf.cave.deployer.rest")
+public class DeployerRestService {
+
+    @Reference
+    private DeployerService deployerService;
+
+    @Reference
+    private HttpService httpService;
+
+    @Activate
+    public void activate(ComponentContext componentContext) throws Exception {
+        DeployerRestApi restApi = new DeployerRestApi(deployerService);
+
+        Map<Class<?>, Object> extensions = new HashMap<>();
+        DestinationRegistry destinationRegistry = new DestinationRegistryImpl();
+        HTTPTransportFactory httpTransportFactory = new HTTPTransportFactory(destinationRegistry);
+        extensions.put(HTTPTransportFactory.class, httpTransportFactory);
+        extensions.put(DestinationRegistry.class, destinationRegistry);
+        Bus bus = new ExtensionManagerBus(extensions, null, getClass().getClassLoader());
+        org.apache.cxf.transport.DestinationFactoryManager destinationFactoryManager = bus.getExtension(org.apache.cxf.transport.DestinationFactoryManager.class);
+        for (String url : HTTPTransportFactory.DEFAULT_NAMESPACES) {
+            destinationFactoryManager.registerDestinationFactory(url, httpTransportFactory);
+        }
+
+        DeployerRestServlet deployerRestServlet = new DeployerRestServlet(restApi, destinationRegistry, bus);
+
+        httpService.registerServlet("/cave/deployer/api", deployerRestServlet, null, null);
+    }
+
+    @Deactivate
+    public void deactivate() throws Exception {
+        httpService.unregister("/cave/deployer/api");
+    }
+
+}
diff --git a/rest/src/main/java/org/apache/karaf/cave/rest/CaveRestServlet.java b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/rest/DeployerRestServlet.java
similarity index 51%
copy from rest/src/main/java/org/apache/karaf/cave/rest/CaveRestServlet.java
copy to deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/rest/DeployerRestServlet.java
index 6801cfe..6f63e30 100644
--- a/rest/src/main/java/org/apache/karaf/cave/rest/CaveRestServlet.java
+++ b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/rest/DeployerRestServlet.java
@@ -14,47 +14,39 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.rest;
+package org.apache.karaf.cave.deployer.service.rest;
 
 import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
+import org.apache.cxf.Bus;
 import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
+import org.apache.cxf.jaxrs.JAXRSServiceFactoryBean;
+import org.apache.cxf.transport.http.DestinationRegistry;
 import org.apache.cxf.transport.servlet.CXFNonSpringServlet;
 
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletException;
 
-public class CaveRestServlet extends CXFNonSpringServlet {
+public class DeployerRestServlet extends CXFNonSpringServlet {
 
-    private RepositoryRest repositoryRest;
-    private DeployerRest deployerRest;
+    private DeployerRestApi restApi;
 
-    public CaveRestServlet(RepositoryRest repositoryRest, DeployerRest deployerRest) {
-        this.repositoryRest = repositoryRest;
-        this.deployerRest = deployerRest;
+    public DeployerRestServlet(DeployerRestApi restApi, DestinationRegistry destinationRegistry, Bus bus) {
+        super(destinationRegistry, false);
+        this.restApi = restApi;
+        this.setBus(bus);
     }
 
     @Override
     public void init(ServletConfig config) throws ServletException {
         super.init(config);
-
-        if (repositoryRest != null) {
-            JAXRSServerFactoryBean repositoryBean = new JAXRSServerFactoryBean();
-            repositoryBean.setAddress("/repository");
-            repositoryBean.setBus(getBus());
-            repositoryBean.setProvider(new JacksonJsonProvider());
-            repositoryBean.setServiceBean(repositoryRest);
-            repositoryBean.create();
-        }
-
-        if (deployerRest != null) {
-            JAXRSServerFactoryBean deployerBean = new JAXRSServerFactoryBean();
-            deployerBean.setAddress("/deployer");
-            deployerBean.setBus(getBus());
-            deployerBean.setProvider(new JacksonJsonProvider());
-            deployerBean.setServiceBean(deployerRest);
-            deployerBean.create();
+        if (restApi != null) {
+            JAXRSServerFactoryBean bean = new JAXRSServerFactoryBean();
+            bean.setAddress("/");
+            bean.setBus(getBus());
+            bean.setProvider(new JacksonJsonProvider());
+            bean.setServiceBean(restApi);
+            bean.create();
         }
-
     }
 
 }
diff --git a/deployer/service/src/test/java/org/apache/karaf/cave/deployer/service/impl/DeployerImplTest.java b/deployer/service/src/test/java/org/apache/karaf/cave/deployer/service/DeployerImplTest.java
similarity index 93%
rename from deployer/service/src/test/java/org/apache/karaf/cave/deployer/service/impl/DeployerImplTest.java
rename to deployer/service/src/test/java/org/apache/karaf/cave/deployer/service/DeployerImplTest.java
index 6b89571..02a792c 100644
--- a/deployer/service/src/test/java/org/apache/karaf/cave/deployer/service/impl/DeployerImplTest.java
+++ b/deployer/service/src/test/java/org/apache/karaf/cave/deployer/service/DeployerImplTest.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.service.impl;
+package org.apache.karaf.cave.deployer.service;
 
-import org.apache.karaf.cave.deployer.api.Config;
-import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.Config;
+import org.apache.karaf.cave.deployer.DeployerService;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -28,18 +28,17 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
 import java.util.zip.ZipOutputStream;
 
 public class DeployerImplTest {
 
-    private Deployer deployer;
+    private DeployerService deployer;
 
     @Before
     public void startup() {
         System.setProperty("java.protocol.handler.pkgs", "org.ops4j.pax.url");
         System.setProperty("java.io.tmpdir", "target");
-        deployer = new DeployerImpl();
+        deployer = new DeployerServiceImpl();
     }
 
     @Test
@@ -117,7 +116,7 @@ public class DeployerImplTest {
     @Test
     public void mvnParseTest() throws Exception {
         String mvnUrl = "mvn:testGroupId/testArtifactId/1.0";
-        Map<String, String> coordonates = DeployerImpl.parse(mvnUrl);
+        Map<String, String> coordonates = DeployerServiceImpl.parse(mvnUrl);
         Assert.assertEquals("testGroupId", coordonates.get("groupId"));
         Assert.assertEquals("testArtifactId", coordonates.get("artifactId"));
         Assert.assertEquals("1.0", coordonates.get("version"));
@@ -125,7 +124,7 @@ public class DeployerImplTest {
         Assert.assertNull(coordonates.get("classifier"));
 
         mvnUrl = "mvn:testGroupId/testArtifactId/1.0/kar";
-        coordonates = DeployerImpl.parse(mvnUrl);
+        coordonates = DeployerServiceImpl.parse(mvnUrl);
         Assert.assertEquals("testGroupId", coordonates.get("groupId"));
         Assert.assertEquals("testArtifactId", coordonates.get("artifactId"));
         Assert.assertEquals("1.0", coordonates.get("version"));
@@ -133,7 +132,7 @@ public class DeployerImplTest {
         Assert.assertNull(coordonates.get("classifier"));
 
         mvnUrl = "mvn:testGroupId/testArtifactId/1.0/xml/features";
-        coordonates = DeployerImpl.parse(mvnUrl);
+        coordonates = DeployerServiceImpl.parse(mvnUrl);
         Assert.assertEquals("testGroupId", coordonates.get("groupId"));
         Assert.assertEquals("testArtifactId", coordonates.get("artifactId"));
         Assert.assertEquals("1.0", coordonates.get("version"));
diff --git a/server/api/pom.xml b/gateway/api/pom.xml
similarity index 78%
rename from server/api/pom.xml
rename to gateway/api/pom.xml
index d10d12a..a3c1688 100644
--- a/server/api/pom.xml
+++ b/gateway/api/pom.xml
@@ -23,29 +23,27 @@
 
     <parent>
         <groupId>org.apache.karaf.cave</groupId>
-        <artifactId>org.apache.karaf.cave.server</artifactId>
-        <version>4.1.3-SNAPSHOT</version>
+        <artifactId>org.apache.karaf.cave.gateway</artifactId>
+        <version>4.2.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 
-    <groupId>org.apache.karaf.cave.server</groupId>
-    <artifactId>org.apache.karaf.cave.server.api</artifactId>
-    <name>Apache Karaf :: Cave :: Server :: API</name>
+    <groupId>org.apache.karaf.cave.gateway</groupId>
+    <artifactId>org.apache.karaf.cave.gateway.api</artifactId>
+    <name>Apache Karaf :: Cave :: Features Gateway :: API</name>
     <packaging>bundle</packaging>
 
-    <dependencies>
-    </dependencies>
-
     <build>
         <plugins>
             <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
+                <inherited>true</inherited>
+                <extensions>true</extensions>
                 <configuration>
                     <instructions>
-                        <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
                         <Export-Package>
-                            org.apache.karaf.cave.server.api
+                            org.apache.karaf.cave.gateway
                         </Export-Package>
                     </instructions>
                 </configuration>
@@ -53,4 +51,4 @@
         </plugins>
     </build>
 
-</project>
+</project>
\ No newline at end of file
diff --git a/server/api/src/main/java/org/apache/karaf/cave/server/api/CaveFeatureGateway.java b/gateway/api/src/main/java/org/apache/karaf/cave/gateway/FeaturesGatewayService.java
similarity index 64%
rename from server/api/src/main/java/org/apache/karaf/cave/server/api/CaveFeatureGateway.java
rename to gateway/api/src/main/java/org/apache/karaf/cave/gateway/FeaturesGatewayService.java
index 196dfe0..b451f6e 100644
--- a/server/api/src/main/java/org/apache/karaf/cave/server/api/CaveFeatureGateway.java
+++ b/gateway/api/src/main/java/org/apache/karaf/cave/gateway/FeaturesGatewayService.java
@@ -14,19 +14,34 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.server.api;
+package org.apache.karaf.cave.gateway;
 
-import java.io.File;
 import java.util.List;
 
-public interface CaveFeatureGateway {
-
-    String STORAGE = System.getProperty("karaf.data") + File.separator + "cave-features-gateway.xml";
+/**
+ * Manage features gateway.
+ */
+public interface FeaturesGatewayService {
 
+    /**
+     * Register a new features repository in the gateway.
+     *
+     * @param url the features repository XML URL.
+     */
     void register(String url) throws Exception;
 
+    /**
+     * Remove a features repository from the gateway.
+     *
+     * @param id the features repository name or URL.
+     */
     void remove(String id) throws Exception;
 
+    /**
+     * List the features repositories registered in the gateway.
+     *
+     * @return the features repositories registered in the gateway.
+     */
     List<String> list() throws Exception;
 
 }
diff --git a/server/pom.xml b/gateway/pom.xml
similarity index 81%
rename from server/pom.xml
rename to gateway/pom.xml
index 6b2f813..7841e34 100644
--- a/server/pom.xml
+++ b/gateway/pom.xml
@@ -24,22 +24,18 @@
     <parent>
         <groupId>org.apache.karaf</groupId>
         <artifactId>cave</artifactId>
-        <version>4.1.3-SNAPSHOT</version>
+        <version>4.2.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 
     <groupId>org.apache.karaf.cave</groupId>
-    <artifactId>org.apache.karaf.cave.server</artifactId>
-    <name>Apache Karaf :: Cave :: Server</name>
+    <artifactId>org.apache.karaf.cave.gateway</artifactId>
+    <name>Apache Karaf :: Cave :: Features Gateway</name>
     <packaging>pom</packaging>
 
     <modules>
         <module>api</module>
-        <module>storage</module>
-        <module>management</module>
-        <module>command</module>
-        <module>http</module>
-        <module>maven</module>
+        <module>service</module>
     </modules>
 
-</project>
+</project>
\ No newline at end of file
diff --git a/server/maven/pom.xml b/gateway/service/pom.xml
similarity index 62%
rename from server/maven/pom.xml
rename to gateway/service/pom.xml
index 4eb8ed4..dd851a7 100644
--- a/server/maven/pom.xml
+++ b/gateway/service/pom.xml
@@ -23,92 +23,79 @@
 
     <parent>
         <groupId>org.apache.karaf.cave</groupId>
-        <artifactId>org.apache.karaf.cave.server</artifactId>
-        <version>4.1.3-SNAPSHOT</version>
+        <artifactId>org.apache.karaf.cave.gateway</artifactId>
+        <version>4.2.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 
-    <groupId>org.apache.karaf.cave.server</groupId>
-    <artifactId>org.apache.karaf.cave.server.maven</artifactId>
+    <groupId>org.apache.karaf.cave.gateway</groupId>
+    <artifactId>org.apache.karaf.cave.gateway.service</artifactId>
+    <name>Apache Karaf :: Cave :: Features Gateway :: Service</name>
     <packaging>bundle</packaging>
-    <name>Apache Karaf :: Cave :: Server :: Maven Proxy</name>
-    <description>Maven Proxy Service</description>
-
-    <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>
-                        <Import-Package>
-                            javax.servlet.*;version="[3.0,4)",
-                            !shaded.*,
-                            *
-                        </Import-Package>
-                        <Export-Package>
-                            !*
-                        </Export-Package>
-                        <Private-Package>
-                            org.apache.karaf.cave.server.maven,
-                            org.apache.karaf.util,
-                            org.apache.karaf.util.base64
-                        </Private-Package>
-                    </instructions>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
 
     <dependencies>
         <dependency>
-            <groupId>org.apache.karaf.cave.server</groupId>
-            <artifactId>org.apache.karaf.cave.server.api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>${servlet.spec.groupId}</groupId>
-            <artifactId>${servlet.spec.artifactId}</artifactId>
+            <groupId>org.apache.karaf.cave.gateway</groupId>
+            <artifactId>org.apache.karaf.cave.gateway.api</artifactId>
         </dependency>
         <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>osgi.core</artifactId>
-            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>osgi.cmpn</artifactId>
-            <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>org.ops4j.pax.url</groupId>
-            <artifactId>pax-url-aether</artifactId>
-            <scope>provided</scope>
+            <groupId>org.apache.karaf.features</groupId>
+            <artifactId>org.apache.karaf.features.core</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.apache.karaf</groupId>
-            <artifactId>org.apache.karaf.util</artifactId>
+            <groupId>${servlet.spec.groupId}</groupId>
+            <artifactId>${servlet.spec.artifactId}</artifactId>
         </dependency>
-
-        <!-- Test Dependencies -->
         <dependency>
-            <groupId>org.ops4j.pax.web</groupId>
-            <artifactId>pax-web-jetty-bundle</artifactId>
-            <scope>test</scope>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.easymock</groupId>
-            <artifactId>easymock</artifactId>
-            <scope>test</scope>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-frontend-jaxrs</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-log4j12</artifactId>
-            <scope>test</scope>
+            <groupId>com.fasterxml.jackson.jaxrs</groupId>
+            <artifactId>jackson-jaxrs-json-provider</artifactId>
         </dependency>
     </dependencies>
 
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <inherited>true</inherited>
+                <configuration>
+                    <instructions>
+                        <Export-Package>
+                            !*
+                        </Export-Package>
+                        <Import-Package>
+                            com.fasterxml.jackson*;version="[2.8,3)",
+                            *
+                        </Import-Package>
+                        <Private-Package>
+                            org.apache.karaf.cave.gateway.service*,
+                            org.apache.karaf.features.internal.model,
+                            org.apache.karaf.features.internal.util,
+                            org.apache.karaf.util,
+                            org.apache.felix.utils*
+                        </Private-Package>
+                        <_dsannotations>*</_dsannotations>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
 </project>
\ No newline at end of file
diff --git a/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/FeaturesGatewayServiceImpl.java b/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/FeaturesGatewayServiceImpl.java
new file mode 100644
index 0000000..58a61cb
--- /dev/null
+++ b/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/FeaturesGatewayServiceImpl.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.cave.gateway.service;
+
+import org.apache.karaf.cave.gateway.FeaturesGatewayService;
+import org.apache.karaf.cave.gateway.service.http.FeaturesGatewayServlet;
+import org.apache.karaf.features.internal.model.Features;
+import org.apache.karaf.features.internal.model.JaxbUtil;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.http.HttpService;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.List;
+
+@Component(name = "org.apache.karaf.cave.gateway", immediate = true, service = FeaturesGatewayService.class)
+public class FeaturesGatewayServiceImpl implements FeaturesGatewayService {
+
+    @Reference
+    private HttpService httpService;
+
+    private String storage;
+    private String alias;
+
+    @Activate
+    public void activate(ComponentContext componentContext) throws Exception {
+        activate(componentContext.getProperties());
+    }
+
+    public void activate(Dictionary<String, Object> properties) throws Exception {
+        storage = (properties.get("storage.location") != null) ? properties.get("storage.location").toString() : System.getProperty("karaf.data") + File.separator + "cave" + File.separator + "features-gateway.xml";
+        alias = (properties.get("http.alias") != null) ? properties.get("http.alias").toString() : "/cave/features-gateway-repository";
+        FeaturesGatewayServlet featuresGatewayServlet = new FeaturesGatewayServlet(storage);
+        httpService.registerServlet(alias, featuresGatewayServlet, null, null);
+    }
+
+    @Deactivate
+    public void deactivate() {
+        httpService.unregister(alias);
+    }
+
+    @Override
+    public void register(String url) throws Exception {
+        File store = new File(storage);
+
+        Features features = new Features();
+        if (store.exists()) {
+            features = JaxbUtil.unmarshal(store.getAbsolutePath(), true);
+        } else {
+            store.getParentFile().mkdirs();
+            store.createNewFile();
+        }
+        features.setName("cave-features-gateway");
+        if (isFeaturesRepositoryRegistered(url, features.getRepository())) {
+            throw new IllegalArgumentException("Features repository " + url + " already registered in the gateway");
+        }
+        features.getRepository().add(url);
+
+        try (FileOutputStream fos = new FileOutputStream(store)) {
+            JaxbUtil.marshal(features, fos);
+        }
+    }
+
+    @Override
+    public void remove(String url) throws Exception {
+        File store = new File(storage);
+
+        if (!store.exists()) {
+            return;
+        }
+
+        Features features = JaxbUtil.unmarshal(store.getAbsolutePath(), true);
+        if (!isFeaturesRepositoryRegistered(url, features.getRepository())) {
+            throw new IllegalArgumentException("Features repository " + url + " is not registered in the gateway");
+        }
+        features.getRepository().remove(url);
+
+        try (FileOutputStream fos = new FileOutputStream(store)) {
+            JaxbUtil.marshal(features, fos);
+        }
+    }
+
+    @Override
+    public List<String> list() throws Exception {
+        List<String> featuresRepositories = new ArrayList<>();
+
+        File store = new File(storage);
+        if (store.exists()) {
+            Features features = JaxbUtil.unmarshal(store.getAbsolutePath(), true);
+            featuresRepositories = features.getRepository();
+        }
+
+        return featuresRepositories;
+    }
+
+    private boolean isFeaturesRepositoryRegistered(String url, List<String> repositories) {
+        for (String repository : repositories) {
+            if (repository.equals(url)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Only visible for testing.
+     */
+    protected void setHttpService(HttpService httpService) {
+        this.httpService = httpService;
+    }
+
+}
diff --git a/server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayListCommand.java b/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/command/FeaturesGatewayListCommand.java
similarity index 73%
rename from server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayListCommand.java
rename to gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/command/FeaturesGatewayListCommand.java
index 07c34b3..3d3f7da 100644
--- a/server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayListCommand.java
+++ b/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/command/FeaturesGatewayListCommand.java
@@ -14,24 +14,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.server.command;
+package org.apache.karaf.cave.gateway.service.command;
 
-import org.apache.karaf.cave.server.api.CaveFeatureGateway;
+import org.apache.karaf.cave.gateway.FeaturesGatewayService;
 import org.apache.karaf.shell.api.action.Action;
 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;
 
 @Service
-@Command(scope = "cave", name = "gateway-list", description = "List the features repositories registered in the gateway")
-public class GatewayListCommand implements Action {
+@Command(scope = "cave", name = "features-gateway-list", description = "List the features repositories registered in the gateway")
+public class FeaturesGatewayListCommand implements Action {
 
     @Reference
-    private CaveFeatureGateway gateway;
+    private FeaturesGatewayService featuresGatewayService;
 
     @Override
     public Object execute() throws Exception {
-        for (String repository : gateway.list()) {
+        for (String repository : featuresGatewayService.list()) {
             System.out.println(repository);
         }
         return null;
diff --git a/server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRegisterCommand.java b/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/command/FeaturesGatewayRegisterCommand.java
similarity index 64%
rename from server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRegisterCommand.java
rename to gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/command/FeaturesGatewayRegisterCommand.java
index 86c5f70..0dea576 100644
--- a/server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRegisterCommand.java
+++ b/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/command/FeaturesGatewayRegisterCommand.java
@@ -14,28 +14,32 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.server.command;
+package org.apache.karaf.cave.gateway.service.command;
 
-import org.apache.karaf.cave.server.api.CaveFeatureGateway;
+import org.apache.karaf.cave.gateway.FeaturesGatewayService;
 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;
 
-@Service
-@Command(scope = "cave", name = "gateway-register", description = "Register a features repository in the gateway")
-public class GatewayRegisterCommand implements Action {
+import java.util.List;
 
-    @Argument(name = "repository", description = "The features repository URL ", required = true, multiValued = false)
-    String repository;
+@Service
+@Command(scope = "cave", name = "features-gateway-register", description = "Register features repository URLs in the gateway")
+public class FeaturesGatewayRegisterCommand implements Action {
 
     @Reference
-    CaveFeatureGateway gateway;
+    private FeaturesGatewayService featuresGatewayService;
+
+    @Argument(index = 0, name = "urls", description = "The features repository URLs to register in the gateway", required = true, multiValued = true)
+    List<String> urls;
 
     @Override
     public Object execute() throws Exception {
-        gateway.register(repository);
+        for (String url : urls) {
+            featuresGatewayService.register(url);
+        }
         return null;
     }
 
diff --git a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureInstalledListCommand.java b/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/command/FeaturesGatewayRemoveCommand.java
similarity index 62%
rename from deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureInstalledListCommand.java
rename to gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/command/FeaturesGatewayRemoveCommand.java
index 4030a3a..981e5da 100644
--- a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureInstalledListCommand.java
+++ b/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/command/FeaturesGatewayRemoveCommand.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.deployer.command;
+package org.apache.karaf.cave.gateway.service.command;
 
-import org.apache.karaf.cave.deployer.api.Deployer;
-import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.cave.gateway.FeaturesGatewayService;
+import org.apache.karaf.cave.gateway.service.command.completers.FeaturesRepositoryUrlCompleter;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
@@ -28,21 +28,20 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 import java.util.List;
 
 @Service
-@Command(scope = "cave", name = "deployer-feature-installed-list", description = "List the features installed on a remote host")
-public class FeatureInstalledListCommand implements Action {
+@Command(scope = "cave", name = "features-gateway-remove", description = "Remove features repository XML URLs from the gateway")
+public class FeaturesGatewayRemoveCommand implements Action {
 
     @Reference
-    private Deployer deployer;
+    private FeaturesGatewayService featuresGatewayService;
 
-    @Argument(index = 0, name = "connection", description = "The connection to use", required = true, multiValued = false)
-    @Completion(ConnectionCompleter.class)
-    String connection;
+    @Argument(index = 0, name = "urls", description = "The features repository URLs to remove from the gateway", required = true, multiValued = true)
+    @Completion(FeaturesRepositoryUrlCompleter.class)
+    List<String> urls;
 
     @Override
     public Object execute() throws Exception {
-        List<String> features = deployer.installedFeatures(connection);
-        for (String feature : features) {
-            System.out.println(feature);
+        for (String url : urls) {
+            featuresGatewayService.remove(url);
         }
         return null;
     }
diff --git a/server/command/src/main/java/org/apache/karaf/cave/server/command/completers/GatewayCompleter.java b/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/command/completers/FeaturesRepositoryUrlCompleter.java
similarity index 81%
rename from server/command/src/main/java/org/apache/karaf/cave/server/command/completers/GatewayCompleter.java
rename to gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/command/completers/FeaturesRepositoryUrlCompleter.java
index 7e85a37..442a7d6 100644
--- a/server/command/src/main/java/org/apache/karaf/cave/server/command/completers/GatewayCompleter.java
+++ b/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/command/completers/FeaturesRepositoryUrlCompleter.java
@@ -14,9 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.server.command.completers;
+package org.apache.karaf.cave.gateway.service.command.completers;
 
-import org.apache.karaf.cave.server.api.CaveFeatureGateway;
+import org.apache.karaf.cave.gateway.FeaturesGatewayService;
 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;
@@ -27,20 +27,20 @@ import org.apache.karaf.shell.support.completers.StringsCompleter;
 import java.util.List;
 
 @Service
-public class GatewayCompleter implements Completer {
+public class FeaturesRepositoryUrlCompleter implements Completer {
 
     @Reference
-    private CaveFeatureGateway gateway;
+    private FeaturesGatewayService featuresGatewayService;
 
     @Override
     public int complete(Session session, CommandLine commandLine, List<String> list) {
         StringsCompleter delegate = new StringsCompleter();
         try {
-            for (String repository : gateway.list()) {
+            for (String repository : featuresGatewayService.list()) {
                 delegate.getStrings().add(repository);
             }
         } catch (Exception e) {
-            // ignore
+            // nothing to do
         }
         return delegate.complete(session, commandLine, list);
     }
diff --git a/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/http/FeaturesGatewayServlet.java b/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/http/FeaturesGatewayServlet.java
new file mode 100644
index 0000000..8d95c11
--- /dev/null
+++ b/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/http/FeaturesGatewayServlet.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.cave.gateway.service.http;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+public class FeaturesGatewayServlet extends HttpServlet {
+
+    private String storage;
+
+    public FeaturesGatewayServlet(String storage) {
+        this.storage = storage;
+    }
+
+    @Override
+    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+        response.setContentType("application/xml");
+        File store = new File(storage);
+        if (store.exists()) {
+            try (BufferedReader reader = new BufferedReader(new FileReader(store))) {
+                try (PrintWriter writer = response.getWriter()) {
+                    String line;
+                    while ((line = reader.readLine()) != null) {
+                        writer.println(line);
+                    }
+                    writer.flush();
+                }
+            }
+        }
+    }
+
+}
diff --git a/server/management/src/main/java/org/apache/karaf/cave/server/management/CaveGatewayMBean.java b/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/management/FeaturesGatewayMBean.java
similarity index 70%
rename from server/management/src/main/java/org/apache/karaf/cave/server/management/CaveGatewayMBean.java
rename to gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/management/FeaturesGatewayMBean.java
index fdca603..9f6da48 100644
--- a/server/management/src/main/java/org/apache/karaf/cave/server/management/CaveGatewayMBean.java
+++ b/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/management/FeaturesGatewayMBean.java
@@ -11,14 +11,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.server.management;
+package org.apache.karaf.cave.gateway.service.management;
 
 import java.util.List;
 
-public interface CaveGatewayMBean {
+public interface FeaturesGatewayMBean {
 
-    List<String> list() throws Exception;
-    void register(String repository) throws Exception;
-    void remove(String repository) throws Exception;
+    List<String> getRepositories() throws Exception;
+
+    void register(String url) throws Exception;
+    void remove(String url) throws Exception;
 
 }
diff --git a/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/management/FeaturesGatewayMBeanService.java b/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/management/FeaturesGatewayMBeanService.java
new file mode 100644
index 0000000..2488202
--- /dev/null
+++ b/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/management/FeaturesGatewayMBeanService.java
@@ -0,0 +1,48 @@
+/*
+ * 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.cave.gateway.service.management;
+
+import org.apache.karaf.cave.gateway.FeaturesGatewayService;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+import javax.management.NotCompliantMBeanException;
+import javax.management.StandardMBean;
+import java.util.List;
+
+@Component(name = "org.apache.karaf.cave.gateway.management", property = { "jmx.objectname=org.apache.karaf.cave:type=gateway" })
+public class FeaturesGatewayMBeanService extends StandardMBean implements FeaturesGatewayMBean {
+
+    @Reference
+    private FeaturesGatewayService featuresGatewayService;
+
+    public FeaturesGatewayMBeanService() throws NotCompliantMBeanException {
+        super(FeaturesGatewayMBean.class);
+    }
+
+    @Override
+    public List<String> getRepositories() throws Exception {
+        return featuresGatewayService.list();
+    }
+
+    @Override
+    public void register(String url) throws Exception {
+        featuresGatewayService.register(url);
+    }
+
+    @Override
+    public void remove(String url) throws Exception {
+        featuresGatewayService.remove(url);
+    }
+}
diff --git a/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/rest/FeaturesGatewayRestApi.java b/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/rest/FeaturesGatewayRestApi.java
new file mode 100644
index 0000000..55bd29f
--- /dev/null
+++ b/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/rest/FeaturesGatewayRestApi.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.cave.gateway.service.rest;
+
+import org.apache.karaf.cave.gateway.FeaturesGatewayService;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import java.util.List;
+
+@Path("/")
+public class FeaturesGatewayRestApi {
+
+    private FeaturesGatewayService featuresGatewayService;
+
+    public FeaturesGatewayRestApi(FeaturesGatewayService featuresGatewayService) {
+        this.featuresGatewayService = featuresGatewayService;
+    }
+
+    @GET
+    @Path("/")
+    @Produces("application/json")
+    public List<String> getFeaturesRepositories() throws Exception {
+        return featuresGatewayService.list();
+    }
+
+    @POST
+    @Path("/")
+    @Consumes("application/json")
+    public void register(@HeaderParam(value = "url") String url) throws Exception {
+        featuresGatewayService.register(url);
+    }
+
+    @DELETE
+    @Path("/")
+    @Consumes("application/json")
+    public void remove(@HeaderParam(value = "url") String url) throws Exception {
+        featuresGatewayService.remove(url);
+    }
+
+}
diff --git a/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/rest/FeaturesGatewayRestService.java b/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/rest/FeaturesGatewayRestService.java
new file mode 100644
index 0000000..b9db810
--- /dev/null
+++ b/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/rest/FeaturesGatewayRestService.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.cave.gateway.service.rest;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.bus.extension.ExtensionManagerBus;
+import org.apache.cxf.transport.http.DestinationRegistry;
+import org.apache.cxf.transport.http.DestinationRegistryImpl;
+import org.apache.cxf.transport.http.HTTPTransportFactory;
+import org.apache.karaf.cave.gateway.FeaturesGatewayService;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.http.HttpService;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Component(name = "org.apache.karaf.cave.gateway.rest")
+public class FeaturesGatewayRestService {
+
+    @Reference
+    private FeaturesGatewayService featuresGatewayService;
+
+    @Reference
+    private HttpService httpService;
+
+    @Activate
+    public void activate(ComponentContext componentContext) throws Exception {
+        FeaturesGatewayRestApi restApi = new FeaturesGatewayRestApi(featuresGatewayService);
+
+        Map<Class<?>, Object> extensions = new HashMap<>();
+        DestinationRegistry destinationRegistry = new DestinationRegistryImpl();
+        HTTPTransportFactory httpTransportFactory = new HTTPTransportFactory(destinationRegistry);
+        extensions.put(HTTPTransportFactory.class, httpTransportFactory);
+        extensions.put(DestinationRegistry.class, destinationRegistry);
+        Bus bus = new ExtensionManagerBus(extensions, null, getClass().getClassLoader());
+        org.apache.cxf.transport.DestinationFactoryManager destinationFactoryManager = bus.getExtension(org.apache.cxf.transport.DestinationFactoryManager.class);
+        for (String url : HTTPTransportFactory.DEFAULT_NAMESPACES) {
+            destinationFactoryManager.registerDestinationFactory(url, httpTransportFactory);
+        }
+
+        FeaturesGatewayRestServlet featuresGatewayRestServlet = new FeaturesGatewayRestServlet(restApi, destinationRegistry, bus);
+        httpService.registerServlet("/cave/features-gateway/api", featuresGatewayRestServlet, null, null);
+    }
+
+    @Deactivate
+    public void deactivate() throws Exception {
+        httpService.unregister("/cave/features-gateway/api");
+    }
+
+}
diff --git a/rest/src/main/java/org/apache/karaf/cave/rest/CaveRestServlet.java b/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/rest/FeaturesGatewayRestServlet.java
similarity index 51%
copy from rest/src/main/java/org/apache/karaf/cave/rest/CaveRestServlet.java
copy to gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/rest/FeaturesGatewayRestServlet.java
index 6801cfe..df973f7 100644
--- a/rest/src/main/java/org/apache/karaf/cave/rest/CaveRestServlet.java
+++ b/gateway/service/src/main/java/org/apache/karaf/cave/gateway/service/rest/FeaturesGatewayRestServlet.java
@@ -14,47 +14,38 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.rest;
+package org.apache.karaf.cave.gateway.service.rest;
 
 import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
+import org.apache.cxf.Bus;
 import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
+import org.apache.cxf.transport.http.DestinationRegistry;
 import org.apache.cxf.transport.servlet.CXFNonSpringServlet;
 
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletException;
 
-public class CaveRestServlet extends CXFNonSpringServlet {
+public class FeaturesGatewayRestServlet extends CXFNonSpringServlet {
 
-    private RepositoryRest repositoryRest;
-    private DeployerRest deployerRest;
+    private FeaturesGatewayRestApi restApi;
 
-    public CaveRestServlet(RepositoryRest repositoryRest, DeployerRest deployerRest) {
-        this.repositoryRest = repositoryRest;
-        this.deployerRest = deployerRest;
+    public FeaturesGatewayRestServlet(FeaturesGatewayRestApi restApi, DestinationRegistry destinationRegistry, Bus bus) {
+        super(destinationRegistry, false);
+        this.restApi = restApi;
+        this.setBus(bus);
     }
 
     @Override
     public void init(ServletConfig config) throws ServletException {
         super.init(config);
-
-        if (repositoryRest != null) {
-            JAXRSServerFactoryBean repositoryBean = new JAXRSServerFactoryBean();
-            repositoryBean.setAddress("/repository");
-            repositoryBean.setBus(getBus());
-            repositoryBean.setProvider(new JacksonJsonProvider());
-            repositoryBean.setServiceBean(repositoryRest);
-            repositoryBean.create();
-        }
-
-        if (deployerRest != null) {
-            JAXRSServerFactoryBean deployerBean = new JAXRSServerFactoryBean();
-            deployerBean.setAddress("/deployer");
-            deployerBean.setBus(getBus());
-            deployerBean.setProvider(new JacksonJsonProvider());
-            deployerBean.setServiceBean(deployerRest);
-            deployerBean.create();
+        if (restApi != null) {
+            JAXRSServerFactoryBean bean = new JAXRSServerFactoryBean();
+            bean.setAddress("/");
+            bean.setBus(getBus());
+            bean.setProvider(new JacksonJsonProvider());
+            bean.setServiceBean(restApi);
+            bean.create();
         }
-
     }
 
 }
diff --git a/gateway/service/src/test/java/org/apache/karaf/cave/gateway/service/FeaturesGatewayServiceImplTest.java b/gateway/service/src/test/java/org/apache/karaf/cave/gateway/service/FeaturesGatewayServiceImplTest.java
new file mode 100644
index 0000000..3d1d2a0
--- /dev/null
+++ b/gateway/service/src/test/java/org/apache/karaf/cave/gateway/service/FeaturesGatewayServiceImplTest.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.cave.gateway.service;
+
+import org.easymock.EasyMock;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.service.http.HttpService;
+
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+public class FeaturesGatewayServiceImplTest {
+
+    private FeaturesGatewayServiceImpl featuresGatewayService;
+
+    @Before
+    public void setup() throws Exception {
+        HttpService httpService = EasyMock.createMock(HttpService.class);
+        featuresGatewayService = new FeaturesGatewayServiceImpl();
+        featuresGatewayService.setHttpService(httpService);
+        Dictionary<String, Object> properties = new Hashtable<>();
+        properties.put("storage.location", "target/cave/cave-features-gateway.xml");
+        featuresGatewayService.activate(properties);
+    }
+
+    @Test
+    public void testRegistrationUnregistration() throws Exception {
+        featuresGatewayService.register("mvn:org.apache.karaf.features/standard/4.2.6/xml/features");
+        Assert.assertEquals(1, featuresGatewayService.list().size());
+        Assert.assertEquals("mvn:org.apache.karaf.features/standard/4.2.6/xml/features", featuresGatewayService.list().get(0));
+        Assert.assertTrue(Files.exists(Paths.get("target/cave/cave-features-gateway.xml")));
+
+        featuresGatewayService.remove("mvn:org.apache.karaf.features/standard/4.2.6/xml/features");
+        Assert.assertEquals(0, featuresGatewayService.list().size());
+    }
+
+}
diff --git a/itest/pom.xml b/itest/pom.xml
new file mode 100644
index 0000000..5a84cbc
--- /dev/null
+++ b/itest/pom.xml
@@ -0,0 +1,133 @@
+<?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>cave</artifactId>
+        <version>4.2.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <groupId>org.apache.karaf.cave</groupId>
+    <artifactId>itest</artifactId>
+    <name>Apache Karaf :: Cave :: Integration Tests</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.karaf.itests</groupId>
+            <artifactId>common</artifactId>
+            <version>${karaf.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>apache-karaf</artifactId>
+            <version>${karaf.version}</version>
+            <scope>test</scope>
+            <type>tar.gz</type>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.karaf</groupId>
+                    <artifactId>org.apache.karaf.client</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-container-karaf</artifactId>
+            <version>4.13.1</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-junit4</artifactId>
+            <version>4.13.1</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.geronimo.specs</groupId>
+            <artifactId>geronimo-atinject_1.0_spec</artifactId>
+            <version>1.1</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
+            <version>3.1.6</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.servicemix.bundles</groupId>
+            <artifactId>org.apache.servicemix.bundles.hamcrest</artifactId>
+            <version>1.3_1</version>
+            <scope>runtime</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.servicemix.tooling</groupId>
+                <artifactId>depends-maven-plugin</artifactId>
+                <version>1.4.0</version>
+                <executions>
+                    <execution>
+                        <id>generate-depends-file</id>
+                        <goals>
+                            <goal>generate-depends-file</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <forkCount>1</forkCount>
+                    <reuseForks>false</reuseForks>
+                    <systemPropertyVariables>
+                        <org.ops4j.pax.logging.DefaultServiceLog.level>INFO</org.ops4j.pax.logging.DefaultServiceLog.level>
+                        <spring31.version>3.1.4.RELEASE</spring31.version>
+                        <spring32.version>3.2.18.RELEASE_1</spring32.version>
+                        <spring40.version>4.0.9.RELEASE_1</spring40.version>
+                        <spring41.version>4.1.9.RELEASE_1</spring41.version>
+                        <spring42.version>4.2.9.RELEASE_1</spring42.version>
+                        <spring43.version>4.3.25.RELEASE_1</spring43.version>
+                        <spring50.version>5.0.15.RELEASE_1</spring50.version>
+                        <spring51.version>5.1.9.RELEASE_1</spring51.version>
+                        <spring.security31.version>3.1.4.RELEASE</spring.security31.version>
+                        <spring.security42.version>4.2.4.RELEASE_1</spring.security42.version>
+                        <spring.security51.version>5.1.5.RELEASE_1</spring.security51.version>
+                        <activemq.version>5.15.9</activemq.version>
+                        <cave.version>${project.version}</cave.version>
+                    </systemPropertyVariables>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file
diff --git a/itest/src/test/java/org/apache/karaf/cave/itest/repository/AllInTest.java b/itest/src/test/java/org/apache/karaf/cave/itest/repository/AllInTest.java
new file mode 100644
index 0000000..c045a5f
--- /dev/null
+++ b/itest/src/test/java/org/apache/karaf/cave/itest/repository/AllInTest.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.cave.itest.repository;
+
+import org.apache.karaf.itests.KarafTestSupport;
+import org.apache.karaf.jaas.boot.principal.RolePrincipal;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.karaf.options.KarafDistributionOption;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerClass;
+
+import java.util.stream.Stream;
+
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerClass.class)
+public class AllInTest extends KarafTestSupport {
+
+    @Configuration
+    public Option[] config() {
+        Option[] options = new Option[]{
+                KarafDistributionOption.editConfigurationFilePut("etc/system.properties", "cave.version", System.getProperty("cave.version"))
+        };
+        return Stream.of(super.config(), options).flatMap(Stream::of).toArray(Option[]::new);
+    }
+
+    @Test
+    public void test() throws Exception {
+        System.out.println(executeCommand("feature:repo-add cave " + System.getProperty("cave.version")));
+        System.out.println("==== Install cave-repository, cave-deployer, cave-features-gateway ====");
+        System.out.println(executeCommand("feature:install cave-repository", new RolePrincipal("admin")));
+        System.out.println(executeCommand("feature:install cave-deployer", new RolePrincipal("admin")));
+        System.out.println(executeCommand("feature:install cave-features-gateway", new RolePrincipal("admin")));
+
+        Thread.sleep(1000);
+
+        System.out.println("==== Installed Features ====");
+        String featureList = executeCommand("feature:list -i");
+        assertContains("cave-repository", featureList);
+        assertContains("cave-deployer", featureList);
+        assertContains("cave-features-gateway", featureList);
+        System.out.println(featureList);
+        String httpList = executeCommand("http:list");
+        System.out.println("==== HTTP List ====");
+        System.out.println(httpList);
+        assertContains("/cave/features-gateway/api", httpList);
+        assertContains("/cave/repository/api", httpList);
+        assertContains("/cave/deployer/api", httpList);
+        assertContainsNot("Failed", httpList);
+    }
+
+}
diff --git a/itest/src/test/java/org/apache/karaf/cave/itest/repository/DeployerTest.java b/itest/src/test/java/org/apache/karaf/cave/itest/repository/DeployerTest.java
new file mode 100644
index 0000000..511eb44
--- /dev/null
+++ b/itest/src/test/java/org/apache/karaf/cave/itest/repository/DeployerTest.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.cave.itest.repository;
+
+import org.apache.karaf.itests.KarafTestSupport;
+import org.apache.karaf.jaas.boot.principal.RolePrincipal;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.karaf.options.KarafDistributionOption;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerClass;
+
+import java.util.stream.Stream;
+
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerClass.class)
+public class DeployerTest extends KarafTestSupport {
+
+    @Configuration
+    public Option[] config() {
+        Option[] options = new Option[]{
+                KarafDistributionOption.editConfigurationFilePut("etc/system.properties", "cave.version", System.getProperty("cave.version"))
+        };
+        return Stream.of(super.config(), options).flatMap(Stream::of).toArray(Option[]::new);
+    }
+
+    @Test
+    public void test() throws Exception {
+        System.out.println(executeCommand("feature:repo-add cave " + System.getProperty("cave.version")));
+        System.out.println(executeCommand("feature:install cave-deployer", new RolePrincipal("admin")));
+        String featureList = executeCommand("feature:list -i");
+        int count = 0;
+        while (!featureList.contains("cave-deployer") && count < 100) {
+            featureList = executeCommand("feature:list -i");
+            Thread.sleep(100);
+            count++;
+        }
+        if (count >= 100) {
+            throw new RuntimeException("cave-deployer feature is not installed");
+        }
+        System.out.println("==== Installed Features ====");
+        System.out.println(featureList);
+        String httpList = executeCommand("http:list");
+        System.out.println("==== HTTP List ====");
+        System.out.println(httpList);
+        assertContains("/cave/deployer/api", httpList);
+
+        System.out.println("==== Create Connection ====");
+        executeCommand("cave:deployer-connection-register TEST " + getJmxServiceUrl() + " root karaf karaf");
+        System.out.println("==== Connections List ====");
+        String connectionList = executeCommand("cave:deployer-connection-list");
+        assertContains("TEST", connectionList);
+        System.out.println(connectionList);
+
+        System.out.println("==== List Features Via Deployer ====");
+        String featureListViaDeployer = executeCommand("cave:deployer-feature-list TEST");
+        assertContains("cave-deployer", featureListViaDeployer);
+        System.out.println(featureListViaDeployer);
+    }
+
+}
diff --git a/itest/src/test/java/org/apache/karaf/cave/itest/repository/FeaturesGatewayTest.java b/itest/src/test/java/org/apache/karaf/cave/itest/repository/FeaturesGatewayTest.java
new file mode 100644
index 0000000..7a1ba0e
--- /dev/null
+++ b/itest/src/test/java/org/apache/karaf/cave/itest/repository/FeaturesGatewayTest.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.cave.itest.repository;
+
+import org.apache.karaf.itests.KarafTestSupport;
+import org.apache.karaf.jaas.boot.principal.RolePrincipal;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.karaf.options.KarafDistributionOption;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerClass;
+
+import java.util.stream.Stream;
+
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerClass.class)
+public class FeaturesGatewayTest extends KarafTestSupport {
+
+    @Configuration
+    public Option[] config() {
+        Option[] options = new Option[]{
+                KarafDistributionOption.editConfigurationFilePut("etc/system.properties", "cave.version", System.getProperty("cave.version"))
+        };
+        return Stream.of(super.config(), options).flatMap(Stream::of).toArray(Option[]::new);
+    }
+
+    @Test
+    public void test() throws Exception {
+        System.out.println(executeCommand("feature:repo-add cave " + System.getProperty("cave.version")));
+        System.out.println(executeCommand("feature:install cave-features-gateway", new RolePrincipal("admin")));
+        String featureList = executeCommand("feature:list -i");
+        int count = 0;
+        while (!featureList.contains("cave-features-gateway") && count < 100) {
+            featureList = executeCommand("feature:list -i");
+            Thread.sleep(100);
+            count++;
+        }
+        if (count >= 100) {
+            throw new RuntimeException("cave-features-gateway feature is not installed");
+        }
+        System.out.println("==== Installed Features ====");
+        System.out.println(featureList);
+        String httpList = executeCommand("http:list");
+        System.out.println("==== HTTP List ====");
+        System.out.println(httpList);
+        assertContains("/cave/features-gateway/api", httpList);
+    }
+
+}
diff --git a/itest/src/test/java/org/apache/karaf/cave/itest/repository/RepositoryTest.java b/itest/src/test/java/org/apache/karaf/cave/itest/repository/RepositoryTest.java
new file mode 100644
index 0000000..03d4738
--- /dev/null
+++ b/itest/src/test/java/org/apache/karaf/cave/itest/repository/RepositoryTest.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.cave.itest.repository;
+
+import org.apache.karaf.itests.KarafTestSupport;
+import org.apache.karaf.jaas.boot.principal.RolePrincipal;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.karaf.options.KarafDistributionOption;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerMethod;
+
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.stream.Stream;
+
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerMethod.class)
+public class RepositoryTest extends KarafTestSupport {
+
+    @Configuration
+    public Option[] config() {
+        Option[] options = new Option[]{
+                KarafDistributionOption.editConfigurationFilePut("etc/system.properties", "cave.version", System.getProperty("cave.version"))
+        };
+        return Stream.of(super.config(), options).flatMap(Stream::of).toArray(Option[]::new);
+    }
+
+    @Test
+    public void installationTest() throws Exception {
+        System.out.println(executeCommand("feature:repo-add cave " + System.getProperty("cave.version")));
+        String repoList = executeCommand("feature:repo-list");
+        System.out.println("==== Features Repositories ====");
+        System.out.println(repoList);
+        assertContains("mvn:org.apache.karaf.cave/apache-karaf-cave/" + System.getProperty("cave.version") + "/xml/features", repoList);
+        System.out.println(executeCommand("feature:install cave-repository", new RolePrincipal("admin")));
+        String featureList = executeCommand("feature:list -i");
+        int count = 0;
+        while (!featureList.contains("cave-repository") && count < 100) {
+            featureList = executeCommand("feature:list -i");
+            Thread.sleep(100);
+            count++;
+        }
+        if (count >= 100) {
+            throw new RuntimeException("cave-repository feature is not installed");
+        }
+        System.out.println("==== Installed Features ====");
+        System.out.println(featureList);
+        String httpList = executeCommand("http:list");
+        System.out.println("==== HTTP List ====");
+        System.out.println(httpList);
+        assertContains("/cave/repository/api", httpList);
+        String repositoryService = executeCommand("service:list org.apache.karaf.cave.repository.RepositoryService");
+        assertContains("[org.apache.karaf.cave.repository.RepositoryService]", repositoryService);
+        assertContains("component.name = org.apache.karaf.cave.repository", repositoryService);
+        System.out.println("==== Cave Repository Service ====");
+        System.out.println(repositoryService);
+    }
+
+    @Test(timeout = 60000L)
+    public void repositoryTestViaCommands() throws Exception {
+        installCaveRepository();
+        System.out.println("==== Create TEST repository ====");
+        executeCommand("cave:repository-create TEST");
+        String repositoryList = executeCommand("cave:repository-list");
+        assertContains("TEST", repositoryList);
+        System.out.println("==== Repository List ====");
+        System.out.println(repositoryList);
+        System.out.println("==== HTTP List ====");
+        String httpList = executeCommand("http:list");
+        assertContains("TEST", httpList);
+        System.out.println(httpList);
+
+        System.out.println("==== Add Artifact in TEST ====");
+        executeCommand("cave:repository-artifact-add TEST mvn:commons-lang/commons-lang/2.6");
+
+        System.out.println("==== Check Artifact on TEST ====");
+        URL testUrl = new URL("http://localhost:" + getHttpPort() + "/cave/repository/TEST/commons-lang/commons-lang/2.6/commons-lang-2.6.jar");
+        HttpURLConnection connection = (HttpURLConnection) testUrl.openConnection();
+        Assert.assertEquals(200, connection.getResponseCode());
+        Assert.assertEquals("OK", connection.getResponseMessage());
+        Assert.assertEquals("application/octet-stream", connection.getContentType());
+
+        System.out.println("==== Delete Artifact from TEST ====");
+        executeCommand("cave:repository-artifact-delete TEST mvn:commons-lang/commons-lang/2.6");
+
+        System.out.println("==== Delete TEST repository ====");
+        executeCommand("cave:repository-remove TEST");
+        repositoryList = executeCommand("cave:repository-list");
+        assertContainsNot("TEST", repositoryList);
+
+        System.out.println("==== Create PROXY repository ====");
+        executeCommand("cave:repository-create -p http://repo1.maven.org/maven2\\@id=Central PROXY");
+
+        System.out.println("==== Repository List ====");
+        repositoryList = executeCommand("cave:repository-list");
+        assertContains("PROXY", repositoryList);
+        System.out.println(repositoryList);
+
+        System.out.println("==== Repository Info PROXY ====");
+        String repositoryInfo = executeCommand("cave:repository-info PROXY");
+        assertContains("Central", repositoryInfo);
+        System.out.println(repositoryInfo);
+
+        System.out.println("==== Try to get artifact on PROXY ====");
+        URL proxyUrl = new URL("http://localhost:" + getHttpPort() + "/cave/repository/PROXY/commons-lang/commons-lang/2.6/commons-lang-2.6.jar");
+        HttpURLConnection proxyUrlConnection = (HttpURLConnection) proxyUrl.openConnection();
+        Assert.assertEquals(200, proxyUrlConnection.getResponseCode());
+        Assert.assertEquals("OK", proxyUrlConnection.getResponseMessage());
+        Assert.assertEquals("application/octet-stream", proxyUrlConnection.getContentType());
+        Assert.assertEquals(284220, proxyUrlConnection.getContentLength());
+        System.out.println(executeCommand("cave:repository-remove PROXY"));
+
+        System.out.println("==== Create SCHEDULED repository ====");
+        executeCommand("cave:repository-create -s \"cron:0/5 * * * * ?\" -sa DELETE SCHEDULED");
+        repositoryInfo = executeCommand("cave:repository-info SCHEDULED");
+        assertContains("DELETE", repositoryInfo);
+        System.out.println(repositoryInfo);
+        System.out.println("==== Scheduler Jobs ====");
+        String scheduler = executeCommand("scheduler:list");
+        assertContains("cave-repository-SCHEDULED", scheduler);
+        System.out.println(scheduler);
+        System.out.println("==== Wait Scheduler ====");
+        repositoryList = executeCommand("cave:repository-list");
+        while (repositoryList.contains("SCHEDULED")) {
+            Thread.sleep(2000);
+            repositoryList = executeCommand("cave:repository-list");
+        }
+        System.out.println(repositoryList);
+    }
+
+    private void installCaveRepository() throws Exception {
+        executeCommand("feature:repo-add cave " + System.getProperty("cave.version"));
+        executeCommand("feature:install cave-repository", new RolePrincipal("admin"));
+    }
+
+}
diff --git a/itest/src/test/resources/etc/org.ops4j.pax.logging.cfg b/itest/src/test/resources/etc/org.ops4j.pax.logging.cfg
new file mode 100644
index 0000000..19bd51e
--- /dev/null
+++ b/itest/src/test/resources/etc/org.ops4j.pax.logging.cfg
@@ -0,0 +1,69 @@
+################################################################################
+#
+#    Licensed to the Apache Software Foundation (ASF) under one or more
+#    contributor license agreements.  See the NOTICE file distributed with
+#    this work for additional information regarding copyright ownership.
+#    The ASF licenses this file to You under the Apache License, Version 2.0
+#    (the "License"); you may not use this file except in compliance with
+#    the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+#
+################################################################################
+
+# Common pattern layout for appenders
+log4j2.pattern = %d{ISO8601} | %-5p | %-16t | %-32c{1} | %X{bundle.id} - %X{bundle.name} - %X{bundle.version} | %m%n
+
+# Root logger
+log4j2.rootLogger.level = INFO
+# uncomment to use asynchronous loggers, which require mvn:com.lmax/disruptor/3.3.2 library
+#log4j2.rootLogger.type = asyncRoot
+#log4j2.rootLogger.includeLocation = false
+log4j2.rootLogger.appenderRefs = RollingFile, PaxOsgi, Console
+log4j2.rootLogger.appenderRef.RollingFile.ref = RollingFile
+log4j2.rootLogger.appenderRef.PaxOsgi.ref = PaxOsgi
+log4j2.rootLogger.appenderRef.Console.ref = Console
+log4j2.rootLogger.appenderRef.Console.filter.threshold.type = ThresholdFilter
+log4j2.rootLogger.appenderRef.Console.filter.threshold.level = ${karaf.log.console:-OFF}
+
+# Appenders configuration
+log4j2.appenders = console, rolling, osgi
+
+# CONSOLE appender not used by default
+log4j2.appender.console.type = Console
+log4j2.appender.console.name = Console
+log4j2.appender.console.layout.type = PatternLayout
+log4j2.appender.console.layout.pattern = ${log4j2.pattern}
+
+# File appender
+log4j2.appender.rolling.type = RollingRandomAccessFile
+log4j2.appender.rolling.name = RollingFile
+log4j2.appender.rolling.fileName = ${karaf.log}/karaf.log
+log4j2.appender.rolling.filePattern = ${karaf.log}/karaf.log.%i
+# uncomment to not force a disk flush
+#log4j2.appender.rolling.immediateFlush = false
+log4j2.appender.rolling.append = true
+log4j2.appender.rolling.layout.type = PatternLayout
+log4j2.appender.rolling.layout.pattern = ${log4j2.pattern}
+log4j2.appender.rolling.policies.type = Policies
+log4j2.appender.rolling.policies.size.type = SizeBasedTriggeringPolicy
+log4j2.appender.rolling.policies.size.size = 16MB
+
+# OSGi appender
+log4j2.appender.osgi.type = PaxOsgi
+log4j2.appender.osgi.name = PaxOsgi
+log4j2.appender.osgi.filter = *
+
+# help with identification of maven-related problems with pax-url-aether
+#log4j2.logger.aether.name = shaded.org.eclipse.aether
+#log4j2.logger.aether.level = TRACE
+#log4j2.logger.http-headers.name = shaded.org.apache.http.headers
+#log4j2.logger.http-headers.level = DEBUG
+#log4j2.logger.maven.name = org.ops4j.pax.url.mvn
+#log4j2.logger.maven.level = TRACE
diff --git a/manual/pom.xml b/manual/pom.xml
index 1528f90..be82698 100644
--- a/manual/pom.xml
+++ b/manual/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.karaf</groupId>
         <artifactId>cave</artifactId>
-        <version>4.1.3-SNAPSHOT</version>
+        <version>4.2.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 
diff --git a/manual/src/main/asciidoc/index.adoc b/manual/src/main/asciidoc/index.adoc
index 9dccd31..3170ae5 100644
--- a/manual/src/main/asciidoc/index.adoc
+++ b/manual/src/main/asciidoc/index.adoc
@@ -12,14 +12,14 @@
 // limitations under the License.
 //
 
-Apache Karaf Cave 4.x - Documentation
+Apache Karaf Cave 4.2.x - Documentation
 ======================================
 Apache Software Foundation
 :doctype: article
 :toc: left
 :toclevels: 3
 :toc-position: left
-:toc-title: Apache Karaf Cave 4.x - Documentation
+:toc-title: Apache Karaf Cave 4.2.x - Documentation
 :numbered:
 
 image::asf_logo.png[pdfwidth=35%,align=center]
@@ -28,20 +28,14 @@ include::overview.adoc[]
 
 == User Guide
 
-include::user-guide/installation.adoc[]
+=== Repositories Manager
 
-include::user-guide/cave-repository.adoc[]
+include::user-guide/repository.adoc[]
 
-include::user-guide/populate-repository.adoc[]
+=== Karaf Features Gateway
 
-include::user-guide/proxy-repository.adoc[]
+include:user-guide/features-gateway.adoc[]
 
-include::user-guide/http-wrapper.adoc[]
-
-include::user-guide/maven-wrapper.adoc[]
-
-include::user-guide/administrate-cave.adoc[]
-
-include::user-guide/features-gateway.adoc[]
+=== Deployer
 
 include::user-guide/deployer.adoc[]
diff --git a/manual/src/main/asciidoc/overview.adoc b/manual/src/main/asciidoc/overview.adoc
index aea2254..d68ab93 100644
--- a/manual/src/main/asciidoc/overview.adoc
+++ b/manual/src/main/asciidoc/overview.adoc
@@ -16,6 +16,16 @@
 
 Apache Karaf Cave is an Apache Karaf sub-project.
 
+Apache Karaf Cave provides three different services:
+
+* Artifact repositories manager
+* Karaf features gateway
+* Deployer
+
+Each service is atomic and doesn't depend to another one. You can only use one service or combine the services.
+
+
+
 Apache Karaf Cave is an implementation of an artifact repository and deployment platform for Apache Karaf supporting:
 
 * OSGi Repository specification
diff --git a/manual/src/main/asciidoc/user-guide/administrate-cave.adoc b/manual/src/main/asciidoc/user-guide/administrate-cave.adoc
deleted file mode 100644
index 666ceb8..0000000
--- a/manual/src/main/asciidoc/user-guide/administrate-cave.adoc
+++ /dev/null
@@ -1,54 +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.
-//
-
-=== Administration
-
-==== JMX
-
-When you install Apache Karaf Cave server, it provides a CaveServerMBean.
-
-This MBean uses the following object name:
-
-----
-org.apache.karaf.cave:type=repository,name=*
-----
-
-Thanks to this MBean, using any JMX client (like jconsole for instance), you can do all the same actions as you can using the `cave:*` commands:
-
-* void createRepository(String name, String location, boolean generate, boolean install) throws Exception;
-* void destroyRepository(String name) throws Exception;
-* void installRepository(String name) throws Exception;
-* void uninstallRepository(String name) throws Exception;
-* void populateRepository(String name, String url, boolean generate, String filter) throws Exception;
-* void proxyRepository(String name, String url, boolean generate, String filter) throws Exception;
-* void updateRepository(String name) throws Exception;
-* void uploadArtifact(String repository, String artifactUrl, boolean generate) throws Exception;
-
-==== REST
-
-Cave provides a complete REST API to manipulate the repositories.
-
-The API is available on:
-
-----
-http://localhost:8181/cave/rest
-----
-
-[NOTE]
-====
-8181 is the default port of the Apache Karaf HTTP service.
-====
-
-* `/cave/rest/repository` allows you to manipulate the Cave repositories.
-* `/cave/rest/deployer` allows you to manipulate the Cave deployer.
diff --git a/manual/src/main/asciidoc/user-guide/cave-repository.adoc b/manual/src/main/asciidoc/user-guide/cave-repository.adoc
deleted file mode 100644
index dbabdee..0000000
--- a/manual/src/main/asciidoc/user-guide/cave-repository.adoc
+++ /dev/null
@@ -1,103 +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.
-//
-
-=== Repository
-
-A Cave repository is a container for:
-
-* Artifacts (files)
-* Repository metadata (optional, only used for OSGi repository)
-
-By default, a repository uses a filesystem backend for the storage, the directory used is KARAF_BASE/cave.
-
-You can change the storage location in the 'etc/org.apache.karaf.cave.server.storage.cfg' configuration file:
-
-----
-################################################################################
-#
-#    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.
-#
-################################################################################
-
-#
-# Storage location where Apache Karaf Cave create repositories by default
-#
-cave.storage.location=cave
-----
-
-For instance, you can define `/var/cave/store` for the `storage.location` property.
-
-==== Create
-
-The `cave:repository-create` command creates a new repository:
-
-----
-karaf@root()> cave:repository-create my-repository
-----
-
-A repository is identified by a name, `my-repository` in our example.
-
-Apache Karaf Cave creates the repository storage for you.
-
-If you want to use an existing directory, and avoid Cave creating one in the storage location, you can use the `-l`
-('--location') option:
-
-----
-karaf@root()> cave:repository-create -l /home/user/.m2/repository m2
-----
-
-By default, Apache Karaf Cave scans the repository storage and creates the repository metadata. You can use the `-no` (`--no-generate`)
-option to avoid this step:
-
-----
-karaf@root()> cave:repository-create -no -l /home/user/.m2/repository m2
-----
-
-By default, Apache Karaf Cave registers (installs) a new repository into the repository service. You can use the `-ns` (`--no-start`)
-option to avoid this step:
-
-----
-karaf@root()> cave:repository-create -ns -l /home/user/m2/repository m2
-----
-
-[NOTE]
-====
-The `-no` and `-ni` options are interesting when you use an existing location for the repository. If you create a
-new empty repository, these options don't have really any effect.
-====
-
-==== List
-
-You can list the repositories:
-
-----
-karaf@root()> cave:repositories
-Name          | Location
-----------------------------------------------------------------
-my-repository | /opt/apache-karaf-4.0.0/data/cave/my-repository
-----
-
diff --git a/manual/src/main/asciidoc/user-guide/deployer.adoc b/manual/src/main/asciidoc/user-guide/deployer.adoc
index 7a783b6..855e448 100644
--- a/manual/src/main/asciidoc/user-guide/deployer.adoc
+++ b/manual/src/main/asciidoc/user-guide/deployer.adoc
@@ -12,115 +12,423 @@
 // limitations under the License.
 //
 
-=== Deployer
-
 Cave Deployer allows you to manage a "farm" of Apache Karaf instances local or remote from the deployer itself.
 
-You have specific commands: `cave:deployer-*`.
-
 ==== Connections
 
-The first component of the deployer is the connection. A connection describes an access to an Apache Karaf instance.
+To interact with a Apache Karaf instance, you have to create a connection describing the access to this instance.
 
-You can create a connection via `cave:deployer-connection-register` command or the REST Deployer API.
-
-A connection contains:
+A connection has:
 
 * an unique name for the connection
 * the JMX URL to the given Apache Karaf instance
 * the name of the Apache Karaf instance
 * username and password to connect to the Apache Karaf instance
 
-For instance:
+===== Create connections
+
+====== `cave:deployer-connection-register` shell command
 
-----
+You can add a new Apache Karaf connection using `cave:deployer-connection-register` shell command:
+
+```
 karaf@root()> cave:deployer-connection-register myconnection service:jmx:rmi:///jndi/rmi://localhost:1099/karaf-root root karaf karaf
-----
+```
+
+====== REST API
+
+You can create a connection via the Cave Deployer REST API on `/cave/deployer/api/connections` using a JSON description of your connection.
+For instance, using `curl`:
+
+```
+curl -X POST -H "Content-Type: application/json" http://localhost:8181/cave/deployer/api/connections -d '{
+ "name": "myconnection",
+ "jmxUrl": "service:jmx:rmi:///jndi/rmi://localhost:1099/karaf-root",
+ "karafName": "root",
+ "user": "karaf",
+ "password": "karaf"
+}'
+```
+
+====== JMX MBean
+
+The `org.apache.karaf.cave:type=deployer` MBean provides the `registerConnection(String name, String jmxUrl, String karafName, String user, String password)` operation
+allowing you to create a new connection.
+
+====== Service
+
+The `org.apache.karaf.cave.deployer.DeployerService` service provides the `registerConnection(Connection connection)` method allowing you to create a new connection.
+
+===== List connections
+
+====== `cave:deployer-connection-list` shell command
 
-You can list the connections using the `cave:deployer-connection` command:
+You can list available connections using the `cave:deployer-connection-list` command:
 
-----
-karaf@root()> cave:deployer-connection
+```
+karaf@root()> cave:deployer-connection-list
 Name         │ JMX URL                                                 │ Instance │ Username │ Password
 ─────────────┼─────────────────────────────────────────────────────────┼──────────┼──────────┼─────────
 myconnection │ service:jmx:rmi:///jndi/rmi://localhost:1099/karaf-root │ root     │ karaf    │ *****
+```
 
-----
+====== REST API
 
-or via REST (with `curl` for instance):
+You can list available connections using the Cave Deployer REST API on `/cave/deployer/api/connections` (GET) URL.
 
-----
-curl -X GET -H "Content-Type: application/json" http://localhost:8181/cave/rest/deployer/connections
+For instance, using `curl`:
+
+```
+curl -X GET -H "Content-Type: application/json" http://localhost:8181/cave/deployer/api/connections
 [{"name":"myconnection","jmxUrl":"service:jmx:rmi:///jndi/rmi://localhost:1099/karaf-root","karafName":"root","user":"karaf","password":"karaf"}]
-----
+```
+
+====== JMX MBean
+
+The `org.apache.karaf.cave:type=deployer` MBean provides the `connections` attribute. It's a tabular data where you can find all connections available.
+
+====== Service
+
+The `org.apache.karaf.cave.deployer.DeployerService` service provides the `connections()` method providing the list of all connections available.
+
+===== Delete a connection
+
+====== `cave:deployer-connection-delete` shell command
 
-You can remove a connection using `cave:deployer-connection-delete` command:
+You can delete an existing connection with `cave:deployer-connection-delete` shell command:
 
-----
+```
 karaf@root()> cave:deployer-connection-delete myconnection
-----
+```
 
-or via REST:
+====== REST API
 
-----
-curl -X DELETE http://localhost:8181/cave/rest/deployer/connections/myconnection
-----
+You can delete an existing connection using Cave Deployer Rest API on `/cave/deployer/api/connections/{name}` URL (DELETE).
+For instance, using `curl`:
+
+```
+curl -X DELETE http://localhost:8181/cave/deployer/api/connections/myconnection
+```
+
+====== JMX MBean
+
+The `org.apache.karaf.cave:type=deployer` MBean provides the `deleteConnection(String name)` operation to delete an existing connection.
+
+====== Service
+
+The `org.apache.karaf.cave.deployer.DeployerService` service provides the `deleteConnection(String connectionName)` method to delete an existing connection.
 
 ==== Artifacts
 
-Cave Deployer to extract zip or kar files to a given Maven repository or local directory, thanks to the `cave:deployer-explode` and `cave:deployer-extract` commands
-and the `/cave/rest/deployer/artifact/explode` and `/cave/rest/deployer/artifact/extract` REST operations.
+===== Download & upload artifacts
+
+Cave Deployer is able to download an artifacts into a target directory.
+
+For instance you can download an artifact using `cave:deployer-download` shell command:
+
+```
+karaf@root()> cave:deployer-download mvn:commons-lang/commons-lang/2.6 /path/to/my/repo
+```
+
+You can also use Cave Deployer REST API on `/cave/deployer/api/download` (POST). The artifact URL is passed using `artifact` header and the directory is passed using `directory` header.
+
+For instance, using `curl`:
+
+```
+curl -X POST -H "artifact: mvn:commons-lang/commons-lang/2.6" -H "directory: /path/to/my/repo" http://localhost:8181/cave/deployer/api/download
+```
+
+You can also use the `download(String url, String directory)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+
+On the `org.apache.karaf.cave.deployer.DeployerService` service, you can also use the `download(String artifact, String directory)` method.
+
+On the other hand, you can also upload an artifact to a target repository providing the Maven coordinates. It's exactly the same as doing `mvn deploy:deploy-file`.
+
+To upload an artifact, you can use the `cave:deployer-upload` shell command:
+
+```
+karaf@root()> cave:deployer-upload -g groupId -a artifactId -v 1.0-SNAPSHOT mvn:foo/bar/x.x http://host/repository
+```
+
+You can also use the Cave Deployer REST API on `/cave/deployer/api/upload` (POST) with the following header parameters:
+
+* `groupId`
+* `artifactId`
+* `version`
+* `artifactUrl`
+* `repositoryUrl`
+
+For instance, using `curl`:
+
+```
+curl -X POST -H "groupId: groupId" -H "artifactId: artifactId" -H "version: 1.0-SNAPSHOT" -H "artifactUrl: mvn:foo/bar/x.x" -H "repositoryUrl: http://host/repository" http://localhost:8181/cave/deployer/api/upload
+```
+
+You can also use the `upload(String groupId, String artifactId, String version, String artifactUrl, String repositoryUrl)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+
+The `org.apache.karaf.cave.deployer.DeployerService` service also provides the `upload(String groupId, String artifactId, String version, String artifactUrl, String repositoryUrl)` method.
+
+===== Extract & explode artifacts
+
+Cave Deployer is also able to process zip and kar artifacts.
+
+Cave Deployer is able to extract zip or kar files to a given Maven repository or local directory:
+
+* explode will download and extract an artifact into a target folder/repository, looking for features repositories XML (typically in KAR files)
+* extract will download and extract an artifact into a target folder/repository
+
+To explode an artifact into a target Maven repository, you can use `cave:deployer-explode` shell command:
+
+```
+karaf@root()> cave:deployer-explode mvn:foo/bar/1.0/kar /path/to/repository
+```
+
+You can also use the Cave Deployer REST API on `/cave/deployer/api/explode` URL (POST) with `url` header for the artifact URL, and `repository` header for the repository URL. For instance, using `curl`:
+
+```
+curl -X POST -H "url: mvn:foo/bar/1.0/kar" -H "repository: /path/to/repository" http://localhost:8181/cave/deployer/api/explode
+["foo.xml"]
+```
+
+You can also use `explode(String url, String repository)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+
+The `org.apache.karaf.cave.deployer.DeployerService` also provides the `explode(String url, String repository)` method.
+
+Similar to explode, you can extract artifact (without looking for features repositories XML).
+
+You can use the `cave:deployer-extract` shell command to extract an artifact:
 
-You can also download an artifact from a Maven repository with the `cave:deployer-download` command (or `/cave/rest/deployer/artifact/download` REST operation)
-or even upload an artifact to a Maven repository thanks to the `cave:deployer-upload` command (or `/cave/rest/deployer/artifact/upload` REST operation).
+```
+karaf@root()> cave:deployer-extract mvn:foo/bar/1.0/zip /path/to/directory
+```
+
+You can also use the Cave Deployer REST API on `/cave/deployer/api/extract` URL (POST) with `url` header for the artifact URL, and `directory` header for the directory. For instance, using `curl`:
+
+```
+curl -X POST -H "url: mvn:foo/bar/1.0/zip" -H "directory: /path/to/directory" http://localhost:8181/cave/deployer/api/extract
+```
+
+The `org.apache.karaf.cave:type=deployer` MBean also provides `extract(String url, String directory)` operation to extract an artifact into a directory.
+
+You can also programmatically use ` extract(String url, String directory)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
 
 ==== Features
 
-Apache Karaf Cave Deployer provides the "classy" operations that you can do around Karaf features:
+===== Assemble features
+
+You can create new Karaf features by composing existing features, configurations or bundles.
+
+For instance, you can create `myfeature` composed by `feature1`, `feature2`, `bundle1`, `bundle2`.
+
+* `karaf@root()> cave:deployer-assemble-feature -g groupId -a artifactId -v 1.0-SNAPSHOT myfeature http://myrepo feature1 feature2`
+* Using `assembleFeature(String groupId, String artifactId, String version, String repositoryUrl, String feature, List<String> repositories, List<String> features, List<String> bundles)` operation on the `org.apache.karaf.cave:type=deployer` MBean
+* Using `assembleFeature(String groupId, String artifactId, String version, String repositoryUrl, String feature, List<String> featureRepositoryUrls, List<String> features, List<String> bundles, List<Config> configs)` method on the `org.apache.karaf.cave.deployer.DeployerService` service
+
+===== Add features repositories
+
+* `karaf@root()> cave:deployer-feature-repo-add myconnection mvn:foo/bar/1.0/xml/features`
+* `curl -X POST -H "artifactUrl: mvn:foo/bar/1.0/xml/features" http://localhost:8181/cave/deployer/api/connections/myconnection/features/repositories`
+* `addFeatureRepository(String url, String connection)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `addFeaturesRepository(String featuresRepositoryUrl, String connection)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
+
+===== List features repositories
+
+* `karaf@root()> cave:deployer-feature-repo-list myconnection`
+* `curl -X GET http://localhost:8181/cave/deployer/api/connections/myconnection/features/repositories`
+* `getFeatureRepositories(String connection)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `featuresRepositories(String connection)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
+
+===== List features provided by a features repository
+
+* `karaf@root()> cave:deployer-feature-repo-provide mvn:foo/bar/1.0/xml/features`
+* `curl -X GET -H "featuresRepositoryUrl: mvn:foo/bar/1.0/xml/features" http://localhost:8181/cave/deployer/api/features/repository`
+* `getProvidedFeatures(String featuresRepositoryUrl)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `providedFeatures(String featuresRepositoryUrl)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
+
+===== Remove features repositories
 
-* add a features repository
-* remove a features repository
-* list of features repositories
-* installing a feature
-* uninstalling a feature
-* list of features
+* `karaf@root()> cave:deployer-feature-repo-remove myconnection mvn:foo/bar/1.0/xml/features`
+* `curl -X DELETE -H "artifactUrl: mvn:foo/bar/1.0/xml/features" http://localhost:8181/cave/deployer/api/connections/myconnection/features/repositories`
+* `removeFeatureRepository(String repository, String connection)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `removeFeaturesRepository(String featuresRepositoryUrl, String connection)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
 
-You have dedicated Cave Deployer commands and REST operations for that.
+===== Install features
 
-You can also create a new "meta" feature by assembling existing features, configurations and bundles. This is done by the `assemble` operation:
+* `karaf@root()> cave:deployer-feature-install myconnection myfeature`
+* `curl -X POST http://localhost:8181/cave/deployer/api/connections/myconnection/features/myfeature`
+* `installFeature(String feature, String connection)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `installFeature(String feature, String connection)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
 
-* `cave:deployer-assemble-feature`
-* `/cave/rest/deployer/feature/assemble`
+===== List features
+
+* `karaf@root()> cave:deployer-feature-list myconnection`
+* `curl -X GET http://localhost:8181/cave/deployer/api/connections/myconnection/features`
+* `getFeatures(String connection)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `features(String connection)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
+
+===== List installed features
+
+* `karaf@root()> cave:deployer-feature-installed-list myconnection`
+* `installedFeatures(String connection)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
+
+===== Uninstall features
+
+* `karaf@root()> cave:deployer-feature-uninstall myconnection myfeature`
+* `curl -X DELETE http://localhost:8181/cave/deployer/api/connections/myconnection/features/myfeature`
+* `uninstallFeature(String feature, String connection)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `uninstallFeature(String feature, String connection)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
+
+==== KARs
+
+===== Install KAR
+
+* `karaf@root()> cave:deployer-kar-install myconnection mvn:foo/bar/1.0/kar`
+* `curl -X POST -H "artifactUrl: mvn:foo/bar/1.0/kar" http://localhost:8181/cave/deployer/api/connections/myconnection/kars`
+* `installKar(String url, String connection)` operation on the `org.apache.karaf.cave:typye=deployer` MBean.
+* `installKar(String karUrl, String connection)` on the `org.apache.karaf.cave.deployer.DeployerService` service.
+
+===== List KARs
+
+* `karaf@root()> cave:deployer-kar-list myconnection`
+* `curl -X GET http://localhost:8181/cave/deployer/api/connections/myconnection/kars`
+* `getKars(String connection)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `kars(String connection)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
+
+===== Uninstall KAR
+
+* `karaf@root()> cave:deployer-kar-uninstall myconnection mykar`
+* `curl -X DELETE http://localhost:8181/cave/deployer/api/connections/myconnection/kars/mykar`
+* `uninstallKar(String id, String connection)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `uninstallKar(String id, String connection)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
 
 ==== Bundles
 
-Apache Karaf Cave Deployer supports:
+===== Install bundle
+
+* `karaf@root()> cave:deployer-bundle-install myconnection mvn:foo/bar/1.0`
+* `curl -X POST -H "artifactUrl: mvn:foo/bar/1.0" http://localhost:8181/cave/deployer/api/connections/myconnection/bundles`
+* `installBundle(String url, String connection)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `installBundle(String artifactUrl, String connection)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
+
+===== List bundles
+
+* `karaf@root()> cave:deployer-bundle-list myconnection`
+* `curl -X GET http://localhost:8181/cave/deployer/api/connections/myconnection/bundles`
+* `getBundles(String connection)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `bundles(String connection)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
+
+===== Start bundle
+
+* `karaf@root()> cave:deployer-bundle-start myconnection 81`
+* `curl -X POST http://localhost:8181/cave/deployer/api/connections/myconnection/bundles/81/start`
+* `startBundle(String id, String connection)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `startBundle(String id, String connection)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
+
+===== Stop bundle
+
+* `karaf@root()> cave:deployer-bundle-stop myconnection 81`
+* `curl -X POST http://localhost:8181/cave/deployer/api/connections/myconnection/bundles/81/stop`
+* `stopBundle(String id, String connection)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `stopBundle(String id, String connection)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
+
+===== Uninstall bundle
+
+* `karaf@root()> cave:deployer-bundle-uninstall myconnection 81`
+* `curl -X DELETE http://localhost:8181/cave/deployer/api/connections/myconnection/bundles/81`
+* `uninstallBundle(String id, String connection)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `uninstallBundle(String id, String connection)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
+
+==== Configurations
+
+===== Create configuration
+
+* `karaf@root()> cave:deployer-config-create myconnection myconfig`
+* `curl -X POST http://localhost:8181/cave/deployer/api/connections/myconnection/configurations/myconfig`
+* `createConfig(String pid, String connection)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `createConfig(String pid, String connection)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
+
+===== Create configuration factory
+
+* `karaf@root()> cave:deployer-config-factory-create myconnection myfactory alias`
+* `curl -X POST http://localhost:8181/cave/deployer/api/connections/myconnection/configurations/factories/myfactory`
+* `createConfigFactory(String factoryPid, String alias, String connection)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `createConfigurationFactory(String factoryPid, String alias, String connection)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
+
+===== List configurations
+
+* `karaf@root()> cave:deployer-config-list myconnection`
+* `curl -X GET http://localhost:8181/cave/deployer/api/connections/myconnection/configurations`
+* `configs(String connection)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `configs(String connection)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
+
+===== Set configuration property
+
+* `karaf@root()> cave:deployer-config-property-set myconnection myconfiguration myproperty myvalue`
+* `setConfigProperty(String pid, String key, String value, String connection)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `setConfigProperty(String pid, String key, String value, String connection)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
+
+===== List configuration properties
+
+* `karaf@root()> cave:deployer-config-property-list myconnection myconfiguration`
+* `curl -X GET http://localhost:8181/cave/deployer/api/connections/myconnection/configurations/myconfiguration/properties`
+* `getConfigProperties(String pid, String connection)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `configProperties(String pid, String connection)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
+
+===== Delete configuration property
+
+* `karaf@root()> cave:deployer-config-property-delete myconnection myconfiguration myproperty`
+* `deleteConfigProperty(String pid, String key, String connection)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `deleteConfigProperty(String pid, String key, String connection)` methood on the `org.apache.karaf.cave.deployer.DeployerService` service.
+
+===== Delete configuration
+
+* `karaf@root()> cave:deployer-config-delete myconnection myconfiguration`
+* `curl -X DELETE http://localhost:8181/cave/deployer/api/connections/myconnection/configurations/myconfiguration`
+* `deleteConfig(String pid, String connection)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `deleteConfig(String pid, String connection)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
+
+==== Karaf Cellar cluster
+
+Cave Deployer is able to administrate a Karaf Cellar cluster.
+
+===== List cluster nodes
+
+* `cave:deployer-cluster-node-list myconnection`
+* `curl -X GET http://localhost:8181/cave/deployer/api/cluster/nodes`
+* `getClusterNodes(String connection)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `clusterNodes(String connection)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
+
+===== List cluster groups
 
-* install bundle
-* start bundle
-* stop bundle
-* uninstall bundle
-* list bundles
+* `cave:deployer-cluster-group-list myconnection`
+* `curl -X GET http://localhost:8181/cave/deployer/api/cluster/groups`
+* `getClusterGroups(String connection)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `clusterGroups(String connection)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
 
-==== KAR
+===== Add features repository to cluster group
 
-Apache Karaf Cave Deployer supports:
+* `cave:deployer-cluster-feature-repo-add myconnection myclustergroup mvn:foo/bar/1.0/xml/features`
+* `curl -X POST -H "url: mvn:foo/bar/1.0/xml/features" http://localhost:8181/cave/deployer/api/connections/myconnection/cluster/groups/myclustergroup/features/repositories`
+* `clusterFeatureRepositoryAdd(String url, String clusterGroup, String connection)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `clusterAddFeaturesRepository(String url, String clusterGroup, String connection)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
 
-* install KAR
-* uninstall KAR
-* list KARs
+===== Remove features repository from cluster group
 
-==== Config
+* `cave:cluster-feature-repo-remove myconnection myclustergroup mvn:foo/bar/1.0/xml/features`
+* `curl -X DELETE -H "url: mvn:foo/bar/1.0/xml/features" http://localhost:8181/cave/deployer/api/connections/myconnection/cluster/groups/myclustergroup/features/repositories`
+* `clusterFeatureRepositoryRemove(String url, String clusterGroup, String connection)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `clusterRemoveFeaturesRepository(String url, String clusterGroup, String Connection)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
 
-Apache Karaf Cave Deployer supports:
+===== Install feature on cluster group
 
-* creating a new configuration
-* update an existing configuration
-* add a new property to a configuration
-* delete a property from a configuration
-* delete a configuration
-* list of configurations
+* `cave:deployer-cluster-feature-install myconnection mygroup myfeature`
+* `curl -X POST http://localhost:8181/cave/deployer/api/connections/myconnection/cluster/groups/mygroup/features/myfeature`
+* `clusterFeatureInstall(String feature, String clusterGroup, String connection)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `clusterFeatureInstall(String feature, String clusterGroup, String connection)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
 
-==== Cellar
+===== Uninstall feature from cluster group
 
-Apache Karaf Cave Deployer supports features handling on a Cellar cluster.
+* `cave:deployer-cluster-feature-uninstall myconnection mygroup myfeature`
+* `curl -X DELETE http://localhost:8181/cave/deployer/api/connections/myconnection/cluster/groups/mygroup/features/myfeature`
+* `clusterFeatureUninstall(String feature, String clusterGroup, String connection)` operation on the `org.apache.karaf.cave:type=deployer` MBean.
+* `clusterFeatureUninstall(String feature, String clusterGroup, String connection)` method on the `org.apache.karaf.cave.deployer.DeployerService` service.
\ No newline at end of file
diff --git a/manual/src/main/asciidoc/user-guide/features-gateway.adoc b/manual/src/main/asciidoc/user-guide/features-gateway.adoc
index 91956e3..021fd62 100644
--- a/manual/src/main/asciidoc/user-guide/features-gateway.adoc
+++ b/manual/src/main/asciidoc/user-guide/features-gateway.adoc
@@ -12,73 +12,112 @@
 // limitations under the License.
 //
 
-=== Cave Features Gateway
-
-Apache Karaf Cave can also act as a central Karaf Features gateway. For instance, you can have an unique Cave server in your organization, providing
+Apache Karaf Cave can also act as a central Karaf Features gateway. For instance, you can have an unique Cave features gateway in your organization, providing
 a single features repository for your Karaf instances.
 
 Your Karaf instances on your ecosystem can connect to this unique Cave features gateway URL.
 
-Then, you manage your features repository on the "central" Cave server, and the Karaf instances get the features (without knowing exactly where they come from).
+Then, you manage your features repository on the "central" Cave Features Gateway, and the Karaf instances get the features (without knowing exactly where they come from).
 
-The Cave Features Gateway is automatically installed by the `cave-server` feature.
+By default, the gateway is expose on `/cave/features-gateway-repository`. It means that, on Karaf instances, you can do:
 
-==== Cave Features Gateway server
+```
+karaf@root()> feature:repo-add http://host:8181/cave/features-gateway-repository
+```
 
-You can manipulate the Features Gateway using specific shell commands or the corresponding MBean.
+NB: the Karaf instances use a cache, so you have to flush the changes on the gateway using the `feature:repo-refresh` command.
 
-===== Shell commands
+==== Installation
 
-You can register a features repository in the gateway using `cave:gateway-register` command. For example, you can register
-Apache Camel feature in the gateway using:
+Once Karaf Cave features repository is registered in a running Karaf instance (using `feature:repo-add cave` command for instance),
+you can install the Cave Features Gateway with the `cave-features-gateway` feature:
 
-----
-karaf@root()> cave:gateway-register mvn:org.apache.camel.karaf/apache-camel/2.18.1/xml/features
-----
+```
+karaf@root()> feature:repo-add cave
+karaf@root()> feature:install cave-features-gateway
+```
 
-Any URL (http, file, mvn, ...) supported by Apache Karaf is supported by Cave Features Gateway.
+==== Registering features repository in the gateway
 
-You can see the features repository registered in the gateway using the `cave:gateway-register` command:
+You can register several features repositories in the gateway. The main generated features repository for the gateway gathers the
+registered features repositories. Thanks to that the gateway is able to "expose" features from several repositories.
 
-----
-karaf@root()> cave:gateway-list
-mvn:org.apache.camel.karaf/apache-camel/2.18.1/xml/features
-----
+===== `cave:features-gateway-register` shell command
 
-You can also delete a features repository from the gateway using `cave:gateway-remove` command:
+You can simply register a features repository in the gateway using `cave:features-gateway-register` shell command. For instance,
+you can register Apache Camel Karaf features repository in the gateway:
 
-----
-karaf@root()> cave:gateway-remove mvn:org.apache.camel.karaf/apache-camel/2.18.1/xml/features
-----
+```
+karaf@root()> cave:features-gateway-register mvn:org.apache.camel.karaf/apache-camel/2.24.1/xml/features
+```
 
-===== MBean
+===== REST API
 
-Similar to the `cave:gateway*` commands, you find similar operation on the CaveGatewayMBean.
+You can also use the Cave Features Gateway REST API to register a features repository on `/cave/features-gateway/api`.
 
-This MBean uses the following object name:
+The repository URL is provided as `url` header.
 
-----
-org.apache.karaf.cave:type=gateway,name=*
-----
+For instance, using `curl`, you can do:
 
-This MBean provides the following operations:
+```
+curl -X POST -H "url: mvn:org.apache.camel.karaf/apache-camel/2.24.1/xml/features" http://localhost:8181/cave/features-gateway/api
+```
 
-* `List<String> list()`
-* `register(repository)`
-* `remove(repository)`
+===== JMX MBean
 
-==== Plugin your Karaf instances with the central gateway
+The `org.apache.karaf.cave:type=gateway` MBean provides the `register(String url)` operation allowing you to register a features repository in the gateway.
 
-The Cave Features Gateway is available on the Cave HTTP service at the following URL:
+===== Service
 
-----
-http://localhost:8181/cave/http/gateway
-----
+The `org.apache.karaf.cave.gateway.FeaturesGatewayService` service provides the `register(String url)` method allowing you to register a features repository in the gateway.
 
-Any Karaf instance can use the gateway using the regular `feature:repo-add` command:
+==== List registered features repositories in the gateway
 
-----
-karaf@root()> feature:repo-add http://cave_server:8181/cave/http/gateway
-----
+You can list the features repositories registered in the gateway.
 
-NB: the Karaf instances use a cache, so you have to flush the changes on the gateway using the `feature:repo-refresh` command.
+===== `cave:features-gateway-list` shell command
+
+The `cave:features-gateway-list` shell command gives you the registered features repositories in the gateway:
+
+```
+karaf@root()> cave:features-gateway-list
+mvn:org.apache.camel.karaf/apache-camel/2.24.1/xml/features
+```
+
+===== REST API
+
+You get list the features repositories registered on the Cave Features Gateway REST API, using `/cave/features-gateway/api` (GET). For instance using `curl`:
+
+```
+curl -X GET http://localhost:8181/cave/features-gateway/api
+["mvn:org.apache.camel.karaf/apache-camel/2.24.1/xml/features"]
+```
+
+===== JMX MBean
+
+The `repositories` attribute on the `org.apache.karaf.cave:type=gateway` MBean provides a tabular data with all registered features repositories.
+
+===== Service
+
+The `list()` method on the `org.apache.karaf.cave.gateway.FeaturesGatewayService` service provides the list of all registered features repositories.
+
+==== Remove a features repository from the gateway
+
+You can remove a features repository from the gateway.
+
+===== `cave:features-gateway-remove` shell command
+
+The `cave:features-gateway-remove` shell command allows you to remove a features repository from the gateway:
+
+```
+karaf@root()> cave:features-gateway-remove mvn:org.apache.camel.karaf/apache-camel/2.24.1/xml/features
+```
+
+===== REST API
+
+You can remove a registered features repository from the gateway using the REST API on `/cave/features-gateway/api` (DELETE). The features repository URL is passed via the `url` header.
+For instance, using `curl`:
+
+```
+curl -X DELETE -H "url: mvn:org.apache.camel.karaf/apache-camel/2.24.1/xml/features" http://localhost:8181/cave/features-gateway/api
+```
diff --git a/manual/src/main/asciidoc/user-guide/http-wrapper.adoc b/manual/src/main/asciidoc/user-guide/http-wrapper.adoc
deleted file mode 100644
index 9109e83..0000000
--- a/manual/src/main/asciidoc/user-guide/http-wrapper.adoc
+++ /dev/null
@@ -1,49 +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.
-//
-
-=== HTTP wrapper service
-
-When you install the Apache Karaf Cave Server, it starts a HTTP service wrapper.
-
-It means that all artifacts and repository metadata present in local repositories are exposed over HTTP.
-
-==== Repository metadata access
-
-Assuming that you have the following repositories:
-
-----
-karaf@root()> cave:repositories
-Name          | Location
------------------------------------------------------------------------------
-my-repository | /opt/apache-karaf-4.0.0/data/cave/my-repository
-----
-
-You can access the repository metadata using the following URL in your favorite browser:
-
-----
-http://localhost:8181/cave/http/my-repository-repository.xml
-----
-
-[NOTE]
-====
-The port 8181 is the default one of the Apache Karaf HTTP service.
-====
-
-You can see that the URL follows the format:
-
-----
-http://[cave_server_hostname]:[http_service_port]/cave/http/[cave_repository_name]-repository.xml
-----
-
-It means that you can register the repositories on remote Apache Karaf instances.
diff --git a/manual/src/main/asciidoc/user-guide/installation.adoc b/manual/src/main/asciidoc/user-guide/installation.adoc
deleted file mode 100644
index 0699d0f..0000000
--- a/manual/src/main/asciidoc/user-guide/installation.adoc
+++ /dev/null
@@ -1,55 +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.
-//
-
-=== Installation
-
-This chapter describes how to install Apache Karaf Cave into an existing Apache Karaf instance.
-
-==== Pre-installation requirements
-
-As Apache Karaf Cave is an Apache Karaf sub-project, it has to be installed into a running Apache Karaf instance.
-
-Apache Karaf Cave is available as Apache Karaf features. The easiest way to install is just to have an internet
-connection from the running Apache Karaf instance.
-
-Apache Karaf Cave 4.0.x is designed to work on Apache Karaf 4.0.x.
-
-==== Registration of the Apache Karaf Cave features
-
-Simply register the Apache Karaf Cave features URL in your Apache Karaf instance:
-
-----
-karaf@root()> feature:repo-add cave 4.1.2
-Adding feature url mvn:org.apache.karaf.cave/apache-karaf-cave/4.1.2/xml/features
-----
-
-==== Starting Apache Karaf Cave
-
-The Apache Karaf Cave Server is installed by the 'cave-server' feature:
-
-----
-karaf@root()> feature:install cave
-----
-
-The `cave` feature installs all Cave features, including the repositories server and the deployer.
-
-After the installation of the cave-server feature, new commands are available:
-
-----
-karaf@root()> cave:<TAB>
-cave:repositories            cave:repository-create       cave:repository-destroy
-cave:repository-install      cave:repository-populate     cave:repository-proxy
-cave:repository-uninstall    cave:repository-update       cave:repository-upload
-...
-----
diff --git a/manual/src/main/asciidoc/user-guide/maven-wrapper.adoc b/manual/src/main/asciidoc/user-guide/maven-wrapper.adoc
deleted file mode 100644
index 3f5ff82..0000000
--- a/manual/src/main/asciidoc/user-guide/maven-wrapper.adoc
+++ /dev/null
@@ -1,54 +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.
-//
-
-=== Maven wrapper service
-
-When you install the Apache Karaf Cave Server, it starts a Maven service wrapper.
-
-It means that all artifacts are available using a Maven structure (groupId/artifactId/version/artifactId-version[-classifier].type).
-
-It allows you to use your Cave repository directly using mvn URL in Karaf, and using Maven itself: Cave acts as a
-complete Maven repository.
-
-For instance, we have the following Cave repository:
-
-----
-karaf@root()> cave:repositories
-Name          | Location
-----------------------------------------------------------------
-my-repository | /opt/apache-karaf-4.0.0/data/cave/my-repository
-----
-
-You can access the corresponding Maven repository using:
-
-----
-http://localhost:8181/cave/maven/repositories/my-repository
-----
-
-[NOTE]
-====
-The port 8181 is the default one of the Apache Karaf HTTP service.
-====
-
-You can see that the URL follows the format:
-
-----
-http://[cave_server_hostname]:[http_service_port]/cave/maven/repositories/my-repository
-----
-
-For instance, if a Cave repository contains the commons-lang 2.6 artifact, it's accessible using:
-
-----
-http://localhost:8181/cave/maven/repositories/my-repository/commons-lang/commons-lang/2.6/commons-lang-2.6.jar
-----
diff --git a/manual/src/main/asciidoc/user-guide/populate-repository.adoc b/manual/src/main/asciidoc/user-guide/populate-repository.adoc
deleted file mode 100644
index 2e36be9..0000000
--- a/manual/src/main/asciidoc/user-guide/populate-repository.adoc
+++ /dev/null
@@ -1,81 +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.
-//
-
-=== Populate repository
-
-You can add new artifacts in a repository.
-
-==== Upload a single artifact
-
-You can upload a single artifact into a Cave Repository:
-
-----
-karaf@root()> cave:repository-upload my-repository file:/home/user/.m2/repository/org/apache/servicemix/bundles/org.apache.servicemix.bundles.asm/3.3_2/org.apache.servicemix.bundles.asm-3.3_2.jar
-karaf@root()> cave:repository-upload my-repository http://svn.apache.org/repos/asf/servicemix/m2-repo/org/apache/qpid/qpid-broker/0.8.0/qpid-broker-0.8.0.jar
-----
-
-You can also use a Maven style URL:
-
-----
-karaf@root()> cave:repository-upload my-repository mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.ant/1.7.0_5
-----
-
-==== Populate from an external repository
-
-You can also make a kind of "bulk" population of your repository, using an external repository:
-
-----
-karaf@root()> cave:repository-populate my-repository file:/home/user/.m2/repository
-----
-
-Apache Karaf Cave supports `file:` but also `http:` URLs. It means that Apache Karaf Cave is able to browse a remote repository and copy the artifacts
-to your "local" repository.
-
-For instance, you can populate your repository using all Ant ServiceMix bundles present on the Central Maven
-repository:
-
-----
-karaf@root()> cave:repository-populate my-repository http://repo1.maven.org/maven2/org/apache/servicemix/bundles/org.apache.servicemix.bundles.ant/
-----
-
-You can also populate with the whole Central Maven Repository:
-
-----
-karaf@root()> cave:repository-populate my-repository http://repo1.maven.org/maven2
-----
-
-Maven Central repository is really huge and populating from the whole Maven Central Repository will take
-a very very long time. It's just for demonstration purposes.
-
-You can filter the artifacts that you want to pick up to populate the repository. The `cave:repository-populate` command accepts
-a regex option for the filter. For instance, to pick up only joda-time version 2 artifact, you can run:
-
-----
-karaf@root()> cave:repository-populate --filter .*joda-time-2.* my-repository http://repo2.maven.org/maven2/joda-time/joda-time
-----
-
-When the remote repository requires HTTP authorization parameters, those can be specified in a Properties file:
-
-----
-karaf@root()> cave:repository-populate --properties <properties-file-path> my-repository <remote-repository-url>
-----
-
-where, `remote-repository-url` is the link to the external remote repository and `<properties-file-path>` is the path to a .properties file containing the following keys:
-
-----
-# Authorization parameters for remote repository
-cave.storage.http.username=
-cave.storage.http.password=
-----
-
diff --git a/manual/src/main/asciidoc/user-guide/proxy-repository.adoc b/manual/src/main/asciidoc/user-guide/proxy-repository.adoc
deleted file mode 100644
index 4f5f5b4..0000000
--- a/manual/src/main/asciidoc/user-guide/proxy-repository.adoc
+++ /dev/null
@@ -1,56 +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.
-//
-
-=== Proxy repository
-
-As you can populate a repository, you can also proxy an "external" repository.
-
-It means that the artifacts stay on the remote repository, Apache Karaf Cave generates the repository metadata in the local repository
-for the remote artifacts:
-
-----
-karaf@root()> cave:repository-proxy my-repository http://repo1.maven.org/maven2/org/apache/servicemix/bundles/org.apache.servicemix.bundles.commons-lang/
-----
-
-[NOTE]
-====
-The Cave repository will only handle the repository metadata, it doesn't monitor the remote repository. It means that you
-have to call the `cave:proxy-repository` command each time the remote repository change (new artifacts, etc).
-====
-
-[NOTE]
-====
-A best practice is to create a Cave repository dedicated for each proxied repository.
-====
-
-The `cave:repository-proxy` command accepts the filter option, like the `cave:repository-populate` command:
-
-----
-karaf@root()> cave:repository-proxy --filter .*joda-time-2.* my-repository http://repo2.maven.org/maven2/joda-time/joda-time
-----
-
-The `cave:repository-proxy` command accepts the properties option, like the `cave:repository-populate` command:
-
-----
-karaf@root()> cave:repository-proxy --properties <properties-file-path> my-repository <remote-repository-url>
-----
-
-where, `remote-repository-url` is the link to the external remote repository and `<properties-file-path>` is the path to the .properties file containing the following keys:
-
-----
-# Authorization parameters for remote repository
-cave.storage.http.username=
-cave.storage.http.password=
-----
-
diff --git a/manual/src/main/asciidoc/user-guide/repository.adoc b/manual/src/main/asciidoc/user-guide/repository.adoc
new file mode 100644
index 0000000..dc0be67
--- /dev/null
+++ b/manual/src/main/asciidoc/user-guide/repository.adoc
@@ -0,0 +1,583 @@
+//
+// 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.
+//
+
+Cave Repositories Manager is a simple but complete artifacts repository manager. You can create repositories used
+to store artifact.
+The repositories are Maven and OSGi Bundle Repository compliant. A repository doesn't necessary store the artifacts, it
+can also serve as proxy or mirror to other remote repositories.
+
+==== Installation
+
+The first step is to register Cave features repository XML in a running Karaf instance:
+
+```
+karaf@root()> features:repo-add cave 4.2.0
+```
+
+Once it's done, you can install the `cave-repository` feature:
+
+```
+karaf@root()> feature:install cave-repository
+```
+
+The Cave Repository Manager service providing:
+
+* shell commands to manipulate the repositories
+* MBean to manipulate the repositories via JMX
+* REST API to manipulate the repositories with REST client (like `curl` for instance)
+
+==== Repositories
+
+A Cave repository is a artifact storage that clients can use (download or uploading artifacts).
+
+Repositories are managed by a `RepositoryService` and each repository has:
+
+* `name` is unique in the repository service.
+* `location` is where the artifacts are actually stored on the filesystem (it can be null if the repository is a proxy).
+* `url` is the HTTP location where the repository is accessible remotely.
+* `proxy` is a list of remote repositories that a repository can proxy.
+* `mirror` is a boolean used when the repository is a proxy. If `true`, when a client download an artifact from the repository, the artifact is download on remote repositories and cached on local repository storage. If `false`, the artifact is not cached on local repository storage.
+* `realm` is a JAAS security realm used to secure the users able to download/upload artifacts on the repository.
+* `download role` is the user role (on the realm) to secure artifacts download operation.
+* `scheduling` is the scheduler configuration (cron or date) to trigger an action on the repository. For instance `cron:0/20 * * * * ?` with execute action on the repository every 20 seconds.
+* `schedulingAction` is the action when scheduler is triggered. Possible actions are `PURGE`, `DELETE`, `COPY targetRepositoryName` and can be combined (comma separated). For instance: `PURGE,DELETE`.
+* `upload role` is the user role (on the realm) to secure artifacts upload operation.
+
+==== Creating repository and repository details
+
+===== `cave:repository-create` and `cave:repository-info` shell commands
+
+You can simply create a repository with the `cave:repository-create` command:
+
+```
+karaf@root()> cave:repository-create myrepo
+```
+
+By the default, `myrepo` repository has the following setup:
+
+* `myrepo` `location` is set to `${KARAF_DATA}/cave/repository/myrepo`. It can be changed at repository creation or later using `cave:repository-location` command (see later).
+* `myrepo` `url` is set to `/cave/repository/myrepo` (based on the Apache Karaf HTTP service). It can be changed at repository creation or later using `cave:repository-url` command (see later).
+* `myrepo` `proxy` is empty as, by default, repositories are "concrete" repository (with storage).
+* `myrepo` `realm` is `karaf` by default. It means that you can use roles defined in the `karaf` JAAS realm. You can take a look on the security and JAAS section of the Apache Karaf user guide for details.
+* `myrepo` `downloadRole` is empty, meaning that anyone can download artifacts from this repository.
+* `myrepo` `uploadRole` is empty, meaning that anyone can upload artifacts to this repository.
+* `myrepo` `scheduling` is empty, meaning no scheduler.
+* `myrepo` `schedulingAction` is empty, meaning no scheduler.
+
+The `cave:repository-create` command provides the options allowing you to specify repository settings:
+
+```
+DESCRIPTION
+        cave:repository-create
+
+        Create a new repository
+
+SYNTAX
+        cave:repository-create [options] name
+
+ARGUMENTS
+        name
+                The repository name
+                (required)
+
+OPTIONS
+        -m, --mirror
+                Enable repository mirror mode (for proxy)
+        -sa, --scheduling-action, --action, --actions
+                The repository scheduling action (DELETE, PURGE, COPY)
+        --help
+                Display this help message
+        -dr, --download-role
+                The repository security download role
+        -r, --realm
+                The repository security realm
+                (defaults to karaf)
+        -ur, --upload-role
+                The repository security upload role
+        -p, --proxy
+                The repository proxy locations
+        -l, --location
+                The repository location
+        -u, --url
+                The repository URL
+        -ps, --pool-size
+                The repository pool size for the HTTP service
+                (defaults to 8)
+        -s, --scheduling, --schedule
+                The repository scheduling (cron: or at:)
+```
+
+You can have details about existing repository using `cave:repository-info` command:
+
+```
+karaf@root()> cave:repository-info myrepo
+Name: myrepo
+Location: /opt/karaf/data/cave/repository/myrepo
+URL: /cave/repository/myrepo
+Proxy:
+Mirror: false
+Realm: karaf
+Download role:
+Upload role:
+Scheduling:
+Scheduling Actions:
+Pool size: 8
+```
+
+You can use "regular" Apache Karaf commands related to the services used by the repository.
+
+For instance, you can see the repository HTTP binding using `http:list` command:
+
+```
+karaf@root()> http:list
+ID  │ Servlet               │ Servlet-Name               │ State       │ Alias                   │ Url
+────┼───────────────────────┼────────────────────────────┼─────────────┼─────────────────────────┼─────────────────
+102 │ CXFNonSpringServlet   │ cxf-osgi-transport-servlet │ Deployed    │ /cxf                    │ [/cxf/*]
+116 │ MavenServlet          │ ServletModel-2             │ Deployed    │ /cave/repository/myrepo │ [/cave/repository/myrepo/*]
+116 │ RepositoryRestServlet │ ServletModel-4             │ Deployed    │ /cave/repository/api    │ [/cave/repository/api/*]
+```
+
+NB: `/cave/repository/api` URL is the default REST API repository service.
+
+It means, using your Internet browser, you can browse `myrepo` on `http://localhost:8181/cave/repository/myrepo/`.
+
+You can also see the realm and login modules with corresponding `jaas:realm-list` command.
+
+===== REST API
+
+It's also possible to create a repository using the REST API. You can find the WADL of the REST API on `/cave/repository/api?_wadl` URL.
+
+For instance, you can create a repository using `curl`:
+
+```
+curl -X POST -H "Content-Type: application/json" http://localhost:8181/cave/repository/api/repositories -d '{ "name":"myrepo", "location": "", "url": "", "proxy":"", "mirror": false,"realm":"karaf","downloadRole":"","uploadRole":"","poolSize":8}'
+```
+
+You can also have details about a repository using the REST API. For instance, using `curl`:
+
+```
+curl -X GET http://localhost:8181/cave/repository/api/repositories/myrepo
+{"name":"myrepo","location":"/opt/karaf/data/cave/repository/myrepo","url":"/cave/repository/myrepo","proxy":null,"mirror":false,"realm":"karaf","downloadRole":null,"uploadRole":null,"poolSize":8}
+```
+
+===== JMX MBean
+
+You can also create a repository using the `create(String name)` or `create(String name, String location, String url, String proxy, boolean mirror, String realm, String downloadRole, String uploadRole, int poolSize)` operations on the `org.apache.karaf.cave:type=repository` MBean.
+
+On the `Repositories` attribute on the `org.apache.karaf.cave:type=repository` MBean, you can get all details about any repository.
+
+===== Service
+
+You can also create a repository programmatically using `create(String name)` or `create(String name, String location, String url, String proxy, boolean mirror, String realm, String downloadRole, String uploadRole, int poolSize)` methods on the `org.apache.karaf.cave.repository.RepositoryService`.
+
+You just have to look for the `RepositoryService` in the Karaf service registry. For instance using DS programming model, you can do:
+
+```
+@Reference
+private org.apache.karaf.cave.repository.RepositoryService repositoryService;
+```
+
+==== Listing repositories
+
+===== `cave:repository-list` shell command
+
+You can list all repositories using the `cave:repository-list` shell command:
+
+```
+karaf@root()> cave:repository-list
+Name   │ Location                               │ URL
+───────┼────────────────────────────────────────┼────────────────────────
+myrepo │ /opt/karaf/data/cave/repository/myrepo │ /cave/repository/myrepo
+```
+
+===== REST API
+
+You can get the list of all repositories via the Cave Repository REST API using GET method on `/cave/repository/api/repositories`. For instance using `curl`:
+
+```
+curl -X GET http://localhost:8181/cave/repository/api/repositories
+[{"name":"myrepo","location":"/home/jbonofre/Downloads/apache-karaf-4.2.7/data/cave/repository/myrepo","url":"/cave/repository/myrepo","proxy":null,"mirror":false,"realm":"karaf","downloadRole":null,"uploadRole":null,"poolSize":8}]
+```
+
+===== JMX MBean
+
+The `repositories` attribute on the `org.apache.karaf.cave:type=repository` MBean provides a tabular data with all repositories.
+
+===== Service
+
+On the `org.apache.karaf.cave.repository.RepositoryService`, you can programmatically get `org.apache.karaf.cave.repository.Repository` collection using `repositories()` method.
+
+==== Changing repository settings
+
+You can change repository settings after it has been created using dedicated operation.
+
+===== `cave:repository-url`, `cave:repository-proxy`, `cave:repository-location`, `cave:repository-security` shell commands
+
+You can see or change the repository location using `cave:repository-location` command:
+
+```
+karaf@root()> cave:repository-location myrepo 
+/opt/karaf/data/cave/repository/myrepo
+karaf@root()> cave:repository-location myrepo /path/to/foo
+/path/to/foo
+```
+
+NB: The repository service will then move the repository location filesystem to the new location.
+
+You can see or change the repository binding HTTP URL using `cave:repository-url` command:
+
+```
+karaf@root()> cave:repository-url myrepo
+/cave/repository/myrepo
+karaf@root()> cave:repository-url myrepo /foo
+/foo
+```
+
+NB: The repository service will stop the repository HTTP service to start a new one on the new URL.
+
+You can see or change the repository proxy settings using `cave:repository-proxy` command:
+
+```
+karaf@root()> cave:repository-proxy myrepo
+null
+karaf@root()> cave:repository-proxy -m myrepo http://repo1.maven.org/maven2@id=central
+http://repo1.maven.org/maven2@id=central (mirror)
+```
+
+NB: When change the proxy settings, the repository location is not changed.
+
+You can see or change the repository security settings using `cave:repository-security` command:
+
+```
+karaf@root()> cave:repository-security myrepo
+Realm: karaf
+Download Role: null
+Upload Role: null
+karaf@root()> cave:repository-security -ur admin myrepo karaf
+Realm: karaf
+Download Role: null
+Upload Role: admin
+```
+
+NB: The repository service will restart the repository HTTP service with the new security settings.
+
+===== REST API
+
+You can use `/cave/repository/api/repositories/myrepo` URL with a updated repository json to update the repository settings. For instance using `curl`:
+
+```
+curl -X POST -H "Content-Type: application/json" http://localhost:8181/cave/repository/api/repositories/myrepo -d '{ "name":"myrepo", "location": "", "url": "", "proxy":"", "mirror": false,"realm":"karaf","downloadRole":"","uploadRole":"","poolSize":8}'
+```
+
+===== JMX MBean
+
+You have dedicated operations on `org.apache.karaf.cave:type=repository` MBean to change repository settings:
+
+* `changeLocation(String repositoryName, String newLocation)` to change the location of a repository. The repository service will actually move the location filesystem.
+* `changeUrl(String repositoryName, String new URL)` to change the URL of a repository. The repository service will stop the previous HTTP repository service to crerate start a new one.
+* `changeProxy(String repositoryName, String proxy, boolean mirror)` to change the proxy settings (URLs and mirror mode). The repository location is not changed.
+* `changeSecurity(String repositoryName, String realm, String downloadRole, String uploadRole)` to change the security settings (realm, download, and upload roles). The repository HTTP service will be restarted with the new security settings.
+
+===== Service
+
+On the `org.apache.karaf.cave.repository.RepositoryService`, you have the following methods to change repository settings:
+
+* `changeLocation(String repositoryName, String newLocation)` to change the location of a repository. The repository service will actually move the location filesystem.
+* `changeUrl(String repositoryName, String new URL)` to change the URL of a repository. The repository service will stop the previous HTTP repository service to crerate start a new one.
+* `changeProxy(String repositoryName, String proxy, boolean mirror)` to change the proxy settings (URLs and mirror mode). The repository location is not changed.
+* `changeSecurity(String repositoryName, String realm, String downloadRole, String uploadRole)` to change the security settings (realm, download, and upload roles). The repository HTTP service will be restarted with the new security settings.
+
+==== Upload artifacts
+
+You can upload artifacts in a repository using a regular HTTP client (via `curl` for instance).
+
+You can also directly use Maven (in your project using `distributionManagement` in the `pom.xml`) or Gradle.
+With Maven, you can also using `deploy:deploy-file` goal:
+
+```
+mvn deploy:deploy-file -Dfile=my.jar -Durl=http://localhost:8181/cave/repository/myrepo -DgroupId=foo -DartifactId=bar -Dversion=1.0-SNAPSHOT -Dpackaging=jar
+```
+
+===== `cave:repository-artifact-add` shell command
+
+
+For convenience, Cave Repository provides `cave:repository-artifact-add` command to upload artifact in a repository:
+
+```
+karaf@root()> cave:repository-artifact-add myrepo mvn:commons-lang/commons-lang/2.6
+```
+
+===== REST API
+
+You can upload an artifact using the Cave Repository REST API via `/cave/repository/api/repositories/myrepo/artifact` URL and providing the artifact URL as header:
+
+```
+curl -X POST -H "artifactUrl: mvn:commons-lang/commons-lang/2.6" http://localhost:8181/cave/repository/api/repositories/myrepo/artifact
+```
+
+===== JMX MBean
+
+The `org.apache.karaf.cave:type=repository` MBean provides the `addArtifact(String repositoryName, String artifactUrl)` operation allowing you to add an artifact in a repository.
+
+===== Service
+
+You can programmatically add artifact in a repository using `org.apache.karaf.cave.repository.RepositoryService` and the `addArtifact(String repositoryName, String artifactUrl)` method.
+
+==== Delete artifacts
+
+===== `cave:repository-artifact-delete` shell command
+
+The `cave:repository-artifact-delete` shell command allows you to delete an artifact from a repository:
+
+```
+karaf@root()> cave:repository-artifact-delete myrepo mvn:commons-lang/commons-lang/2.6
+```
+
+===== REST API
+
+You can delete an artifact from a repository using the Cave Repository REST API via `/cave/repository/api/repositories/myrepo/artifact` URL and providing the artifact URL as header:
+
+```
+curl -X DELETE -H "artifactUrl: mvn:commons-lang/commons-lang/2.6" http://localhost:8181/cave/repository/api/repositories/myrepo/artifact
+```
+
+===== JMX MBean
+
+The `org.apache.karaf.cave:type=repository` MBean provides the `deleteArtifact(String repositoryName, String artifactUrl)` operation allowing you to delete an artifact in a repository.
+
+===== Service
+
+You can programmatically delete artifact in a repository using `org.apache.karaf.cave.repository.RepositoryService` and the `addArtifact(String repositoryName, String artifactUrl)` method.
+
+==== Copy repositories content
+
+You can copy the whole content of a repository location into another repository location (it's a full recursive copy).
+
+===== `cave:repository-copy` shell command
+
+The `cave:repository-copy` shell command copies the whole content of a source repository to a destination repository:
+
+```
+karaf@root()> cave:repository-copy myrepo anotherrepo
+```
+
+===== REST API
+
+Copy is not allowed on the Cave Repository REST API.
+
+===== JMX MBean
+
+The `org.apache.karaf.cave:type=repository` MBean provides the `copy(String sourceRepository, String destinationRepository)` operation.
+
+===== Service
+
+The `org.apache.karaf.cave.repository.RepositoryService` service provides `copy(String sourceRepository, String destinationRepository)` method.
+
+==== Proxy and mirror
+
+A repository can proxy other repositories. The client (for instance Maven) request an artifact on the repository which "proxy" the request to other repositories define.
+
+If the mirror mode is enabled, the artifacts are copied into the repository storage location in addition of being delivered to the client (it's kind of artifacts caching).
+If the mirror mode is disabled, the repository storage is not used at all, and the artifact is passed directly.
+
+The proxy setting define the list of remote repositories (separated with coma). In addition, you can use `@id=` to give a name to the remote repository (just like in the `settings.xml`, but this is optional but recommended), `@snapshots` to indicate the remote repository can contains SNAPSHOT artifacts, `@noreleases` to indicate the remote repository doesn't only contains release artifacts.
+
+For instance, you can proxy Maven Central with the following command:
+
+```
+karaf@root()> cave:repository-create -p http://repo1.maven.org/maven2@id=central myrepo
+karaf@root()> cave:repository-info myrepo
+Name: myrepo
+Location: null
+URL: /cave/repository/myrepo
+Proxy: http://repo1.maven.org/maven2@id=central
+Mirror: false
+Realm: karaf
+Download role:
+Upload role:
+Pool size: 8
+```
+
+Now, let's request an artifact on `myrepo` repository using `http://localhost:8181/cave/repository/myrepo/commons-lang/commons-lang/2.6/commons-lang-2.6.jar` (remember `myrepo` is empty and doesn't have any location):
+
+```
+curl -O http://localhost:8181/cave/repository/myrepo/commons-lang/commons-lang/2.6/commons-lang-2.6.jar
+  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
+                                 Dload  Upload   Total   Spent    Left  Speed
+100  277k  100  277k    0     0  16.9M      0 --:--:-- --:--:-- --:--:-- 16.9M
+```
+
+We can see here, `myrepo` has correctly proxy the request to Maven Central.
+
+Now, let's add a location and enable `mirror` on `myrepo` repository:
+
+```
+karaf@root()> cave:repository-location myrepo /tmp
+karaf@root()> cave:repository-proxy -m myrepo http://repo1.apache.org/maven2@id=Central
+karaf@root()> cave:repository-info myrepo
+Name: myrepo
+Location: /tmp
+URL: /cave/repository/myrepo
+Proxy: http://repo1.apache.org/maven2@id=Central
+Mirror: true
+Realm: karaf
+Download role:
+Upload role:
+Scheduling:
+Scheduling Actions:
+Pool size: 8
+```
+
+And now, we perform the same request using `curl`:
+
+```
+curl -O http://localhost:8181/cave/repository/myrepo/commons-lang/commons-lang/2.6/commons-lang-2.6.jar
+  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
+                                 Dload  Upload   Total   Spent    Left  Speed
+100  277k  100  277k    0     0  16.9M      0 --:--:-- --:--:-- --:--:-- 16.9M
+```
+
+Now, if we check in the `myrepo` storage location (that we defined to `/tmp`), we can see the "cached" artifact:
+
+```
+ls -ltr /tmp/commons-lang/commons-lang/2.6/commons-lang-2.6.jar
+-rw-r--r-- 1 karaf karaf 284220 oct.  13 10:56 /tmp/commons-lang/commons-lang/2.6/commons-lang-2.6.jar
+```
+
+==== OSGi Bundle Repository
+
+Cave Repository supports OSGi Bundle Repository descriptor generation (aka `repository.xml`).
+
+It can scan all artifacts in a repository to generate the `repository.xml`.
+
+===== `cave:repository-update-bundle-descriptor` shell command
+
+The `cave:repository-update-bundle-descriptor` shell command trigger the scan of all artifacts in a repository to generate a `repository.xml`:
+
+```
+karaf@root()> cave:repository-update-bundle-descriptor myrepo
+```
+
+We now have a `repository.xml` generated (or updated) in the repository storage location. Of course, it's also available via HTTP on `http://localhost:8181/cave/repository/myrepo/repository.xml`.
+
+===== REST API
+
+You can trigger `repository.xml` update via the Cave Repository REST API on `/cave/repository/api/repositories/myrepo/bundle`. For instance using `curl`:
+
+```
+curl -X POST http://localhost:8181/cave/repository/api/repositories/myrepo/bundle
+```
+
+===== JMX MBean
+
+The `org.apache.karaf.cave:type=repository` MBean provides the `updateBundleRepositoryDescriptor(String repositoryName)` operation to update the OSGi Bundle Repository `repository.xml`.
+
+===== Service
+
+The `org.apache.karaf.cave.repository.RepositoryService` service provides the `updateBundleRepositoryDescriptor(String name)` method to update the OSGi Bundle Repository `repository.xml`.
+
+==== Purge
+
+You can completely purge a repository store, removing all artifacts.
+
+===== `cave:repository-purge` shell command
+
+You can cleanup completely a repository storage location using `cave:repository-purge` command:
+
+```
+karaf@root()> cave:repository-purge myrepo
+```
+
+NB: you will have an error if the repository doesn't have any storage location defined, for instance when the repository is only a proxy.
+
+===== REST API
+
+You can cleanup completely a repository storage using `/cave/repository/api/repositories/myrepo/purge` URL (POST). For instance, using `curl`:
+
+```
+curl X POST http://localhost:8181/cave/repository/api/repositories/myrepo/purge
+```
+
+===== JMX MBean
+
+The `org.apache.karaf.cave:type=repository` MBean provides the `purge(String repositoryName)` operation to trigger a repository location purge.
+
+===== Service
+
+The `org.apache.karaf.cave.repository.RepositoryService` service provides the `purge(String repositoryName)` method to trigger a repository location purge.
+
+==== Remove
+
+You can completely remove a repository from the Cave Repository Service (by default, the repository storage is not deleted). Optionally, you can also purge the storage location.
+
+===== `cave:repository-remove` shell command
+
+The `cave:repository-remove` shell command removes a repository, optionally (using `-p, --purge` option) removing the repository storage:
+
+```
+karaf@root()> cave:repository-remove -p myrepo
+```
+
+===== REST API
+
+You can delete a repository using `/cave/repository/api/repositories/myrepo` (DELETE). For instance, using `curl`:
+
+```
+curl -X DELETE http://localhost:8181/cave/repository/api/repositories/myrepo
+```
+
+NB: it's not possible to purge the repository storage when removing the repository via the REST API. You have first to purge the repository location before removing the repository.
+
+===== JMX MBean
+
+The `org.apache.karaf.cave;type=repository` MBean provides the `remove(String repositoryName, boolean purgeLocation)` operation to remove a repository. If `purgeLocation` is true, the repository storage will be cleanup, `false` else.
+
+===== Service
+
+The `org.apache.karaf.cave.repository.RepositoryService` service provides the `remove(String repositoryName, boolean purgeLocation)` method to remove a repository. If `purgeLocation` is true, the repository storage will be cleanup, `false` else.
+
+==== Repository Scheduler
+
+You can trigger action on a repository using Cave Scheduling feature.
+
+The repository scheduling supports cron (to periodically perform actions) or date (to execute actions at a specific time). For instance, you can perform actions every 5 seconds using `cron:0/5 * * * * ?` as repository scheduling.
+You can also execute actions at a certain date using `at:2020-05-13T13:56:45`.
+
+The valid repository scheduling actions are:
+
+* `PURGE` to cleanup the repository storage location.
+* `DELETE` to remove the repository.
+* `COPY targetRepositoryName` to copy all artifacts from the repository storage to another repository.
+
+The actions can be combined (comma separated). For instance, you can copy and purge using `COPY myrepo,PURGE` or purge and remove using `PURGE,DELETE`, etc.
+
+The repository scheduling and actions can be defined at repository creation time (using `-s` and `-sa` options on `cave:repository-create` shell command for instance), or later.
+
+===== `cave:repository-schedule` shell command
+
+The `cave:repository-schedule` shell command displays or set the current repository scheduling.
+
+For instance, you can set the scheduling using:
+
+```
+karaf@root()> cave:repository-schedule myrepo "cron:0/20 * * * * ?" "COPY destination"
+```
+
+===== JMX MBean
+
+The `org.apache.karaf.cave:type=repository` MBean provides the `changeScheduling(String name, String scheduling, String actions)` operation to change the repository scheduling.
+
+===== Service
+
+The `org.apache.karaf.cave.repository.RepositoryService` service provides the `changeScheduling(String name, String scheduling, String schedulingAction)` method to change the repository scheduling.
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index eeab177..18e7139 100644
--- a/pom.xml
+++ b/pom.xml
@@ -30,18 +30,19 @@
 
     <groupId>org.apache.karaf</groupId>
     <artifactId>cave</artifactId>
-    <version>4.1.3-SNAPSHOT</version>
+    <version>4.2.0-SNAPSHOT</version>
     <packaging>pom</packaging>
     <name>Apache Karaf :: Cave</name>
 
     <properties>
         <commons-io.version>2.6</commons-io.version>
-        <cxf.version>3.1.18</cxf.version>
-        <httpclient.version>4.3.4</httpclient.version>
-        <jsoup.version>1.7.3</jsoup.version>
-        <karaf.version>4.1.7</karaf.version>
+        <cxf.version>3.3.2</cxf.version>
+        <httpclient.version>4.5.10</httpclient.version>
+        <jackson.version>2.10.0</jackson.version>
+        <jsoup.version>1.12.1</jsoup.version>
+        <karaf.version>4.2.7</karaf.version>
         <osgi.version>6.0.0</osgi.version>
-        <commons-codec.version>1.10</commons-codec.version>
+        <commons-codec.version>1.13</commons-codec.version>
         <wagon.version>1.0</wagon.version>
 
         <servlet.spec.groupId>javax.servlet</servlet.spec.groupId>
@@ -52,63 +53,55 @@
     </properties>
 
     <modules>
-        <module>server</module>
+        <module>repository</module>
+        <module>gateway</module>
         <module>deployer</module>
-        <module>rest</module>
         <module>manual</module>
         <module>assembly</module>
+        <module>itest</module>
     </modules>
 
     <scm>
         <connection>scm:git:https://gitbox.apache.org/repos/asf/karaf-cave.git</connection>
         <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/karaf-cave.git</developerConnection>
         <url>https://gitbox.apache.org/repos/asf?p=karaf-cave.git;a=summary</url>
-      <tag>HEAD</tag>
   </scm>
 
     <dependencyManagement>
         <dependencies>
             <dependency>
-                <groupId>org.apache.karaf.cave.server</groupId>
-                <artifactId>org.apache.karaf.cave.server.api</artifactId>
+                <groupId>org.apache.karaf.cave.repository</groupId>
+                <artifactId>org.apache.karaf.cave.repository.api</artifactId>
                 <version>${project.version}</version>
             </dependency>
             <dependency>
-                <groupId>org.apache.karaf.cave.server</groupId>
-                <artifactId>org.apache.karaf.cave.server.storage</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>org.apache.karaf.cave.server</groupId>
-                <artifactId>org.apache.karaf.cave.server.command</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>org.apache.karaf.cave.server</groupId>
-                <artifactId>org.apache.karaf.cave.server.management</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>org.apache.karaf.cave.server</groupId>
-                <artifactId>org.apache.karaf.cave.server.maven</artifactId>
+                <groupId>org.apache.karaf.cave.repository</groupId>
+                <artifactId>org.apache.karaf.cave.repository.service</artifactId>
                 <version>${project.version}</version>
             </dependency>
+
             <dependency>
-                <groupId>org.apache.karaf.cave.server</groupId>
-                <artifactId>org.apache.karaf.cave.server.rest</artifactId>
+                <groupId>org.apache.karaf.cave.gateway</groupId>
+                <artifactId>org.apache.karaf.cave.gateway.api</artifactId>
                 <version>${project.version}</version>
             </dependency>
             <dependency>
-                <groupId>org.apache.karaf.cave.server</groupId>
-                <artifactId>org.apache.karaf.cave.server.http</artifactId>
+                <groupId>org.apache.karaf.cave.gateway</groupId>
+                <artifactId>org.apache.karaf.cave.gateway.service</artifactId>
                 <version>${project.version}</version>
             </dependency>
+
             <dependency>
                 <groupId>org.apache.cxf</groupId>
                 <artifactId>cxf-rt-frontend-jaxrs</artifactId>
                 <version>${cxf.version}</version>
             </dependency>
             <dependency>
+                <groupId>com.fasterxml.jackson.jaxrs</groupId>
+                <artifactId>jackson-jaxrs-json-provider</artifactId>
+                <version>${jackson.version}</version>
+            </dependency>
+            <dependency>
                 <groupId>org.apache.karaf.shell</groupId>
                 <artifactId>org.apache.karaf.shell.core</artifactId>
                 <version>${karaf.version}</version>
@@ -119,6 +112,11 @@
                 <version>${karaf.version}</version>
             </dependency>
             <dependency>
+                <groupId>org.apache.karaf.scheduler</groupId>
+                <artifactId>org.apache.karaf.scheduler.core</artifactId>
+                <version>${karaf.version}</version>
+            </dependency>
+            <dependency>
                 <groupId>org.apache.karaf</groupId>
                 <artifactId>org.apache.karaf.util</artifactId>
                 <version>${karaf.version}</version>
@@ -178,7 +176,13 @@
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
-            <version>4.11</version>
+            <version>4.12</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.easymock</groupId>
+            <artifactId>easymock</artifactId>
+            <version>4.0.2</version>
             <scope>test</scope>
         </dependency>
         <dependency>
@@ -222,7 +226,7 @@
                 <plugin>
                     <groupId>org.apache.felix</groupId>
                     <artifactId>maven-bundle-plugin</artifactId>
-                    <version>4.1.0</version>
+                    <version>4.2.1</version>
                     <extensions>true</extensions>
                     <configuration>
                         <instructions>
@@ -244,8 +248,8 @@
                 <artifactId>maven-compiler-plugin</artifactId>
                 <version>3.1</version>
                 <configuration>
-                    <source>1.7</source>
-                    <target>1.7</target>
+                    <source>1.8</source>
+                    <target>1.8</target>
                 </configuration>
             </plugin>
             <plugin>
@@ -425,7 +429,7 @@
                         <artifactId>maven-javadoc-plugin</artifactId>
                         <version>2.9.1</version>
                         <configuration>
-                            <source>1.7</source>
+                            <source>1.8</source>
                         </configuration>
                         <executions>
                             <execution>
diff --git a/server/command/pom.xml b/repository/api/pom.xml
similarity index 64%
rename from server/command/pom.xml
rename to repository/api/pom.xml
index 79cb1a3..90094e5 100644
--- a/server/command/pom.xml
+++ b/repository/api/pom.xml
@@ -23,43 +23,32 @@
 
     <parent>
         <groupId>org.apache.karaf.cave</groupId>
-        <artifactId>org.apache.karaf.cave.server</artifactId>
-        <version>4.1.3-SNAPSHOT</version>
+        <artifactId>org.apache.karaf.cave.repository</artifactId>
+        <version>4.2.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 
-    <groupId>org.apache.karaf.cave.server</groupId>
-    <artifactId>org.apache.karaf.cave.server.command</artifactId>
-    <name>Apache Karaf :: Cave :: Server :: Command</name>
+    <groupId>org.apache.karaf.cave.repository</groupId>
+    <artifactId>org.apache.karaf.cave.repository.api</artifactId>
+    <name>Apache Karaf :: Cave :: Repository :: API</name>
     <packaging>bundle</packaging>
 
-    <dependencies>
-        <dependency>
-            <groupId>org.apache.karaf.shell</groupId>
-            <artifactId>org.apache.karaf.shell.core</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.karaf.cave.server</groupId>
-            <artifactId>org.apache.karaf.cave.server.api</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>
+                <inherited>true</inherited>
+                <extensions>true</extensions>
                 <configuration>
                     <instructions>
-                        <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
+                        <Export-Package>
+                            org.apache.karaf.cave.repository
+                        </Export-Package>
                     </instructions>
                 </configuration>
             </plugin>
         </plugins>
     </build>
 
-</project>
+</project>
\ No newline at end of file
diff --git a/repository/api/src/main/java/org/apache/karaf/cave/repository/Repository.java b/repository/api/src/main/java/org/apache/karaf/cave/repository/Repository.java
new file mode 100644
index 0000000..e479438
--- /dev/null
+++ b/repository/api/src/main/java/org/apache/karaf/cave/repository/Repository.java
@@ -0,0 +1,238 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.cave.repository;
+
+/**
+ * Cave repository.
+ */
+public class Repository {
+
+    private String name;
+    private String location;
+    private String url;
+    private String proxy;
+    private boolean mirror;
+    private String realm;
+    private String downloadRole;
+    private String uploadRole;
+    private String scheduling;
+    private String schedulingAction;
+    private int poolSize;
+
+    /**
+     * Get repository name.
+     *
+     * @return the repository name.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Set the repository name.
+     *
+     * @param name the repository name.
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Get the repository storage location.
+     *
+     * @return the repository storage location.
+     */
+    public String getLocation() {
+        return location;
+    }
+
+    /**
+     * Set the repository storage location.
+     *
+     * @param location the repository storage location.
+     */
+    public void setLocation(String location) {
+        this.location = location;
+    }
+
+    /**
+     * Get the HTTP URL of the repository.
+     *
+     * @return The repository HTTP URL.
+     */
+    public String getUrl() {
+        return url;
+    }
+
+    /**
+     * Set the HTTP URL of the repository.
+     *
+     * @param url The repository HTTP URL.
+     */
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    /**
+     * Get the proxied repositories by this one.
+     *
+     * @return The repositories proxied by this repository.
+     */
+    public String getProxy() {
+        return proxy;
+    }
+
+    /**
+     * Set the proxied repositories by this one.
+     *
+     * @param proxy The repositories proxied by this repository.
+     */
+    public void setProxy(String proxy) {
+        this.proxy = proxy;
+    }
+
+    /**
+     * Get the proxy mode (mirroring or not).
+     *
+     * @return The repositories proxy mode, true for mirroring, false else.
+     */
+    public boolean isMirror() {
+        return mirror;
+    }
+
+    /**
+     * Set the proxy mode (mirroring or not).
+     *
+     * @param mirror true to set the proxy mode to mirror, false else.
+     */
+    public void setMirror(boolean mirror) {
+        this.mirror = mirror;
+    }
+
+    /**
+     * Get the JAAS realm used to secure the repository access.
+     *
+     * @return the name of JAAS realm.
+     */
+    public String getRealm() {
+        return realm;
+    }
+
+    /**
+     * Set the JAAS realm name used to secure the repository access.
+     *
+     * @param realm the name of the JAAS realm to use with this repository.
+     */
+    public void setRealm(String realm) {
+        this.realm = realm;
+    }
+
+    /**
+     * Get the user role allowed to download artifacts on this repository.
+     *
+     * @return the user role name.
+     */
+    public String getDownloadRole() {
+        return downloadRole;
+    }
+
+    /**
+     * Set the user role name allowed to download artifacts on this repository.
+     *
+     * @param downloadRole the user role name.
+     */
+    public void setDownloadRole(String downloadRole) {
+        this.downloadRole = downloadRole;
+    }
+
+    /**
+     * Get the user role allowed to upload artifacts on this repository.
+     *
+     * @return the user role name.
+     */
+    public String getUploadRole() {
+        return uploadRole;
+    }
+
+    /**
+     * Set the user role name allowed to upload artifacts on this repository.
+     *
+     * @param uploadRole the user role name.
+     */
+    public void setUploadRole(String uploadRole) {
+        this.uploadRole = uploadRole;
+    }
+
+    /**
+     * Get the scheduling of the repository. Valid format is cron:xx, at:xx or simply cron.
+     * For instance:
+     *    cron:0 0/10 * * * ?
+     *    at:2014-05-13T13:56:45
+     * @return the current repository scheduling or {@code null} if not defined.
+     */
+    public String getScheduling() {
+        return scheduling;
+    }
+
+    /**
+     * Set the repository scheduling. Valid format is cron:xx, at:xx or simply cron.
+     * For instance:
+     *    cron:0 0/10 * * * ?
+     *    at:2014-05-13T13:56:45
+     *
+     * @param scheduling the new repository scheduling.
+     */
+    public void setScheduling(String scheduling) {
+        this.scheduling = scheduling;
+    }
+
+    /**
+     * Get the action performed at scheduling. Valid values are DELETE, PURGE, COPY (it can be combined using comma as separator).
+     *
+     * @return the action performed at scheduling (DELETE, PURGE, COPY).
+     */
+    public String getSchedulingAction() {
+        return schedulingAction;
+    }
+
+    /**
+     * Set the action performed at scheduling. Valid values are DELETE, PURGE, COPY (it can combined using comma as separator).
+     *
+     * @param schedulingAction the action performed at scheduling (DELETE, PURGE, COPY).
+     */
+    public void setSchedulingAction(String schedulingAction) {
+        this.schedulingAction = schedulingAction;
+    }
+
+    /**
+     * Get the pool size for the repository Maven servlet.
+     *
+     * @return the pool size.
+     */
+    public int getPoolSize() {
+        return poolSize;
+    }
+
+    /**
+     * Set the pool size for the repository Maven servlet.
+     *
+     * @param poolSize the pool size.
+     */
+    public void setPoolSize(int poolSize) {
+        this.poolSize = poolSize;
+    }
+}
diff --git a/repository/api/src/main/java/org/apache/karaf/cave/repository/RepositoryService.java b/repository/api/src/main/java/org/apache/karaf/cave/repository/RepositoryService.java
new file mode 100644
index 0000000..2b4e980
--- /dev/null
+++ b/repository/api/src/main/java/org/apache/karaf/cave/repository/RepositoryService.java
@@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.cave.repository;
+
+import java.util.Collection;
+
+/**
+ * Manage Cave repositories.
+ */
+public interface RepositoryService {
+
+    /**
+     * Create a repository with a given name.
+     *
+     * @param name the repository name.
+     * @return the {@link Repository} created.
+     */
+    Repository create(String name) throws Exception;
+
+    /**
+     * Create a repository with a given name and a storage location.
+     *
+     * @param name the repository name.
+     * @param location the repository storage location.
+     * @return the {@link Repository} created.
+     */
+    Repository create(String name, String location) throws Exception;
+
+    /**
+     * Create a repositoty with a given name, storage location, and proxy URL.
+     *
+     * @param name the repository name.
+     * @param location the repository storage location.
+     * @param proxy the repository proxy URL.
+     * @return the {@link Repository} created.
+     */
+    Repository create(String name, String location, String proxy) throws Exception;
+
+    /**
+     * Create a repository with a given name, storage location, proxy and mirror repositories.
+     *
+     * @param name the repository name.
+     * @param location the repository storage location.
+     * @param proxy the repository proxy URL.
+     * @param mirror the repository proxy mode (true for mirroring, false else).
+     * @return the {@link Repository} created.
+     */
+    Repository create(String name, String location, String proxy, boolean mirror) throws Exception;
+
+    /**
+     * Create a repository with a given name, a storage location and all security settings (realm and user roles).
+     *
+     * @param name the repository name.
+     * @param location the repository storage location.
+     * @param url the repository HTTP URL.
+     * @param proxy the repositories proxied by this repository.
+     * @param mirror the repository proxy mode (true for mirroring, false else).
+     * @param realm the JAAS realm name.
+     * @param downloadRole the user role name allowed to download artifacts on this repository.
+     * @param uploadRole the user role name allowed to upload artifacts on this repository.
+     * @param scheduling the scheduling on this repository.
+     * @param schedulingAction the action performed at scheduling time.
+     * @param poolSize the pool size used internally by the repository Maven servlet.
+     * @return the {@link Repository} created.
+     */
+    Repository create(String name, String location, String url, String proxy, boolean mirror, String realm, String downloadRole, String uploadRole, String scheduling, String schedulingAction, int poolSize) throws Exception;
+
+    /**
+     * Remove a repository.
+     * NB: by default, the repository storage is not cleanup.
+     *
+     * @param name the repository name.
+     */
+    void remove(String name) throws Exception;
+
+    /**
+     * Remove a repository, eventually cleaning the repository storage.
+     *
+     * @param name the repository name.
+     * @param storageCleanup true to cleanup the repository storage, false else.
+     */
+    void remove(String name, boolean storageCleanup) throws Exception;
+
+    /**
+     * Purge the storage of an existing repository.
+     *
+     * @param name the repository name.
+     */
+    void purge(String name) throws Exception;
+
+    /**
+     * Change the location of an existing repository.
+     *
+     * @param name the repository name.
+     * @param location the new repository location.
+     */
+    void changeLocation(String name, String location) throws Exception;
+
+    /**
+     * Change the URL of an existing repository.
+     *
+     * @param name the repository name.
+     * @param url the new repository URL.
+     */
+    void changeUrl(String name, String url) throws Exception;
+
+    /**
+     * Change the proxy URL of an existing repository.
+     *
+     * @param name the repository name.
+     * @param proxy the new repository proxy URL.
+     * @param mirror the new repository proxy mode (true for mirroring, false else).
+     */
+    void changeProxy(String name, String proxy, boolean mirror) throws Exception;
+
+    /**
+     * Change the repository security configuration.
+     *
+     * @param name the repository name.
+     * @param realm the JAAS realm to use with.
+     * @param downloadRole the JAAS role used for download.
+     * @param uploadRole the JAAS role used for upload.
+     */
+    void changeSecurity(String name, String realm, String downloadRole, String uploadRole) throws Exception;
+
+    /**
+     * Change the repository scheduling configuration.
+     *
+     * @param name the repository name.
+     * @param scheduling the scheduling (cron or trigger) for this repository.
+     * @param schedulingAction the action performed at scheduling time.
+     */
+    void changeScheduling(String name, String scheduling, String schedulingAction) throws Exception;
+
+    /**
+     * Copy storage of a repository into another repository.
+     *
+     * @param sourceRepository the source repository name.
+     * @param destinationRepository the destination repository name.
+     */
+    void copy(String sourceRepository, String destinationRepository) throws Exception;
+
+    /**
+     * Get the list of existing repositories.
+     *
+     * @return the {@link Collection} of repositories.
+     */
+    Collection<Repository> repositories();
+
+    /**
+     * Get a repository identified by a name.
+     *
+     * @param name the repository name.
+     * @return the corresponding {@link Repository} or {@code null} if it doesn't exist.
+     */
+    Repository repository(String name);
+
+    /**
+     * Add an artifact in the repository identified by the given name.
+     *
+     * @param url the artifact URL.
+     * @param name the repository name.
+     */
+    void addArtifact(String url, String name) throws Exception;
+
+    /**
+     * Add an artifact in the repository identified by the given name, using provided Maven coordinates.
+     *
+     * @param url the artifact URL.
+     * @param groupId the artifact groupId.
+     * @param artifactId the artifact artifactId.
+     * @param version the artifact version.
+     * @param type the artifact type.
+     * @param classifier the artifact classifier (or null).
+     * @param name the repository name.
+     */
+    void addArtifact(String url, String groupId, String artifactId, String version, String type, String classifier, String name) throws Exception;
+
+    /**
+     * Delete an artifact in the given repository.
+     *
+     * @param artifactUrl the artifact URL in the repository (could be relative path or mvn URL).
+     * @param name the repository name.
+     */
+    void deleteArtifact(String artifactUrl, String name) throws Exception;
+
+    /**
+     * Delete an artifact (identified by Maven coordinates) in the given repository.
+     *
+     * @param groupId the artifact groupId.
+     * @param artifactId the artifact artifactId.
+     * @param version the artifact version.
+     * @param type the artifact type.
+     * @param classifier the artifact classifier.
+     * @param name the repository name.
+     */
+    void deleteArtifact(String groupId, String artifactId, String version, String type, String classifier, String name) throws Exception;
+
+    /**
+     * Create/update bundle repository.xml (formerly OBR) for the given repository.
+     *
+     * @param name the repository name.
+     */
+    void updateBundleRepositoryDescriptor(String name) throws Exception;
+
+}
diff --git a/deployer/pom.xml b/repository/pom.xml
similarity index 86%
copy from deployer/pom.xml
copy to repository/pom.xml
index 5d23b7e..0e5f05f 100644
--- a/deployer/pom.xml
+++ b/repository/pom.xml
@@ -24,20 +24,18 @@
     <parent>
         <groupId>org.apache.karaf</groupId>
         <artifactId>cave</artifactId>
-        <version>4.1.3-SNAPSHOT</version>
+        <version>4.2.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 
     <groupId>org.apache.karaf.cave</groupId>
-    <artifactId>org.apache.karaf.cave.deployer</artifactId>
-    <name>Apache Karaf :: Cave :: Deployer</name>
+    <artifactId>org.apache.karaf.cave.repository</artifactId>
+    <name>Apache Karaf :: Cave :: Repository</name>
     <packaging>pom</packaging>
 
     <modules>
         <module>api</module>
         <module>service</module>
-        <module>command</module>
-        <module>management</module>
     </modules>
 
 </project>
\ No newline at end of file
diff --git a/deployer/service/pom.xml b/repository/service/pom.xml
similarity index 59%
copy from deployer/service/pom.xml
copy to repository/service/pom.xml
index 6009585..801e795 100644
--- a/deployer/service/pom.xml
+++ b/repository/service/pom.xml
@@ -23,29 +23,57 @@
 
     <parent>
         <groupId>org.apache.karaf.cave</groupId>
-        <artifactId>org.apache.karaf.cave.deployer</artifactId>
-        <version>4.1.3-SNAPSHOT</version>
+        <artifactId>org.apache.karaf.cave.repository</artifactId>
+        <version>4.2.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 
-    <groupId>org.apache.karaf.cave.deployer</groupId>
-    <artifactId>org.apache.karaf.cave.deployer.service</artifactId>
-    <name>Apache Karaf :: Cave :: Deployer :: Service</name>
+    <groupId>org.apache.karaf.cave.repository</groupId>
+    <artifactId>org.apache.karaf.cave.repository.service</artifactId>
+    <name>Apache Karaf :: Cave :: Repository :: Service</name>
     <packaging>bundle</packaging>
 
     <properties>
-        <maven.version>3.1.1</maven.version>
         <aether.version>1.0.1.v20141111</aether.version>
+        <maven.version>3.1.1</maven.version>
     </properties>
 
     <dependencies>
         <dependency>
-            <groupId>org.apache.karaf.cave.deployer</groupId>
-            <artifactId>org.apache.karaf.cave.deployer.api</artifactId>
-            <version>${project.version}</version>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.cmpn</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.cave.repository</groupId>
+            <artifactId>org.apache.karaf.cave.repository.api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${servlet.spec.groupId}</groupId>
+            <artifactId>${servlet.spec.artifactId}</artifactId>
+            <version>${servlet.spec.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.url</groupId>
+            <artifactId>pax-url-aether</artifactId>
+            <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.scheduler</groupId>
+            <artifactId>org.apache.karaf.scheduler.core</artifactId>
         </dependency>
 
-        <!-- Maven dependencies -->
         <dependency>
             <groupId>org.apache.maven</groupId>
             <artifactId>maven-core</artifactId>
@@ -78,8 +106,6 @@
             <artifactId>maven-aether-provider</artifactId>
             <version>${maven.version}</version>
         </dependency>
-
-        <!-- Eclipse Aether -->
         <dependency>
             <groupId>org.eclipse.aether</groupId>
             <artifactId>aether-connector-wagon</artifactId>
@@ -146,28 +172,6 @@
             </exclusions>
         </dependency>
         <dependency>
-            <groupId>org.apache.httpcomponents</groupId>
-            <artifactId>httpcore-osgi</artifactId>
-            <version>4.2.5</version>
-            <exclusions>
-                <exclusion>
-                    <groupId>commons-codec</groupId>
-                    <artifactId>commons-codec</artifactId>
-                </exclusion>
-            </exclusions>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.httpcomponents</groupId>
-            <artifactId>httpclient-osgi</artifactId>
-            <version>4.2.5</version>
-            <exclusions>
-                <exclusion>
-                    <groupId>commons-codec</groupId>
-                    <artifactId>commons-codec</artifactId>
-                </exclusion>
-            </exclusions>
-        </dependency>
-        <dependency>
             <groupId>org.eclipse.sisu</groupId>
             <artifactId>org.eclipse.sisu.plexus</artifactId>
             <version>0.1.1</version>
@@ -200,12 +204,8 @@
                     <artifactId>jsr305</artifactId>
                 </exclusion>
                 <exclusion>
-                    <artifactId>guava</artifactId>
-                    <groupId>com.google.guava</groupId>
-                </exclusion>
-                <exclusion>
-                    <artifactId>javax.inject</artifactId>
                     <groupId>javax.inject</groupId>
+                    <artifactId>javax.inject</artifactId>
                 </exclusion>
             </exclusions>
         </dependency>
@@ -219,66 +219,20 @@
             <artifactId>sisu-guava</artifactId>
             <version>0.9.9</version>
         </dependency>
-        <!--
-        <dependency>
-            <groupId>javax.inject</groupId>
-            <artifactId>com.springsource.javax.inject</artifactId>
-            <version>1.0.0</version>
-        </dependency>
-        -->
-
-        <!-- Guava -->
         <dependency>
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
             <version>18.0</version>
         </dependency>
 
-        <!-- OSGi & Karaf-->
-        <dependency>
-            <groupId>org.osgi</groupId>
-            <artifactId>osgi.core</artifactId>
-        </dependency>
         <dependency>
-            <groupId>org.osgi</groupId>
-            <artifactId>osgi.cmpn</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.karaf</groupId>
-            <artifactId>org.apache.karaf.util</artifactId>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-frontend-jaxrs</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.apache.karaf.features</groupId>
-            <artifactId>org.apache.karaf.features.core</artifactId>
-            <version>${karaf.version}</version>
+            <groupId>com.fasterxml.jackson.jaxrs</groupId>
+            <artifactId>jackson-jaxrs-json-provider</artifactId>
         </dependency>
-
-        <!-- Logging -->
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-api</artifactId>
-        </dependency>
-
-        <!-- Testing -->
-        <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-            <version>4.12</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.ops4j.pax.url</groupId>
-            <artifactId>pax-url-aether</artifactId>
-            <version>2.4.6</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-simple</artifactId>
-            <version>1.7.25</version>
-            <scope>test</scope>
-        </dependency>
-
     </dependencies>
 
     <build>
@@ -290,82 +244,48 @@
             <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
-                <version>2.5.4</version>
-                <extensions>true</extensions>
                 <inherited>true</inherited>
+                <extensions>true</extensions>
                 <configuration>
                     <instructions>
+                        <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
+                        <Export-Package>!*</Export-Package>
                         <Import-Package>
-                            org.apache.karaf.cave.deployer.api,
-                            org.slf4j;resolution:=optional,
-                            org.junit;resolution:=optional,
-                            org.testng*;resolution:=optional,
-                            junit.framework.*;resolution:=optional,
-                            org.apache.maven.model.*;resolution:=optional,
-                            org.apache.maven.artifact.*;resolution:=optional,
-                            org.apache.maven.cli.*;resolution:=optional,
-                            org.apache.maven.settings.merge.*;resolution:=optional,
-                            org.apache.maven.wagon.events.*;resolution:=optional,
-                            org.apache.commons.cli.*;resolution:=optional,
-                            org.apache.tools.ant.*;resolution:=optional,
-                            org.codehaus.plexus.component.repository.exception.*,
-                            org.codehaus.plexus.component.annotations.*;resolution:=optional,
-                            org.codehaus.plexus.util.*;resolution:=optional,
-                            org.sonatype.plexus.components.cipher.*;resolution:=optional,
-                            org.sonatype.plexus.components.sec.dispatcher.*;resolution:=optional,
-                            hudson.maven.*;resolution:=optional,
-                            org.eclipse.aether.util.repository.layout.*;resolution:=optional,
-                            org.apache.maven.wagon.*;resolution:=optional,
+                            !org.apache.http*,
+                            !org.eclipse.aether*,
+                            !org.sonatype*,
                             ch.qos.logback*;resolution:=optional,
+                            junit.framework;resolution:=optional,
+                            org.junit;resolution:=optional,
+                            org.slf4j.impl;resolution:=optional,
+                            org.testng.annotations;resolution:=optional,
+                            javax.xml.bind*;version="[2,3)",
+                            org.osgi.service.repository;resolution:=optional,
+                            com.fasterxml.jackson*;version="[2.8,3)",
                             *
                         </Import-Package>
                         <Private-Package>
-                            org.apache.karaf.cave.deployer.service.impl,
-                            org.apache.karaf.features.internal.model,
-                            org.apache.felix.utils.version,
-                            org.apache.felix.utils.properties,
-                            org.apache.karaf.util
+                            org.apache.karaf.cave.repository.service*,
+                            org.apache.karaf.util*,
+                            org.apache.maven*,
+                            org.eclipse.aether*,
+                            com.google*,
+                            javax.inject,
+                            org.apache.commons.cli,
+                            org.apache.http*,
+                            org.codehaus.plexus*,
+                            org.codehaus.classworlds,
+                            org.eclipse.sisu*,
+                            javax.enterprise.inject,
+                            javax.enterprise.context,
+                            javax.enterprise.util,
+                            org.sonatype*,
+                            org.apache.felix.utils.*,
+                            org.apache.felix.resolver,
+                            org.apache.felix.resolver.util,
+                            org.apache.felix.resolver.reason
                         </Private-Package>
-                        <Embed-Dependency>
-                            sisu-guice,
-                            sisu-guava,
-                            sisu-inject-bean,
-                            javax.interceptor-api,
-                            javax.el-api,
-                            cdi-api,
-                            aether-transport-http,
-                            aether-transport-file,
-                            aether-connector-basic,
-                            wagon-provider-api,
-                            aether-util,
-                            aether-spi,
-                            aether-impl,
-                            aether-api,
-                            aether-connector-wagon,
-                            maven-aether-provider,
-                            maven-model,
-                            maven-compat,
-                            ant,
-                            maven-settings-builder,
-                            maven-settings,
-                            org.eclipse.sisu.plexus,
-                            org.eclipse.sisu.inject,
-                            plexus-component-annotations,
-                            plexus-sec-dispatcher,
-                            plexus-cipher,
-                            commons-cli,
-                            maven-embedder,
-                            plexus-classworlds,
-                            maven-model-builder,
-                            maven-artifact,
-                            maven-repository-metadata,
-                            maven-core,
-                            maven-plugin-api,
-                            plexus-utils,
-                            plexus-interpolation
-                        </Embed-Dependency>
-                        <Embed-Directory>jars</Embed-Directory>
-                        <Embed-Transitive>true</Embed-Transitive>
+                        <_dsannotations>*</_dsannotations>
                     </instructions>
                 </configuration>
             </plugin>
diff --git a/repository/service/src/main/java/org/apache/karaf/cave/repository/service/RepositoryServiceImpl.java b/repository/service/src/main/java/org/apache/karaf/cave/repository/service/RepositoryServiceImpl.java
new file mode 100644
index 0000000..37e2900
--- /dev/null
+++ b/repository/service/src/main/java/org/apache/karaf/cave/repository/service/RepositoryServiceImpl.java
@@ -0,0 +1,989 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.cave.repository.service;
+
+import org.apache.karaf.cave.repository.Repository;
+import org.apache.karaf.cave.repository.RepositoryService;
+import org.apache.karaf.cave.repository.service.bundlerepository.BundleRepository;
+import org.apache.karaf.cave.repository.service.bundlerepository.ResourceBuilder;
+import org.apache.karaf.cave.repository.service.bundlerepository.ResourceImpl;
+import org.apache.karaf.cave.repository.service.bundlerepository.ResourceUtils;
+import org.apache.karaf.cave.repository.service.maven.ConsoleRepositoryListener;
+import org.apache.karaf.cave.repository.service.maven.ConsoleTransferListener;
+import org.apache.karaf.cave.repository.service.maven.MavenServlet;
+import org.apache.karaf.cave.repository.service.scheduler.RepositoryJob;
+import org.apache.karaf.scheduler.ScheduleOptions;
+import org.apache.karaf.scheduler.Scheduler;
+import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
+import org.eclipse.aether.impl.DefaultServiceLocator;
+import org.eclipse.aether.installation.InstallRequest;
+import org.eclipse.aether.repository.LocalRepository;
+import org.eclipse.aether.repository.LocalRepositoryManager;
+import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
+import org.eclipse.aether.spi.connector.transport.TransporterFactory;
+import org.eclipse.aether.transport.file.FileTransporterFactory;
+import org.eclipse.aether.transport.http.HttpTransporterFactory;
+import org.ops4j.pax.url.mvn.MavenResolver;
+import org.ops4j.pax.url.mvn.MavenResolvers;
+import org.osgi.framework.BundleException;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Resource;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.http.HttpService;
+
+import javax.xml.bind.DatatypeConverter;
+import javax.xml.stream.XMLStreamException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.FileVisitor;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import static java.nio.file.StandardCopyOption.ATOMIC_MOVE;
+import static java.util.jar.JarFile.MANIFEST_NAME;
+import static org.osgi.service.repository.ContentNamespace.*;
+
+@Component(
+        name = "org.apache.karaf.cave.repository",
+        immediate = true
+)
+public class RepositoryServiceImpl implements RepositoryService {
+
+    @Reference
+    private HttpService httpService;
+
+    @Reference
+    private Scheduler scheduler;
+
+    private final static Pattern mvnPattern = Pattern.compile("mvn:([^/ ]+)/([^/ ]+)/([^/ ]*)(/([^/ ]+)(/([^/ ]+))?)?");
+
+    private static final String STORAGE_FILE = "repositories.db";
+
+    private File baseStorage;
+    private final Map<String, Repository> repositories = new ConcurrentHashMap<>();
+    private String httpContext;
+
+    @Activate
+    public void activate(ComponentContext componentContext) throws Exception {
+        activate(componentContext.getProperties());
+    }
+
+    /**
+     * Only visible for testing purpose
+     */
+    protected void activate(Dictionary<String, Object> properties) throws Exception {
+        baseStorage = new File((properties.get("storage.location") != null) ? properties.get("storage.location").toString() : System.getProperty("karaf.data") + File.separator + "cave" + File.separator + "repository");
+        httpContext = (properties.get("http.context") != null) ? properties.get("http.context").toString() : "/cave/repository";
+        // load repositories db to populate the map and register the servlet
+        load();
+        for (Repository repository : repositories.values()) {
+            registerMavenServlet(repository);
+        }
+    }
+
+    @Deactivate
+    public void deactivate(ComponentContext componentContext) throws Exception {
+        Dictionary<String, Object> properties = componentContext.getProperties();
+        // unregister repository servlets
+        for (Repository repository : repositories.values()) {
+            unregisterMavenServlet(repository);
+        }
+    }
+
+    @Override
+    public Repository create(String name) throws Exception {
+        return create(name, null);
+    }
+
+    @Override
+    public Repository create(String name, String location) throws Exception {
+        return create(name, location, httpContext + "/" + name, null, false, "karaf", null, null, null, null, 8);
+    }
+
+    @Override
+    public Repository create(String name, String location, String proxy) throws Exception {
+        return create(name, location, httpContext + "/" + name, proxy, false, "karaf", null, null, null, null,8);
+    }
+
+    @Override
+    public Repository create(String name, String location, String proxy, boolean mirror) throws Exception {
+        return create(name, location, httpContext + "/" + name, proxy, mirror, "karaf", null, null, null, null, 8);
+    }
+
+    @Override
+    public Repository create(String name, String location, String url, String proxy, boolean mirror, String realm, String downloadRole, String uploadRole, String scheduling, String schedulingAction, int poolSize) throws Exception {
+        if (name == null || name.isEmpty()) {
+            throw new IllegalArgumentException("Repository name is mandatory");
+        }
+        if (repositories.get(name) != null) {
+            throw new IllegalArgumentException("Repository " + name + " already exists");
+        }
+        if (url == null || url.isEmpty()) {
+            url = httpContext + "/" + name;
+        }
+        if (proxy == null || proxy.isEmpty()) {
+            location = new File(baseStorage, name).getAbsolutePath();
+        }
+        // create the repository storage
+        if (location != null && !location.isEmpty() && !Files.exists(Paths.get(location))) {
+            Files.createDirectories(Paths.get(location));
+        }
+        // create the repository model
+        Repository repository = new Repository();
+        repository.setName(name);
+        repository.setLocation(location);
+        repository.setUrl(url);
+        repository.setProxy(proxy);
+        repository.setMirror(mirror);
+        repository.setRealm(realm);
+        repository.setDownloadRole(downloadRole);
+        repository.setUploadRole(uploadRole);
+        repository.setPoolSize(poolSize);
+        repository.setScheduling(scheduling);
+        repository.setSchedulingAction(schedulingAction);
+        repositories.put(name, repository);
+        // register the repository servlet
+        registerMavenServlet(repository);
+        // optionally register the repository scheduling job
+        scheduleRepository(repository);
+        // update repositories DB
+        save();
+        return repository;
+    }
+
+    @Override
+    public void changeLocation(String name, String location) throws Exception {
+        if (repositories.get(name) == null) {
+            throw new IllegalArgumentException("Repository " + name + " doesn't exist");
+        }
+        Repository repository = repositories.get(name);
+        if (repository.getLocation() != null && !repository.getLocation().isEmpty()) {
+            if (!Files.exists(Paths.get(location))) {
+                Files.createDirectories(Paths.get(location));
+            }
+            final Path source = Paths.get(repository.getLocation());
+            final Path target = Paths.get(location);
+            Files.move(source, target, ATOMIC_MOVE);
+        }
+        if (location != null && !location.isEmpty()) {
+            File locationFile = new File(location);
+            repository.setLocation(locationFile.getAbsolutePath());
+        } else {
+            repository.setLocation(location);
+        }
+        repositories.put(name, repository);
+        save();
+    }
+
+    @Override
+    public void changeUrl(String name, String url) throws Exception {
+        if (repositories.get(name) == null) {
+            throw new IllegalArgumentException("Repository " + name + " doesn't exist");
+        }
+        if (url == null) {
+            throw new IllegalArgumentException("URL can't be null");
+        }
+        Repository repository = repositories.get(name);
+        unregisterMavenServlet(repository);
+        repository.setUrl(url);
+        registerMavenServlet(repository);
+        repositories.put(name, repository);
+        save();
+    }
+
+    @Override
+    public void changeProxy(String name, String proxy, boolean mirror) throws Exception {
+        if (repositories.get(name) == null) {
+            throw new IllegalArgumentException("Repository " + name + " doesn't exist");
+        }
+        Repository repository = repositories.get(name);
+        unregisterMavenServlet(repository);
+        repository.setProxy(proxy);
+        repository.setMirror(mirror);
+        registerMavenServlet(repository);
+        repositories.put(name, repository);
+        save();
+    }
+
+    @Override
+    public void changeSecurity(String name, String realm, String downloadRole, String uploadRole) throws Exception {
+        if (repositories.get(name) == null) {
+            throw new IllegalArgumentException("Repository " + name + " doesn't exist");
+        }
+        Repository repository = repositories.get(name);
+        unregisterMavenServlet(repository);
+        repository.setRealm(realm);
+        repository.setDownloadRole(downloadRole);
+        repository.setUploadRole(uploadRole);
+        registerMavenServlet(repository);
+        repositories.put(name, repository);
+        save();
+    }
+
+    @Override
+    public void changeScheduling(String name, String scheduling, String schedulingAction) throws Exception {
+        if (repositories.get(name) == null) {
+            throw new IllegalArgumentException("Repository " + name + " doesn't exist");
+        }
+        Repository repository = repositories.get(name);
+        unscheduleRepository(repository);
+        repository.setScheduling(scheduling);
+        repository.setSchedulingAction(schedulingAction);
+        scheduleRepository(repository);
+        repositories.put(name, repository);
+        save();
+    }
+
+    @Override
+    public void copy(String sourceRepositoryName, String destinationRepositoryName) throws Exception {
+        if (repositories.get(sourceRepositoryName) == null) {
+            throw new IllegalArgumentException("Repository " + sourceRepositoryName + " doesn't exist");
+        }
+        if (repositories.get(destinationRepositoryName) == null) {
+            throw new IllegalArgumentException("Repository " + destinationRepositoryName + " doesn't exist");
+        }
+        Repository sourceRepository = repositories.get(sourceRepositoryName);
+        Repository destinationRepository = repositories.get(destinationRepositoryName);
+        if (sourceRepository.getLocation() == null || sourceRepository.getLocation().isEmpty()) {
+            throw new IllegalStateException("Source repository " + sourceRepositoryName + " location is not defined");
+        }
+        if (destinationRepository.getLocation() == null || destinationRepository.getLocation().isEmpty()) {
+            throw new IllegalStateException("Destination repository " + destinationRepositoryName + " location is not defined");
+        }
+        final Path source = Paths.get(sourceRepository.getLocation());
+        final Path target = Paths.get(destinationRepository.getLocation());
+        Files.walkFileTree(source, new FileVisitor<Path>() {
+            @Override
+            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
+                Path newDir = target.resolve(source.relativize(dir));
+                try {
+                    Files.copy(dir, newDir, StandardCopyOption.COPY_ATTRIBUTES);
+                } catch (FileAlreadyExistsException faee) {
+                    // ignore
+                } catch (IOException ioe) {
+                    return FileVisitResult.SKIP_SUBTREE;
+                }
+                return FileVisitResult.CONTINUE;
+            }
+
+            @Override
+            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                Files.copy(file, target.resolve(source.relativize(file)), StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
+                return FileVisitResult.CONTINUE;
+            }
+
+            @Override
+            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
+                return FileVisitResult.CONTINUE;
+            }
+
+            @Override
+            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+                if (exc == null) {
+                    Path newDir = target.resolve(source.relativize(dir));
+                    FileTime time = Files.getLastModifiedTime(dir);
+                    Files.setLastModifiedTime(newDir, time);
+                }
+                return FileVisitResult.CONTINUE;
+            }
+        });
+    }
+
+    @Override
+    public void remove(String name) throws Exception {
+        remove(name, false);
+    }
+
+    @Override
+    public void remove(String name, boolean storageCleanup) throws Exception {
+        if (repositories.get(name) == null) {
+            throw new IllegalArgumentException("Repository " + name + " doesn't exist");
+        }
+        Repository repository = repositories.get(name);
+        // cleanup storage
+        if (storageCleanup && repository.getLocation() != null && !repository.getLocation().isEmpty()) {
+            purge(repository);
+        }
+        // unregister repository servlet
+        unregisterMavenServlet(repository);
+        // unschedule
+        unscheduleRepository(repository);
+        // remove the repository from the map and update repositories DB
+        repositories.remove(name);
+        save();
+    }
+
+    @Override
+    public void purge(String name) throws Exception {
+        if (repositories.get(name) == null) {
+            throw new IllegalArgumentException("Repository " + name + " doesn't exist");
+        }
+        purge(repositories.get(name));
+    }
+
+    @Override
+    public synchronized Collection<Repository> repositories() {
+        return repositories.values();
+    }
+
+    @Override
+    public synchronized Repository repository(String name) {
+        return repositories.get(name);
+    }
+
+    @Override
+    public void addArtifact(String artifactUrl, String name) throws Exception {
+        Map<String, String> mavenCoordinates = new HashMap<>();
+        if (isMavenUrl(artifactUrl)) {
+            mavenCoordinates = parseMvnUrl(artifactUrl);
+        } else {
+            int index = artifactUrl.lastIndexOf('.');
+            if (index != -1) {
+                mavenCoordinates.put("extension", artifactUrl.substring(index + 1));
+                int slashIndex = artifactUrl.lastIndexOf('/');
+                if (slashIndex != -1) {
+                    mavenCoordinates.put("artifactId", artifactUrl.substring(slashIndex + 1, index));
+                } else {
+                    throw new IllegalArgumentException("Can't find possible artifactId in the provided artifact URL");
+                }
+            } else {
+                mavenCoordinates.put("extension", "jar");
+                int slashIndex = artifactUrl.lastIndexOf('/');
+                if (slashIndex != -1) {
+                    mavenCoordinates.put("artifactId", artifactUrl.substring(slashIndex + 1));
+                } else {
+                    throw new IllegalArgumentException("Can't find possible artifactId in the provided artifact URL");
+                }
+            }
+        }
+        addArtifact(artifactUrl, mavenCoordinates.get("groupId"), mavenCoordinates.get("artifactId"), mavenCoordinates.get("version"), mavenCoordinates.get("extension"), mavenCoordinates.get("classifier"), name);
+    }
+
+    @Override
+    public void addArtifact(String artifactUrl, String groupId, String artifactId, String version, String type, String classifier, String name) throws Exception {
+        if (repositories.get(name) == null) {
+            throw new IllegalArgumentException("Repository " + name + " doesn't exist");
+        }
+        if (artifactUrl == null) {
+            throw new IllegalArgumentException("Artifact URL can't be null");
+        }
+
+        if (repositories.get(name).getLocation() == null || repositories.get(name).getLocation().isEmpty()) {
+            throw new IllegalStateException("Repository " + name + " location is not defined");
+        }
+
+        File artifactFile = File.createTempFile(artifactId, type);
+        try (FileOutputStream os = new FileOutputStream(artifactFile)) {
+            copyStream(new URI(artifactUrl).toURL().openStream(), os);
+            os.flush();
+        }
+
+        DefaultServiceLocator defaultServiceLocator = MavenRepositorySystemUtils.newServiceLocator();
+        defaultServiceLocator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class);
+        defaultServiceLocator.addService(TransporterFactory.class, FileTransporterFactory.class);
+        defaultServiceLocator.addService(TransporterFactory.class, HttpTransporterFactory.class);
+        RepositorySystem repositorySystem = defaultServiceLocator.getService(RepositorySystem.class);
+        DefaultRepositorySystemSession repositorySystemSession = MavenRepositorySystemUtils.newSession();
+        LocalRepository localRepository = new LocalRepository(repositories.get(name).getLocation());
+        LocalRepositoryManager localRepositoryManager = repositorySystem.newLocalRepositoryManager(repositorySystemSession, localRepository);
+        repositorySystemSession.setLocalRepositoryManager(localRepositoryManager);
+        repositorySystemSession.setTransferListener(new ConsoleTransferListener(System.out));
+        repositorySystemSession.setRepositoryListener(new ConsoleRepositoryListener(System.out));
+        Artifact artifact;
+        if (classifier != null) {
+            artifact = new DefaultArtifact(groupId, artifactId, classifier, type, version);
+        } else {
+            artifact = new DefaultArtifact(groupId, artifactId, type, version);
+        }
+        artifact = artifact.setFile(artifactFile);
+
+        InstallRequest installRequest = new InstallRequest();
+        installRequest.addArtifact(artifact);
+        repositorySystem.install(repositorySystemSession, installRequest);
+    }
+
+    /**
+     * Check if an URL is a mvn one or not.
+     * <p>
+     * Visible for testing purpose.
+     *
+     * @param url the URL to check.
+     * @return true if the URL is a mvn URL, false else.
+     */
+    protected static boolean isMavenUrl(String url) {
+        Matcher matcher = mvnPattern.matcher(url);
+        return matcher.matches();
+    }
+
+    /**
+     * Extract Maven coordinates from a given URL.
+     * <p>
+     * Visible for testing purpose.
+     *
+     * @param artifactUrl the artifact URL.
+     * @return the extracted Maven coordinates.
+     */
+    protected static Map<String, String> parseMvnUrl(String artifactUrl) {
+        Matcher matcher = mvnPattern.matcher(artifactUrl);
+        if (!matcher.matches()) {
+            return null;
+        }
+        Map<String, String> result = new HashMap<>();
+        result.put("groupId", matcher.group(1));
+        result.put("artifactId", matcher.group(2));
+        result.put("version", matcher.group(3));
+        if (matcher.group(5) == null) {
+            result.put("extension", "jar");
+        } else {
+            result.put("extension", matcher.group(5));
+        }
+        result.put("classifier", matcher.group(7));
+        return result;
+    }
+
+    static long copyStream(InputStream is, OutputStream os) throws IOException {
+        byte[] buffer = new byte[4096];
+        long count = 0;
+        int n = 0;
+        while (-1 != (n = is.read(buffer))) {
+            os.write(buffer, 0, n);
+            count += n;
+        }
+        return count;
+    }
+
+    @Override
+    public void deleteArtifact(String artifactUrl, String name) throws Exception {
+        if (repositories.get(name) == null) {
+            throw new IllegalArgumentException("Repository " + name + " doesn't exist");
+        }
+        if (repositories.get(name).getLocation() == null || repositories.get(name).getLocation().isEmpty()) {
+            throw new IllegalStateException("Repository " + name + " location is not defined");
+        }
+        Path path;
+        // if the URL is a mvn URL
+        if (artifactUrl.startsWith("mvn:")) {
+            path = Paths.get(repositories.get(name).getLocation()).resolve(Paths.get(convertMvnUrlToPath(artifactUrl)));
+        } else {
+            // the artifact location is relative to the repository storage
+            path = Paths.get(repositories.get(name).getLocation() + "/" + artifactUrl);
+        }
+        if (Files.exists(path)) {
+            if (Files.isDirectory(path)) {
+                Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
+                    @Override
+                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                        Files.delete(file);
+                        return FileVisitResult.CONTINUE;
+                    }
+
+                    @Override
+                    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+                        Files.delete(dir);
+                        return FileVisitResult.CONTINUE;
+                    }
+                });
+            } else {
+                Files.delete(path);
+            }
+        }
+    }
+
+    /**
+     * Visible for testing.
+     */
+    protected static String convertMvnUrlToPath(String mvnUrl) {
+        Map<String, String> coordinates = parseMvnUrl(mvnUrl);
+        return convertMvnCoordinatesToPath(coordinates);
+    }
+
+    /**
+     * Visible for testing.
+     */
+    protected static String convertMvnCoordinatesToPath(Map<String, String> coordinates) {
+        StringBuilder builder = new StringBuilder();
+        if (coordinates.get("groupId") != null) {
+            builder.append(coordinates.get("groupId").replace(".", "/")).append("/");
+        }
+        builder.append(coordinates.get("artifactId")).append("/");
+        builder.append(coordinates.get("version")).append("/");
+        builder.append(coordinates.get("artifactId")).append("-").append(coordinates.get("version"));
+        if (coordinates.get("classifier") != null) {
+            builder.append("-").append(coordinates.get("classifier"));
+        }
+        builder.append(".").append(coordinates.get("extension"));
+        return builder.toString();
+    }
+
+    @Override
+    public void deleteArtifact(String groupId, String artifactId, String version, String type, String classifier, String name) throws Exception {
+        Map<String, String> coordinates = new HashMap<>();
+        coordinates.put("groupId", groupId);
+        coordinates.put("artifactId", artifactId);
+        coordinates.put("version", version);
+        if (type == null) {
+            coordinates.put("extension", "jar");
+        } else {
+            coordinates.put("extension", type);
+        }
+        coordinates.put("classifier", classifier);
+        if (repositories.get(name).getLocation() != null) {
+            Path path = Paths.get(repositories.get(name).getLocation()).resolve(Paths.get(convertMvnCoordinatesToPath(coordinates)));
+            if (Files.exists(path)) {
+                if (Files.isDirectory(path)) {
+                    Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
+                        @Override
+                        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                            Files.delete(file);
+                            return FileVisitResult.CONTINUE;
+                        }
+
+                        @Override
+                        public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+                            Files.delete(dir);
+                            return FileVisitResult.CONTINUE;
+                        }
+                    });
+                } else {
+                    Files.delete(path);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void updateBundleRepositoryDescriptor(String name) throws Exception {
+        if (repositories.get(name) == null) {
+            throw new IllegalArgumentException("Repository " + name + " doesn't exist");
+        }
+        if (repositories.get(name).getLocation() != null && !repositories.get(name).getLocation().isEmpty()) {
+            Path bundleRepositoryXmlPath = Paths.get(repositories.get(name).getLocation()).resolve("repository.xml");
+            BundleRepository bundleRepository = new BundleRepository(bundleRepositoryXmlPath.toUri().toString(), name);
+            if (!Files.exists(bundleRepositoryXmlPath)) {
+                // init the repository XML
+                try (Writer writer = Files.newBufferedWriter(bundleRepositoryXmlPath, StandardCharsets.UTF_8)) {
+                    bundleRepository.writeRepository(writer);
+                }
+            }
+            List<Resource> resources = new ArrayList<>();
+            updateBundleRepositoryDescriptor(new File(repositories.get(name).getLocation()), resources, repositories.get(name).getLocation());
+            addResources(bundleRepository, resources);
+        }
+    }
+
+    private void addResources(BundleRepository repository, List<Resource> resources) throws IOException, XMLStreamException {
+        if (!resources.isEmpty()) {
+            Map<String, Resource> ids = new HashMap<>();
+            for (Resource resource : repository.getResources()) {
+                ids.put(ResourceUtils.getUri(resource), resource);
+            }
+            List<Resource> toAdd = new ArrayList<>();
+            for (Resource resource : resources) {
+                String uri = ResourceUtils.getUri(resource);
+                if (!ids.containsKey(uri)) {
+                    toAdd.add(resource);
+                    ids.put(uri, resource);
+                }
+            }
+            if (!toAdd.isEmpty()) {
+                repository.addResourcesAndSave(resources);
+            }
+        }
+    }
+
+    private void updateBundleRepositoryDescriptor(File entry, List<Resource> resources, String location) throws Exception {
+        if (entry.isDirectory()) {
+            File[] children = entry.listFiles();
+            if (children != null) {
+                for (File child : children) {
+                    updateBundleRepositoryDescriptor(child, resources, location);
+                }
+            }
+        } else {
+            try {
+                URL bundleUrl = entry.toURI().toURL();
+                if (isBundle(bundleUrl.toString())) {
+                    ResourceImpl resource = createResource(bundleUrl, location);
+                    resources.add(resource);
+                }
+            } catch (BundleException be) {
+                // nothing to do
+            }
+        }
+    }
+
+    private boolean isBundle(String bundleUrl) {
+        return !bundleUrl.matches(".*\\.sha1") && !bundleUrl.matches(".*\\.pom")
+                && !bundleUrl.matches(".*\\.xml") && !bundleUrl.matches(".*\\.repositories")
+                && !bundleUrl.matches(".*\\.properties") && !bundleUrl.matches(".*\\.lastUpdated");
+    }
+
+    private ResourceImpl createResource(URL url, String location) throws BundleException, IOException, NoSuchAlgorithmException {
+        return createResource(url.openConnection(), location);
+    }
+
+    private ResourceImpl createResource(URLConnection urlConnection, String location) throws BundleException, IOException, NoSuchAlgorithmException {
+        return createResource(urlConnection, urlConnection.getURL().toExternalForm(), location, true);
+    }
+
+    private ResourceImpl createResource(URLConnection urlConnection, String uri, String location, boolean readFully) throws BundleException, IOException, NoSuchAlgorithmException {
+        Map<String, String> headers = null;
+        String digest = null;
+        long size = -1;
+        // find headers, estimate length and checksum
+        try (ContentInputStream is = new ContentInputStream(urlConnection.getInputStream())) {
+            ZipInputStream zis = new ZipInputStream(is);
+            ZipEntry entry;
+            while ((entry = zis.getNextEntry()) != null) {
+                if (MANIFEST_NAME.equals(entry.getName())) {
+                    Attributes attributes = new Manifest(zis).getMainAttributes();
+                    headers = new HashMap<>();
+                    for (Map.Entry attr : attributes.entrySet()) {
+                        headers.put(attr.getKey().toString(), attr.getValue().toString());
+                    }
+                    if (!readFully) {
+                        break;
+                    }
+                }
+            }
+            if (readFully) {
+                digest = is.getDigest();
+                size = is.getSize();
+            }
+        }
+        if (headers == null) {
+            throw new BundleException("Resource " + urlConnection.getURL() + " does not contain a manifest");
+        }
+        // fix the content directive
+        try {
+            ResourceImpl resource = ResourceBuilder.build(uri, headers);
+            for (Capability cap : resource.getCapabilities(null)) {
+                if (cap.getNamespace().equals(CONTENT_NAMESPACE)) {
+                    String resourceURI = cap.getAttributes().get(CAPABILITY_URL_ATTRIBUTE).toString();
+                    String locationURI = "file:" + location;
+                    if (resourceURI.startsWith(locationURI)) {
+                        resourceURI = resourceURI.substring(locationURI.length() + 1);
+                        cap.getAttributes().put(CAPABILITY_URL_ATTRIBUTE, resourceURI);
+                    }
+                    if (readFully) {
+                        cap.getAttributes().put(CONTENT_NAMESPACE, digest);
+                        cap.getAttributes().put(CAPABILITY_SIZE_ATTRIBUTE, size);
+                    }
+                    cap.getAttributes().put(CAPABILITY_MIME_ATTRIBUTE, "application/vnd.osgi.bundle");
+                    break;
+                }
+            }
+            return resource;
+        } catch (BundleException e) {
+            throw new BundleException("Unable to create resource from " + uri + ": " + e.getMessage(), e);
+        }
+    }
+
+    private static class ContentInputStream extends FilterInputStream {
+        final MessageDigest md;
+        long size = 0;
+
+        public ContentInputStream(InputStream is) throws NoSuchAlgorithmException {
+            super(is);
+            md = MessageDigest.getInstance("SHA-256");
+        }
+
+        @Override
+        public int read() throws IOException {
+            int b = super.read();
+            if (b >= 0) {
+                md.update((byte) b);
+                size++;
+            }
+            return b;
+        }
+
+        @Override
+        public int read(byte[] b, int off, int len) throws IOException {
+            int length = super.read(b, off, len);
+            if (length > 0) {
+                md.update(b, off, length);
+                this.size += length;
+            }
+            return length;
+        }
+
+        public String getDigest() {
+            byte[] digest = md.digest();
+            StringBuilder builder = new StringBuilder();
+            for (byte b : digest) {
+                builder.append(String.format("%02x", b));
+            }
+            return builder.toString();
+        }
+
+        public long getSize() {
+            return size;
+        }
+
+    }
+
+    /**
+     * Delete (purge) a repository location.
+     *
+     * @param repository the {@link Repository} to purge.
+     */
+    private void purge(Repository repository) throws Exception {
+        if (repository.getLocation() == null || repository.getLocation().isEmpty()) {
+            throw new IllegalStateException("Repository " + repository.getName() + " location is not defined");
+        }
+        if (Files.isDirectory(Paths.get(repository.getLocation()))) {
+            Files.walkFileTree(Paths.get(repository.getLocation()), new SimpleFileVisitor<Path>() {
+                @Override
+                public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) throws IOException {
+                    Files.delete(file);
+                    return FileVisitResult.CONTINUE;
+                }
+
+                @Override
+                public FileVisitResult postVisitDirectory(Path dir, IOException ioe) throws IOException {
+                    Files.delete(dir);
+                    return FileVisitResult.CONTINUE;
+                }
+            });
+        }
+    }
+
+    /**
+     * Save in the repositories DB storage.
+     * <p>
+     * Only visible for testing purpose.
+     */
+    protected synchronized void save() throws Exception {
+        Properties storage = new Properties();
+        storage.setProperty("count", Integer.toString(repositories.values().size()));
+        int i = 0;
+        for (Repository repository : repositories.values()) {
+            storage.setProperty("item." + i + ".name", repository.getName());
+            storage.setProperty("item." + i + ".location", (repository.getLocation() != null) ? repository.getLocation() : "");
+            storage.setProperty("item." + i + ".url", repository.getUrl());
+            storage.setProperty("item." + i + ".proxy", (repository.getProxy() != null) ? repository.getProxy() : "");
+            storage.setProperty("item." + i + ".mirror", (repository.isMirror()) ? "true" : "false");
+            storage.setProperty("item." + i + ".realm", (repository.getRealm() != null) ? repository.getRealm() : "");
+            storage.setProperty("item." + i + ".downloadRole", (repository.getDownloadRole() != null) ? repository.getDownloadRole() : "");
+            storage.setProperty("item." + i + ".uploadRole", (repository.getUploadRole() != null) ? repository.getUploadRole() : "");
+            storage.setProperty("item." + i + ".poolSize", Integer.toString(repository.getPoolSize()));
+            i++;
+        }
+        saveStorage(storage, new File(baseStorage, STORAGE_FILE), "Cave Repositories DB");
+    }
+
+    /**
+     * Load repositories DB storage.
+     * <p>
+     * Only visible for testing purpose.
+     */
+    protected synchronized void load() throws Exception {
+        File storageFile = new File(baseStorage, STORAGE_FILE);
+        Properties storage = loadStorage(storageFile);
+        int count = 0;
+        if (storage.getProperty("count") != null) {
+            count = Integer.parseInt(storage.getProperty("count"));
+        }
+        for (int i = 0; i < count; i++) {
+            String name = storage.getProperty("item." + i + ".name");
+            String location = (storage.getProperty("item." + i + ".location").isEmpty()) ? null : storage.getProperty("item." + i + ".location");
+            String url = storage.getProperty("item." + i + ".url");
+            String proxy = (storage.getProperty("item." + i + ".proxy").isEmpty()) ? null : storage.getProperty("item." + i + ".proxy");
+            boolean mirror = Boolean.parseBoolean(storage.getProperty("item." + i + ".mirror"));
+            String realm = (storage.getProperty("item." + i + ".realm").isEmpty()) ? null : storage.getProperty("item." + i + ".realm");
+            String downloadRole = (storage.getProperty("item." + i + ".downloadRole").isEmpty()) ? null : storage.getProperty("item." + i + ".downloadRole");
+            String uploadRole = (storage.getProperty("item." + i + ".uploadRole").isEmpty()) ? null : storage.getProperty("item." + i + ".uploadRole");
+            int poolSize = Integer.parseInt(storage.getProperty("item." + i + ".poolSize"));
+            Repository repository = new Repository();
+            repository.setName(name);
+            repository.setLocation(location);
+            repository.setUrl(url);
+            repository.setProxy(proxy);
+            repository.setMirror(mirror);
+            repository.setRealm(realm);
+            repository.setDownloadRole(downloadRole);
+            repository.setUploadRole(uploadRole);
+            repository.setPoolSize(poolSize);
+            repositories.put(name, repository);
+        }
+    }
+
+    /**
+     * Write the repositories DB.
+     *
+     * @param properties the repositories storage model.
+     * @param location   the repositories DB location.
+     * @param comment    a header comment in the DB file.
+     */
+    private void saveStorage(Properties properties, File location, String comment) throws Exception {
+        if (!location.exists()) {
+            location.getParentFile().mkdirs();
+            location.createNewFile();
+        }
+        try (OutputStream outputStream = new FileOutputStream(location)) {
+            properties.store(outputStream, comment);
+        }
+    }
+
+    /**
+     * Load the repositories DB.
+     *
+     * @param location the repositories DB location.
+     * @return the repositories storage model.
+     */
+    private Properties loadStorage(File location) throws Exception {
+        Properties properties = new Properties();
+        if (location.exists()) {
+            try (InputStream inputStream = new FileInputStream(location)) {
+                properties.load(inputStream);
+            }
+        }
+        return properties;
+    }
+
+    /**
+     * Register a Maven Servlet in the HTTP Service for the given repository.
+     *
+     * @param repository the {@link Repository} to publish.
+     */
+    private void registerMavenServlet(Repository repository) throws Exception {
+        Hashtable<String, String> mavenResolverConfig = new Hashtable<>();
+        mavenResolverConfig.put("defaultRepositories", "file:" + repository.getLocation() + "@id=" + repository.getName() + "@snapshots@releases");
+        mavenResolverConfig.put("defaultLocalRepoAsRemote", "false");
+        mavenResolverConfig.put("useFallbackRepositories", "false");
+        if (repository.getProxy() == null || repository.getProxy().isEmpty() || repository.isMirror()) {
+            if (repository.getLocation() != null && !repository.getLocation().isEmpty()) {
+                mavenResolverConfig.put("localRepository", repository.getLocation());
+            }
+        }
+        if (repository.getProxy() != null && !repository.getProxy().isEmpty()) {
+            mavenResolverConfig.put("repositories", repository.getProxy() + ",file:" + repository.getLocation() + "@id=" + repository.getName() + "@snapshots");
+        } else {
+            mavenResolverConfig.put("repositories", "file:" + repository.getLocation() + "@id=" + repository.getName() + "@snapshots");
+        }
+        MavenResolver mavenResolver = MavenResolvers.createMavenResolver(mavenResolverConfig, null);
+        MavenServlet mavenServlet = new MavenServlet(mavenResolver, repository.getName(), repository.getLocation(), repository.getPoolSize(), repository.getRealm(), repository.getDownloadRole(), repository.getUploadRole());
+        httpService.registerServlet(repository.getUrl(), mavenServlet, null, null);
+    }
+
+    /**
+     * Register repository scheduling in the scheduler service.
+     *
+     * @param repository the repository to schedule.
+     */
+    private void scheduleRepository(Repository repository) throws Exception {
+        if (repository.getScheduling() != null) {
+            ScheduleOptions scheduleOptions;
+            if (repository.getScheduling().contains(":")) {
+                String[] schedule = repository.getScheduling().split(":");
+                if (schedule[0].equalsIgnoreCase("cron")) {
+                    scheduleOptions = scheduler.EXPR(schedule[1]);
+                } else if (schedule[0].equalsIgnoreCase("at")) {
+                    scheduleOptions = scheduler.AT(DatatypeConverter.parseDateTime(schedule[1]).getTime());
+                } else {
+                    throw new IllegalStateException("Unknown scheduling definition: " + repository.getScheduling());
+                }
+            } else {
+                scheduleOptions = scheduler.EXPR(repository.getScheduling());
+            }
+            scheduleOptions.name("cave-repository-" + repository.getName());
+            scheduler.schedule(new RepositoryJob(this, repository), scheduleOptions);
+        }
+    }
+
+    private void unscheduleRepository(Repository repository) throws Exception {
+        if (scheduler != null && scheduler.getJobs() != null) {
+            if (scheduler.getJobs().get("cave-repository-" + repository.getName()) != null) {
+                scheduler.unschedule("cave-repository-" + repository.getName());
+            }
+        }
+    }
+
+    /**
+     * Unregister the Maven servlet for a repository.
+     *
+     * @param repository the {@link Repository}.
+     */
+    private void unregisterMavenServlet(Repository repository) {
+        httpService.unregister(repository.getUrl());
+    }
+
+    /**
+     * Only visible for testing purpose.
+     */
+    protected void setHttpService(HttpService httpService) {
+        this.httpService = httpService;
+    }
+
+    /**
+     * Only visible for testing purpose.
+     */
+    protected void setScheduler(Scheduler scheduler) {
+        this.scheduler = scheduler;
+    }
+
+    /**
+     * Only visible for testing purpose.
+     */
+    protected void clear() {
+        repositories.clear();
+    }
+}
diff --git a/repository/service/src/main/java/org/apache/karaf/cave/repository/service/bundlerepository/BaseClause.java b/repository/service/src/main/java/org/apache/karaf/cave/repository/service/bundlerepository/BaseClause.java
new file mode 100644
index 0000000..1217e6d
--- /dev/null
+++ b/repository/service/src/main/java/org/apache/karaf/cave/repository/service/bundlerepository/BaseClause.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.cave.repository.service.bundlerepository;
+
+import org.osgi.framework.Version;
+import org.osgi.resource.Resource;
+
+import java.util.Map;
+
+public abstract class BaseClause {
+
+    public abstract Resource getResource();
+
+    public abstract String getNamespace();
+
+    public abstract Map<String, String> getDirectives();
+
+    public abstract Map<String, Object> getAttributes();
+
+    @Override
+    public String toString() {
+        return toString(getResource(), getNamespace(), getAttributes(), getDirectives());
+    }
+
+    public static String toString(Resource res, String namespace, Map<String, Object> attrs, Map<String, String> dirs) {
+        StringBuilder sb = new StringBuilder();
+        if (res != null) {
+            sb.append("[").append(res).append("]");
+        }
+        sb.append(namespace);
+        for (String key : attrs.keySet()) {
+            sb.append("; ");
+            append(sb, key, attrs.get(key), true);
+        }
+        for (String key : dirs.keySet()) {
+            sb.append("; ");
+            append(sb, key, dirs.get(key), false);
+        }
+        return sb.toString();
+    }
+
+    private static void append(StringBuilder sb, String key, Object val, boolean attribute) {
+        sb.append(key);
+        if (val instanceof Version) {
+            sb.append(":Version=").append(val);
+        } else if (val instanceof Long) {
+            sb.append(":Long=").append(val);
+        } else if (val instanceof Double) {
+            sb.append(":Double=").append(val);
+        } else if (val instanceof Iterable) {
+            Iterable it = (Iterable) val;
+            String scalar = null;
+            for (Object o : it) {
+                String ts;
+                if (o instanceof String) {
+                    ts = "String";
+                } else if (o instanceof Long) {
+                    ts = "Long";
+                } else if (o instanceof Double) {
+                    ts = "Double";
+                } else if (o instanceof Version) {
+                    ts = "Version";
+                } else {
+                    throw new IllegalArgumentException("Unsupported scalar type: " + o);
+                }
+                if (scalar == null) {
+                    scalar = ts;
+                } else if (!scalar.equals(ts)) {
+                    throw new IllegalArgumentException("Inconsistent list type for attribute " + key);
+                }
+            }
+            sb.append(":List<").append(scalar).append(">=");
+            sb.append("\"");
+            boolean first = true;
+            for (Object o : it) {
+                if (first) {
+                    first = false;
+                } else {
+                    sb.append(",");
+                }
+                sb.append(o.toString().replace("\"", "\\\"").replace(",", "\\,"));
+            }
+            sb.append("\"");
+        } else {
+            sb.append(attribute ? "=" : ":=");
+            String s = val.toString();
+            if (s.matches("[0-9a-zA-Z_\\-.]*")) {
+                sb.append(s);
+            } else {
+                sb.append("\"").append(s.replace("\"", "\\\\")).append("\"");
+            }
+        }
+    }
+
+}
diff --git a/repository/service/src/main/java/org/apache/karaf/cave/repository/service/bundlerepository/BaseRepository.java b/repository/service/src/main/java/org/apache/karaf/cave/repository/service/bundlerepository/BaseRepository.java
new file mode 100644
index 0000000..801de9f
--- /dev/null
+++ b/repository/service/src/main/java/org/apache/karaf/cave/repository/service/bundlerepository/BaseRepository.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.cave.repository.service.bundlerepository;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.osgi.framework.Constants;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+import org.osgi.service.repository.ExpressionCombiner;
+import org.osgi.service.repository.Repository;
+import org.osgi.service.repository.RequirementBuilder;
+import org.osgi.service.repository.RequirementExpression;
+import org.osgi.util.promise.Promise;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class BaseRepository implements Repository {
+
+    protected final Logger logger = LoggerFactory.getLogger(getClass());
+    protected final List<Resource> resources;
+    protected final Map<String, CapabilitySet> capSets;
+
+    public BaseRepository() {
+        this.resources = new ArrayList<Resource>();
+        this.capSets = new HashMap<String, CapabilitySet>();
+    }
+
+    public BaseRepository(Collection<Resource> resources) {
+        this();
+        for (Resource resource : resources) {
+            addResource(resource);
+        }
+    }
+
+    protected void addResource(Resource resource) {
+        for (Capability cap : resource.getCapabilities(null)) {
+            String ns = cap.getNamespace();
+            capSets.computeIfAbsent(ns, n -> new CapabilitySet(Collections.singletonList(n))).addCapability(cap);
+        }
+        resources.add(resource);
+    }
+
+    public List<Resource> getResources() {
+        return resources;
+    }
+
+    @Override
+    public Map<Requirement, Collection<Capability>> findProviders(Collection<? extends Requirement> requirements) {
+        Map<Requirement, Collection<Capability>> result = new HashMap<Requirement, Collection<Capability>>();
+        for (Requirement requirement : requirements) {
+            CapabilitySet set = capSets.get(requirement.getNamespace());
+            if (set != null) {
+                SimpleFilter sf;
+                if (requirement instanceof RequirementImpl) {
+                    sf = ((RequirementImpl) requirement).getFilter();
+                } else {
+                    String filter = requirement.getDirectives().get(Constants.FILTER_DIRECTIVE);
+                    sf = (filter != null)
+                            ? SimpleFilter.parse(filter)
+                            : new SimpleFilter(null, null, SimpleFilter.MATCH_ALL);
+                }
+                result.put(requirement, set.match(sf, true));
+            } else {
+                result.put(requirement, Collections.<Capability>emptyList());
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public Promise<Collection<Resource>> findProviders(RequirementExpression expression) {
+        return null;
+    }
+
+    @Override
+    public ExpressionCombiner getExpressionCombiner() {
+        return null;
+    }
+
+    @Override
+    public RequirementBuilder newRequirementBuilder(String namespace) {
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/server/storage/src/main/java/org/apache/karaf/cave/server/storage/OsgiRepository.java b/repository/service/src/main/java/org/apache/karaf/cave/repository/service/bundlerepository/BundleRepository.java
similarity index 88%
rename from server/storage/src/main/java/org/apache/karaf/cave/server/storage/OsgiRepository.java
rename to repository/service/src/main/java/org/apache/karaf/cave/repository/service/bundlerepository/BundleRepository.java
index d7a25de..1c605a7 100644
--- a/server/storage/src/main/java/org/apache/karaf/cave/server/storage/OsgiRepository.java
+++ b/repository/service/src/main/java/org/apache/karaf/cave/repository/service/bundlerepository/BundleRepository.java
@@ -14,8 +14,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.cave.server.storage;
+package org.apache.karaf.cave.repository.service.bundlerepository;
 
+import org.osgi.resource.Resource;
+
+import javax.xml.stream.XMLStreamException;
 import java.io.IOException;
 import java.io.Writer;
 import java.net.URI;
@@ -24,17 +27,11 @@ import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.util.List;
 
-import javax.xml.stream.XMLStreamException;
-
-import org.apache.karaf.features.internal.repository.StaxParser;
-import org.apache.karaf.features.internal.repository.XmlRepository;
-import org.osgi.resource.Resource;
-
-public class OsgiRepository extends XmlRepository {
+public class BundleRepository extends XmlRepository {
 
     OsgiLoader loader;
 
-    public OsgiRepository(String url, String name) {
+    public BundleRepository(String url, String name) {
         this(url);
         StaxParser.XmlRepository repository = new StaxParser.XmlRepository();
         repository.name = name;
@@ -42,7 +39,7 @@ public class OsgiRepository extends XmlRepository {
         getLoaders().put(url, loader);
     }
 
-    public OsgiRepository(String url) {
+    public BundleRepository(String url) {
         super(url, -1, false);
     }
 
@@ -56,7 +53,7 @@ public class OsgiRepository extends XmlRepository {
     }
 
     private void load() {
-        // Force repository load
+        // force repository load
         getResources();
     }
 
@@ -78,6 +75,7 @@ public class OsgiRepository extends XmlRepository {
     }
 
     protected static class OsgiLoader extends XmlLoader {
+
         public OsgiLoader(String url) {
             super(url, -1);
         }
diff --git a/repository/service/src/main/java/org/apache/karaf/cave/repository/service/bundlerepository/CapabilityImpl.java b/repository/service/src/main/java/org/apache/karaf/cave/repository/service/bundlerepository/CapabilityImpl.java
new file mode 100644
index 0000000..5259a6b
--- /dev/null
+++ b/repository/service/src/main/java/org/apache/karaf/cave/repository/service/bundlerepository/CapabilityImpl.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.cave.repository.service.bundlerepository;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.osgi.framework.Constants;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Resource;
+
+public class CapabilityImpl extends BaseClause implements Capability {
+
+    private final Resource resource;
+    private final String namespace;
+    private final Map<String, String> dirs;
+    private final Map<String, Object> attrs;
+    private final Set<String> mandatory;
+
+    public CapabilityImpl(Resource resource, String namespace,
+                          Map<String, String> dirs, Map<String, Object> attrs) {
+        this.namespace = namespace;
+        this.resource = resource;
+        this.dirs = dirs;
+        this.attrs = attrs;
+
+        // Handle mandatory directive
+        Set<String> mandatory = Collections.emptySet();
+        String value = this.dirs.get(Constants.MANDATORY_DIRECTIVE);
+        if (value != null) {
+            List<String> names = ResourceBuilder.parseDelimitedString(value, ",");
+            mandatory = new HashSet<>(names.size());
+            for (String name : names) {
+                // If attribute exists, then record it as mandatory.
+                if (this.attrs.containsKey(name)) {
+                    mandatory.add(name);
+                    // Otherwise, report an error.
+                } else {
+                    throw new IllegalArgumentException("Mandatory attribute '" + name + "' does not exist.");
+                }
+            }
+        }
+        this.mandatory = mandatory;
+    }
+
+    public Resource getResource() {
+        return resource;
+    }
+
+    public String getNamespace() {
+        return namespace;
+    }
+
+    public Map<String, String> getDirectives() {
+        return dirs;
+    }
+
+    public Map<String, Object> getAttributes() {
+        return attrs;
+    }
+
+    public boolean isAttributeMandatory(String name) {
+        return !mandatory.isEmpty() && mandatory.contains(name);
+    }
+
+}
\ No newline at end of file
diff --git a/repository/service/src/main/java/org/apache/karaf/cave/repository/service/bundlerepository/CapabilitySet.java b/repository/service/src/main/java/org/apache/karaf/cave/repository/service/bundlerepository/CapabilitySet.java
new file mode 100644
index 0000000..0dd28cd
--- /dev/null
+++ b/repository/service/src/main/java/org/apache/karaf/cave/repository/service/bundlerepository/CapabilitySet.java
@@ -0,0 +1,463 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.cave.repository.service.bundlerepository;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.apache.felix.utils.version.VersionTable;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+import org.osgi.resource.Capability;
+
+public class CapabilitySet {
+
+    private static final Class<?>[] STRING_CLASS = new Class[] {String.class};
+
+    private final Map<String, Map<Object, Set<Capability>>> indices;
+    private final Set<Capability> capSet = new HashSet<>();
+
+    public CapabilitySet(List<String> indexProps) {
+        indices = new TreeMap<>();
+        for (int i = 0; (indexProps != null) && (i < indexProps.size()); i++) {
+            indices.put(
+                    indexProps.get(i), new HashMap<Object, Set<Capability>>());
+        }
+    }
+
+    public void dump() {
+        for (Entry<String, Map<Object, Set<Capability>>> entry : indices.entrySet()) {
+            boolean header1 = false;
+            for (Entry<Object, Set<Capability>> entry2 : entry.getValue().entrySet()) {
+                boolean header2 = false;
+                for (Capability cap : entry2.getValue()) {
+                    if (!header1) {
+                        System.out.println(entry.getKey() + ":");
+                        header1 = true;
+                    }
+                    if (!header2) {
+                        System.out.println("   " + entry2.getKey());
+                        header2 = true;
+                    }
+                    System.out.println("      " + cap);
+                }
+            }
+        }
+    }
+
+    public void addCapability(Capability cap) {
+        capSet.add(cap);
+
+        // Index capability.
+        for (Entry<String, Map<Object, Set<Capability>>> entry : indices.entrySet()) {
+            Object value = cap.getAttributes().get(entry.getKey());
+            if (value != null) {
+                if (value.getClass().isArray()) {
+                    value = convertArrayToList(value);
+                }
+
+                Map<Object, Set<Capability>> index = entry.getValue();
+
+                if (value instanceof Collection) {
+                    Collection c = (Collection) value;
+                    for (Object o : c) {
+                        indexCapability(index, cap, o);
+                    }
+                } else {
+                    indexCapability(index, cap, value);
+                }
+            }
+        }
+    }
+
+    private void indexCapability(
+            Map<Object, Set<Capability>> index, Capability cap, Object capValue) {
+        index.computeIfAbsent(capValue, k -> new HashSet<>()).add(cap);
+    }
+
+    public void removeCapability(Capability cap) {
+        if (capSet.remove(cap)) {
+            for (Entry<String, Map<Object, Set<Capability>>> entry : indices.entrySet()) {
+                Object value = cap.getAttributes().get(entry.getKey());
+                if (value != null) {
+                    if (value.getClass().isArray()) {
+                        value = convertArrayToList(value);
+                    }
+
+                    Map<Object, Set<Capability>> index = entry.getValue();
+
+                    if (value instanceof Collection) {
+                        Collection c = (Collection) value;
+                        for (Object o : c) {
+                            deindexCapability(index, cap, o);
+                        }
+                    } else {
+                        deindexCapability(index, cap, value);
+                    }
+                }
+            }
+        }
+    }
+
+    private void deindexCapability(
+            Map<Object, Set<Capability>> index, Capability cap, Object value) {
+        Set<Capability> caps = index.get(value);
+        if (caps != null) {
+            caps.remove(cap);
+            if (caps.isEmpty()) {
+                index.remove(value);
+            }
+        }
+    }
+
+    public Set<Capability> match(SimpleFilter sf, boolean obeyMandatory) {
+        Set<Capability> matches = match(capSet, sf);
+        return obeyMandatory
+                ? matchMandatory(matches, sf)
+                : matches;
+    }
+
+    @SuppressWarnings("unchecked")
+    private Set<Capability> match(Set<Capability> caps, SimpleFilter sf) {
+        Set<Capability> matches = new HashSet<>();
+
+        if (sf.getOperation() == SimpleFilter.MATCH_ALL) {
+            matches.addAll(caps);
+        } else if (sf.getOperation() == SimpleFilter.AND) {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For AND we calculate the intersection of each subfilter.
+            // We can short-circuit the AND operation if there are no
+            // remaining capabilities.
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (int i = 0; (caps.size() > 0) && (i < sfs.size()); i++) {
+                matches = match(caps, sfs.get(i));
+                caps = matches;
+            }
+        } else if (sf.getOperation() == SimpleFilter.OR) {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For OR we calculate the union of each subfilter.
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (SimpleFilter sf1 : sfs) {
+                matches.addAll(match(caps, sf1));
+            }
+        } else if (sf.getOperation() == SimpleFilter.NOT) {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For OR we calculate the union of each subfilter.
+            matches.addAll(caps);
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (SimpleFilter sf1 : sfs) {
+                matches.removeAll(match(caps, sf1));
+            }
+        } else {
+            Map<Object, Set<Capability>> index = indices.get(sf.getName());
+            if ((sf.getOperation() == SimpleFilter.EQ) && (index != null)) {
+                Set<Capability> existingCaps = index.get(sf.getValue());
+                if (existingCaps != null) {
+                    matches.addAll(existingCaps);
+                    matches.retainAll(caps);
+                }
+            } else {
+                for (Capability cap : caps) {
+                    Object lhs = cap.getAttributes().get(sf.getName());
+                    if (lhs != null) {
+                        if (compare(lhs, sf.getValue(), sf.getOperation())) {
+                            matches.add(cap);
+                        }
+                    }
+                }
+            }
+        }
+
+        return matches;
+    }
+
+    public static boolean matches(Capability cap, SimpleFilter sf) {
+        return matchesInternal(cap, sf) && matchMandatory(cap, sf);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static boolean matchesInternal(Capability cap, SimpleFilter sf) {
+        boolean matched = true;
+
+        if (sf.getOperation() == SimpleFilter.MATCH_ALL) {
+            matched = true;
+        } else if (sf.getOperation() == SimpleFilter.AND) {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For AND we calculate the intersection of each subfilter.
+            // We can short-circuit the AND operation if there are no
+            // remaining capabilities.
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (int i = 0; matched && (i < sfs.size()); i++) {
+                matched = matchesInternal(cap, sfs.get(i));
+            }
+        } else if (sf.getOperation() == SimpleFilter.OR) {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For OR we calculate the union of each subfilter.
+            matched = false;
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (int i = 0; !matched && (i < sfs.size()); i++) {
+                matched = matchesInternal(cap, sfs.get(i));
+            }
+        } else if (sf.getOperation() == SimpleFilter.NOT) {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For OR we calculate the union of each subfilter.
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (SimpleFilter sf1 : sfs) {
+                matched = !(matchesInternal(cap, sf1));
+            }
+        } else {
+            matched = false;
+            Object lhs = cap.getAttributes().get(sf.getName());
+            if (lhs != null) {
+                matched = compare(lhs, sf.getValue(), sf.getOperation());
+            }
+        }
+
+        return matched;
+    }
+
+    private static Set<Capability> matchMandatory(
+            Set<Capability> caps, SimpleFilter sf) {
+        for (Iterator<Capability> it = caps.iterator(); it.hasNext();) {
+            Capability cap = it.next();
+            if (!matchMandatory(cap, sf)) {
+                it.remove();
+            }
+        }
+        return caps;
+    }
+
+    private static boolean matchMandatory(Capability cap, SimpleFilter sf) {
+        if (cap instanceof CapabilityImpl) {
+            for (Entry<String, Object> entry : cap.getAttributes().entrySet()) {
+                if (((CapabilityImpl) cap).isAttributeMandatory(entry.getKey())
+                        && !matchMandatoryAttribute(entry.getKey(), sf)) {
+                    return false;
+                }
+            }
+        } else {
+            String value = cap.getDirectives().get(Constants.MANDATORY_DIRECTIVE);
+            if (value != null) {
+                List<String> names = ResourceBuilder.parseDelimitedString(value, ",");
+                for (Entry<String, Object> entry : cap.getAttributes().entrySet()) {
+                    if (names.contains(entry.getKey())
+                            && !matchMandatoryAttribute(entry.getKey(), sf)) {
+                        return false;
+                    }
+                }
+            }
+
+        }
+        return true;
+    }
+
+    private static boolean matchMandatoryAttribute(String attrName, SimpleFilter sf) {
+        if ((sf.getName() != null) && sf.getName().equals(attrName)) {
+            return true;
+        } else if (sf.getOperation() == SimpleFilter.AND) {
+            List list = (List) sf.getValue();
+            for (Object aList : list) {
+                SimpleFilter sf2 = (SimpleFilter) aList;
+                if ((sf2.getName() != null)
+                        && sf2.getName().equals(attrName)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static boolean compare(Object lhs, Object rhsUnknown, int op) {
+        if (lhs == null) {
+            return false;
+        }
+
+        // If this is a PRESENT operation, then just return true immediately
+        // since we wouldn't be here if the attribute wasn't present.
+        if (op == SimpleFilter.PRESENT) {
+            return true;
+        }
+
+        // If the type is comparable, then we can just return the
+        // result immediately.
+        if (lhs instanceof Comparable) {
+            // Spec says SUBSTRING is false for all types other than string.
+            if ((op == SimpleFilter.SUBSTRING) && !(lhs instanceof String)) {
+                return false;
+            }
+
+            Object rhs;
+            if (op == SimpleFilter.SUBSTRING) {
+                rhs = rhsUnknown;
+            } else {
+                try {
+                    rhs = coerceType(lhs, (String) rhsUnknown);
+                } catch (Exception ex) {
+                    return false;
+                }
+            }
+
+            switch (op) {
+                case SimpleFilter.EQ:
+                    try {
+                        return ((Comparable) lhs).compareTo(rhs) == 0;
+                    } catch (Exception ex) {
+                        return false;
+                    }
+                case SimpleFilter.GTE:
+                    try {
+                        return ((Comparable) lhs).compareTo(rhs) >= 0;
+                    } catch (Exception ex) {
+                        return false;
+                    }
+                case SimpleFilter.LTE:
+                    try {
+                        return ((Comparable) lhs).compareTo(rhs) <= 0;
+                    } catch (Exception ex) {
+                        return false;
+                    }
+                case SimpleFilter.APPROX:
+                    return compareApproximate(lhs, rhs);
+                case SimpleFilter.SUBSTRING:
+                    return SimpleFilter.compareSubstring((List<String>) rhs, (String) lhs);
+                default:
+                    throw new RuntimeException("Unknown comparison operator: " + op);
+            }
+        }
+
+        // If the LHS is not a comparable or boolean, check if it is an
+        // array. If so, convert it to a list so we can treat it as a
+        // collection.
+        if (lhs.getClass().isArray()) {
+            lhs = convertArrayToList(lhs);
+        }
+
+        // If LHS is a collection, then call compare() on each element
+        // of the collection until a match is found.
+        if (lhs instanceof Collection) {
+            for (Object o : (Collection) lhs) {
+                if (compare(o, rhsUnknown, op)) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        // Spec says SUBSTRING is false for all types other than string.
+        if (op == SimpleFilter.SUBSTRING) {
+            return false;
+        }
+
+        // Since we cannot identify the LHS type, then we can only perform
+        // equality comparison.
+        try {
+            return lhs.equals(coerceType(lhs, (String) rhsUnknown));
+        } catch (Exception ex) {
+            return false;
+        }
+    }
+
+    private static boolean compareApproximate(Object lhs, Object rhs) {
+        if (rhs instanceof String) {
+            return removeWhitespace((String) lhs)
+                    .equalsIgnoreCase(removeWhitespace((String) rhs));
+        } else if (rhs instanceof Character) {
+            return Character.toLowerCase((Character) lhs)
+                    == Character.toLowerCase((Character) rhs);
+        }
+        return lhs.equals(rhs);
+    }
+
+    private static String removeWhitespace(String s) {
+        StringBuilder sb = new StringBuilder(s.length());
+        for (int i = 0; i < s.length(); i++) {
+            if (!Character.isWhitespace(s.charAt(i))) {
+                sb.append(s.charAt(i));
+            }
+        }
+        return sb.toString();
+    }
+
+    private static Object coerceType(Object lhs, String rhsString) throws Exception {
+        // If the LHS expects a string, then we can just return
+        // the RHS since it is a string.
+        if (lhs.getClass() == rhsString.getClass()) {
+            return rhsString;
+        }
+
+        // Try to convert the RHS type to the LHS type by using
+        // the string constructor of the LHS class, if it has one.
+        Object rhs;
+        try {
+            if (lhs instanceof Version) {
+                rhs = VersionTable.getVersion(rhsString, false);
+            } else
+                // The Character class is a special case, since its constructor
+                // does not take a string, so handle it separately.
+                if (lhs instanceof Character) {
+                    rhs = rhsString.charAt(0);
+                } else {
+                    // Spec says we should trim number types.
+                    if ((lhs instanceof Number) || (lhs instanceof Boolean)) {
+                        rhsString = rhsString.trim();
+                    }
+                    Constructor ctor = lhs.getClass().getConstructor(STRING_CLASS);
+                    ctor.setAccessible(true);
+                    rhs = ctor.newInstance(rhsString);
+                }
+        } catch (Exception ex) {
+            throw new Exception(
+                    "Could not instantiate class "
+                            + lhs.getClass().getName()
+                            + " from string constructor with argument '"
+                            + rhsString + "' because " + ex
+            );
+        }
+
+        return rhs;
+    }
+
+    /**
+     * This is an ugly utility method to convert an array of primitives
+     * to an array of primitive wrapper objects. This method simplifies
+     * processing LDAP filters since the special case of primitive arrays
+     * can be ignored.
+     *
+     * @param array An array of primitive types.
+     * @return An corresponding array using pritive wrapper objects.
+     */
+    private static List<Object> convertArrayToList(Object array) {
+        int len = Array.getLength(array);
+        List<Object> list = new ArrayList<>(len);
+        for (int i = 0; i < len; i++) {
+            list.add(Array.get(array, i));
+        }
+        return list;
+    }
+}
\ No newline at end of file
diff --git a/repository/service/src/main/java/org/apache/karaf/cave/repository/service/bundlerepository/RequirementImpl.java b/repository/service/src/main/java/org/apache/karaf/cave/repository/service/bundlerepository/RequirementImpl.java
new file mode 100644
index 0000000..645e127
--- /dev/null
+++ b/repository/service/src/main/java/org/apache/karaf/cave/repository/service/bundlerepository/RequirementImpl.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.cave.repository.service.bundlerepository;
+
+import java.util.Map;
+
+import org.osgi.framework.Constants;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+public class RequirementImpl extends BaseClause implements Requirement {
+
+    private final Resource resource;
+    private final String namespace;
+    private final SimpleFilter filter;
+    private final boolean optional;
+    private final Map<String, String> dirs;
+    private final Map<String, Object> attrs;
+
+    public RequirementImpl(
+            Resource resource, String namespace,
+            Map<String, String> dirs, Map<String, Object> attrs, SimpleFilter filter) {
+        this.resource = resource;
+        this.namespace = namespace;
+        this.dirs = dirs;
+        this.attrs = attrs;
+        this.filter = filter;
+        // Find resolution import directives.
+        optional = Constants.RESOLUTION_OPTIONAL.equals(this.dirs.get(Constants.RESOLUTION_DIRECTIVE));
+    }
+
+    public RequirementImpl(
+            Resource resource, String namespace,
+            Map<String, String> dirs, Map<String, Object> attrs) {
+        this(resource, namespace, dirs, attrs, SimpleFilter.convert(attrs));
+    }
+
+    public String getNamespace() {
+        return namespace;
+    }
+
+    public Map<String, String> getDirectives() {
+        return dirs;
+    }
+
+    public Map<String, Object> getAttributes() {
+        return attrs;
+    }
+
+    public Resource getResource() {
+        return resource;
+    }
+
+    public boolean matches(Capability cap) {
+        return CapabilitySet.matches(cap, getFilter());
+    }
+
+    public boolean isOptional() {
+        return optional;
+    }
+
+    public SimpleFilter getFilter() {
+        return filter;
+    }
+
+}
\ No newline at end of file
diff --git a/repository/service/src/main/java/org/apache/karaf/cave/repository/service/bundlerepository/ResourceBuilder.java b/repository/service/src/main/java/org/apache/karaf/cave/repository/service/bundlerepository/ResourceBuilder.java
new file mode 100644
index 0000000..4f995d9
--- /dev/null
+++ b/repository/service/src/main/java/org/apache/karaf/cave/repository/service/bundlerepository/ResourceBuilder.java
@@ -0,0 +1,1203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.cave.repository.service.bundlerepository;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.felix.utils.version.VersionRange;
+import org.apache.felix.utils.version.VersionTable;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+import org.osgi.framework.namespace.ExecutionEnvironmentNamespace;
+import org.osgi.framework.namespace.IdentityNamespace;
+import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.namespace.service.ServiceNamespace;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+import org.osgi.service.repository.ContentNamespace;
+
+public final class ResourceBuilder {
+
+    public static final String RESOLUTION_DYNAMIC = "dynamic";
+
+    private static final char EOF = (char) -1;
+
+    private static final int CLAUSE_START = 0;
+    private static final int PARAMETER_START = 1;
+    private static final int KEY = 2;
+    private static final int DIRECTIVE_OR_TYPEDATTRIBUTE = 4;
+    private static final int ARGUMENT = 8;
+    private static final int VALUE = 16;
+
+    private static final int CHAR = 1;
+    private static final int DELIMITER = 2;
+    private static final int STARTQUOTE = 4;
+    private static final int ENDQUOTE = 8;
+
+    private ResourceBuilder() {
+    }
+
+    public static ResourceImpl build(String uri, Map<String, String> headerMap) throws BundleException {
+        return build(new ResourceImpl(), uri, headerMap, false);
+    }
+
+    public static ResourceImpl build(String uri, Map<String, String> headerMap, boolean removeServiceRequirements) throws BundleException {
+        return build(new ResourceImpl(), uri, headerMap, removeServiceRequirements);
+    }
+
+    public static ResourceImpl build(ResourceImpl resource, String uri, Map<String, String> headerMap) throws BundleException {
+        return build(resource, uri, headerMap, false);
+    }
+
+    public static ResourceImpl build(ResourceImpl resource, String uri, Map<String, String> headerMap, boolean removeServiceRequirements) throws BundleException {
+        try {
+            return doBuild(resource, uri, headerMap, removeServiceRequirements);
+        } catch (Exception e) {
+            throw new BundleException("Unable to build resource for " + uri + ": " + e.getMessage(), e);
+        }
+    }
+
+    private static ResourceImpl doBuild(ResourceImpl resource, String uri, Map<String, String> headerMap, boolean removeServiceRequirements) throws BundleException {
+        // Verify that only manifest version 2 is specified.
+        String manifestVersion = getManifestVersion(headerMap);
+        if (manifestVersion == null || !manifestVersion.equals("2")) {
+            throw new BundleException("Unsupported 'Bundle-ManifestVersion' value: " + manifestVersion);
+        }
+
+        //
+        // Parse bundle version.
+        //
+
+        Version bundleVersion = Version.emptyVersion;
+        if (headerMap.get(Constants.BUNDLE_VERSION) != null) {
+            bundleVersion = VersionTable.getVersion(headerMap.get(Constants.BUNDLE_VERSION));
+        }
+
+        //
+        // Parse bundle symbolic name.
+        //
+
+        String bundleSymbolicName;
+        ParsedHeaderClause bundleCap = parseBundleSymbolicName(headerMap);
+        if (bundleCap == null) {
+            throw new BundleException("Bundle manifest must include bundle symbolic name");
+        }
+        bundleSymbolicName = (String) bundleCap.attrs.get(BundleRevision.BUNDLE_NAMESPACE);
+
+        // Now that we have symbolic name and version, create the resource
+        String type = headerMap.get(Constants.FRAGMENT_HOST) == null ? IdentityNamespace.TYPE_BUNDLE : IdentityNamespace.TYPE_FRAGMENT;
+        {
+            Map<String, String> dirs = new HashMap<>();
+            Map<String, Object> attrs = new HashMap<>();
+            attrs.put(IdentityNamespace.IDENTITY_NAMESPACE, bundleSymbolicName);
+            attrs.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, type);
+            attrs.put(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, bundleVersion);
+            CapabilityImpl identity = new CapabilityImpl(resource, IdentityNamespace.IDENTITY_NAMESPACE, dirs, attrs);
+            resource.addCapability(identity);
+        }
+        if (uri != null) {
+            Map<String, Object> attrs = new HashMap<>();
+            attrs.put(ContentNamespace.CAPABILITY_URL_ATTRIBUTE, uri);
+            resource.addCapability(new CapabilityImpl(resource, ContentNamespace.CONTENT_NAMESPACE, Collections.<String, String>emptyMap(), attrs));
+        }
+
+        // Add a bundle and host capability to all
+        // non-fragment bundles. A host capability is the same
+        // as a require capability, but with a different capability
+        // namespace. Bundle capabilities resolve required-bundle
+        // dependencies, while host capabilities resolve fragment-host
+        // dependencies.
+        if (headerMap.get(Constants.FRAGMENT_HOST) == null) {
+            // All non-fragment bundles have bundle capability.
+            resource.addCapability(new CapabilityImpl(resource, BundleRevision.BUNDLE_NAMESPACE, bundleCap.dirs, bundleCap.attrs));
+            // A non-fragment bundle can choose to not have a host capability.
+            String attachment = bundleCap.dirs.get(Constants.FRAGMENT_ATTACHMENT_DIRECTIVE);
+            attachment = (attachment == null) ? Constants.FRAGMENT_ATTACHMENT_RESOLVETIME : attachment;
+            if (!attachment.equalsIgnoreCase(Constants.FRAGMENT_ATTACHMENT_NEVER)) {
+                Map<String, Object> hostAttrs = new HashMap<>(bundleCap.attrs);
+                Object value = hostAttrs.remove(BundleRevision.BUNDLE_NAMESPACE);
+                hostAttrs.put(BundleRevision.HOST_NAMESPACE, value);
+                resource.addCapability(new CapabilityImpl(
+                        resource, BundleRevision.HOST_NAMESPACE,
+                        bundleCap.dirs,
+                        hostAttrs));
+            }
+        }
+
+        //
+        // Parse Fragment-Host.
+        //
+
+        List<RequirementImpl> hostReqs = parseFragmentHost(resource, headerMap);
+
+        //
+        // Parse Require-Bundle
+        //
+
+        List<ParsedHeaderClause> rbClauses = parseStandardHeader(headerMap.get(Constants.REQUIRE_BUNDLE));
+        rbClauses = normalizeRequireClauses(rbClauses);
+        List<Requirement> rbReqs = convertRequires(rbClauses, resource);
+
+        //
+        // Parse Import-Package.
+        //
+
+        List<ParsedHeaderClause> importClauses = parseStandardHeader(headerMap.get(Constants.IMPORT_PACKAGE));
+        importClauses = normalizeImportClauses(importClauses);
+        List<Requirement> importReqs = convertImports(importClauses, resource);
+
+        //
+        // Parse DynamicImport-Package.
+        //
+
+        List<ParsedHeaderClause> dynamicClauses = parseStandardHeader(headerMap.get(Constants.DYNAMICIMPORT_PACKAGE));
+        dynamicClauses = normalizeDynamicImportClauses(dynamicClauses);
+        List<Requirement> dynamicReqs = convertImports(dynamicClauses, resource);
+
+        //
+        // Parse Require-Capability.
+        //
+
+        List<ParsedHeaderClause> requireClauses = parseStandardHeader(headerMap.get(Constants.REQUIRE_CAPABILITY));
+        requireClauses = normalizeRequireCapabilityClauses(requireClauses);
+        List<Requirement> requireReqs = convertRequireCapabilities(requireClauses, resource);
+
+        //
+        // Parse Bundle-RequiredExecutionEnvironment.
+        //
+        List<Requirement> breeReqs =
+                parseBreeHeader((String) headerMap.get(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT), resource);
+
+        //
+        // Parse Export-Package.
+        //
+
+        List<ParsedHeaderClause> exportClauses = parseStandardHeader(headerMap.get(Constants.EXPORT_PACKAGE));
+        exportClauses = normalizeExportClauses(exportClauses, bundleSymbolicName, bundleVersion);
+        List<Capability> exportCaps = convertExports(exportClauses, resource);
+
+        //
+        // Parse Provide-Capability.
+        //
+
+        List<ParsedHeaderClause> provideClauses = parseStandardHeader(headerMap.get(Constants.PROVIDE_CAPABILITY));
+        provideClauses = normalizeProvideCapabilityClauses(provideClauses);
+        List<Capability> provideCaps = convertProvideCapabilities(provideClauses, resource);
+
+        //
+        // Parse Import-Service and Export-Service
+        // if Require-Capability and Provide-Capability are not set for services
+        //
+
+        boolean hasServiceReferenceCapability = false;
+        for (Capability cap : exportCaps) {
+            hasServiceReferenceCapability |= ServiceNamespace.SERVICE_NAMESPACE.equals(cap.getNamespace());
+        }
+        if (!hasServiceReferenceCapability) {
+            List<ParsedHeaderClause> exportServices = parseStandardHeader(headerMap.get(Constants.EXPORT_SERVICE));
+            List<Capability> caps = convertExportService(exportServices, resource);
+            provideCaps.addAll(caps);
+        }
+
+        boolean hasServiceReferenceRequirement = false;
+        for (Requirement req : requireReqs) {
+            hasServiceReferenceRequirement |= ServiceNamespace.SERVICE_NAMESPACE.equals(req.getNamespace());
+        }
+        if (!hasServiceReferenceRequirement) {
+            List<ParsedHeaderClause> importServices = parseStandardHeader(headerMap.get(Constants.IMPORT_SERVICE));
+            List<Requirement> reqs = convertImportService(importServices, resource);
+            if (!reqs.isEmpty()) {
+                requireReqs.addAll(reqs);
+                hasServiceReferenceRequirement = true;
+            }
+        }
+
+        if (hasServiceReferenceRequirement && removeServiceRequirements) {
+            for (Iterator<Requirement> iterator = requireReqs.iterator(); iterator.hasNext();) {
+                Requirement req = iterator.next();
+                if (ServiceNamespace.SERVICE_NAMESPACE.equals(req.getNamespace())) {
+                    iterator.remove();
+                }
+            }
+        }
+
+        // Combine all capabilities.
+        resource.addCapabilities(exportCaps);
+        resource.addCapabilities(provideCaps);
+
+        // Combine all requirements.
+        resource.addRequirements(hostReqs);
+        resource.addRequirements(importReqs);
+        resource.addRequirements(rbReqs);
+        resource.addRequirements(requireReqs);
+        resource.addRequirements(dynamicReqs);
+
+        return resource;
+    }
+
+    public static List<Requirement> parseRequirement(Resource resource, String requirement) throws BundleException {
+        List<ParsedHeaderClause> requireClauses = parseStandardHeader(requirement);
+        requireClauses = normalizeRequireCapabilityClauses(requireClauses);
+        return convertRequireCapabilities(requireClauses, resource);
+    }
+
+    public static List<Capability> parseCapability(Resource resource, String capability) throws BundleException {
+        List<ParsedHeaderClause> provideClauses = parseStandardHeader(capability);
+        provideClauses = normalizeProvideCapabilityClauses(provideClauses);
+        return convertProvideCapabilities(provideClauses, resource);
+    }
+
+    @SuppressWarnings("deprecation")
+    private static List<ParsedHeaderClause> normalizeImportClauses(List<ParsedHeaderClause> clauses) throws BundleException {
+        // Verify that the values are equals if the package specifies
+        // both version and specification-version attributes.
+        Set<String> dupeSet = new HashSet<>();
+        for (ParsedHeaderClause clause : clauses) {
+            // Check for "version" and "specification-version" attributes
+            // and verify they are the same if both are specified.
+            Object v = clause.attrs.get(Constants.VERSION_ATTRIBUTE);
+            Object sv = clause.attrs.get(Constants.PACKAGE_SPECIFICATION_VERSION);
+            if ((v != null) && (sv != null)) {
+                // Verify they are equal.
+                if (!((String) v).trim().equals(((String) sv).trim())) {
+                    throw new IllegalArgumentException(
+                            "Both version and specification-version are specified, but they are not equal.");
+                }
+            }
+
+            // Ensure that only the "version" attribute is used and convert
+            // it to the VersionRange type.
+            if ((v != null) || (sv != null)) {
+                clause.attrs.remove(Constants.PACKAGE_SPECIFICATION_VERSION);
+                v = (v == null) ? sv : v;
+                clause.attrs.put(Constants.VERSION_ATTRIBUTE, VersionRange.parseVersionRange(v.toString()));
+            }
+
+            // If bundle version is specified, then convert its type to VersionRange.
+            v = clause.attrs.get(Constants.BUNDLE_VERSION_ATTRIBUTE);
+            if (v != null) {
+                clause.attrs.put(Constants.BUNDLE_VERSION_ATTRIBUTE, VersionRange.parseVersionRange(v.toString()));
+            }
+
+            // Verify java.* is not imported, nor any duplicate imports.
+            for (String pkgName : clause.paths) {
+                if (!dupeSet.contains(pkgName)) {
+                    // Verify that java.* packages are not imported.
+                    if (pkgName.startsWith("java.")) {
+                        throw new BundleException("Importing java.* packages not allowed: " + pkgName);
+                        // The character "." has no meaning in the OSGi spec except
+                        // when placed on the bundle class path. Some people, however,
+                        // mistakenly think it means the default package when imported
+                        // or exported. This is not correct. It is invalid.
+                    } else if (pkgName.equals(".")) {
+                        throw new BundleException("Importing '.' is invalid.");
+                        // Make sure a package name was specified.
+                    } else if (pkgName.length() == 0) {
+                        throw new BundleException(
+                                "Imported package names cannot be zero length.");
+                    }
+                    dupeSet.add(pkgName);
+                } else {
+                    throw new BundleException("Duplicate import: " + pkgName);
+                }
+            }
+        }
+
+        return clauses;
+    }
+
+    private static List<Capability> convertExportService(List<ParsedHeaderClause> clauses, Resource resource) {
+        List<Capability> capList = new ArrayList<>();
+        for (ParsedHeaderClause clause : clauses) {
+            for (String path : clause.paths) {
+                Map<String, String> dirs = new LinkedHashMap<>();
+                dirs.put(ServiceNamespace.CAPABILITY_EFFECTIVE_DIRECTIVE, ServiceNamespace.EFFECTIVE_ACTIVE);
+                Map<String, Object> attrs = new LinkedHashMap<>();
+                attrs.put(Constants.OBJECTCLASS, path);
+                attrs.putAll(clause.attrs);
+                capList.add(new CapabilityImpl(
+                        resource,
+                        ServiceNamespace.SERVICE_NAMESPACE,
+                        dirs,
+                        attrs));
+            }
+        }
+        return capList;
+    }
+
+    private static List<Requirement> convertImportService(List<ParsedHeaderClause> clauses, Resource resource) throws BundleException {
+        try {
+            List<Requirement> reqList = new ArrayList<>();
+            for (ParsedHeaderClause clause : clauses) {
+                for (String path : clause.paths) {
+                    String multiple = clause.dirs.get("multiple");
+                    String avail = clause.dirs.get("availability");
+                    String filter = (String) clause.attrs.get("filter");
+                    Map<String, String> dirs = new LinkedHashMap<>();
+                    dirs.put(ServiceNamespace.REQUIREMENT_EFFECTIVE_DIRECTIVE, ServiceNamespace.EFFECTIVE_ACTIVE);
+                    if ("optional".equals(avail)) {
+                        dirs.put(ServiceNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE, ServiceNamespace.RESOLUTION_OPTIONAL);
+                    }
+                    if ("true".equals(multiple)) {
+                        dirs.put(ServiceNamespace.REQUIREMENT_CARDINALITY_DIRECTIVE, ServiceNamespace.CARDINALITY_MULTIPLE);
+                    }
+                    if (filter == null) {
+                        filter = "(" + Constants.OBJECTCLASS + "=" + path + ")";
+                    } else if (!filter.startsWith("(") && !filter.endsWith(")")) {
+                        filter = "(&(" + Constants.OBJECTCLASS + "=" + path + ")(" + filter + "))";
+                    } else {
+                        filter = "(&(" + Constants.OBJECTCLASS + "=" + path + ")" + filter + ")";
+                    }
+                    dirs.put(ServiceNamespace.REQUIREMENT_FILTER_DIRECTIVE, filter);
+                    reqList.add(new RequirementImpl(
+                            resource,
+                            ServiceNamespace.SERVICE_NAMESPACE,
+                            dirs,
+                            Collections.<String, Object>emptyMap(),
+                            SimpleFilter.parse(filter)));
+                }
+            }
+            return reqList;
+        } catch (Exception ex) {
+            throw new BundleException("Error creating requirement: " + ex, ex);
+        }
+    }
+
+    private static List<Requirement> convertImports(List<ParsedHeaderClause> clauses, Resource resource) {
+        // Now convert generic header clauses into requirements.
+        List<Requirement> reqList = new ArrayList<>();
+        for (ParsedHeaderClause clause : clauses) {
+            for (String path : clause.paths) {
+                // Prepend the package name to the array of attributes.
+                Map<String, Object> attrs = clause.attrs;
+                // Note that we use a linked hash map here to ensure the
+                // package attribute is first, which will make indexing
+                // more efficient.
+                // TODO: OSGi R4.3 - This is ordering is kind of hacky.
+                // Prepend the package name to the array of attributes.
+                Map<String, Object> newAttrs = new LinkedHashMap<>(attrs.size() + 1);
+                // We want this first from an indexing perspective.
+                newAttrs.put(BundleRevision.PACKAGE_NAMESPACE, path);
+                newAttrs.putAll(attrs);
+                // But we need to put it again to make sure it wasn't overwritten.
+                newAttrs.put(BundleRevision.PACKAGE_NAMESPACE, path);
+
+                // Create filter now so we can inject filter directive.
+                SimpleFilter sf = SimpleFilter.convert(newAttrs);
+
+                // Inject filter directive.
+                // TODO: OSGi R4.3 - Can we insert this on demand somehow?
+                Map<String, String> dirs = clause.dirs;
+                Map<String, String> newDirs = new HashMap<>(dirs.size() + 1);
+                newDirs.putAll(dirs);
+                newDirs.put(Constants.FILTER_DIRECTIVE, sf.toString());
+
+                // Create package requirement and add to requirement list.
+                reqList.add(
+                        new RequirementImpl(
+                                resource,
+                                BundleRevision.PACKAGE_NAMESPACE,
+                                newDirs,
+                                Collections.<String, Object>emptyMap(),
+                                sf)
+                );
+            }
+        }
+
+        return reqList;
+    }
+
+    @SuppressWarnings("deprecation")
+    private static List<ParsedHeaderClause> normalizeDynamicImportClauses(List<ParsedHeaderClause> clauses) throws BundleException {
+        // Verify that the values are equals if the package specifies
+        // both version and specification-version attributes.
+        for (ParsedHeaderClause clause : clauses) {
+            // Add the resolution directive to indicate that these are
+            // dynamic imports.
+            clause.dirs.put(Constants.RESOLUTION_DIRECTIVE, RESOLUTION_DYNAMIC);
+
+            // Check for "version" and "specification-version" attributes
+            // and verify they are the same if both are specified.
+            Object v = clause.attrs.get(Constants.VERSION_ATTRIBUTE);
+            Object sv = clause.attrs.get(Constants.PACKAGE_SPECIFICATION_VERSION);
+            if ((v != null) && (sv != null)) {
+                // Verify they are equal.
+                if (!((String) v).trim().equals(((String) sv).trim())) {
+                    throw new IllegalArgumentException(
+                            "Both version and specification-version are specified, but they are not equal.");
+                }
+            }
+
+            // Ensure that only the "version" attribute is used and convert
+            // it to the VersionRange type.
+            if ((v != null) || (sv != null)) {
+                clause.attrs.remove(Constants.PACKAGE_SPECIFICATION_VERSION);
+                v = (v == null) ? sv : v;
+                clause.attrs.put(Constants.VERSION_ATTRIBUTE, VersionRange.parseVersionRange(v.toString()));
+            }
+
+            // If bundle version is specified, then convert its type to VersionRange.
+            v = clause.attrs.get(Constants.BUNDLE_VERSION_ATTRIBUTE);
+            if (v != null) {
+                clause.attrs.put(Constants.BUNDLE_VERSION_ATTRIBUTE, VersionRange.parseVersionRange(v.toString()));
+            }
+
+            // Dynamic imports can have duplicates, so verify that java.*
+            // packages are not imported.
+            for (String pkgName : clause.paths) {
+                if (pkgName.startsWith("java.")) {
+                    throw new BundleException("Dynamically importing java.* packages not allowed: " + pkgName);
+                } else if (!pkgName.equals("*") && pkgName.endsWith("*") && !pkgName.endsWith(".*")) {
+                    throw new BundleException("Partial package name wild carding is not allowed: " + pkgName);
+                }
+            }
+        }
+
+        return clauses;
+    }
+
+    private static List<ParsedHeaderClause> normalizeRequireCapabilityClauses(
+            List<ParsedHeaderClause> clauses) throws BundleException {
+
+        // Convert attributes into specified types.
+        for (ParsedHeaderClause clause : clauses) {
+            for (Map.Entry<String, Object> entry : clause.attrs.entrySet()) {
+                if (entry.getKey().equals("version")) {
+                    clause.attrs.put(entry.getKey(), new VersionRange(entry.getValue().toString()));
+                }
+            }
+            for (Map.Entry<String, String> entry : clause.types.entrySet()) {
+                String type = entry.getValue();
+                if (!type.equals("String")) {
+                    if (type.equals("Double")) {
+                        clause.attrs.put(
+                                entry.getKey(),
+                                new Double(clause.attrs.get(entry.getKey()).toString().trim()));
+                    } else if (type.equals("Version")) {
+                        clause.attrs.put(
+                                entry.getKey(),
+                                new Version(clause.attrs.get(entry.getKey()).toString().trim()));
+                    } else if (type.equals("Long")) {
+                        clause.attrs.put(
+                                entry.getKey(),
+                                new Long(clause.attrs.get(entry.getKey()).toString().trim()));
+                    } else if (type.startsWith("List")) {
+                        int startIdx = type.indexOf('<');
+                        int endIdx = type.indexOf('>');
+                        if (((startIdx > 0) && (endIdx <= startIdx))
+                                || ((startIdx < 0) && (endIdx > 0))) {
+                            throw new BundleException(
+                                    "Invalid Provide-Capability attribute list type for '"
+                                            + entry.getKey()
+                                            + "' : "
+                                            + type
+                            );
+                        }
+
+                        String listType = "String";
+                        if (endIdx > startIdx) {
+                            listType = type.substring(startIdx + 1, endIdx).trim();
+                        }
+
+                        List<String> tokens = parseDelimitedString(
+                                clause.attrs.get(entry.getKey()).toString(), ",", false);
+                        List<Object> values = new ArrayList<>(tokens.size());
+                        for (String token : tokens) {
+                            switch (listType) {
+                                case "String":
+                                    values.add(token);
+                                    break;
+                                case "Double":
+                                    values.add(new Double(token.trim()));
+                                    break;
+                                case "Version":
+                                    values.add(new Version(token.trim()));
+                                    break;
+                                case "Long":
+                                    values.add(new Long(token.trim()));
+                                    break;
+                                default:
+                                    throw new BundleException(
+                                            "Unknown Provide-Capability attribute list type for '"
+                                                    + entry.getKey()
+                                                    + "' : "
+                                                    + type
+                                    );
+                            }
+                        }
+                        clause.attrs.put(
+                                entry.getKey(),
+                                values);
+                    } else {
+                        throw new BundleException(
+                                "Unknown Provide-Capability attribute type for '"
+                                        + entry.getKey()
+                                        + "' : "
+                                        + type
+                        );
+                    }
+                }
+            }
+        }
+
+        return clauses;
+    }
+
+    private static List<ParsedHeaderClause> normalizeProvideCapabilityClauses(
+            List<ParsedHeaderClause> clauses) throws BundleException {
+
+        // Convert attributes into specified types.
+        for (ParsedHeaderClause clause : clauses) {
+            for (Map.Entry<String, String> entry : clause.types.entrySet()) {
+                String type = entry.getValue();
+                if (!type.equals("String")) {
+                    if (type.equals("Double")) {
+                        clause.attrs.put(
+                                entry.getKey(),
+                                new Double(clause.attrs.get(entry.getKey()).toString().trim()));
+                    } else if (type.equals("Version")) {
+                        clause.attrs.put(
+                                entry.getKey(),
+                                new Version(clause.attrs.get(entry.getKey()).toString().trim()));
+                    } else if (type.equals("Long")) {
+                        clause.attrs.put(
+                                entry.getKey(),
+                                new Long(clause.attrs.get(entry.getKey()).toString().trim()));
+                    } else if (type.startsWith("List")) {
+                        int startIdx = type.indexOf('<');
+                        int endIdx = type.indexOf('>');
+                        if (((startIdx > 0) && (endIdx <= startIdx))
+                                || ((startIdx < 0) && (endIdx > 0))) {
+                            throw new BundleException(
+                                    "Invalid Provide-Capability attribute list type for '"
+                                            + entry.getKey()
+                                            + "' : "
+                                            + type
+                            );
+                        }
+
+                        String listType = "String";
+                        if (endIdx > startIdx) {
+                            listType = type.substring(startIdx + 1, endIdx).trim();
+                        }
+
+                        List<String> tokens = parseDelimitedString(
+                                clause.attrs.get(entry.getKey()).toString(), ",", false);
+                        List<Object> values = new ArrayList<>(tokens.size());
+                        for (String token : tokens) {
+                            switch (listType) {
+                                case "String":
+                                    values.add(token);
+                                    break;
+                                case "Double":
+                                    values.add(new Double(token.trim()));
+                                    break;
+                                case "Version":
+                                    values.add(new Version(token.trim()));
+                                    break;
+                                case "Long":
+                                    values.add(new Long(token.trim()));
+                                    break;
+                                default:
+                                    throw new BundleException(
+                                            "Unknown Provide-Capability attribute list type for '"
+                                                    + entry.getKey()
+                                                    + "' : "
+                                                    + type
+                                    );
+                            }
+                        }
+                        clause.attrs.put(
+                                entry.getKey(),
+                                values);
+                    } else {
+                        throw new BundleException(
+                                "Unknown Provide-Capability attribute type for '"
+                                        + entry.getKey()
+                                        + "' : "
+                                        + type
+                        );
+                    }
+                }
+            }
+        }
+
+        return clauses;
+    }
+
+    private static List<Requirement> convertRequireCapabilities(
+            List<ParsedHeaderClause> clauses, Resource resource) throws BundleException {
+
+        // Now convert generic header clauses into requirements.
+        List<Requirement> reqList = new ArrayList<>();
+        for (ParsedHeaderClause clause : clauses) {
+            try {
+                String filterStr = clause.dirs.get(Constants.FILTER_DIRECTIVE);
+                SimpleFilter sf = (filterStr != null)
+                        ? SimpleFilter.parse(filterStr)
+                        : SimpleFilter.convert(clause.attrs);
+                for (String path : clause.paths) {
+                    // Create requirement and add to requirement list.
+                    reqList.add(new RequirementImpl(
+                            resource, path, clause.dirs, clause.attrs, sf));
+                }
+            } catch (Exception ex) {
+                throw new BundleException("Error creating requirement: " + ex, ex);
+            }
+        }
+
+        return reqList;
+    }
+
+    private static List<Capability> convertProvideCapabilities(
+            List<ParsedHeaderClause> clauses, Resource resource) throws BundleException {
+
+        List<Capability> capList = new ArrayList<>();
+        for (ParsedHeaderClause clause : clauses) {
+            for (String path : clause.paths) {
+                if (path.startsWith("osgi.wiring.")) {
+//                    throw new BundleException("Manifest cannot use Provide-Capability for '" + path + "' namespace.");
+                }
+
+                // Create package capability and add to capability list.
+                capList.add(new CapabilityImpl(resource, path, clause.dirs, clause.attrs));
+            }
+        }
+
... 9653 lines suppressed ...