FELIX-1457: refactor features management layer

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@801925 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/karaf/features/command/pom.xml b/karaf/features/command/pom.xml
new file mode 100644
index 0000000..ac6304e
--- /dev/null
+++ b/karaf/features/command/pom.xml
@@ -0,0 +1,115 @@
+<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/maven-v4_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.felix.karaf.features</groupId>
+        <artifactId>features</artifactId>
+        <version>1.2.0-SNAPSHOT</version>
+    </parent>
+
+    <groupId>org.apache.felix.karaf.features</groupId>
+    <artifactId>org.apache.felix.karaf.features.command</artifactId>
+    <packaging>bundle</packaging>
+    <version>1.2.0-SNAPSHOT</version>
+    <name>Apache Felix Karaf :: Features Command</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix.karaf.features</groupId>
+            <artifactId>org.apache.felix.karaf.features.core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.bundlerepository</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix.karaf.gshell</groupId>
+            <artifactId>org.apache.felix.karaf.gshell.console</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix.karaf.gshell</groupId>
+            <artifactId>org.apache.felix.karaf.gshell.obr</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.osgi</groupId>
+            <artifactId>spring-osgi-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.servicemix.bundles</groupId>
+            <artifactId>org.apache.servicemix.bundles.junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.easymock</groupId>
+            <artifactId>easymock</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-jdk14</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <configuration>
+                    <instructions>
+                        <Bundle-SymbolicName>${artifactId}</Bundle-SymbolicName>
+                        <Export-Package>${pom.artifactId}*;version=${project.version}</Export-Package>
+                        <Import-Package>
+                            javax.management,
+                            javax.management.loading,
+                            org.osgi.service.command,
+                            org.apache.felix.gogo.commands,
+                            org.apache.felix.karaf.gshell.console,
+                            *
+                        </Import-Package>
+                        <Private-Package>!*</Private-Package>
+                        <_versionpolicy>${bnd.version.policy}</_versionpolicy>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/AddUrlCommand.java b/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/AddUrlCommand.java
new file mode 100644
index 0000000..78c207d
--- /dev/null
+++ b/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/AddUrlCommand.java
@@ -0,0 +1,37 @@
+/*
+ * 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.felix.karaf.features.command;
+
+import java.net.URI;
+import java.util.List;
+
+import org.apache.felix.karaf.features.FeaturesService;
+import org.apache.felix.gogo.commands.Argument;
+import org.apache.felix.gogo.commands.Command;
+
+@Command(scope = "features", name = "addUrl", description = "Add a list of repository URLs to the features service.")
+public class AddUrlCommand extends FeaturesCommandSupport {
+
+    @Argument(required = true, multiValued = true, description = "Repository URLs")
+    List<String> urls;
+
+    protected void doExecute(FeaturesService admin) throws Exception {
+        for (String url : urls) {
+            admin.addRepository(new URI(url));
+        }
+    }
+}
diff --git a/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/FeaturesCommandSupport.java b/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/FeaturesCommandSupport.java
new file mode 100644
index 0000000..3014f35
--- /dev/null
+++ b/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/FeaturesCommandSupport.java
@@ -0,0 +1,49 @@
+/*
+ * 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.felix.karaf.features.command;
+
+import org.apache.felix.karaf.gshell.console.OsgiCommandSupport;
+import org.apache.felix.karaf.features.FeaturesService;
+import org.osgi.framework.ServiceReference;
+
+public abstract class FeaturesCommandSupport extends OsgiCommandSupport {
+
+    protected Object doExecute() throws Exception {
+        // Get repository admin service.
+        ServiceReference ref = getBundleContext().getServiceReference(FeaturesService.class.getName());
+        if (ref == null) {
+            System.out.println("FeaturesService service is unavailable.");
+            return null;
+        }
+        try {
+            FeaturesService admin = (FeaturesService) getBundleContext().getService(ref);
+            if (admin == null) {
+                System.out.println("FeaturesService service is unavailable.");
+                return null;
+            }
+
+            doExecute(admin);
+        }
+        finally {
+            getBundleContext().ungetService(ref);
+        }
+        return null;
+    }
+
+    protected abstract void doExecute(FeaturesService admin) throws Exception;
+
+}
diff --git a/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/InstallFeatureCommand.java b/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/InstallFeatureCommand.java
new file mode 100644
index 0000000..aff684d
--- /dev/null
+++ b/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/InstallFeatureCommand.java
@@ -0,0 +1,38 @@
+/*
+ * 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.felix.karaf.features.command;
+
+import org.apache.felix.karaf.features.FeaturesService;
+import org.apache.felix.gogo.commands.Argument;
+import org.apache.felix.gogo.commands.Command;
+
+@Command(scope = "features", name = "install", description = "Install a feature.")
+public class InstallFeatureCommand extends FeaturesCommandSupport {
+
+    @Argument(required = true, description = "The name of the feature")
+    String name;
+    @Argument(description = "The version of the feature", index = 1)
+    String version;
+
+    protected void doExecute(FeaturesService admin) throws Exception {
+    	if (version != null && version.length() > 0) {
+    		admin.installFeature(name, version);
+    	} else {
+    		admin.installFeature(name);
+    	}
+    }
+}
diff --git a/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/ListFeaturesCommand.java b/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/ListFeaturesCommand.java
new file mode 100644
index 0000000..33e942b
--- /dev/null
+++ b/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/ListFeaturesCommand.java
@@ -0,0 +1,82 @@
+/*
+ * 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.felix.karaf.features.command;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.felix.karaf.features.FeaturesService;
+import org.apache.felix.karaf.features.Feature;
+import org.apache.felix.gogo.commands.Option;
+import org.apache.felix.gogo.commands.Command;
+
+@Command(scope = "features", name = "list", description = "List existing features.")
+public class ListFeaturesCommand extends FeaturesCommandSupport {
+
+    @Option(name = "-i", aliases={"--installed"}, description="Display the list of installed features")
+    boolean installed;
+
+    protected void doExecute(FeaturesService admin) throws Exception {
+        List<Feature> features;
+        List<Feature> installedFeatures;
+        if (installed) {
+            features = Arrays.asList(admin.listInstalledFeatures());
+            installedFeatures = features;
+            if (features == null || features.size() == 0) {
+                System.out.println("No features installed.");
+                return;
+            }
+        } else {
+            features = Arrays.asList(admin.listFeatures());
+            installedFeatures = Arrays.asList(admin.listInstalledFeatures());
+            if (features == null || features.size() == 0) {
+                System.out.println("No features available.");
+                return;
+            }
+        }
+        int maxVersionSize = 7;
+        for (Feature feature : features) {
+            maxVersionSize = Math.max(maxVersionSize, feature.getVersion().length());
+        }
+        StringBuilder sb = new StringBuilder();
+        sb.append("  State         Version    ");
+        for (int i = 7; i < maxVersionSize; i++) {
+            sb.append(" ");
+        }
+        sb.append("Name");
+        System.out.println(sb.toString());
+        for (Feature feature : features) {
+            sb.setLength(0);
+            sb.append("[");
+            if (installedFeatures.contains(feature)) {
+                sb.append("installed  ");
+            } else {
+                sb.append("uninstalled");
+            }
+            sb.append("] [");
+            String v = feature.getVersion();
+            sb.append(v);
+            for (int i = v.length(); i < maxVersionSize; i++) {
+                sb.append(" ");
+            }
+            sb.append("] ");
+            sb.append(feature.getName());
+            System.out.println(sb.toString());
+        }
+    }
+
+}
diff --git a/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/ListUrlCommand.java b/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/ListUrlCommand.java
new file mode 100644
index 0000000..e9c5306
--- /dev/null
+++ b/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/ListUrlCommand.java
@@ -0,0 +1,35 @@
+/*
+ * 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.felix.karaf.features.command;
+
+import org.apache.felix.karaf.features.FeaturesService;
+import org.apache.felix.karaf.features.Repository;
+
+
+public class ListUrlCommand extends FeaturesCommandSupport {
+
+    protected void doExecute(FeaturesService admin) throws Exception {
+        Repository[] repos = admin.listRepositories();
+        if ((repos != null) && (repos.length > 0)) {
+            for (int i = 0; i < repos.length; i++) {
+                System.out.println(repos[i].getURI());
+            }
+        } else {
+            System.out.println("No repository URLs are set.");
+        }
+    }
+}
diff --git a/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/RefreshUrlCommand.java b/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/RefreshUrlCommand.java
new file mode 100644
index 0000000..45b400d
--- /dev/null
+++ b/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/RefreshUrlCommand.java
@@ -0,0 +1,47 @@
+/*
+ * 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.felix.karaf.features.command;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.felix.gogo.commands.Argument;
+import org.apache.felix.gogo.commands.Command;
+import org.apache.felix.karaf.features.FeaturesService;
+import org.apache.felix.karaf.features.Repository;
+
+@Command(scope = "features", name = "refreshUrl", description = "Reload the repositories to obtain a fresh list of features.")
+public class RefreshUrlCommand extends FeaturesCommandSupport {
+
+    @Argument(required = false, multiValued = true, description = "Repository URLs (leave empty for all)")
+    List<String> urls;
+
+    protected void doExecute(FeaturesService admin) throws Exception {
+        if (urls == null || urls.isEmpty()) {
+            urls = new ArrayList<String>();
+            for (Repository repo : admin.listRepositories()) {
+                urls.add(repo.getURI().toString());
+            }
+        }
+        for (String strUri : urls) {
+            URI uri = new URI(strUri);
+            admin.removeRepository(uri);
+            admin.addRepository(uri);
+        }
+    }
+}
diff --git a/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/RemoveUrlCommand.java b/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/RemoveUrlCommand.java
new file mode 100644
index 0000000..17db749
--- /dev/null
+++ b/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/RemoveUrlCommand.java
@@ -0,0 +1,37 @@
+/*
+ * 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.felix.karaf.features.command;
+
+import java.net.URI;
+import java.util.List;
+
+import org.apache.felix.gogo.commands.Argument;
+import org.apache.felix.gogo.commands.Command;
+import org.apache.felix.karaf.features.FeaturesService;
+
+@Command(scope = "features", name = "removeUrl", description = "Remove a list of repository URLs from the features service.")
+public class RemoveUrlCommand extends FeaturesCommandSupport {
+
+    @Argument(required = true, multiValued = true, description = "Repository URLs")
+    List<String> urls;
+
+    protected void doExecute(FeaturesService admin) throws Exception {
+        for (String url : urls) {
+            admin.removeRepository(new URI(url));
+        }
+    }
+}
diff --git a/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/UninstallFeatureCommand.java b/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/UninstallFeatureCommand.java
new file mode 100644
index 0000000..229d43c
--- /dev/null
+++ b/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/UninstallFeatureCommand.java
@@ -0,0 +1,38 @@
+/*
+ * 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.felix.karaf.features.command;
+
+import org.apache.felix.gogo.commands.Argument;
+import org.apache.felix.gogo.commands.Command;
+import org.apache.felix.karaf.features.FeaturesService;
+
+@Command(scope = "features", name = "uninstall", description = "Uninstall a feature.")
+public class UninstallFeatureCommand extends FeaturesCommandSupport {
+
+    @Argument(required = true, description = "The name of the feature")
+    String name;
+    @Argument(description = "The version of the feature", index = 1)
+    String version;
+
+    protected void doExecute(FeaturesService admin) throws Exception {
+    	if (version != null && version.length() > 0) {
+    		admin.uninstallFeature(name, version);
+    	} else {
+    		admin.uninstallFeature(name );
+    	}
+    }
+}
diff --git a/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/completers/AvailableFeatureCompleter.java b/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/completers/AvailableFeatureCompleter.java
new file mode 100644
index 0000000..0a826c6
--- /dev/null
+++ b/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/completers/AvailableFeatureCompleter.java
@@ -0,0 +1,53 @@
+/*
+ * 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.felix.karaf.features.command.completers;
+
+import java.util.List;
+
+import org.apache.felix.karaf.gshell.console.completer.StringsCompleter;
+import org.apache.felix.karaf.gshell.console.Completer;
+import org.apache.felix.karaf.features.FeaturesService;
+import org.apache.felix.karaf.features.Feature;
+
+/**
+ * {@link jline.Completor} for available features.
+ *
+ * Displays a list of available features from installed repositories.
+ *
+ */
+public class AvailableFeatureCompleter implements Completer {
+
+    private FeaturesService featuresService;
+
+    public void setFeaturesService(FeaturesService featuresService) {
+        this.featuresService = featuresService;
+    }
+
+    public int complete(final String buffer, final int cursor, final List candidates) {
+        StringsCompleter delegate = new StringsCompleter();
+        try {
+            for (Feature feature : featuresService.listFeatures()) {
+                delegate.getStrings().add(feature.getName());
+            }
+        } catch (Exception e) {
+            // Ignore
+        }
+        return delegate.complete(buffer, cursor, candidates);
+    }
+
+
+}
diff --git a/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/completers/FeatureRepositoryCompleter.java b/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/completers/FeatureRepositoryCompleter.java
new file mode 100644
index 0000000..80a8b79
--- /dev/null
+++ b/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/completers/FeatureRepositoryCompleter.java
@@ -0,0 +1,53 @@
+/*
+ * 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.felix.karaf.features.command.completers;
+
+import java.util.List;
+
+import org.apache.felix.karaf.gshell.console.completer.StringsCompleter;
+import org.apache.felix.karaf.gshell.console.Completer;
+import org.apache.felix.karaf.features.FeaturesService;
+import org.apache.felix.karaf.features.Repository;
+
+/**
+ * {@link jline.Completor} for Feature Repository URLs.
+ *
+ * Displays a list of currently installed Feature repositories.
+ *
+ */
+
+public class FeatureRepositoryCompleter implements Completer {
+
+    private FeaturesService featuresService;
+
+    public void setFeaturesService(FeaturesService featuresService) {
+        this.featuresService = featuresService;
+    }
+
+    public int complete(final String buffer, final int cursor, final List candidates) {
+        StringsCompleter delegate = new StringsCompleter();
+        try {
+            for (Repository repository : featuresService.listRepositories()) {
+                delegate.getStrings().add(repository.getURI().toString());
+            }
+        } catch (Exception e) {
+            // Ignore
+        }
+        return delegate.complete(buffer, cursor, candidates);
+    }
+
+}
diff --git a/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/completers/InstalledFeatureCompleter.java b/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/completers/InstalledFeatureCompleter.java
new file mode 100644
index 0000000..ba538c7
--- /dev/null
+++ b/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/completers/InstalledFeatureCompleter.java
@@ -0,0 +1,52 @@
+/*
+ * 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.felix.karaf.features.command.completers;
+
+import java.util.List;
+
+import org.apache.felix.karaf.gshell.console.Completer;
+import org.apache.felix.karaf.gshell.console.completer.StringsCompleter;
+import org.apache.felix.karaf.features.FeaturesService;
+import org.apache.felix.karaf.features.Feature;
+
+/**
+ * {@link jline.Completor} for installed features.
+ *
+ * Displays a list of currently installed features.
+ *
+ */
+public class InstalledFeatureCompleter implements Completer {
+
+    private FeaturesService featuresService;
+
+    public void setFeaturesService(FeaturesService featuresService) {
+        this.featuresService = featuresService;
+    }
+
+    public int complete(final String buffer, final int cursor, final List candidates) {
+        StringsCompleter delegate = new StringsCompleter();
+        try {
+            for (Feature feature : featuresService.listInstalledFeatures()) {
+                delegate.getStrings().add(feature.getName());
+            }
+        } catch (Exception e) {
+            // Ignore
+        }
+        return delegate.complete(buffer, cursor, candidates);
+    }
+
+}
diff --git a/karaf/features/command/src/main/resources/OSGI-INF/blueprint/features-command.xml b/karaf/features/command/src/main/resources/OSGI-INF/blueprint/features-command.xml
new file mode 100644
index 0000000..1ebb11e
--- /dev/null
+++ b/karaf/features/command/src/main/resources/OSGI-INF/blueprint/features-command.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
+           xmlns:ext="http://geronimo.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0">
+
+    <command-bundle xmlns="http://felix.apache.org/karaf/xmlns/gshell/v1.0.0">
+        <command name="features/addUrl">
+            <action class="org.apache.felix.karaf.features.command.AddUrlCommand"/>
+        </command>
+        <command name="features/listUrl">
+            <action class="org.apache.felix.karaf.features.command.ListUrlCommand"/>
+        </command>
+        <command name="features/removeUrl">
+            <action class="org.apache.felix.karaf.features.command.RemoveUrlCommand"/>
+            <completers>
+                <ref component-id="removeUrlCompleter" />
+            </completers>
+        </command>
+        <command name="features/refreshUrl">
+            <action class="org.apache.felix.karaf.features.command.RefreshUrlCommand"/>
+        </command>
+        <command name="features/install">
+            <action class="org.apache.felix.karaf.features.command.InstallFeatureCommand"/>
+            <completers>
+                <ref component-id="installFeatureCompleter" />
+            </completers>
+        </command>
+        <command name="features/uninstall">
+            <action class="org.apache.felix.karaf.features.command.UninstallFeatureCommand"/>
+            <completers>
+                <ref component-id="uninstallFeatureCompleter" />
+            </completers>
+        </command>
+        <command name="features/list">
+            <action class="org.apache.felix.karaf.features.command.ListFeaturesCommand"/>
+        </command>
+    </command-bundle>
+
+    <reference id="featuresService" interface="org.apache.felix.karaf.features.FeaturesService" />
+
+    <bean id="installFeatureCompleter" class="org.apache.felix.karaf.features.command.completers.AvailableFeatureCompleter">
+        <property name="featuresService" ref="featuresService" />
+    </bean>
+
+    <bean id="uninstallFeatureCompleter" class="org.apache.felix.karaf.features.command.completers.InstalledFeatureCompleter">
+        <property name="featuresService" ref="featuresService" />
+    </bean>
+
+    <bean id="removeUrlCompleter" class="org.apache.felix.karaf.features.command.completers.FeatureRepositoryCompleter">
+        <property name="featuresService" ref="featuresService" />
+    </bean>
+
+</blueprint>
diff --git a/karaf/features/core/pom.xml b/karaf/features/core/pom.xml
new file mode 100644
index 0000000..e7e03e7
--- /dev/null
+++ b/karaf/features/core/pom.xml
@@ -0,0 +1,110 @@
+<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/maven-v4_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.felix.karaf.features</groupId>
+        <artifactId>features</artifactId>
+        <version>1.2.0-SNAPSHOT</version>
+    </parent>
+
+    <groupId>org.apache.felix.karaf.features</groupId>
+    <artifactId>org.apache.felix.karaf.features.core</artifactId>
+    <packaging>bundle</packaging>
+    <version>1.2.0-SNAPSHOT</version>
+    <name>Apache Felix Karaf :: Features Core</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.bundlerepository</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix.karaf.gshell</groupId>
+            <artifactId>org.apache.felix.karaf.gshell.console</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix.karaf.gshell</groupId>
+            <artifactId>org.apache.felix.karaf.gshell.obr</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.osgi</groupId>
+            <artifactId>spring-osgi-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.servicemix.bundles</groupId>
+            <artifactId>org.apache.servicemix.bundles.junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.easymock</groupId>
+            <artifactId>easymock</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-jdk14</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <configuration>
+                    <instructions>
+                        <Bundle-SymbolicName>${artifactId}</Bundle-SymbolicName>
+                        <Export-Package>org.apache.felix.karaf.features;version=${project.version}</Export-Package>
+                        <Import-Package>
+                            javax.management,
+                            javax.management.loading,
+                            org.osgi.service.command,
+                            org.apache.felix.gogo.commands,
+                            org.apache.felix.karaf.gshell.console,
+                            *
+                        </Import-Package>
+                        <Private-Package>org.apache.felix.karaf.features.internal</Private-Package>
+                        <_versionpolicy>${bnd.version.policy}</_versionpolicy>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/karaf/features/core/src/main/java/org/apache/felix/karaf/features/Feature.java b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/Feature.java
new file mode 100644
index 0000000..6fc7f23
--- /dev/null
+++ b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/Feature.java
@@ -0,0 +1,39 @@
+/*
+ * 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.felix.karaf.features;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A feature is a list of bundles associated identified by its name.
+ */
+public interface Feature {
+
+    String getId();
+
+    String getName();
+
+    String getVersion();
+
+    List<Feature> getDependencies();
+
+    List<String> getBundles();
+
+    Map<String, Map<String, String>> getConfigurations();
+
+}
diff --git a/karaf/features/core/src/main/java/org/apache/felix/karaf/features/FeatureEvent.java b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/FeatureEvent.java
new file mode 100644
index 0000000..f368ff8
--- /dev/null
+++ b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/FeatureEvent.java
@@ -0,0 +1,50 @@
+/*
+ * 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.felix.karaf.features;
+
+import java.util.EventObject;
+
+public class FeatureEvent extends EventObject {
+
+    public static enum EventType {
+        FeatureInstalled,
+        FeatureUninstalled
+    }
+
+    private final EventType type;
+    private final Feature feature;
+    private final boolean replay;
+
+    public FeatureEvent(Feature feature, EventType type, boolean replay) {
+        super(feature);
+        this.type = type;
+        this.feature = feature;
+        this.replay = replay;
+    }
+
+    public EventType getType() {
+        return type;
+    }
+
+    public Feature getFeature() {
+        return feature;
+    }
+
+    public boolean isReplay() {
+        return replay;
+    }
+}
diff --git a/karaf/features/core/src/main/java/org/apache/felix/karaf/features/FeaturesListener.java b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/FeaturesListener.java
new file mode 100644
index 0000000..1b54ed9
--- /dev/null
+++ b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/FeaturesListener.java
@@ -0,0 +1,25 @@
+/*
+ * 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.felix.karaf.features;
+
+public interface FeaturesListener {
+
+    void featureEvent(FeatureEvent event);
+
+    void repositoryEvent(RepositoryEvent event);
+
+}
diff --git a/karaf/features/core/src/main/java/org/apache/felix/karaf/features/FeaturesService.java b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/FeaturesService.java
new file mode 100644
index 0000000..de7c36a
--- /dev/null
+++ b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/FeaturesService.java
@@ -0,0 +1,44 @@
+/*
+ * 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.felix.karaf.features;
+
+import java.net.URI;
+
+/**
+ * The service managing features repositories.
+ */
+public interface FeaturesService {
+
+    void addRepository(URI url) throws Exception;
+
+    void removeRepository(URI url);
+
+    Repository[] listRepositories();
+
+    void installFeature(String name) throws Exception;
+    
+    void installFeature(String name, String version) throws Exception;
+
+    void uninstallFeature(String name) throws Exception;
+    
+    void uninstallFeature(String name, String version) throws Exception;
+
+    Feature[] listFeatures() throws Exception;
+
+    Feature[] listInstalledFeatures();
+
+}
diff --git a/karaf/features/core/src/main/java/org/apache/felix/karaf/features/Repository.java b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/Repository.java
new file mode 100644
index 0000000..25ffbad
--- /dev/null
+++ b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/Repository.java
@@ -0,0 +1,32 @@
+/*
+ * 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.felix.karaf.features;
+
+import java.net.URI;
+
+/**
+ * A repository of features.
+ */
+public interface Repository {
+
+    URI getURI();
+
+    URI[] getRepositories() throws Exception;
+
+    Feature[] getFeatures() throws Exception;
+
+}
diff --git a/karaf/features/core/src/main/java/org/apache/felix/karaf/features/RepositoryEvent.java b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/RepositoryEvent.java
new file mode 100644
index 0000000..f62076a
--- /dev/null
+++ b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/RepositoryEvent.java
@@ -0,0 +1,50 @@
+/*
+ * 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.felix.karaf.features;
+
+import java.util.EventObject;
+
+public class RepositoryEvent extends EventObject {
+
+    public static enum EventType {
+        RepositoryAdded,
+        RepositoryRemoved,
+    }
+
+    private final EventType type;
+    private final Repository repository;
+    private final boolean replay;
+
+    public RepositoryEvent(Repository repository, EventType type, boolean replay) {
+        super(repository);
+        this.type = type;
+        this.repository = repository;
+        this.replay = replay;
+    }
+
+    public EventType getType() {
+        return type;
+    }
+
+    public Repository getRepository() {
+        return repository;
+    }
+
+    public boolean isReplay() {
+        return replay;
+    }
+}
\ No newline at end of file
diff --git a/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/FeatureImpl.java b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/FeatureImpl.java
new file mode 100644
index 0000000..0ae7c6b
--- /dev/null
+++ b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/FeatureImpl.java
@@ -0,0 +1,125 @@
+/*
+ * 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.felix.karaf.features.internal;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.felix.karaf.features.Feature;
+
+/**
+ * A feature
+ */
+public class FeatureImpl implements Feature {
+
+    private String id;
+    private String name;
+    private String version;
+    private List<Feature> dependencies = new ArrayList<Feature>();
+    private List<String> bundles = new ArrayList<String>();
+    private Map<String, Map<String,String>> configs = new HashMap<String, Map<String,String>>();
+    public static String SPLIT_FOR_NAME_AND_VERSION = "_split_for_name_and_version_";
+    public static String DEFAULT_VERSION = "0.0.0";
+    
+    public FeatureImpl(String name) {
+        this(name, DEFAULT_VERSION);
+    }
+    
+    public FeatureImpl(String name, String version) {
+    	this.name = name;
+    	this.version = version;
+        this.id = name + "-" + version;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getVersion() {
+		return version;
+	}
+    
+	public void setVersion(String version) {
+		this.version = version;
+	}
+
+    public List<Feature> getDependencies() {
+        return dependencies;
+    }
+
+    public List<String> getBundles() {
+        return bundles;
+    }
+
+    public Map<String, Map<String, String>> getConfigurations() {
+        return configs;
+    }
+
+    public void addDependency(Feature dependency) {
+        dependencies.add(dependency);
+    }
+
+    public void addBundle(String bundle) {
+        bundles.add(bundle);
+    }
+
+    public void addConfig(String name, Map<String,String> properties) {
+        configs.put(name, properties);
+    }
+
+    public String toString() {
+    	String ret = getName() + SPLIT_FOR_NAME_AND_VERSION + getVersion();
+    	return ret;
+    }
+    
+    public static Feature valueOf(String str) {
+    	if (str.indexOf(SPLIT_FOR_NAME_AND_VERSION) >= 0) {
+    		String strName = str.substring(0, str.indexOf(SPLIT_FOR_NAME_AND_VERSION));
+        	String strVersion = str.substring(str.indexOf(SPLIT_FOR_NAME_AND_VERSION) 
+        			+ SPLIT_FOR_NAME_AND_VERSION.length(), str.length());
+        	return new FeatureImpl(strName, strVersion);
+    	} else {
+    		return new FeatureImpl(str);
+    	}
+    			
+    	
+    }
+
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        FeatureImpl feature = (FeatureImpl) o;
+
+        if (!name.equals(feature.name)) return false;
+        if (!version.equals(feature.version)) return false;
+
+        return true;
+    }
+
+    public int hashCode() {
+        int result = name.hashCode();
+        result = 31 * result + version.hashCode();
+        return result;
+    }
+}
diff --git a/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/FeaturesServiceImpl.java b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/FeaturesServiceImpl.java
new file mode 100644
index 0000000..39a5e90
--- /dev/null
+++ b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/FeaturesServiceImpl.java
@@ -0,0 +1,602 @@
+/*
+ * 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.felix.karaf.features.internal;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.felix.karaf.features.FeaturesService;
+import org.apache.felix.karaf.features.Feature;
+import org.apache.felix.karaf.features.Repository;
+import org.apache.felix.karaf.features.FeaturesListener;
+import org.apache.felix.karaf.features.FeatureEvent;
+import org.apache.felix.karaf.features.RepositoryEvent;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.Version;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.prefs.BackingStoreException;
+import org.osgi.service.prefs.Preferences;
+import org.osgi.service.prefs.PreferencesService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The Features service implementation.
+ * Adding a repository url will load the features contained in this repository and
+ * create dummy sub shells.  When invoked, these commands will prompt the user for
+ * installing the needed bundles.
+ *
+ */
+public class FeaturesServiceImpl implements FeaturesService {
+
+    private static final String ALIAS_KEY = "_alias_factory_pid";
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(FeaturesServiceImpl.class);
+
+    private BundleContext bundleContext;
+    private ConfigurationAdmin configAdmin;
+    private PreferencesService preferences;
+    private Set<URI> uris;
+    private Map<URI, RepositoryImpl> repositories = new HashMap<URI, RepositoryImpl>();
+    private Map<String, Map<String, Feature>> features;
+    private Map<Feature, Set<Long>> installed = new HashMap<Feature, Set<Long>>();
+    private String boot;
+    private boolean bootFeaturesInstalled;
+    private List<FeaturesListener> listeners = new CopyOnWriteArrayList<FeaturesListener>();
+
+    public BundleContext getBundleContext() {
+        return bundleContext;
+    }
+
+    public void setBundleContext(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+    }
+
+    public ConfigurationAdmin getConfigAdmin() {
+        return configAdmin;
+    }
+
+    public void setConfigAdmin(ConfigurationAdmin configAdmin) {
+        this.configAdmin = configAdmin;
+    }
+
+    public PreferencesService getPreferences() {
+        return preferences;
+    }
+
+    public void setPreferences(PreferencesService preferences) {
+        this.preferences = preferences;
+    }
+
+    public void registerListener(FeaturesListener listener) {
+        listeners.add(listener);
+        for (Repository repository : listRepositories()) {
+            listener.repositoryEvent(new RepositoryEvent(repository, RepositoryEvent.EventType.RepositoryAdded, true));
+        }
+        for (Feature feature : listInstalledFeatures()) {
+            listener.featureEvent(new FeatureEvent(feature, FeatureEvent.EventType.FeatureInstalled, true));
+        }
+    }
+
+    public void unregisterListener(FeaturesListener listener) {
+        listeners.remove(listener);
+    }
+
+    public void setUrls(String uris) throws URISyntaxException {
+        String[] s = uris.split(",");
+        this.uris = new HashSet<URI>();
+        for (String value : s) {
+            this.uris.add(new URI(value));
+        }
+    }
+
+    public void setBoot(String boot) {
+        this.boot = boot;
+    }
+
+    public void addRepository(URI uri) throws Exception {
+        if (!repositories.containsKey(uri)) {
+            internalAddRepository(uri);
+            saveState();
+        }
+    }
+
+    protected RepositoryImpl internalAddRepository(URI uri) throws Exception {
+        RepositoryImpl repo = new RepositoryImpl(uri);
+        repositories.put(uri, repo);
+        callListeners(new RepositoryEvent(repo, RepositoryEvent.EventType.RepositoryAdded, false));
+        features = null;
+        return repo;
+    }
+
+    public void removeRepository(URI uri) {
+        if (repositories.containsKey(uri)) {
+            internalRemoveRepository(uri);
+            saveState();
+        }
+    }
+
+    public void internalRemoveRepository(URI uri) {
+        Repository repo = repositories.remove(uri);
+        callListeners(new RepositoryEvent(repo, RepositoryEvent.EventType.RepositoryRemoved, false));
+        features = null;
+    }
+
+    public Repository[] listRepositories() {
+        Collection<RepositoryImpl> repos = repositories.values();
+        return repos.toArray(new Repository[repos.size()]);
+    }
+
+    public void installAllFeatures(URI uri) throws Exception {
+        RepositoryImpl repo = internalAddRepository(uri);
+        for (Feature f : repo.getFeatures()) {
+            installFeature(f.getName(), f.getVersion());
+        }
+        internalRemoveRepository(uri);            
+    }
+
+    public void uninstallAllFeatures(URI uri) throws Exception {
+        RepositoryImpl repo = internalAddRepository(uri);
+        for (Feature f : repo.getFeatures()) {
+            uninstallFeature(f.getName(), f.getVersion());
+        }
+        internalRemoveRepository(uri);            
+    }
+
+    public void installFeature(String name) throws Exception {
+    	installFeature(name, FeatureImpl.DEFAULT_VERSION);
+    }
+
+    public void installFeature(String name, String version) throws Exception {
+        Feature f = getFeature(name, version);
+        if (f == null) {
+            throw new Exception("No feature named '" + name 
+            		+ "' with version '" + version + "' available");
+        }
+        for (Feature dependency : f.getDependencies()) {
+        	installFeature(dependency.getName(), dependency.getVersion());
+        }
+        for (String config : f.getConfigurations().keySet()) {
+            Dictionary<String,String> props = new Hashtable<String, String>(f.getConfigurations().get(config));
+            String[] pid = parsePid(config);
+            if (pid[1] != null) {
+                props.put(ALIAS_KEY, pid[1]);
+            }
+            Configuration cfg = getConfiguration(configAdmin, pid[0], pid[1]);
+            if (cfg.getBundleLocation() != null) {
+                cfg.setBundleLocation(null);
+            }
+            cfg.update(props);
+        }
+        Set<Long> bundles = new HashSet<Long>();
+        for (String bundleLocation : f.getBundles()) {
+            Bundle b = installBundleIfNeeded(bundleLocation);
+            bundles.add(b.getBundleId());
+        }
+        for (long id : bundles) {
+            bundleContext.getBundle(id).start();
+        }
+
+        callListeners(new FeatureEvent(f, FeatureEvent.EventType.FeatureInstalled, false));
+        installed.put(f, bundles);
+        saveState();
+    }
+    protected Bundle installBundleIfNeeded(String bundleLocation) throws IOException, BundleException {
+        LOGGER.debug("Checking " + bundleLocation);
+        InputStream is;
+        try {
+            is = new BufferedInputStream(new URL(bundleLocation).openStream());
+        } catch (RuntimeException e) {
+            LOGGER.error(e.getMessage());
+            throw e;
+        }
+        try {
+            is.mark(256 * 1024);
+            JarInputStream jar = new JarInputStream(is);
+            Manifest m = jar.getManifest();
+            String sn = m.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
+            String vStr = m.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
+            Version v = vStr == null ? Version.emptyVersion : Version.parseVersion(vStr);
+            for (Bundle b : bundleContext.getBundles()) {
+                if (b.getSymbolicName() != null && b.getSymbolicName().equals(sn)) {
+                    vStr = (String) b.getHeaders().get(Constants.BUNDLE_VERSION);
+                    Version bv = vStr == null ? Version.emptyVersion : Version.parseVersion(vStr);
+                    if (v.equals(bv)) {
+                        LOGGER.debug("  found installed bundle: " + b);
+                        return b;
+                    }
+                }
+            }
+            try {
+                is.reset();
+            } catch (IOException e) {
+                is.close();
+                is = new BufferedInputStream(new URL(bundleLocation).openStream());
+            }
+            LOGGER.debug("Installing bundle " + bundleLocation);
+            return getBundleContext().installBundle(bundleLocation, is);
+        } finally {
+            is.close();
+        }
+    }
+
+    public void uninstallFeature(String name) throws Exception {
+        List<String> versions = new ArrayList<String>();
+        for (Feature f : installed.keySet()) {
+            if (name.equals(f.getName())) {
+                versions.add(f.getVersion());
+            }
+        }
+        if (versions.size() == 0) {
+            throw new Exception("Feature named '" + name + "' is not installed");
+        } else if (versions.size() > 1) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("Feature named '").append(name).append("' has multiple versions installed (");
+            for (int i = 0; i < versions.size(); i++) {
+                if (i > 0) {
+                    sb.append(", ");
+                }
+                sb.append(versions.get(i));
+            }
+            sb.append("). Please specify the version to uninstall.");
+            throw new Exception(sb.toString());
+        }
+        uninstallFeature(name, versions.get(0));
+    }
+    
+    public void uninstallFeature(String name, String version) throws Exception {
+    	Feature feature = getFeature(name, version);
+        if (feature == null || !installed.containsKey(feature)) {
+            throw new Exception("Feature named '" + name 
+            		+ "' with version '" + version + "' is not installed");
+        }
+        // Grab all the bundles installed by this feature
+        // and remove all those who will still be in use.
+        // This gives this list of bundles to uninstall.
+        Set<Long> bundles = installed.remove(feature);
+        for (Set<Long> b : installed.values()) {
+            bundles.removeAll(b);
+        }
+        for (long bundleId : bundles) {
+            getBundleContext().getBundle(bundleId).uninstall();
+        }
+        callListeners(new FeatureEvent(feature, FeatureEvent.EventType.FeatureInstalled, false));
+        saveState();
+    }
+
+    public Feature[] listFeatures() throws Exception {
+        Collection<Feature> features = new ArrayList<Feature>();
+        for (Map<String, Feature> featureWithDifferentVersion : getFeatures().values()) {
+			for (Feature f : featureWithDifferentVersion.values()) {
+                features.add(f);
+            }
+        }
+        return features.toArray(new Feature[features.size()]);
+    }
+
+    public Feature[] listInstalledFeatures() {
+        Set<Feature> result = installed.keySet();
+        return result.toArray(new Feature[result.size()]);
+    }
+
+    protected Feature getFeature(String name, String version) throws Exception {
+        if (version != null) {
+            version = version.trim();
+        }
+        Map<String, Feature> versions = getFeatures().get(name);
+        if (versions == null || versions.isEmpty()) {
+            return null;
+        } else {
+            Feature feature = versions.get(version);
+            if (feature == null && FeatureImpl.DEFAULT_VERSION.equals(version)) {
+                Version latest = new Version(cleanupVersion(version));
+                for (String available : versions.keySet()) {
+                    Version availableVersion = new Version(cleanupVersion(available));
+                    if (availableVersion.compareTo(latest) > 0) {
+                        feature = versions.get(available);
+                        latest = availableVersion;
+                    }
+                }
+            }
+            return feature;
+        }
+    }
+
+    protected Map<String, Map<String, Feature>> getFeatures() throws Exception {
+        if (features == null) {
+        	//the outer map's key is feature name, the inner map's key is feature version       
+            Map<String, Map<String, Feature>> map = new HashMap<String, Map<String, Feature>>();
+            // Two phase load:
+            // * first load dependent repositories
+            for (;;) {
+                boolean newRepo = false;
+                for (Repository repo : listRepositories()) {
+                    for (URI uri : repo.getRepositories()) {
+                        if (!repositories.containsKey(uri)) {
+                            internalAddRepository(uri);
+                            newRepo = true;
+                        }
+                    }
+                }
+                if (!newRepo) {
+                    break;
+                }
+            }
+            // * then load all features
+            for (Repository repo : repositories.values()) {
+                for (Feature f : repo.getFeatures()) {
+                	if (map.get(f.getName()) == null) {
+                		Map<String, Feature> versionMap = new HashMap<String, Feature>();
+                		versionMap.put(f.getVersion(), f);
+                		map.put(f.getName(), versionMap);
+                	} else {
+                		map.get(f.getName()).put(f.getVersion(), f);
+                	}
+                }
+            }
+            features = map;
+        }
+        return features;
+    }
+
+    public void start() throws Exception {
+        if (!loadState()) {
+            if (uris != null) {
+                for (URI uri : uris) {
+                    internalAddRepository(uri);
+                }
+            }
+            saveState();
+        }
+        if (boot != null && !bootFeaturesInstalled) {
+            new Thread() {
+                public void run() {
+                    String[] list = boot.split(",");
+                    for (String f : list) {
+                        if (f.length() > 0) {
+                            try {
+                                installFeature(f);
+                            } catch (Exception e) {
+                                LOGGER.error("Error installing boot feature " + f, e);
+                            }
+                        }
+                    }
+                    bootFeaturesInstalled = true;
+                    saveState();
+                }
+            }.start();
+        }
+    }
+
+    public void stop() throws Exception {
+        uris = new HashSet<URI>(repositories.keySet());
+        while (!repositories.isEmpty()) {
+            internalRemoveRepository(repositories.keySet().iterator().next());
+        }
+    }
+
+    protected String[] parsePid(String pid) {
+        int n = pid.indexOf('-');
+        if (n > 0) {
+            String factoryPid = pid.substring(n + 1);
+            pid = pid.substring(0, n);
+            return new String[]{pid, factoryPid};
+        } else {
+            return new String[]{pid, null};
+        }
+    }
+
+    protected Configuration getConfiguration(ConfigurationAdmin configurationAdmin,
+                                             String pid, String factoryPid) throws IOException, InvalidSyntaxException {
+        if (factoryPid != null) {
+            Configuration[] configs = configurationAdmin.listConfigurations("(|(" + ALIAS_KEY + "=" + pid + ")(.alias_factory_pid=" + factoryPid + "))");
+            if (configs == null || configs.length == 0) {
+                return configurationAdmin.createFactoryConfiguration(pid, null);
+            } else {
+                return configs[0];
+            }
+        } else {
+            return configurationAdmin.getConfiguration(pid, null);
+        }
+    }
+    
+    protected void saveState() {
+        try {
+            Preferences prefs = preferences.getUserPreferences("FeaturesServiceState");
+            saveSet(prefs.node("repositories"), repositories.keySet());
+            saveMap(prefs.node("features"), installed);
+            prefs.putBoolean("bootFeaturesInstalled", bootFeaturesInstalled);
+            prefs.flush();
+        } catch (Exception e) {
+            LOGGER.error("Error persisting FeaturesService state", e);
+        }
+    }
+
+    protected boolean loadState() {
+        try {
+            Preferences prefs = preferences.getUserPreferences("FeaturesServiceState");
+            if (prefs.nodeExists("repositories")) {
+                Set<URI> repositories = loadSet(prefs.node("repositories"));
+                for (URI repo : repositories) {
+                    internalAddRepository(repo);
+                }
+                installed = loadMap(prefs.node("features"));
+                for (Feature f : installed.keySet()) {
+                    callListeners(new FeatureEvent(f, FeatureEvent.EventType.FeatureInstalled, true));
+                }
+                bootFeaturesInstalled = prefs.getBoolean("bootFeaturesInstalled", false);
+                return true;
+            }
+        } catch (Exception e) {
+            LOGGER.error("Error loading FeaturesService state", e);
+        }
+        return false;
+    }
+
+    protected void saveSet(Preferences node, Set<URI> set) throws BackingStoreException {
+        List<URI> l = new ArrayList<URI>(set);
+        node.clear();
+        node.putInt("count", l.size());
+        for (int i = 0; i < l.size(); i++) {
+            node.put("item." + i, l.get(i).toString());
+        }
+    }
+
+    protected Set<URI> loadSet(Preferences node) {
+        Set<URI> l = new HashSet<URI>();
+        int count = node.getInt("count", 0);
+        for (int i = 0; i < count; i++) {
+            l.add(URI.create(node.get("item." + i, null)));
+        }
+        return l;
+    }
+
+    protected void saveMap(Preferences node, Map<Feature, Set<Long>> map) throws BackingStoreException {
+        node.clear();
+        for (Map.Entry<Feature, Set<Long>> entry : map.entrySet()) {
+            Feature key = entry.getKey();
+            String val = createValue(entry.getValue());
+            node.put(key.toString(), val);
+        }
+    }
+
+    protected Map<Feature, Set<Long>> loadMap(Preferences node) throws BackingStoreException {
+        Map<Feature, Set<Long>> map = new HashMap<Feature, Set<Long>>();
+        for (String key : node.keys()) {
+            String val = node.get(key, null);
+            Set<Long> set = readValue(val);
+            map.put(FeatureImpl.valueOf(key), set);
+        }
+        return map;
+    }
+
+    protected String createValue(Set<Long> set) {
+        StringBuilder sb = new StringBuilder();
+        for (long i : set) {
+            if (sb.length() > 0) {
+                sb.append(",");
+            }
+            sb.append(i);
+        }
+        return sb.toString();
+    }
+
+    protected Set<Long> readValue(String val) {
+        Set<Long> set = new HashSet<Long>();
+        for (String str : val.split(",")) {
+            set.add(Long.parseLong(str));
+        }
+        return set;
+    }
+
+    protected void callListeners(FeatureEvent event) {
+        for (FeaturesListener listener : listeners) {
+            listener.featureEvent(event);
+        }
+    }
+
+    protected void callListeners(RepositoryEvent event) {
+        for (FeaturesListener listener : listeners) {
+            listener.repositoryEvent(event);
+        }
+    }
+
+    static Pattern fuzzyVersion  = Pattern.compile("(\\d+)(\\.(\\d+)(\\.(\\d+))?)?([^a-zA-Z0-9](.*))?",
+                                                   Pattern.DOTALL);
+    static Pattern fuzzyModifier = Pattern.compile("(\\d+[.-])*(.*)",
+                                                   Pattern.DOTALL);
+
+    /**
+     * Clean up version parameters. Other builders use more fuzzy definitions of
+     * the version syntax. This method cleans up such a version to match an OSGi
+     * version.
+     *
+     * @param version
+     * @return
+     */
+    static public String cleanupVersion(String version) {
+        Matcher m = fuzzyVersion.matcher(version);
+        if (m.matches()) {
+            StringBuffer result = new StringBuffer();
+            String d1 = m.group(1);
+            String d2 = m.group(3);
+            String d3 = m.group(5);
+            String qualifier = m.group(7);
+
+            if (d1 != null) {
+                result.append(d1);
+                if (d2 != null) {
+                    result.append(".");
+                    result.append(d2);
+                    if (d3 != null) {
+                        result.append(".");
+                        result.append(d3);
+                        if (qualifier != null) {
+                            result.append(".");
+                            cleanupModifier(result, qualifier);
+                        }
+                    } else if (qualifier != null) {
+                        result.append(".0.");
+                        cleanupModifier(result, qualifier);
+                    }
+                } else if (qualifier != null) {
+                    result.append(".0.0.");
+                    cleanupModifier(result, qualifier);
+                }
+                return result.toString();
+            }
+        }
+        return version;
+    }
+
+    static void cleanupModifier(StringBuffer result, String modifier) {
+        Matcher m = fuzzyModifier.matcher(modifier);
+        if (m.matches())
+            modifier = m.group(2);
+
+        for (int i = 0; i < modifier.length(); i++) {
+            char c = modifier.charAt(i);
+            if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z')
+                    || (c >= 'A' && c <= 'Z') || c == '_' || c == '-')
+                result.append(c);
+        }
+    }
+
+}
diff --git a/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/RepositoryImpl.java b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/RepositoryImpl.java
new file mode 100644
index 0000000..0d413c3
--- /dev/null
+++ b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/RepositoryImpl.java
@@ -0,0 +1,165 @@
+/*
+ * 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.felix.karaf.features.internal;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import org.apache.felix.karaf.features.Repository;
+import org.apache.felix.karaf.features.Feature;
+import org.xml.sax.SAXException;
+
+/**
+ * The repository implementation.
+ */
+public class RepositoryImpl implements Repository {
+
+    private URI uri;
+    private List<Feature> features;
+    private List<URI> repositories;
+
+    public RepositoryImpl(URI uri) {
+        this.uri = uri;
+    }
+
+    public URI getURI() {
+        return uri;
+    }
+
+    public URI[] getRepositories() throws Exception {
+        if (repositories == null) {
+            load();
+        }
+        return repositories.toArray(new URI[repositories.size()]);
+    }
+
+    public Feature[] getFeatures() throws Exception {
+        if (features == null) {
+            load();
+        }
+        return features.toArray(new Feature[features.size()]);
+    }
+
+    public void load() throws IOException {
+        try {
+            repositories = new ArrayList<URI>();
+            features = new ArrayList<Feature>();
+            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+            URLConnection conn = uri.toURL().openConnection();
+            conn.setDefaultUseCaches(false);
+            Document doc = factory.newDocumentBuilder().parse(conn.getInputStream());
+            
+            NodeList nodes = doc.getDocumentElement().getChildNodes();
+            for (int i = 0; i < nodes.getLength(); i++) {
+                Node node = nodes.item(i);
+                if (!(node instanceof Element)) {
+                    continue;
+                }
+                if ("repository".equals(node.getNodeName())) {
+                    Element e = (Element) nodes.item(i);
+                    repositories.add(new URI(e.getTextContent()));
+                } else if ("feature".equals(node.getNodeName())) {
+                    Element e = (Element) nodes.item(i);
+                    String name = e.getAttribute("name");
+                    String version = e.getAttribute("version");
+                    FeatureImpl f;
+                    if (version != null && version.length() > 0) {
+                        f = new FeatureImpl(name, version);
+                    } else {
+                        f = new FeatureImpl(name);
+                    }
+
+                    NodeList featureNodes = e.getElementsByTagName("feature");
+                    for (int j = 0; j < featureNodes.getLength(); j++) {
+                        Element b = (Element) featureNodes.item(j);
+                        String dependencyFeatureVersion = b.getAttribute("version");
+                        if (dependencyFeatureVersion != null && dependencyFeatureVersion.length() > 0) {
+                        	f.addDependency(new FeatureImpl(b.getTextContent(), dependencyFeatureVersion));
+                        } else {
+                        	f.addDependency(new FeatureImpl(b.getTextContent()));
+                        }
+                    }
+                    NodeList configNodes = e.getElementsByTagName("config");
+                    for (int j = 0; j < configNodes.getLength(); j++) {
+                        Element c = (Element) configNodes.item(j);
+                        String cfgName = c.getAttribute("name");
+                        String data = c.getTextContent();
+                        Properties properties = new Properties();
+                        properties.load(new ByteArrayInputStream(data.getBytes()));
+                        interpolation(properties);
+                        Map<String, String> hashtable = new Hashtable<String, String>();
+                        for (Object key : properties.keySet()) {
+                            String n = key.toString();
+                            hashtable.put(n, properties.getProperty(n));
+                        }
+                        f.addConfig(cfgName, hashtable);
+                    }
+                    NodeList bundleNodes = e.getElementsByTagName("bundle");
+                    for (int j = 0; j < bundleNodes.getLength(); j++) {
+                        Element b = (Element) bundleNodes.item(j);
+                        f.addBundle(b.getTextContent());
+                    }
+                    features.add(f);
+                }
+            }
+        } catch (SAXException e) {
+            throw (IOException) new IOException().initCause(e);
+        } catch (ParserConfigurationException e) {
+            throw (IOException) new IOException().initCause(e);
+        } catch (URISyntaxException e) {
+            throw (IOException) new IOException(e.getMessage() + " : " + uri).initCause(e);
+        } catch (IllegalArgumentException e) {
+            throw (IOException) new IOException(e.getMessage() + " : " + uri).initCause(e);
+        }
+    }
+
+    protected void interpolation(Properties properties) {
+        for (Enumeration e = properties.propertyNames(); e.hasMoreElements();) {
+            String key = (String)e.nextElement();
+            String val = properties.getProperty(key);
+            Matcher matcher = Pattern.compile("\\$\\{([^}]+)\\}").matcher(val);
+            while (matcher.find()) {
+                String rep = System.getProperty(matcher.group(1));
+                if (rep != null) {
+                    val = val.replace(matcher.group(0), rep);
+                    matcher.reset(val);
+                }
+            }
+            properties.put(key, val);
+        }
+    }
+
+}
diff --git a/karaf/features/core/src/main/resources/OSGI-INF/blueprint/gshell-features.xml b/karaf/features/core/src/main/resources/OSGI-INF/blueprint/gshell-features.xml
new file mode 100644
index 0000000..0d7e2c4
--- /dev/null
+++ b/karaf/features/core/src/main/resources/OSGI-INF/blueprint/gshell-features.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
+           xmlns:ext="http://geronimo.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0">
+
+    <ext:property-placeholder placeholder-prefix="$(" placeholder-suffix=")"/>
+
+    <ext:property-placeholder placeholder-prefix="$[" placeholder-suffix="]" ignore-missing-locations="true">
+        <ext:default-properties>
+            <ext:property name="featuresRepositories" value=""/>
+            <ext:property name="featuresBoot" value=""/>
+        </ext:default-properties>
+        <ext:location>file:$(karaf.home)/etc/org.apache.felix.karaf.features.cfg</ext:location>
+    </ext:property-placeholder>
+
+    <bean id="featuresService" class="org.apache.felix.karaf.features.internal.FeaturesServiceImpl" init-method="start" destroy-method="stop">
+        <property name="urls" value="$[featuresRepositories]" />
+        <property name="boot" value="$[featuresBoot]" />
+        <property name="configAdmin" ref="configAdmin" />
+        <property name="preferences" ref="preferences" />
+        <property name="bundleContext" ref="blueprintBundleContext" />
+    </bean>
+
+    <reference-list id="featuresListeners" interface="org.apache.felix.karaf.features.FeaturesListener" availability="optional">
+        <reference-listener ref="featuresService"
+                            bind-method="registerListener"
+                            unbind-method="unregisterListener" />
+    </reference-list>
+
+    <reference id="configAdmin" interface="org.osgi.service.cm.ConfigurationAdmin" />
+
+    <reference id="preferences" interface="org.osgi.service.prefs.PreferencesService" availability="optional"/>
+
+    <service ref="featuresService" interface="org.apache.felix.karaf.features.FeaturesService" />
+
+</blueprint>
diff --git a/karaf/features/core/src/test/java/org/apache/felix/karaf/features/FeatureTest.java b/karaf/features/core/src/test/java/org/apache/felix/karaf/features/FeatureTest.java
new file mode 100644
index 0000000..bd88118
--- /dev/null
+++ b/karaf/features/core/src/test/java/org/apache/felix/karaf/features/FeatureTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.felix.karaf.features;
+
+import junit.framework.TestCase;
+import org.apache.felix.karaf.features.internal.FeatureImpl;
+
+public class FeatureTest extends TestCase {
+	
+	public void testValueOf() {
+		Feature feature = FeatureImpl.valueOf("name" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + "version");
+		assertEquals(feature.getName(), "name");
+		assertEquals(feature.getVersion(), "version");
+		feature = FeatureImpl.valueOf("name");
+		assertEquals(feature.getName(), "name");
+		assertEquals(feature.getVersion(), FeatureImpl.DEFAULT_VERSION);
+	}
+	
+}
diff --git a/karaf/features/core/src/test/java/org/apache/felix/karaf/features/FeaturesServiceTest.java b/karaf/features/core/src/test/java/org/apache/felix/karaf/features/FeaturesServiceTest.java
new file mode 100644
index 0000000..0fcec78
--- /dev/null
+++ b/karaf/features/core/src/test/java/org/apache/felix/karaf/features/FeaturesServiceTest.java
@@ -0,0 +1,685 @@
+/*
+ * 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.felix.karaf.features;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.net.URI;
+
+import junit.framework.TestCase;
+import org.apache.felix.karaf.features.internal.FeaturesServiceImpl;
+import org.apache.felix.karaf.features.internal.FeatureImpl;
+import org.easymock.EasyMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.isA;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reset;
+import static org.easymock.EasyMock.verify;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.prefs.Preferences;
+import org.osgi.service.prefs.PreferencesService;
+import org.springframework.context.ApplicationContext;
+
+public class FeaturesServiceTest extends TestCase {
+
+    public void testInstallFeature() throws Exception {
+
+        String name = ApplicationContext.class.getName();
+        name = name.replace(".", "/")  + ".class";
+        name = getClass().getClassLoader().getResource(name).toString();
+        name = name.substring("jar:".length(), name.indexOf('!'));
+
+        File tmp = File.createTempFile("smx", ".feature");
+        PrintWriter pw = new PrintWriter(new FileWriter(tmp));
+        pw.println("<features>");
+        pw.println("  <feature name=\"f1\">");
+        pw.println("    <bundle>" + name + "</bundle>");
+        pw.println("  </feature>");
+        pw.println("</features>");
+        pw.close();
+
+        URI uri = tmp.toURI();
+
+        Preferences prefs = EasyMock.createMock(Preferences.class);
+        PreferencesService preferencesService = EasyMock.createMock(PreferencesService.class);
+        Preferences repositoriesNode = EasyMock.createMock(Preferences.class);
+        Preferences featuresNode = EasyMock.createMock(Preferences.class);
+        BundleContext bundleContext = EasyMock.createMock(BundleContext.class);
+        Bundle installedBundle = EasyMock.createMock(Bundle.class);
+
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 1);
+        repositoriesNode.put("item.0", uri.toString());
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();
+
+        replay(preferencesService, prefs, repositoriesNode, featuresNode, bundleContext, installedBundle);
+
+        FeaturesServiceImpl svc = new FeaturesServiceImpl();
+        svc.setPreferences(preferencesService);
+        svc.setBundleContext(bundleContext);
+        svc.addRepository(uri);
+        
+        Repository[] repositories = svc.listRepositories();
+        assertNotNull(repositories);
+        assertEquals(1, repositories.length);
+        assertNotNull(repositories[0]);
+        Feature[] features = repositories[0].getFeatures();
+        assertNotNull(features);
+        assertEquals(1, features.length);
+        assertNotNull(features[0]);
+        assertEquals("f1", features[0].getName());
+        assertNotNull(features[0].getDependencies());
+        assertEquals(0, features[0].getDependencies().size());
+        assertNotNull(features[0].getBundles());
+        assertEquals(1, features[0].getBundles().size());
+        assertEquals(name, features[0].getBundles().get(0));
+
+        verify(preferencesService, prefs, repositoriesNode, featuresNode, bundleContext, installedBundle);
+
+        reset(preferencesService, prefs, repositoriesNode, featuresNode, bundleContext, installedBundle);
+
+        expect(bundleContext.getBundles()).andReturn(new Bundle[0]);
+        expect(bundleContext.installBundle(isA(String.class),
+                                           isA(InputStream.class))).andReturn(installedBundle);
+        expect(installedBundle.getBundleId()).andReturn(12345L);
+        expect(bundleContext.getBundle(12345L)).andReturn(installedBundle);
+        installedBundle.start();
+
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 1);
+        repositoriesNode.put("item.0", uri.toString());
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();
+        featuresNode.put("f1" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + FeatureImpl.DEFAULT_VERSION, "12345");
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();
+
+        replay(preferencesService, prefs, repositoriesNode, featuresNode, bundleContext, installedBundle);
+
+        svc.installFeature("f1");
+        
+        Feature[] installed = svc.listInstalledFeatures();
+        assertEquals(1, installed.length);
+        assertEquals("f1", installed[0].getName());
+    }
+
+    public void testUninstallFeature() throws Exception {
+    	
+        String name = ApplicationContext.class.getName();
+        name = name.replace(".", "/")  + ".class";
+        name = getClass().getClassLoader().getResource(name).toString();
+        name = name.substring("jar:".length(), name.indexOf('!'));
+
+        File tmp = File.createTempFile("smx", ".feature");
+        PrintWriter pw = new PrintWriter(new FileWriter(tmp));
+        pw.println("<features>");
+        pw.println("  <feature name=\"f1\" version=\"0.1\">");
+        pw.println("    <bundle>" + name + "</bundle>");
+        pw.println("  </feature>");
+        pw.println("  <feature name=\"f1\" version=\"0.2\">");
+        pw.println("    <bundle>" + name + "</bundle>");
+        pw.println("  </feature>");
+        pw.println("</features>");
+        pw.close();
+
+        URI uri = tmp.toURI();
+        
+        Preferences prefs = EasyMock.createMock(Preferences.class);
+        PreferencesService preferencesService = EasyMock.createMock(PreferencesService.class);
+        Preferences repositoriesNode = EasyMock.createMock(Preferences.class);
+        Preferences featuresNode = EasyMock.createMock(Preferences.class);
+        BundleContext bundleContext = EasyMock.createMock(BundleContext.class);
+        Bundle installedBundle = EasyMock.createMock(Bundle.class);
+
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 1);
+        repositoriesNode.put("item.0", uri.toString());
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();
+
+        replay(preferencesService, prefs, repositoriesNode, featuresNode, bundleContext, installedBundle);
+
+        FeaturesServiceImpl svc = new FeaturesServiceImpl();
+        svc.setPreferences(preferencesService);
+        svc.setBundleContext(bundleContext);
+        svc.addRepository(uri);
+        
+        verify(preferencesService, prefs, repositoriesNode, featuresNode, bundleContext, installedBundle);
+
+        reset(preferencesService, prefs, repositoriesNode, featuresNode, bundleContext, installedBundle);
+
+        // Installs f1 and 0.1
+        expect(bundleContext.getBundles()).andReturn(new Bundle[0]);
+        expect(bundleContext.installBundle(isA(String.class),
+                                           isA(InputStream.class))).andReturn(installedBundle);
+        expect(installedBundle.getBundleId()).andReturn(12345L);
+        expect(bundleContext.getBundle(12345L)).andReturn(installedBundle);
+        installedBundle.start();
+
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 1);
+        repositoriesNode.put("item.0", uri.toString());
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();
+        featuresNode.put("f1" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + "0.1", "12345");
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();
+        
+        // Installs f1 and 0.2
+        expect(bundleContext.getBundles()).andReturn(new Bundle[0]);
+        expect(bundleContext.installBundle(isA(String.class),
+                                           isA(InputStream.class))).andReturn(installedBundle);
+        expect(installedBundle.getBundleId()).andReturn(123456L);
+        expect(bundleContext.getBundle(123456L)).andReturn(installedBundle);
+        installedBundle.start();
+
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 1);
+        repositoriesNode.put("item.0", uri.toString());
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();
+        featuresNode.put("f1" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + "0.1", "12345");
+        featuresNode.put("f1" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + "0.2", "123456");
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();
+
+        // UnInstalls f1 and 0.1
+        expect(bundleContext.getBundle(12345)).andReturn(installedBundle);
+        installedBundle.uninstall();
+
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 1);
+        repositoriesNode.put("item.0", uri.toString());
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();
+        featuresNode.put("f1" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + "0.2", "123456");
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();
+
+        // UnInstalls f1 and 0.2
+        expect(bundleContext.getBundle(123456)).andReturn(installedBundle);
+        installedBundle.uninstall();
+
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 1);
+        repositoriesNode.put("item.0", uri.toString());
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();
+
+        replay(preferencesService, prefs, repositoriesNode, featuresNode, bundleContext, installedBundle);
+
+        try {
+            svc.uninstallFeature("f1");
+            fail("Uninstall should have failed as feature is not installed");
+        } catch (Exception e) {
+            // ok
+        }
+
+        svc.installFeature("f1", "0.1");
+        svc.installFeature("f1", "0.2");
+
+        try {
+            svc.uninstallFeature("f1");
+            fail("Uninstall should have failed as feature is installed in multiple versions");
+        } catch (Exception e) {
+            // ok
+        }
+
+        svc.uninstallFeature("f1", "0.1");
+        svc.uninstallFeature("f1");
+    }    
+    
+    // Tests Add and Remove Repository
+    public void testAddAndRemoveRepository() throws Exception {        
+
+    	String name = ApplicationContext.class.getName();
+        name = name.replace(".", "/")  + ".class";
+        name = getClass().getClassLoader().getResource(name).toString();
+        name = name.substring("jar:".length(), name.indexOf('!'));        
+
+        File tmp = File.createTempFile("smx", ".feature");
+        PrintWriter pw = new PrintWriter(new FileWriter(tmp));
+        pw.println("<features>");
+        pw.println("  <feature name=\"f1\" version=\"0.1\">");
+        pw.println("    <bundle>" + name + "</bundle>");
+        pw.println("  </feature>");
+        pw.println("  <feature name=\"f1\" version=\"0.2\">");
+        pw.println("    <bundle>" + name + "</bundle>");
+        pw.println("  </feature>");
+        pw.println("  <feature name=\"f2\" version=\"0.2\">");
+        pw.println("    <bundle>" + name + "</bundle>");
+        pw.println("  </feature>");
+        pw.println("</features>");
+        pw.close();
+
+        URI uri = tmp.toURI();
+
+        // loads the state
+        Preferences prefs = EasyMock.createMock(Preferences.class);
+        PreferencesService preferencesService = EasyMock.createMock(PreferencesService.class);
+        Preferences repositoriesNode = EasyMock.createMock(Preferences.class);
+        Preferences featuresNode = EasyMock.createMock(Preferences.class);
+        BundleContext bundleContext = EasyMock.createMock(BundleContext.class);        
+        Bundle installedBundle = EasyMock.createMock(Bundle.class);        
+
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 1);
+        repositoriesNode.put("item.0", uri.toString());        
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();               
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();
+        
+        // SaveState for addRepository
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 1);
+        repositoriesNode.put("item.0", uri.toString());        
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();
+        
+        // SaveState for removeRepository
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 0);              
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();        
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();
+        replay(preferencesService, prefs, repositoriesNode, featuresNode, bundleContext, installedBundle);
+
+        FeaturesServiceImpl svc = new FeaturesServiceImpl();
+        svc.setPreferences(preferencesService);
+        svc.setBundleContext(bundleContext);        
+
+        // Adds Repository
+        svc.addRepository(uri);                                                     
+        
+        // Removes Repository
+        svc.removeRepository(uri);        
+    }
+
+    // Tests installing all features in a repo and uninstalling
+    // all features in a repo
+    public void testInstallUninstallAllFeatures() throws Exception {        
+
+    	String name = ApplicationContext.class.getName();
+        name = name.replace(".", "/")  + ".class";
+        name = getClass().getClassLoader().getResource(name).toString();
+        name = name.substring("jar:".length(), name.indexOf('!'));        
+
+        File tmp = File.createTempFile("smx", ".feature");
+        PrintWriter pw = new PrintWriter(new FileWriter(tmp));
+        pw.println("<features>");
+        pw.println("  <feature name=\"f1\" version=\"0.1\">");
+        pw.println("    <bundle>" + name + "</bundle>");
+        pw.println("  </feature>");
+        pw.println("  <feature name=\"f1\" version=\"0.2\">");
+        pw.println("    <bundle>" + name + "</bundle>");
+        pw.println("  </feature>");
+        pw.println("  <feature name=\"f2\" version=\"0.2\">");
+        pw.println("    <bundle>" + name + "</bundle>");
+        pw.println("  </feature>");
+        pw.println("</features>");
+        pw.close();
+
+        URI uri = tmp.toURI();
+
+        // loads the state
+        Preferences prefs = EasyMock.createMock(Preferences.class);
+        PreferencesService preferencesService = EasyMock.createMock(PreferencesService.class);
+        Preferences repositoriesNode = EasyMock.createMock(Preferences.class);
+        Preferences featuresNode = EasyMock.createMock(Preferences.class);
+        BundleContext bundleContext = EasyMock.createMock(BundleContext.class);        
+        Bundle installedBundle = EasyMock.createMock(Bundle.class);        
+
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 1);
+        repositoriesNode.put("item.0", uri.toString());        
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();
+        
+        // Installs first feature name = f1, version = 0.1 
+        expect(bundleContext.getBundles()).andReturn(new Bundle[0]);
+        expect(bundleContext.installBundle(isA(String.class),
+                                           isA(InputStream.class))).andReturn(installedBundle);
+        expect(installedBundle.getBundleId()).andReturn(12345L);
+        expect(bundleContext.getBundle(12345L)).andReturn(installedBundle);
+        installedBundle.start();
+        
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 1);
+        repositoriesNode.put("item.0", uri.toString());        
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();
+        featuresNode.put("f1" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + "0.1", "12345");
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();
+        
+        // Installs second feature name = f1, version = 0.2
+        expect(bundleContext.getBundles()).andReturn(new Bundle[0]);
+        expect(bundleContext.installBundle(isA(String.class),
+                                           isA(InputStream.class))).andReturn(installedBundle);
+        expect(installedBundle.getBundleId()).andReturn(123456L);
+        expect(bundleContext.getBundle(123456L)).andReturn(installedBundle);
+        installedBundle.start();
+        
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 1);
+        repositoriesNode.put("item.0", uri.toString());        
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();
+        featuresNode.put("f1" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + "0.1", "12345");
+        featuresNode.put("f1" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + "0.2", "123456");
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();
+        
+        // Installs third feature name = f2, version = 0.2
+        expect(bundleContext.getBundles()).andReturn(new Bundle[0]);
+        expect(bundleContext.installBundle(isA(String.class),
+                                           isA(InputStream.class))).andReturn(installedBundle);
+        expect(installedBundle.getBundleId()).andReturn(1234567L);
+        expect(bundleContext.getBundle(1234567L)).andReturn(installedBundle);
+        installedBundle.start();
+        
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 1);
+        repositoriesNode.put("item.0", uri.toString());        
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();
+        featuresNode.put("f1" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + "0.1", "12345");
+        featuresNode.put("f1" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + "0.2", "123456");
+        featuresNode.put("f2" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + "0.2", "1234567");
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();
+        
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 0);
+        repositoriesNode.put("item.0", uri.toString());        
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();
+        featuresNode.put("f1" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + "0.1", "12345");
+        featuresNode.put("f1" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + "0.2", "123456");
+        featuresNode.put("f2" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + "0.2", "1234567");
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();
+
+        // uninstallAllFeatures 
+        
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 1);
+        repositoriesNode.put("item.0", uri.toString());        
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();
+        featuresNode.put("f1" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + "0.1", "12345");
+        featuresNode.put("f1" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + "0.2", "123456");
+        featuresNode.put("f2" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + "0.2", "1234567");
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();
+        
+        // uninstalls first feature name = f1, version = 0.1
+        expect(bundleContext.getBundle(12345)).andReturn(installedBundle);
+        installedBundle.uninstall();
+
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 1);
+        repositoriesNode.put("item.0", uri.toString());        
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();
+        featuresNode.put("f1" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + "0.2", "123456");
+        featuresNode.put("f2" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + "0.2", "1234567");
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();
+        
+        // uninstalls third feature name = f2, version = 0.2
+        expect(bundleContext.getBundle(1234567)).andReturn(installedBundle);
+        installedBundle.uninstall();
+
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 1);
+        repositoriesNode.put("item.0", uri.toString());        
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();        
+        featuresNode.put("f1" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + "0.2", "123456");
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();
+
+        // uninstalls second feature name = f1, version = 0.2
+        expect(bundleContext.getBundle(123456)).andReturn(installedBundle);
+        installedBundle.uninstall();
+
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 1);
+        repositoriesNode.put("item.0", uri.toString());        
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();        
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();
+
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 0);
+        repositoriesNode.put("item.0", uri.toString());        
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();
+
+        replay(preferencesService, prefs, repositoriesNode, featuresNode, bundleContext, installedBundle);
+
+        FeaturesServiceImpl svc = new FeaturesServiceImpl();
+        svc.setPreferences(preferencesService);
+        svc.setBundleContext(bundleContext);        
+        svc.installAllFeatures(uri);
+        
+        // Uninstalls features with versions.
+        svc.uninstallAllFeatures(uri);    
+    }    
+
+
+    // Tests install of a Repository that includes a feature 
+    // with a feature dependency  
+    // The dependant feature is in the same repository 
+    // Tests uninstall of features
+    public void testInstallFeatureWithDependantFeatures() throws Exception {          
+
+    	String name = ApplicationContext.class.getName();
+        name = name.replace(".", "/")  + ".class";
+        name = getClass().getClassLoader().getResource(name).toString();
+        name = name.substring("jar:".length(), name.indexOf('!'));        
+
+        File tmp = File.createTempFile("smx", ".feature");
+        PrintWriter pw = new PrintWriter(new FileWriter(tmp));
+        pw.println("<features>");
+        pw.println("  <feature name=\"f1\" version=\"0.1\">");
+        pw.println("  <feature version=\"0.1\">f2</feature>");
+        pw.println("    <bundle>" + name + "</bundle>");
+        pw.println("  </feature>");
+        pw.println("  <feature name=\"f2\" version=\"0.1\">");
+        pw.println("    <bundle>" + name + "</bundle>");
+        pw.println("  </feature>");
+        pw.println("</features>");
+        pw.close();
+
+        URI uri = tmp.toURI();
+
+        // loads the state
+        Preferences prefs = EasyMock.createMock(Preferences.class);
+        PreferencesService preferencesService = EasyMock.createMock(PreferencesService.class);
+        Preferences repositoriesNode = EasyMock.createMock(Preferences.class);
+        Preferences repositoriesAvailableNode = EasyMock.createMock(Preferences.class);
+        Preferences featuresNode = EasyMock.createMock(Preferences.class);
+        BundleContext bundleContext = EasyMock.createMock(BundleContext.class);        
+        Bundle installedBundle = EasyMock.createMock(Bundle.class);        
+
+        // savestate from addRepository
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 1);
+        repositoriesNode.put("item.0", uri.toString());        
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();
+
+        // Installs feature f1 with dependency on f2
+        // so will install f2 first
+        expect(bundleContext.getBundles()).andReturn(new Bundle[0]);
+        expect(bundleContext.installBundle(isA(String.class),
+                                           isA(InputStream.class))).andReturn(installedBundle);
+        expect(installedBundle.getBundleId()).andReturn(12345L);
+        expect(bundleContext.getBundle(12345L)).andReturn(installedBundle);
+        installedBundle.start();
+        
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 1);
+        repositoriesNode.put("item.0", uri.toString());        
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();
+        featuresNode.put("f2" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + "0.1", "12345");
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();
+        
+        // Then installs f1
+        expect(bundleContext.getBundles()).andReturn(new Bundle[0]);
+        expect(bundleContext.installBundle(isA(String.class),
+                                           isA(InputStream.class))).andReturn(installedBundle);
+        expect(installedBundle.getBundleId()).andReturn(1234L);
+        expect(bundleContext.getBundle(1234L)).andReturn(installedBundle);
+        installedBundle.start();
+        
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 1);
+        repositoriesNode.put("item.0", uri.toString());        
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();
+        featuresNode.put("f2" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + "0.1", "12345");
+        featuresNode.put("f1" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + "0.1", "1234");
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();                      
+
+        // uninstalls first feature name = f1, version = 0.1
+        expect(bundleContext.getBundle(1234)).andReturn(installedBundle);
+        installedBundle.uninstall();
+
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 1);
+        repositoriesNode.put("item.0", uri.toString());        
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();
+        featuresNode.put("f2" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + "0.1", "12345");        
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();
+        
+       // uninstalls first feature name = f2, version = 0.1
+        expect(bundleContext.getBundle(12345)).andReturn(installedBundle);
+        installedBundle.uninstall();
+
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 1);
+        repositoriesNode.put("item.0", uri.toString());        
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();        
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();                        
+        
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 0);
+        repositoriesNode.put("item.0", uri.toString());        
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();        
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();                        
+        
+        replay(preferencesService, prefs, repositoriesNode, featuresNode, bundleContext, installedBundle);
+
+        FeaturesServiceImpl svc = new FeaturesServiceImpl();
+        svc.setPreferences(preferencesService);
+        svc.setBundleContext(bundleContext);        
+        svc.addRepository(uri);    
+
+        svc.installFeature("f1", "0.1");
+                
+        // Uninstall repository
+        svc.uninstallFeature("f1", "0.1");
+        svc.uninstallFeature("f2", "0.1");
+        
+    }
+
+}
diff --git a/karaf/features/core/src/test/java/org/apache/felix/karaf/features/RepositoryTest.java b/karaf/features/core/src/test/java/org/apache/felix/karaf/features/RepositoryTest.java
new file mode 100644
index 0000000..737acaf
--- /dev/null
+++ b/karaf/features/core/src/test/java/org/apache/felix/karaf/features/RepositoryTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.felix.karaf.features;
+
+import java.net.URI;
+
+import junit.framework.TestCase;
+import org.apache.felix.karaf.features.internal.RepositoryImpl;
+import org.apache.felix.karaf.features.internal.FeatureImpl;
+
+
+public class RepositoryTest extends TestCase {
+
+    public void testLoad() throws Exception {
+        RepositoryImpl r = new RepositoryImpl(getClass().getResource("repo1.xml").toURI());
+        // Check repo
+        URI[] repos = r.getRepositories();
+        assertNotNull(repos);
+        assertEquals(1, repos.length);
+        assertEquals(URI.create("urn:r1"), repos[0]);
+        // Check features
+        Feature[] features = r.getFeatures();
+        assertNotNull(features);
+        assertEquals(2, features.length);
+        assertNotNull(features[0]);
+        assertEquals("f1", features[0].getName());
+        assertNotNull(features[0].getConfigurations());
+        assertEquals(1, features[0].getConfigurations().size());
+        assertNotNull(features[0].getConfigurations().get("c1"));
+        assertEquals(1, features[0].getConfigurations().get("c1").size());
+        assertEquals("v", features[0].getConfigurations().get("c1").get("k"));
+        assertNotNull(features[0].getDependencies());
+        assertEquals(0, features[0].getDependencies().size());
+        assertNotNull(features[0].getBundles());
+        assertEquals(2, features[0].getBundles().size());
+        assertEquals("b1", features[0].getBundles().get(0));
+        assertEquals("b2", features[0].getBundles().get(1));
+        assertNotNull(features[1]);
+        assertEquals("f2", features[1].getName());
+        assertNotNull(features[1].getConfigurations());
+        assertEquals(0, features[1].getConfigurations().size());
+        assertNotNull(features[1].getDependencies());
+        assertEquals(1, features[1].getDependencies().size());
+        assertEquals("f1" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + FeatureImpl.DEFAULT_VERSION, features[1].getDependencies().get(0).toString());
+        assertNotNull(features[1].getBundles());
+        assertEquals(1, features[1].getBundles().size());
+        assertEquals("b3", features[1].getBundles().get(0));
+    }
+    
+    public void testShowWrongUriInException() throws Exception {
+        String uri = "src/test/resources/org/apache/felix/karaf/gshell/features/repo1.xml";
+        RepositoryImpl r = new RepositoryImpl(new URI(uri));
+        try {
+            r.load();
+        } catch (Exception e) {
+            assertTrue(e.getMessage().contains(uri));
+        }
+    }
+}
diff --git a/karaf/features/core/src/test/java/org/apache/felix/karaf/features/internal/FeaturesServiceImplTest.java b/karaf/features/core/src/test/java/org/apache/felix/karaf/features/internal/FeaturesServiceImplTest.java
new file mode 100644
index 0000000..8c8cb10
--- /dev/null
+++ b/karaf/features/core/src/test/java/org/apache/felix/karaf/features/internal/FeaturesServiceImplTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.felix.karaf.features.internal;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+import org.apache.felix.karaf.features.Feature;
+
+/**
+ * Test cases for {@link FeaturesServiceImpl}
+ */
+public class FeaturesServiceImplTest extends TestCase {
+    
+    public void testGetFeature() throws Exception {
+        final Map<String, Map<String, Feature>> features = new HashMap<String, Map<String,Feature>>();
+        Map<String, Feature> versions = new HashMap<String, Feature>();
+        FeatureImpl feature = new FeatureImpl("transaction");
+        versions.put("1.0.0", feature);
+        features.put("transaction", versions);
+        final FeaturesServiceImpl impl = new FeaturesServiceImpl() {
+            protected Map<String,Map<String,Feature>> getFeatures() throws Exception {
+                return features;
+            };
+        };
+        assertNotNull(impl.getFeature("transaction", FeatureImpl.DEFAULT_VERSION));
+        assertSame(feature, impl.getFeature("transaction", FeatureImpl.DEFAULT_VERSION));
+    }
+    
+    public void testGetFeatureStripVersion() throws Exception {
+        final Map<String, Map<String, Feature>> features = new HashMap<String, Map<String,Feature>>();
+        Map<String, Feature> versions = new HashMap<String, Feature>();
+        FeatureImpl feature = new FeatureImpl("transaction");
+        versions.put("1.0.0", feature);
+        features.put("transaction", versions);
+        final FeaturesServiceImpl impl = new FeaturesServiceImpl() {
+            protected Map<String,Map<String,Feature>> getFeatures() throws Exception {
+                return features;
+            };
+        };
+        assertNotNull(impl.getFeature("transaction", "  1.0.0  "));
+        assertSame(feature, impl.getFeature("transaction", "  1.0.0   "));
+    }
+    
+    public void testGetFeatureNotAvailable() throws Exception {
+        final Map<String, Map<String, Feature>> features = new HashMap<String, Map<String,Feature>>();
+        Map<String, Feature> versions = new HashMap<String, Feature>();
+        versions.put("1.0.0", new FeatureImpl("transaction"));
+        features.put("transaction", versions);
+        final FeaturesServiceImpl impl = new FeaturesServiceImpl() {
+            protected Map<String,Map<String,Feature>> getFeatures() throws Exception {
+                return features;
+            };
+        };
+        assertNull(impl.getFeature("activemq", FeatureImpl.DEFAULT_VERSION));
+    }
+    
+    public void testGetFeatureHighestAvailable() throws Exception {
+        final Map<String, Map<String, Feature>> features = new HashMap<String, Map<String,Feature>>();
+        Map<String, Feature> versions = new HashMap<String, Feature>();
+        versions.put("1.0.0", new FeatureImpl("transaction", "1.0.0"));
+        versions.put("2.0.0", new FeatureImpl("transaction", "2.0.0"));
+        features.put("transaction", versions);
+        final FeaturesServiceImpl impl = new FeaturesServiceImpl() {
+            protected Map<String,Map<String,Feature>> getFeatures() throws Exception {
+                return features;
+            };
+        };
+        assertNotNull(impl.getFeature("transaction", FeatureImpl.DEFAULT_VERSION));
+        assertSame("2.0.0", impl.getFeature("transaction", FeatureImpl.DEFAULT_VERSION).getVersion());
+    }
+
+}
diff --git a/karaf/features/core/src/test/resources/org/apache/felix/karaf/features/repo1.xml b/karaf/features/core/src/test/resources/org/apache/felix/karaf/features/repo1.xml
new file mode 100644
index 0000000..3284661
--- /dev/null
+++ b/karaf/features/core/src/test/resources/org/apache/felix/karaf/features/repo1.xml
@@ -0,0 +1,31 @@
+<!--
+
+    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.
+-->
+<features>
+    <repository>urn:r1</repository>
+    <feature name="f1">
+        <config name="c1">
+            k=v
+        </config>
+        <bundle>b1</bundle>
+        <bundle>b2</bundle>
+    </feature>
+    <feature name="f2">
+        <feature>f1</feature>
+        <bundle>b3</bundle>
+    </feature>
+</features>
diff --git a/karaf/features/management/pom.xml b/karaf/features/management/pom.xml
new file mode 100644
index 0000000..6490511
--- /dev/null
+++ b/karaf/features/management/pom.xml
@@ -0,0 +1,118 @@
+<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/maven-v4_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.felix.karaf.features</groupId>
+        <artifactId>features</artifactId>
+        <version>1.2.0-SNAPSHOT</version>
+    </parent>
+
+    <groupId>org.apache.felix.karaf.features</groupId>
+    <artifactId>org.apache.felix.karaf.features.management</artifactId>
+    <packaging>bundle</packaging>
+    <version>1.2.0-SNAPSHOT</version>
+    <name>Apache Felix Karaf :: Features Management</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix.karaf.features</groupId>
+            <artifactId>org.apache.felix.karaf.features.core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.bundlerepository</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix.karaf.gshell</groupId>
+            <artifactId>org.apache.felix.karaf.gshell.console</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix.karaf.gshell</groupId>
+            <artifactId>org.apache.felix.karaf.gshell.obr</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.osgi</groupId>
+            <artifactId>spring-osgi-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.servicemix.bundles</groupId>
+            <artifactId>org.apache.servicemix.bundles.junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.easymock</groupId>
+            <artifactId>easymock</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-jdk14</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <configuration>
+                    <instructions>
+                        <Bundle-SymbolicName>${artifactId}</Bundle-SymbolicName>
+                        <Export-Package>
+                            ${artifactId}*;version=${version}
+                        </Export-Package>
+                        <Import-Package>
+                            javax.management,
+                            javax.management.loading,
+                            org.osgi.service.command,
+                            org.apache.felix.gogo.commands,
+                            org.apache.felix.karaf.gshell.console,
+                            !${artifactId}*,
+                            *
+                        </Import-Package>
+                        <Private-Package>org.apache.felix.karaf.features.management.internal</Private-Package>
+                        <_versionpolicy>${bnd.version.policy}</_versionpolicy>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/karaf/features/management/src/main/java/org/apache/felix/karaf/features/management/FeaturesServiceMBean.java b/karaf/features/management/src/main/java/org/apache/felix/karaf/features/management/FeaturesServiceMBean.java
new file mode 100644
index 0000000..dac88da
--- /dev/null
+++ b/karaf/features/management/src/main/java/org/apache/felix/karaf/features/management/FeaturesServiceMBean.java
@@ -0,0 +1,114 @@
+/*
+ * 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.felix.karaf.features.management;
+
+import javax.management.openmbean.TabularData;
+
+public interface FeaturesServiceMBean {
+
+    TabularData getFeatures() throws Exception;
+
+    TabularData getRepositories() throws Exception;
+
+    void addRepository(String url) throws Exception;
+
+    void removeRepositroy(String url) throws Exception;
+
+    void installFeature(String name) throws Exception;
+
+    void installFeature(String name, String version) throws Exception;
+
+    void uninstallFeature(String name) throws Exception;
+
+    void uninstallFeature(String name, String version) throws Exception;
+
+    String FEATURE_NAME = "Name";
+
+    String FEATURE_VERSION = "Version";
+
+    String FEATURE_DEPENDENCIES = "Dependencies";
+
+    String FEATURE_BUNDLES = "Bundles";
+
+    String FEATURE_CONFIGURATIONS = "Configurations";
+
+    String FEATURE_INSTALLED = "Installed";
+
+    String FEATURE_CONFIG_PID = "Pid";
+    String FEATURE_CONFIG_ELEMENTS = "Elements";
+    String FEATURE_CONFIG_ELEMENT_KEY = "Key";
+    String FEATURE_CONFIG_ELEMENT_VALUE = "Value";
+
+    /**
+     * The type of the event which is emitted for features events
+     */
+    String FEATURE_EVENT_TYPE = "org.apache.felix.karaf.features.featureEvent";
+
+    String FEATURE_EVENT_EVENT_TYPE = "Type";
+
+    String FEATURE_EVENT_EVENT_TYPE_INSTALLED = "Installed";
+
+    String FEATURE_EVENT_EVENT_TYPE_UNINSTALLED = "Uninstalled";
+
+    /**
+     * The item names in the CompositeData representing a feature
+     */
+    String[] FEATURE = { FEATURE_NAME, FEATURE_VERSION, FEATURE_DEPENDENCIES, FEATURE_BUNDLES,
+                         FEATURE_CONFIGURATIONS, FEATURE_INSTALLED };
+
+    String[] FEATURE_IDENTIFIER = { FEATURE_NAME, FEATURE_VERSION };
+
+    String[] FEATURE_CONFIG = { FEATURE_CONFIG_PID, FEATURE_CONFIG_ELEMENTS };
+
+    String[] FEATURE_CONFIG_ELEMENT = { FEATURE_CONFIG_ELEMENT_KEY, FEATURE_CONFIG_ELEMENT_VALUE };
+
+    /**
+     * The item names in the CompositeData representing the event raised for
+     * feature events within the OSGi container by this bean
+     */
+    String[] FEATURE_EVENT = { FEATURE_NAME, FEATURE_VERSION, FEATURE_EVENT_EVENT_TYPE };
+
+
+    String REPOSITORY_URI = "Uri";
+
+    String REPOSITORY_REPOSITORIES = "Repositories";
+
+    String REPOSITORY_FEATURES = "Features";
+
+    /**
+     * The type of the event which is emitted for repositories events
+     */
+    String REPOSITORY_EVENT_TYPE = "org.apache.felix.karaf.features.repositoryEvent";
+
+    String REPOSITORY_EVENT_EVENT_TYPE = "Type";
+
+    String REPOSITORY_EVENT_EVENT_TYPE_ADDED = "Added";
+
+    String REPOSITORY_EVENT_EVENT_TYPE_REMOVED = "Removed";
+
+    /**
+     * The item names in the CompositeData representing a feature
+     */
+    String[] REPOSITORY = { REPOSITORY_URI,  REPOSITORY_REPOSITORIES, REPOSITORY_FEATURES };
+
+    /**
+     * The item names in the CompositeData representing the event raised for
+     * feature events within the OSGi container by this bean
+     */
+    String[] REPOSITORY_EVENT = { REPOSITORY_URI, REPOSITORY_EVENT_EVENT_TYPE };
+
+}
diff --git a/karaf/features/management/src/main/java/org/apache/felix/karaf/features/management/codec/JmxFeature.java b/karaf/features/management/src/main/java/org/apache/felix/karaf/features/management/codec/JmxFeature.java
new file mode 100644
index 0000000..f471282
--- /dev/null
+++ b/karaf/features/management/src/main/java/org/apache/felix/karaf/features/management/codec/JmxFeature.java
@@ -0,0 +1,259 @@
+/*
+ * 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.felix.karaf.features.management.codec;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.TabularType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.ArrayType;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.TabularDataSupport;
+
+import org.apache.felix.karaf.features.Feature;
+import org.apache.felix.karaf.features.management.FeaturesServiceMBean;
+
+public class JmxFeature {
+
+    /**
+     * The CompositeType which represents a single feature
+     */
+    public final static CompositeType FEATURE;
+
+    /**
+     * The TabularType which represents a list of features
+     */
+    public final static TabularType FEATURE_TABLE;
+
+    public final static CompositeType FEATURE_IDENTIFIER;
+
+    public final static TabularType FEATURE_IDENTIFIER_TABLE;
+
+    public final static CompositeType FEATURE_CONFIG_ELEMENT;
+
+    public final static TabularType FEATURE_CONFIG_ELEMENT_TABLE;
+
+    public final static CompositeType FEATURE_CONFIG;
+
+    public final static TabularType FEATURE_CONFIG_TABLE;
+
+
+    private final CompositeData data;
+
+    public JmxFeature(Feature feature, boolean installed) {
+        try {
+            String[] itemNames = FeaturesServiceMBean.FEATURE;
+            Object[] itemValues = new Object[itemNames.length];
+            itemValues[0] = feature.getName();
+            itemValues[1] = feature.getVersion();
+            itemValues[2] = getFeatureIdentifierTable(feature.getDependencies());
+            itemValues[3] = feature.getBundles().toArray(new String[feature.getBundles().size()]);
+            itemValues[4]  = getConfigTable(feature.getConfigurations());
+            itemValues[5] = installed;
+            data = new CompositeDataSupport(FEATURE, itemNames, itemValues);
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Cannot form feature open data", e);
+        }
+    }
+
+    public CompositeData asCompositeData() {
+        return data;
+    }
+
+    public static TabularData tableFrom(Collection<JmxFeature> features) {
+        TabularDataSupport table = new TabularDataSupport(FEATURE_TABLE);
+        for (JmxFeature feature : features) {
+            table.put(feature.asCompositeData());
+        }
+        return table;
+    }
+
+    static TabularData getFeatureIdentifierTable(List<Feature> features) throws OpenDataException {
+        TabularDataSupport table = new TabularDataSupport(FEATURE_IDENTIFIER_TABLE);
+        for (Feature feature : features) {
+            String[] itemNames = new String[] { FeaturesServiceMBean.FEATURE_NAME, FeaturesServiceMBean.FEATURE_VERSION };
+            Object[] itemValues = new Object[] { feature.getName(), feature.getVersion() };
+            CompositeData ident = new CompositeDataSupport(FEATURE_IDENTIFIER, itemNames, itemValues);
+            table.put(ident);
+        }
+        return table;
+    }
+
+    static TabularData getConfigTable(Map<String, Map<String, String>> configs) throws OpenDataException {
+        TabularDataSupport table = new TabularDataSupport(FEATURE_CONFIG_TABLE);
+        for (Map.Entry<String, Map<String, String>> entry : configs.entrySet()) {
+            String[] itemNames = FeaturesServiceMBean.FEATURE_CONFIG;
+            Object[] itemValues = new Object[2];
+            itemValues[0] = entry.getKey();
+            itemValues[1] = getConfigElementTable(entry.getValue());
+            CompositeData config = new CompositeDataSupport(FEATURE_CONFIG, itemNames, itemValues);
+            table.put(config);
+        }
+        return table;
+    }
+
+    static TabularData getConfigElementTable(Map<String, String> config) throws OpenDataException {
+        TabularDataSupport table = new TabularDataSupport(FEATURE_CONFIG_ELEMENT_TABLE);
+        for (Map.Entry<String, String> entry : config.entrySet()) {
+            String[] itemNames = FeaturesServiceMBean.FEATURE_CONFIG_ELEMENT;
+            Object[] itemValues = { entry.getKey(), entry.getValue() };
+            CompositeData element = new CompositeDataSupport(FEATURE_CONFIG_ELEMENT, itemNames, itemValues);
+            table.put(element);
+        }
+        return table;
+    }
+
+
+    static {
+        FEATURE_IDENTIFIER = createFeatureIdentifierType();
+        FEATURE_IDENTIFIER_TABLE = createFeatureIdentifierTableType();
+        FEATURE_CONFIG_ELEMENT = createFeatureConfigElementType();
+        FEATURE_CONFIG_ELEMENT_TABLE = createFeatureConfigElementTableType();
+        FEATURE_CONFIG = createFeatureConfigType();
+        FEATURE_CONFIG_TABLE = createFeatureConfigTableType();
+        FEATURE = createFeatureType();
+        FEATURE_TABLE = createFeatureTableType();
+    }
+
+    private static CompositeType createFeatureIdentifierType() {
+        try {
+            String description = "This type identify a Karaf features";
+            String[] itemNames = FeaturesServiceMBean.FEATURE_IDENTIFIER;
+            OpenType[] itemTypes = new OpenType[itemNames.length];
+            String[] itemDescriptions = new String[itemNames.length];
+            itemTypes[0] = SimpleType.STRING;
+            itemTypes[1] = SimpleType.STRING;
+
+            itemDescriptions[0] = "The id of the feature";
+            itemDescriptions[1] = "The version of the feature";
+
+            return new CompositeType("FeatureIdentifier", description, itemNames,
+                    itemDescriptions, itemTypes);
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Unable to build featureIdentifier type", e);
+        }
+    }
+
+    private static TabularType createFeatureIdentifierTableType() {
+        try {
+            return new TabularType("Features", "The table of featureIdentifiers",
+                    FEATURE_IDENTIFIER, new String[] { FeaturesServiceMBean.FEATURE_NAME, FeaturesServiceMBean.FEATURE_VERSION });
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Unable to build featureIdentifier table type", e);
+        }
+    }
+
+    private static CompositeType createFeatureConfigElementType() {
+        try {
+            String description = "This type encapsulates Karaf feature config element";
+            String[] itemNames = FeaturesServiceMBean.FEATURE_CONFIG_ELEMENT;
+            OpenType[] itemTypes = new OpenType[itemNames.length];
+            String[] itemDescriptions = new String[itemNames.length];
+            itemTypes[0] = SimpleType.STRING;
+            itemTypes[1] = SimpleType.STRING;
+
+            itemDescriptions[0] = "The key";
+            itemDescriptions[1] = "The value";
+
+            return new CompositeType("ConfigElement", description, itemNames,
+                    itemDescriptions, itemTypes);
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Unable to build configElement type", e);
+        }
+    }
+
+    private static TabularType createFeatureConfigElementTableType() {
+        try {
+            return new TabularType("ConfigElement", "The table of configurations elements",
+                    FEATURE_CONFIG_ELEMENT, new String[] { FeaturesServiceMBean.FEATURE_CONFIG_ELEMENT_KEY});
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Unable to build feature table type", e);
+        }
+    }
+
+    private static CompositeType createFeatureConfigType() {
+        try {
+            String description = "This type encapsulates Karaf feature config";
+            String[] itemNames = FeaturesServiceMBean.FEATURE_CONFIG;
+            OpenType[] itemTypes = new OpenType[itemNames.length];
+            String[] itemDescriptions = new String[itemNames.length];
+            itemTypes[0] = SimpleType.STRING;
+            itemTypes[1] = FEATURE_CONFIG_ELEMENT_TABLE;
+
+            itemDescriptions[0] = "The PID of the config";
+            itemDescriptions[1] = "The configuration elements";
+
+            return new CompositeType("Config", description, itemNames,
+                    itemDescriptions, itemTypes);
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Unable to build configElement type", e);
+        }
+    }
+
+    private static TabularType createFeatureConfigTableType() {
+        try {
+            return new TabularType("Features", "The table of configurations",
+                    FEATURE_CONFIG, new String[] { FeaturesServiceMBean.FEATURE_CONFIG_PID});
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Unable to build feature table type", e);
+        }
+    }
+
+    private static CompositeType createFeatureType() {
+        try {
+            String description = "This type encapsulates Karaf features";
+            String[] itemNames = FeaturesServiceMBean.FEATURE;
+            OpenType[] itemTypes = new OpenType[itemNames.length];
+            String[] itemDescriptions = new String[itemNames.length];
+            itemTypes[0] = SimpleType.STRING;
+            itemTypes[1] = SimpleType.STRING;
+            itemTypes[2] = FEATURE_IDENTIFIER_TABLE;
+            itemTypes[3] = new ArrayType(1, SimpleType.STRING);
+            itemTypes[4] = FEATURE_CONFIG_TABLE;
+            itemTypes[5] = SimpleType.BOOLEAN;
+
+            itemDescriptions[0] = "The name of the feature";
+            itemDescriptions[1] = "The version of the feature";
+            itemDescriptions[2] = "The feature dependencies";
+            itemDescriptions[3] = "The feature bundles";
+            itemDescriptions[4] = "The feature configurations";
+            itemDescriptions[5] = "Whether the feature is installed";
+
+            return new CompositeType("Feature", description, itemNames,
+                    itemDescriptions, itemTypes);
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Unable to build feature type", e);
+        }
+    }
+
+    private static TabularType createFeatureTableType() {
+        try {
+            return new TabularType("Features", "The table of all features",
+                    FEATURE, new String[] { FeaturesServiceMBean.FEATURE_NAME, FeaturesServiceMBean.FEATURE_VERSION });
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Unable to build feature table type", e);
+        }
+    }
+
+}
diff --git a/karaf/features/management/src/main/java/org/apache/felix/karaf/features/management/codec/JmxFeatureEvent.java b/karaf/features/management/src/main/java/org/apache/felix/karaf/features/management/codec/JmxFeatureEvent.java
new file mode 100644
index 0000000..03f024a
--- /dev/null
+++ b/karaf/features/management/src/main/java/org/apache/felix/karaf/features/management/codec/JmxFeatureEvent.java
@@ -0,0 +1,80 @@
+/*
+ * 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.felix.karaf.features.management.codec;
+
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+
+import org.apache.felix.karaf.features.FeatureEvent;
+import org.apache.felix.karaf.features.management.FeaturesServiceMBean;
+
+public class JmxFeatureEvent {
+
+    public static final CompositeType FEATURE_EVENT;
+
+    private final CompositeData data;
+
+    public JmxFeatureEvent(FeatureEvent event) {
+        try {
+            String[] itemNames = FeaturesServiceMBean.FEATURE_EVENT;
+            Object[] itemValues = new Object[itemNames.length];
+            itemValues[0] = event.getFeature().getName();
+            itemValues[1] = event.getFeature().getVersion();
+            switch (event.getType()) {
+                case FeatureInstalled:   itemValues[2] = FeaturesServiceMBean.FEATURE_EVENT_EVENT_TYPE_INSTALLED; break;
+                case FeatureUninstalled: itemValues[2] = FeaturesServiceMBean.FEATURE_EVENT_EVENT_TYPE_UNINSTALLED; break;
+                default: throw new IllegalStateException("Unsupported event type: " + event.getType());
+            }
+            data = new CompositeDataSupport(FEATURE_EVENT, itemNames, itemValues);
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Cannot form feature event open data", e);
+        }
+    }
+
+    public CompositeData asCompositeData() {
+        return data;
+    }
+
+    static {
+        FEATURE_EVENT = createFeatureEventType();
+    }
+
+    private static CompositeType createFeatureEventType() {
+        try {
+            String description = "This type identify a Karaf feature event";
+            String[] itemNames = FeaturesServiceMBean.FEATURE_EVENT;
+            OpenType[] itemTypes = new OpenType[itemNames.length];
+            String[] itemDescriptions = new String[itemNames.length];
+            itemTypes[0] = SimpleType.STRING;
+            itemTypes[1] = SimpleType.STRING;
+            itemTypes[2] = SimpleType.STRING;
+
+            itemDescriptions[0] = "The id of the feature";
+            itemDescriptions[1] = "The version of the feature";
+            itemDescriptions[2] = "The type of the event";
+
+            return new CompositeType("FeatureEvent", description, itemNames,
+                    itemDescriptions, itemTypes);
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Unable to build featureEvent type", e);
+        }
+    }
+}
diff --git a/karaf/features/management/src/main/java/org/apache/felix/karaf/features/management/codec/JmxRepository.java b/karaf/features/management/src/main/java/org/apache/felix/karaf/features/management/codec/JmxRepository.java
new file mode 100644
index 0000000..c2897a1
--- /dev/null
+++ b/karaf/features/management/src/main/java/org/apache/felix/karaf/features/management/codec/JmxRepository.java
@@ -0,0 +1,116 @@
+/*
+ * 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.felix.karaf.features.management.codec;
+
+import java.util.Collection;
+import java.util.Arrays;
+import java.net.URI;
+
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.TabularType;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.ArrayType;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.TabularDataSupport;
+import javax.management.openmbean.CompositeDataSupport;
+
+import org.apache.felix.karaf.features.Repository;
+import org.apache.felix.karaf.features.management.FeaturesServiceMBean;
+
+public class JmxRepository {
+
+    public final static CompositeType REPOSITORY;
+
+    public final static TabularType REPOSITORY_TABLE;
+
+    private final CompositeData data;
+
+    public JmxRepository(Repository repository) {
+        try {
+            String[] itemNames = FeaturesServiceMBean.REPOSITORY;
+            Object[] itemValues = new Object[itemNames.length];
+            itemValues[0] = repository.getURI().toString();
+            itemValues[1] = toStringArray(repository.getRepositories());
+            itemValues[2] = JmxFeature.getFeatureIdentifierTable(Arrays.asList(repository.getFeatures()));
+            data = new CompositeDataSupport(REPOSITORY, itemNames, itemValues);
+        } catch (Exception e) {
+            throw new IllegalStateException("Cannot form repository open data", e);
+        }
+    }
+
+    public CompositeData asCompositeData() {
+        return data;
+    }
+
+    public static TabularData tableFrom(Collection<JmxRepository> repositories) {
+        TabularDataSupport table = new TabularDataSupport(REPOSITORY_TABLE);
+        for (JmxRepository repository : repositories) {
+            table.put(repository.asCompositeData());
+        }
+        return table;
+    }
+
+    private static String[] toStringArray(URI[] uris) {
+        if (uris == null) {
+            return null;
+        }
+        String[] res = new String[uris.length];
+        for (int i = 0; i < res.length; i++) {
+            res[i] = uris[i].toString();
+        }
+        return res;
+    }
+
+    static {
+        REPOSITORY = createRepositoryType();
+        REPOSITORY_TABLE = createRepositoryTableType();
+    }
+
+    private static CompositeType createRepositoryType() {
+        try {
+            String description = "This type identify a Karaf repository";
+            String[] itemNames = FeaturesServiceMBean.REPOSITORY;
+            OpenType[] itemTypes = new OpenType[itemNames.length];
+            String[] itemDescriptions = new String[itemNames.length];
+            itemTypes[0] = SimpleType.STRING;
+            itemTypes[1] = new ArrayType(1, SimpleType.STRING);
+            itemTypes[2] = JmxFeature.FEATURE_IDENTIFIER_TABLE;
+
+            itemDescriptions[0] = "The uri of the repository";
+            itemDescriptions[1] = "The dependent repositories";
+            itemDescriptions[2] = "The list of included features";
+
+            return new CompositeType("Repository", description, itemNames,
+                    itemDescriptions, itemTypes);
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Unable to build repository type", e);
+        }
+    }
+
+    private static TabularType createRepositoryTableType() {
+        try {
+            return new TabularType("Features", "The table of repositories",
+                    REPOSITORY, new String[] { FeaturesServiceMBean.REPOSITORY_URI });
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Unable to build repository table type", e);
+        }
+    }
+
+}
diff --git a/karaf/features/management/src/main/java/org/apache/felix/karaf/features/management/codec/JmxRepositoryEvent.java b/karaf/features/management/src/main/java/org/apache/felix/karaf/features/management/codec/JmxRepositoryEvent.java
new file mode 100644
index 0000000..6e8954f
--- /dev/null
+++ b/karaf/features/management/src/main/java/org/apache/felix/karaf/features/management/codec/JmxRepositoryEvent.java
@@ -0,0 +1,77 @@
+/*
+ * 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.felix.karaf.features.management.codec;
+
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+
+import org.apache.felix.karaf.features.RepositoryEvent;
+import org.apache.felix.karaf.features.management.FeaturesServiceMBean;
+
+public class JmxRepositoryEvent {
+
+    public static final CompositeType REPOSITORY_EVENT;
+
+    private final CompositeData data;
+
+    public JmxRepositoryEvent(RepositoryEvent event) {
+        try {
+            String[] itemNames = FeaturesServiceMBean.REPOSITORY_EVENT;
+            Object[] itemValues = new Object[itemNames.length];
+            itemValues[0] = event.getRepository().getURI().toString();
+            switch (event.getType()) {
+                case RepositoryAdded:   itemValues[2] = FeaturesServiceMBean.REPOSITORY_EVENT_EVENT_TYPE_ADDED; break;
+                case RepositoryRemoved: itemValues[2] = FeaturesServiceMBean.REPOSITORY_EVENT_EVENT_TYPE_REMOVED; break;
+                default: throw new IllegalStateException("Unsupported event type: " + event.getType());
+            }
+            data = new CompositeDataSupport(REPOSITORY_EVENT, itemNames, itemValues);
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Cannot form repository event open data", e);
+        }
+    }
+
+    public CompositeData asCompositeData() {
+        return data;
+    }
+
+    static {
+        REPOSITORY_EVENT = createRepositoryEventType();
+    }
+
+    private static CompositeType createRepositoryEventType() {
+        try {
+            String description = "This type identify a Karaf repository event";
+            String[] itemNames = FeaturesServiceMBean.REPOSITORY_EVENT;
+            OpenType[] itemTypes = new OpenType[itemNames.length];
+            String[] itemDescriptions = new String[itemNames.length];
+            itemTypes[0] = SimpleType.STRING;
+            itemTypes[1] = SimpleType.STRING;
+
+            itemDescriptions[0] = "The uri of the repository";
+            itemDescriptions[1] = "The type of event";
+
+            return new CompositeType("RepositoryEvent", description, itemNames,
+                    itemDescriptions, itemTypes);
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Unable to build repositoryEvent type", e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/karaf/features/management/src/main/java/org/apache/felix/karaf/features/management/internal/FeaturesServiceMBeanImpl.java b/karaf/features/management/src/main/java/org/apache/felix/karaf/features/management/internal/FeaturesServiceMBeanImpl.java
new file mode 100644
index 0000000..c7b37b4
--- /dev/null
+++ b/karaf/features/management/src/main/java/org/apache/felix/karaf/features/management/internal/FeaturesServiceMBeanImpl.java
@@ -0,0 +1,194 @@
+/*
+ * 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.felix.karaf.features.management.internal;
+
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.net.URI;
+
+import javax.management.MBeanRegistration;
+import javax.management.MBeanServer;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.ObjectName;
+import javax.management.Notification;
+import javax.management.openmbean.TabularData;
+
+import org.apache.felix.karaf.features.management.FeaturesServiceMBean;
+import org.apache.felix.karaf.features.management.codec.JmxFeature;
+import org.apache.felix.karaf.features.management.codec.JmxFeatureEvent;
+import org.apache.felix.karaf.features.management.codec.JmxRepository;
+import org.apache.felix.karaf.features.management.codec.JmxRepositoryEvent;
+import org.apache.felix.karaf.features.FeaturesListener;
+import org.apache.felix.karaf.features.FeatureEvent;
+import org.apache.felix.karaf.features.RepositoryEvent;
+import org.apache.felix.karaf.features.FeaturesService;
+import org.apache.felix.karaf.features.Feature;
+import org.apache.felix.karaf.features.Repository;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ *
+ */
+
+public class FeaturesServiceMBeanImpl extends NotificationBroadcasterSupport
+                                      implements MBeanRegistration, FeaturesServiceMBean {
+
+    private ServiceRegistration registration;
+
+    private BundleContext bundleContext;
+
+	private ObjectName objectName;
+
+	private volatile long sequenceNumber = 0;
+
+	private MBeanServer server;
+
+    private FeaturesService featuresService;
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see javax.management.MBeanRegistration#preRegister(javax.manamement.MBeanServer, javax.management.ObjectName)
+     */
+	public ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception {
+		objectName = name;
+		this.server = server;
+		return name;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 *
+	 * @see javax.management.MBeanRegistration#postRegister(java.lang.Boolean)
+	 */
+	public void postRegister(Boolean registrationDone) {
+        registration = bundleContext.registerService(
+                            FeaturesListener.class.getName(),
+                            getFeaturesListener(),
+                            new Hashtable());
+	}
+
+	/*
+	 * (non-Javadoc)
+	 *
+	 * @see javax.management.MBeanRegistration#preDeregister()
+	 */
+	public void preDeregister() throws Exception {
+        registration.unregister();
+	}
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see javax.management.MBeanRegistration#postDeregister()
+     */
+    public void postDeregister() {
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see org.apache.felix.karaf.features.management.FeaturesServiceMBean#getFeatures()
+     */
+    public TabularData getFeatures() throws Exception {
+        try {
+            List<Feature> allFeatures = Arrays.asList(featuresService.listFeatures());
+            List<Feature> insFeatures = Arrays.asList(featuresService.listInstalledFeatures());
+            ArrayList<JmxFeature> features = new ArrayList<JmxFeature>();
+            for (Feature feature : allFeatures) {
+                features.add(new JmxFeature(feature, insFeatures.contains(feature)));
+            }
+            TabularData table = JmxFeature.tableFrom(features);
+            return table;
+        } catch (Throwable t) {
+            t.printStackTrace();
+            return null;
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see org.apache.felix.karaf.features.management.FeaturesServiceMBean#getRepositories()
+     */
+    public TabularData getRepositories() throws Exception {
+        try {
+            List<Repository> allRepositories = Arrays.asList(featuresService.listRepositories());
+            ArrayList<JmxRepository> repositories = new ArrayList<JmxRepository>();
+            for (Repository repository : allRepositories) {
+                repositories.add(new JmxRepository(repository));
+            }
+            TabularData table = JmxRepository.tableFrom(repositories);
+            return table;
+        } catch (Throwable t) {
+            t.printStackTrace();
+            return null;
+        }
+    }
+
+    public void addRepository(String uri) throws Exception {
+        featuresService.addRepository(new URI(uri));
+    }
+
+    public void removeRepositroy(String uri) throws Exception {
+        featuresService.removeRepository(new URI(uri));
+    }
+
+    public void installFeature(String name) throws Exception {
+        featuresService.installFeature(name);
+    }
+
+    public void installFeature(String name, String version) throws Exception {
+        featuresService.installFeature(name, version);
+    }
+
+    public void uninstallFeature(String name) throws Exception {
+        featuresService.uninstallFeature(name);
+    }
+
+    public void uninstallFeature(String name, String version) throws Exception {
+        featuresService.uninstallFeature(name, version);
+    }
+
+    public void setBundleContext(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+    }
+
+    public void setFeaturesService(FeaturesService featuresService) {
+        this.featuresService = featuresService;
+    }
+
+    public FeaturesListener getFeaturesListener() {
+        return new FeaturesListener() {
+            public void featureEvent(FeatureEvent event) {
+                if (!event.isReplay()) {
+                    Notification notification = new Notification(FEATURE_EVENT_TYPE, objectName, sequenceNumber++);
+                    notification.setUserData(new JmxFeatureEvent(event).asCompositeData());
+                    sendNotification(notification);
+                }
+            }
+            public void repositoryEvent(RepositoryEvent event) {
+                if (!event.isReplay()) {
+                    Notification notification = new Notification(REPOSITORY_EVENT_TYPE, objectName, sequenceNumber++);
+                    notification.setUserData(new JmxRepositoryEvent(event).asCompositeData());
+                    sendNotification(notification);
+                }
+            }
+        };
+    }
+
+}
\ No newline at end of file
diff --git a/karaf/features/management/src/main/java/org/apache/felix/karaf/features/management/internal/MBeanRegistrer.java b/karaf/features/management/src/main/java/org/apache/felix/karaf/features/management/internal/MBeanRegistrer.java
new file mode 100644
index 0000000..004ad00
--- /dev/null
+++ b/karaf/features/management/src/main/java/org/apache/felix/karaf/features/management/internal/MBeanRegistrer.java
@@ -0,0 +1,72 @@
+/*
+ * 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.felix.karaf.features.management.internal;
+
+import java.util.Map;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.management.MBeanRegistrationException;
+import javax.management.InstanceNotFoundException;
+import javax.management.MalformedObjectNameException;
+import javax.management.NotCompliantMBeanException;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.JMException;
+
+public class MBeanRegistrer {
+
+    private MBeanServer mbeanServer;
+
+    private Map<Object, String> mbeans;
+
+    public void setMbeans(Map<Object, String> mbeans) {
+        this.mbeans = mbeans;
+    }
+
+    public void registerMBeanServer(MBeanServer mbeanServer) throws JMException {
+        if (this.mbeanServer != mbeanServer) {
+            unregisterMBeans();
+        }
+        this.mbeanServer = mbeanServer;
+        registerMBeans();
+    }
+
+    public void unregisterMBeanServer(MBeanServer mbeanServer) throws JMException {
+        unregisterMBeans();
+        this.mbeanServer = null;
+    }
+
+    public void init() throws Exception {
+        registerMBeans();
+    }
+
+    protected void registerMBeans() throws JMException {
+        if (mbeanServer != null && mbeans != null) {
+            for (Map.Entry<Object, String> entry : mbeans.entrySet()) {
+                mbeanServer.registerMBean(entry.getKey(), new ObjectName(entry.getValue()));
+            }
+        }
+    }
+
+    protected void unregisterMBeans() throws JMException {
+        if (mbeanServer != null && mbeans != null) {
+            for (Map.Entry<Object, String> entry : mbeans.entrySet()) {
+                mbeanServer.unregisterMBean(new ObjectName(entry.getValue()));
+            }
+        }
+    }
+}
diff --git a/karaf/features/management/src/main/resources/OSGI-INF/blueprint/features-management.xml b/karaf/features/management/src/main/resources/OSGI-INF/blueprint/features-management.xml
new file mode 100644
index 0000000..98f1041
--- /dev/null
+++ b/karaf/features/management/src/main/resources/OSGI-INF/blueprint/features-management.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
+           xmlns:ext="http://geronimo.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0">
+
+    <reference id="featuresService" interface="org.apache.felix.karaf.features.FeaturesService" />
+
+    <reference id="mbeanServer" interface="javax.management.MBeanServer">
+        <reference-listener ref="mbeanRegister" bind-method="registerMBeanServer" unbind-method="unregisterMBeanServer" />
+    </reference>
+
+    <bean id="mbeanImpl" class="org.apache.felix.karaf.features.management.internal.FeaturesServiceMBeanImpl">
+        <property name="bundleContext" ref="blueprintBundleContext" />
+        <property name="featuresService" ref="featuresService" />
+    </bean>
+
+    <bean id="mbeanRegister" class="org.apache.felix.karaf.features.management.internal.MBeanRegistrer">
+        <property name="mbeans">
+            <map>
+                <entry>
+                    <key>
+                        <bean class="javax.management.StandardMBean">
+                            <argument ref="mbeanImpl" />
+                            <argument value="org.apache.felix.karaf.features.management.FeaturesServiceMBean"/>
+                        </bean>
+                    </key>
+                    <value>org.apache.felix.karaf:service=features</value>
+                </entry>
+            </map>
+        </property>
+    </bean>
+
+</blueprint>
diff --git a/karaf/features/pom.xml b/karaf/features/pom.xml
new file mode 100644
index 0000000..f9f15f1
--- /dev/null
+++ b/karaf/features/pom.xml
@@ -0,0 +1,42 @@
+<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/maven-v4_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.felix.karaf</groupId>
+        <artifactId>karaf</artifactId>
+        <version>1.2.0-SNAPSHOT</version>
+    </parent>
+
+    <groupId>org.apache.felix.karaf.features</groupId>
+    <artifactId>features</artifactId>
+    <packaging>pom</packaging>
+    <version>1.2.0-SNAPSHOT</version>
+    <name>Apache Felix Karaf :: Features</name>
+
+    <modules>
+        <module>core</module>
+        <module>command</module>
+        <module>management</module>
+    </modules>
+
+</project>