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>