You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by gn...@apache.org on 2014/03/05 16:08:23 UTC

[01/10] [KARAF-2805] Clean console and commands model

Repository: karaf
Updated Branches:
  refs/heads/master bd56b49b0 -> 2e2b9324d


http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/support/completers/AggregateCompleter.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/support/completers/AggregateCompleter.java b/shell/core/src/main/java/org/apache/karaf/shell/support/completers/AggregateCompleter.java
new file mode 100644
index 0000000..580f4d2
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/support/completers/AggregateCompleter.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.karaf.shell.support.completers;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+
+
+/**
+ * Completer which contains multiple completers and aggregates them together.
+ */
+public class AggregateCompleter implements Completer
+{
+    private final Collection<Completer> completers;
+
+    public AggregateCompleter(final Collection<Completer> completers) {
+        assert completers != null;
+        this.completers = completers;
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public int complete(final Session session, final CommandLine commandLine, final List candidates) {
+        // buffer could be null
+        assert candidates != null;
+
+        List<Completion> completions = new ArrayList<Completion>(completers.size());
+
+        // Run each completer, saving its completion results
+        int max = -1;
+        for (Completer completer : completers) {
+            Completion completion = new Completion(candidates);
+            completion.complete(session, completer, commandLine);
+
+            // Compute the max cursor position
+            if (completion.cursor > max) {
+                completions.clear();
+                completions.add(completion);
+                max = completion.cursor;
+            } else if (completion.cursor == max) {
+                completions.add(completion);
+            }
+        }
+
+        // Append candidates from completions which have the same cursor position as max
+        for (Completion completion : completions) {
+            // noinspection unchecked
+            candidates.addAll(completion.candidates);
+        }
+
+        return max;
+    }
+
+    private class Completion
+    {
+        public final List<String> candidates;
+
+        public int cursor;
+
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        public Completion(final List candidates) {
+            assert candidates != null;
+
+            // noinspection unchecked
+            this.candidates = new LinkedList<String>(candidates);
+        }
+
+        public void complete(final Session session, final Completer completer, final CommandLine commandLine) {
+            assert completer != null;
+
+            this.cursor = completer.complete(session, commandLine, candidates);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/support/completers/CommandNamesCompleter.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/support/completers/CommandNamesCompleter.java b/shell/core/src/main/java/org/apache/karaf/shell/support/completers/CommandNamesCompleter.java
new file mode 100644
index 0000000..3f41fe1
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/support/completers/CommandNamesCompleter.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.support.completers;
+
+import org.apache.karaf.shell.api.console.Completer;
+
+/**
+ * Marker class.  An instance of this class is published by the console.
+ */
+public abstract class CommandNamesCompleter implements Completer {
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/support/completers/CommandsCompleter.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/support/completers/CommandsCompleter.java b/shell/core/src/main/java/org/apache/karaf/shell/support/completers/CommandsCompleter.java
new file mode 100644
index 0000000..756c276
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/support/completers/CommandsCompleter.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.support.completers;
+
+import org.apache.karaf.shell.api.console.Completer;
+
+/**
+ * Marker class.  An instance of this class is published by the console.
+ */
+public abstract class CommandsCompleter implements Completer {
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/support/completers/FileCompleter.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/support/completers/FileCompleter.java b/shell/core/src/main/java/org/apache/karaf/shell/support/completers/FileCompleter.java
new file mode 100644
index 0000000..20a812f
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/support/completers/FileCompleter.java
@@ -0,0 +1,147 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.shell.support.completers;
+
+import java.io.File;
+import java.util.List;
+
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+
+
+/**
+ * A file name completer takes the buffer and issues a list of
+ * potential completions.
+ * <p/>
+ * This completer tries to behave as similar as possible to
+ * <i>bash</i>'s file name completion (using GNU readline)
+ * with the following exceptions:
+ * <p/>
+ * <ul>
+ * <li>Candidates that are directories will end with "/"</li>
+ * <li>Wildcard regular expressions are not evaluated or replaced</li>
+ * <li>The "~" character can be used to represent the user's home,
+ * but it cannot complete to other users' homes, since java does
+ * not provide any way of determining that easily</li>
+ * </ul>
+ *
+ * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
+ * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
+ * @since 2.3
+ */
+public class FileCompleter implements Completer
+{
+    private static String OS = System.getProperty("os.name").toLowerCase();
+
+    // TODO: Handle files with spaces in them
+
+    private static final boolean OS_IS_WINDOWS = isWindows();
+    
+    public static boolean isWindows() {
+        return (OS.indexOf("win") >= 0);
+
+    }
+
+    public int complete(final Session session, CommandLine commandLine, final List<String> candidates) {
+        // buffer can be null
+        if (candidates == null) {
+            return 0;
+        }
+
+        String buffer = commandLine.getBuffer();
+        if (buffer == null) {
+            buffer = "";
+        }
+
+        if (OS_IS_WINDOWS) {
+            buffer = buffer.replace('/', '\\');
+        }
+
+        String translated = buffer;
+
+        File homeDir = getUserHome();
+
+        // Special character: ~ maps to the user's home directory
+        if (translated.startsWith("~" + separator())) {
+            translated = homeDir.getPath() + translated.substring(1);
+        }
+        else if (translated.startsWith("~")) {
+            translated = homeDir.getParentFile().getAbsolutePath();
+        }
+        else if (!(translated.startsWith(separator()))) {
+            String cwd = getUserDir().getAbsolutePath();
+            translated = cwd + separator() + translated;
+        }
+
+        File file = new File(translated);
+        final File dir;
+
+        if (translated.endsWith(separator())) {
+            dir = file;
+        }
+        else {
+            dir = file.getParentFile();
+        }
+
+        File[] entries = dir == null ? new File[0] : dir.listFiles();
+
+        return matchFiles(buffer, translated, entries, candidates);
+    }
+
+    protected String separator() {
+        return File.separator;
+    }
+
+    protected File getUserHome() {
+        return new File(System.getProperty("user.home"));
+    }
+
+    protected File getUserDir() {
+        return new File(".");
+    }
+
+    protected int matchFiles(final String buffer, final String translated, final File[] files, final List<String> candidates) {
+        if (files == null) {
+            return -1;
+        }
+
+        int matches = 0;
+
+        // first pass: just count the matches
+        for (File file : files) {
+            if (file.getAbsolutePath().startsWith(translated)) {
+                matches++;
+            }
+        }
+        for (File file : files) {
+            if (file.getAbsolutePath().startsWith(translated)) {
+                CharSequence name = file.getName() + (matches == 1 && file.isDirectory() ? separator() : " ");
+                candidates.add(render(file, name).toString());
+            }
+        }
+
+        final int index = buffer.lastIndexOf(separator());
+
+        return index + separator().length();
+    }
+
+    protected CharSequence render(final File file, final CharSequence name) {
+        return name;
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/support/completers/NullCompleter.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/support/completers/NullCompleter.java b/shell/core/src/main/java/org/apache/karaf/shell/support/completers/NullCompleter.java
new file mode 100644
index 0000000..4347fe3
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/support/completers/NullCompleter.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.support.completers;
+
+import java.util.List;
+
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+
+public class NullCompleter implements Completer {
+
+    public static final NullCompleter INSTANCE = new NullCompleter();
+
+    public int complete(final Session session, CommandLine commandLine, List<String> candidates) {
+        return -1;
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/support/completers/StringsCompleter.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/support/completers/StringsCompleter.java b/shell/core/src/main/java/org/apache/karaf/shell/support/completers/StringsCompleter.java
new file mode 100644
index 0000000..1317095
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/support/completers/StringsCompleter.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.support.completers;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+
+
+/**
+ * Completer for a set of strings.
+ */
+public class StringsCompleter
+    implements Completer
+{
+    private final SortedSet<String> strings;
+    private final boolean caseSensitive;
+
+    public StringsCompleter() {
+        this(true);
+    }
+
+    public StringsCompleter(final boolean caseSensitive) {
+        this.strings = new TreeSet<String>(new Comparator<String>() {
+            @Override
+            public int compare(String o1, String o2) {
+                return caseSensitive ? o1.compareTo(o2) : o1.compareToIgnoreCase(o2);
+            }
+        });
+        this.caseSensitive = caseSensitive;
+    }
+
+    public StringsCompleter(final Collection<String> strings) {
+        this();
+        assert strings != null;
+        getStrings().addAll(strings);
+    }
+
+    public StringsCompleter(final String[] strings, boolean caseSensitive) {
+        this(Arrays.asList(strings), caseSensitive);
+    }
+
+    public StringsCompleter(final Collection<String> strings, boolean caseSensitive) {
+        this(caseSensitive);
+        assert strings != null;
+        getStrings().addAll(strings);
+    }
+
+    public StringsCompleter(final String[] strings) {
+        this(Arrays.asList(strings));
+    }
+
+    public SortedSet<String> getStrings() {
+        return strings;
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public int complete(final Session session, final CommandLine commandLine, final List<String> candidates) {
+        // buffer could be null
+        assert candidates != null;
+
+        String buffer = commandLine.getBuffer();
+        if (buffer == null) {
+            buffer = "";
+        }
+        if (!caseSensitive) {
+            buffer = buffer.toLowerCase();
+        }
+
+        // KARAF-421, use getStrings() instead strings field.
+        SortedSet<String> matches = getStrings().tailSet(buffer);
+
+        for (String match : matches) {
+            String s = caseSensitive ? match : match.toLowerCase();
+            if (!s.startsWith(buffer)) {
+                break;
+            }
+
+            // noinspection unchecked
+            candidates.add(match);
+        }
+
+        if (candidates.size() == 1) {
+            // noinspection unchecked
+            candidates.set(0, candidates.get(0) + " ");
+        }
+
+        return candidates.isEmpty() ? -1 : 0;
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/support/converter/DefaultConverter.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/support/converter/DefaultConverter.java b/shell/core/src/main/java/org/apache/karaf/shell/support/converter/DefaultConverter.java
new file mode 100644
index 0000000..cc75c1e
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/support/converter/DefaultConverter.java
@@ -0,0 +1,403 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.support.converter;
+
+import java.io.ByteArrayInputStream;
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Queue;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.regex.Pattern;
+
+public class DefaultConverter
+{
+
+    private Object loader;
+
+    public DefaultConverter(Object loader) {
+        this.loader = loader;
+    }
+
+    public Object convert(Object source, Type target) throws Exception {
+        return convert( source, new GenericType(target));
+    }
+
+    public Object convert(Object fromValue, ReifiedType type) throws Exception {
+        // Discard null values
+        if (fromValue == null) {
+            return null;
+        }
+        // If the object is an instance of the type, just return it
+        if (isAssignable(fromValue, type)) {
+            return fromValue;
+        }
+        Object value = convertWithConverters(fromValue, type);
+        if (value == null) {
+            if (String.class.isAssignableFrom(toClass(type))) {
+                return fromValue.toString();
+            } else if (fromValue instanceof Number && Number.class.isAssignableFrom(unwrap(toClass(type)))) {
+                return convertToNumber((Number) fromValue, toClass(type));
+            } else if (fromValue instanceof String) {
+                return convertFromString((String) fromValue, toClass(type), loader);
+            } else if (toClass(type).isArray() && (fromValue instanceof Collection || fromValue.getClass().isArray())) {
+                return convertToArray(fromValue, type);
+            } else if (Map.class.isAssignableFrom(toClass(type)) && (fromValue instanceof Map || fromValue instanceof Dictionary)) {
+                return convertToMap(fromValue, type);
+            } else if (Dictionary.class.isAssignableFrom(toClass(type)) && (fromValue instanceof Map || fromValue instanceof Dictionary)) {
+                return convertToDictionary(fromValue, type);
+            } else if (Collection.class.isAssignableFrom(toClass(type)) && (fromValue instanceof Collection || fromValue.getClass().isArray())) {
+                return convertToCollection(fromValue, type);
+            } else {
+                throw new Exception("Unable to convert value " + fromValue + " to type " + type);
+            }
+        }
+        return value;
+    }
+
+    private Object convertWithConverters(Object source, ReifiedType type) throws Exception {
+        Object value = null;
+//        for (Converter converter : converters) {
+//            if (converter.canConvert(source, type)) {
+//                value = converter.convert(source, type);
+//                if (value != null) {
+//                    return value;
+//                }
+//            }
+//        }
+        return value;
+    }
+
+    public Object convertToNumber(Number value, Class toType) throws Exception {
+        toType = unwrap(toType);
+        if (AtomicInteger.class == toType) {
+            return new AtomicInteger((Integer) convertToNumber(value, Integer.class));
+        } else if (AtomicLong.class == toType) {
+            return new AtomicLong((Long) convertToNumber(value, Long.class));
+        } else if (Integer.class == toType) {
+            return value.intValue();
+        } else if (Short.class == toType) {
+            return value.shortValue();
+        } else if (Long.class == toType) {
+            return value.longValue();
+        } else if (Float.class == toType) {
+            return value.floatValue();
+        } else if (Double.class == toType) {
+            return value.doubleValue();
+        } else if (Byte.class == toType) {
+            return value.byteValue();
+        } else if (BigInteger.class == toType) {
+            return new BigInteger(value.toString());
+        } else if (BigDecimal.class == toType) {
+            return new BigDecimal(value.toString());
+        } else {
+            throw new Exception("Unable to convert number " + value + " to " + toType);
+        }
+    }
+
+    public Object convertFromString(String value, Class toType, Object loader) throws Exception {
+        toType = unwrap(toType);
+        if (ReifiedType.class == toType) {
+            try {
+                return GenericType.parse(value, loader);
+            } catch (ClassNotFoundException e) {
+                throw new Exception("Unable to convert", e);
+            }
+        } else if (Class.class == toType) {
+            try {
+                return GenericType.parse(value, loader).getRawClass();
+            } catch (ClassNotFoundException e) {
+                throw new Exception("Unable to convert", e);
+            }
+        } else if (Locale.class == toType) {
+            String[] tokens = value.split("_");
+            if (tokens.length == 1) {
+                return new Locale(tokens[0]);
+            } else if (tokens.length == 2) {
+                return new Locale(tokens[0], tokens[1]);
+            } else if (tokens.length == 3) {
+                return new Locale(tokens[0], tokens[1], tokens[2]);
+            } else {
+                throw new Exception("Invalid locale string:" + value);
+            }
+        } else if (Pattern.class == toType) {
+            return Pattern.compile(value);
+        } else if (Properties.class == toType) {
+            Properties props = new Properties();
+            ByteArrayInputStream in = new ByteArrayInputStream(value.getBytes("UTF8"));
+            props.load(in);
+            return props;
+        } else if (Boolean.class == toType) {
+            if ("yes".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value) || "on".equalsIgnoreCase(value)) {
+                return Boolean.TRUE;
+            } else if ("no".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value) || "off".equalsIgnoreCase(value)) {
+                return Boolean.FALSE;
+            } else {
+                throw new RuntimeException("Invalid boolean value: " + value);
+            }
+        } else if (Integer.class == toType) {
+            return Integer.valueOf(value);
+        } else if (Short.class == toType) {
+            return Short.valueOf(value);
+        } else if (Long.class == toType) {
+            return Long.valueOf(value);
+        } else if (Float.class == toType) {
+            return Float.valueOf(value);
+        } else if (Double.class == toType) {
+            return Double.valueOf(value);
+        } else if (Character.class == toType) {
+            if (value.length() == 6 && value.startsWith("\\u")) {
+                int code = Integer.parseInt(value.substring(2), 16);
+                return (char)code;
+            } else if (value.length() == 1) {
+                return value.charAt(0);
+            } else {
+                throw new Exception("Invalid value for character type: " + value);
+            }
+        } else if (Byte.class == toType) {
+            return Byte.valueOf(value);
+        } else if (Enum.class.isAssignableFrom(toType)) {
+            return Enum.valueOf((Class<Enum>) toType, value);
+        } else {
+            return createObject(value, toType);
+        }
+    }
+
+    private static Object createObject(String value, Class type) throws Exception {
+        if (type.isInterface() || Modifier.isAbstract(type.getModifiers())) {
+            throw new Exception("Unable to convert value " + value + " to type " + type + ". Type " + type + " is an interface or an abstract class");
+        }
+        Constructor constructor = null;
+        try {
+            constructor = type.getConstructor(String.class);
+        } catch (NoSuchMethodException e) {
+            throw new RuntimeException("Unable to convert to " + type);
+        }
+        try {
+            return constructor.newInstance(value);
+        } catch (Exception e) {
+            throw new Exception("Unable to convert ", getRealCause(e));
+        }
+    }
+
+    private static Throwable getRealCause(Throwable t) {
+        if (t instanceof InvocationTargetException && t.getCause() != null) {
+            return t.getCause();
+        }
+        return t;
+    }
+
+    private Object convertToCollection(Object obj, ReifiedType type) throws Exception {
+        ReifiedType valueType = type.getActualTypeArgument(0);
+        Collection newCol = (Collection) getCollection(toClass(type)).newInstance();
+        if (obj.getClass().isArray()) {
+            for (int i = 0; i < Array.getLength(obj); i++) {
+                try {
+                    newCol.add(convert(Array.get(obj, i), valueType));
+                } catch (Exception t) {
+                    throw new Exception("Unable to convert from " + obj + " to " + type + "(error converting array element)", t);
+                }
+            }
+        } else {
+            for (Object item : (Collection) obj) {
+                try {
+                    newCol.add(convert(item, valueType));
+                } catch (Exception t) {
+                    throw new Exception("Unable to convert from " + obj + " to " + type + "(error converting collection entry)", t);
+                }
+            }
+        }
+        return newCol;
+    }
+
+    private Object convertToDictionary(Object obj, ReifiedType type) throws Exception {
+        ReifiedType keyType = type.getActualTypeArgument(0);
+        ReifiedType valueType = type.getActualTypeArgument(1);
+        Dictionary newDic = new Hashtable();
+        if (obj instanceof Dictionary) {
+            Dictionary dic = (Dictionary) obj;
+            for (Enumeration keyEnum = dic.keys(); keyEnum.hasMoreElements();) {
+                Object key = keyEnum.nextElement();
+                try {
+                    newDic.put(convert(key, keyType), convert(dic.get(key), valueType));
+                } catch (Exception t) {
+                    throw new Exception("Unable to convert from " + obj + " to " + type + "(error converting map entry)", t);
+                }
+            }
+        } else {
+            for (Map.Entry e : ((Map<Object,Object>) obj).entrySet()) {
+                try {
+                    newDic.put(convert(e.getKey(), keyType), convert(e.getValue(), valueType));
+                } catch (Exception t) {
+                    throw new Exception("Unable to convert from " + obj + " to " + type + "(error converting map entry)", t);
+                }
+            }
+        }
+        return newDic;
+    }
+
+    private Object convertToMap(Object obj, ReifiedType type) throws Exception {
+        ReifiedType keyType = type.getActualTypeArgument(0);
+        ReifiedType valueType = type.getActualTypeArgument(1);
+        Map newMap = (Map) getMap(toClass(type)).newInstance();
+        if (obj instanceof Dictionary) {
+            Dictionary dic = (Dictionary) obj;
+            for (Enumeration keyEnum = dic.keys(); keyEnum.hasMoreElements();) {
+                Object key = keyEnum.nextElement();
+                try {
+                    newMap.put(convert(key, keyType), convert(dic.get(key), valueType));
+                } catch (Exception t) {
+                    throw new Exception("Unable to convert from " + obj + " to " + type + "(error converting map entry)", t);
+                }
+            }
+        } else {
+            for (Map.Entry e : ((Map<Object,Object>) obj).entrySet()) {
+                try {
+                    newMap.put(convert(e.getKey(), keyType), convert(e.getValue(), valueType));
+                } catch (Exception t) {
+                    throw new Exception("Unable to convert from " + obj + " to " + type + "(error converting map entry)", t);
+                }
+            }
+        }
+        return newMap;
+    }
+
+    private Object convertToArray(Object obj, ReifiedType type) throws Exception {
+        if (obj instanceof Collection) {
+            obj = ((Collection) obj).toArray();
+        }
+        if (!obj.getClass().isArray()) {
+            throw new Exception("Unable to convert from " + obj + " to " + type);
+        }
+        ReifiedType componentType;
+        if (type.size() > 0) {
+            componentType = type.getActualTypeArgument(0);
+        } else {
+            componentType = new GenericType(type.getRawClass().getComponentType());
+        }
+        Object array = Array.newInstance(toClass(componentType), Array.getLength(obj));
+        for (int i = 0; i < Array.getLength(obj); i++) {
+            try {
+                Array.set(array, i, convert(Array.get(obj, i), componentType));
+            } catch (Exception t) {
+                throw new Exception("Unable to convert from " + obj + " to " + type + "(error converting array element)", t);
+            }
+        }
+        return array;
+    }
+
+    public static boolean isAssignable(Object source, ReifiedType target) {
+        return source == null
+                || (target.size() == 0
+                    && unwrap(target.getRawClass()).isAssignableFrom(unwrap(source.getClass())));
+    }
+
+    private static Class unwrap(Class c) {
+        Class u = primitives.get(c);
+        return u != null ? u : c;
+    }
+
+    private static Class getMap(Class type) {
+        if (hasDefaultConstructor(type)) {
+            return type;
+        } else if (SortedMap.class.isAssignableFrom(type)) {
+            return TreeMap.class;
+        } else if (ConcurrentMap.class.isAssignableFrom(type)) {
+            return ConcurrentHashMap.class;
+        } else {
+            return LinkedHashMap.class;
+        }
+    }
+
+    private static Class getCollection(Class type) {
+        if (hasDefaultConstructor(type)) {
+            return type;
+        } else if (SortedSet.class.isAssignableFrom(type)) {
+            return TreeSet.class;
+        } else if (Set.class.isAssignableFrom(type)) {
+            return LinkedHashSet.class;
+        } else if (List.class.isAssignableFrom(type)) {
+            return ArrayList.class;
+        } else if (Queue.class.isAssignableFrom(type)) {
+            return LinkedList.class;
+        } else {
+            return ArrayList.class;
+        }
+    }
+
+    private static boolean hasDefaultConstructor(Class type) {
+        if (!Modifier.isPublic(type.getModifiers())) {
+            return false;
+        }
+        if (Modifier.isAbstract(type.getModifiers())) {
+            return false;
+        }
+        Constructor[] constructors = type.getConstructors();
+        for (Constructor constructor : constructors) {
+            if (Modifier.isPublic(constructor.getModifiers()) &&
+                    constructor.getParameterTypes().length == 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static final Map<Class, Class> primitives;
+    static {
+        primitives = new HashMap<Class, Class>();
+        primitives.put(byte.class, Byte.class);
+        primitives.put(short.class, Short.class);
+        primitives.put(char.class, Character.class);
+        primitives.put(int.class, Integer.class);
+        primitives.put(long.class, Long.class);
+        primitives.put(float.class, Float.class);
+        primitives.put(double.class, Double.class);
+        primitives.put(boolean.class, Boolean.class);
+    }
+
+    private Class toClass(ReifiedType type) {
+        return type.getRawClass();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/support/converter/GenericType.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/support/converter/GenericType.java b/shell/core/src/main/java/org/apache/karaf/shell/support/converter/GenericType.java
new file mode 100644
index 0000000..13e3bbe
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/support/converter/GenericType.java
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.support.converter;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.osgi.framework.Bundle;
+
+public class GenericType extends ReifiedType {
+
+	private static final GenericType[] EMPTY = new GenericType[0];
+
+    private static final Map<String, Class> primitiveClasses = new HashMap<String, Class>();
+
+    static {
+        primitiveClasses.put("int", int.class);
+        primitiveClasses.put("short", short.class);
+        primitiveClasses.put("long", long.class);
+        primitiveClasses.put("byte", byte.class);
+        primitiveClasses.put("char", char.class);
+        primitiveClasses.put("float", float.class);
+        primitiveClasses.put("double", double.class);
+        primitiveClasses.put("boolean", boolean.class);
+    }
+
+    private GenericType[] parameters;
+
+	public GenericType(Type type) {
+		this(getConcreteClass(type), parametersOf(type));
+	}
+
+    public GenericType(Class clazz, GenericType... parameters) {
+        super(clazz);
+        this.parameters = parameters;
+    }
+
+    public static GenericType parse(String type, Object loader) throws ClassNotFoundException, IllegalArgumentException {
+        type = type.trim();
+        // Check if this is an array
+        if (type.endsWith("[]")) {
+            GenericType t = parse(type.substring(0, type.length() - 2), loader);
+            return new GenericType(Array.newInstance(t.getRawClass(), 0).getClass(), t);
+        }
+        // Check if this is a generic
+        int genericIndex = type.indexOf('<');
+        if (genericIndex > 0) {
+            if (!type.endsWith(">")) {
+                throw new IllegalArgumentException("Can not load type: " + type);
+            }
+            GenericType base = parse(type.substring(0, genericIndex), loader);
+            String[] params = type.substring(genericIndex + 1, type.length() - 1).split(",");
+            GenericType[] types = new GenericType[params.length];
+            for (int i = 0; i < params.length; i++) {
+                types[i] = parse(params[i], loader);
+            }
+            return new GenericType(base.getRawClass(), types);
+        }
+        // Primitive
+        if (primitiveClasses.containsKey(type)) {
+            return new GenericType(primitiveClasses.get(type));
+        }
+        // Class
+        if (loader instanceof ClassLoader) {
+            return new GenericType(((ClassLoader) loader).loadClass(type));
+        } else if (loader instanceof Bundle) {
+            return new GenericType(((Bundle) loader).loadClass(type));
+        } else {
+            throw new IllegalArgumentException("Unsupported loader: " + loader);
+        }
+    }
+
+    @Override
+    public ReifiedType getActualTypeArgument(int i) {
+        if (parameters.length == 0) {
+            return super.getActualTypeArgument(i);
+        }
+        return parameters[i];
+    }
+
+    @Override
+    public int size() {
+        return parameters.length;
+    }
+
+    @Override
+    public String toString() {
+        Class cl = getRawClass();
+        if (cl.isArray()) {
+            if (parameters.length > 0) {
+                return parameters[0].toString() + "[]";
+            } else {
+                return cl.getComponentType().getName() + "[]";
+            }
+        }
+        if (parameters.length > 0) {
+            StringBuilder sb = new StringBuilder();
+            sb.append(cl.getName());
+            sb.append("<");
+            for (int i = 0; i < parameters.length; i++) {
+                if (i > 0) {
+                    sb.append(",");
+                }
+                sb.append(parameters[i].toString());
+            }
+            sb.append(">");
+            return sb.toString();
+        }
+        return cl.getName();
+    }
+
+    static GenericType[] parametersOf(Type type ) {
+		if ( type instanceof Class ) {
+		    Class clazz = (Class) type;
+		    if (clazz.isArray()) {
+                GenericType t = new GenericType(clazz.getComponentType());
+                if (t.size() > 0) {
+		            return new GenericType[] { t };
+                } else {
+                    return EMPTY;
+                }
+		    } else {
+		        return EMPTY;
+		    }
+		}
+        if ( type instanceof ParameterizedType ) {
+            ParameterizedType pt = (ParameterizedType) type;
+            Type [] parameters = pt.getActualTypeArguments();
+            GenericType[] gts = new GenericType[parameters.length];
+            for ( int i =0; i<gts.length; i++) {
+                gts[i] = new GenericType(parameters[i]);
+            }
+            return gts;
+        }
+        if ( type instanceof GenericArrayType ) {
+            return new GenericType[] { new GenericType(((GenericArrayType) type).getGenericComponentType()) };
+        }
+        throw new IllegalStateException();
+	}
+
+	static Class<?> getConcreteClass(Type type) {
+		Type ntype = collapse(type);
+		if ( ntype instanceof Class )
+			return (Class<?>) ntype;
+
+		if ( ntype instanceof ParameterizedType )
+			return getConcreteClass(collapse(((ParameterizedType)ntype).getRawType()));
+
+		throw new RuntimeException("Unknown type " + type );
+	}
+
+	static Type collapse(Type target) {
+		if (target instanceof Class || target instanceof ParameterizedType ) {
+			return target;
+		} else if (target instanceof TypeVariable) {
+			return collapse(((TypeVariable<?>) target).getBounds()[0]);
+		} else if (target instanceof GenericArrayType) {
+			Type t = collapse(((GenericArrayType) target)
+					.getGenericComponentType());
+			while ( t instanceof ParameterizedType )
+				t = collapse(((ParameterizedType)t).getRawType());
+			return Array.newInstance((Class<?>)t, 0).getClass();
+		} else if (target instanceof WildcardType) {
+			WildcardType wct = (WildcardType) target;
+			if (wct.getLowerBounds().length == 0)
+				return collapse(wct.getUpperBounds()[0]);
+			else
+				return collapse(wct.getLowerBounds()[0]);
+		}
+		throw new RuntimeException("Huh? " + target);
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/support/converter/ReifiedType.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/support/converter/ReifiedType.java b/shell/core/src/main/java/org/apache/karaf/shell/support/converter/ReifiedType.java
new file mode 100644
index 0000000..6f89857
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/support/converter/ReifiedType.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) OSGi Alliance (2008, 2009). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.shell.support.converter;
+
+/**
+ * Provides access to a concrete type and its optional generic type arguments.
+ *
+ * Java 5 and later support generic types. These types consist of a raw class
+ * with type arguments. This class models such a <code>Type</code> class but
+ * ensures that the type is <em>reified</em>. Reification means that the Type
+ * graph associated with a Java 5 <code>Type</code> instance is traversed
+ * until the type becomes a concrete class. In Java 1.4 a class has no
+ * arguments. This concrete class implements the Reified Type for Java 1.4.
+ *
+ * In Java 1.4, this class works with non-generic types. In that cases, a
+ * Reified Type provides access to the class and has zero type arguments, though
+ * a subclass that provide type arguments should be respected. Blueprint
+ * extender implementations can subclass this class and provide access to the
+ * generics type graph if used in a conversion. Such a subclass must
+ * <em>reify<em> the different Java 5 <code>Type</code> instances into the
+ * reified form. That is, a form where the raw Class is available with its optional type arguments as Reified Types.
+ *
+ * @Immutable
+ */
+public class ReifiedType {
+	final static ReifiedType ALL = new ReifiedType(Object.class);
+
+	private final Class clazz;
+
+	/**
+	 * Create a Reified Type for a raw Java class without any generic arguments.
+	 * Subclasses can provide the optional generic argument information. Without
+	 * subclassing, this instance has no type arguments.
+	 *
+	 * @param clazz
+	 *            The raw class of the Reified Type.
+	 */
+	public ReifiedType(Class clazz) {
+		this.clazz = clazz;
+	}
+
+	/**
+	 * Access to the raw class.
+	 *
+	 * The raw class represents the concrete class that is associated with a
+	 * type declaration. This class could have been deduced from the generics
+	 * type graph of the declaration. For example, in the following example:
+	 *
+	 * <pre>
+	 * Map&lt;String, Object&gt; map;
+	 * </pre>
+	 *
+	 * The raw class is the Map class.
+	 *
+	 * @return the collapsed raw class that represents this type.
+	 */
+	public Class<?> getRawClass() {
+		return clazz;
+	}
+
+	/**
+	 * Access to a type argument.
+	 *
+	 * The type argument refers to a argument in a generic type declaration
+	 * given by index <code>i</code>. This method returns a Reified Type that
+	 * has Object as class when no generic type information is available. Any
+	 * object is assignable to Object and therefore no conversion is then
+	 * necessary, this is compatible with older Javas than 5. For this reason,
+	 * the implementation in this class always returns the
+	 * <code>Object<code> class, regardless of the given index.
+	 *
+	 * This method should be overridden by a subclass that provides access to
+	 * the generic information.
+	 *
+	 * For example, in the following example:
+	 *
+	 * <pre>
+	 * Map&lt;String, Object&gt; map;
+	 * </pre>
+	 *
+	 * The type argument 0 is <code>String</code>, and type argument 1 is
+	 * <code>Object</code>.
+	 *
+	 * @param i
+	 *            The index of the type argument
+	 * @return <code>ReifiedType(Object.class)<code>, subclasses must override this and return the generic argument at index <code>i</code>
+	 */
+	public ReifiedType getActualTypeArgument(int i) {
+		return ALL;
+	}
+
+	/**
+	 * Return the number of type arguments.
+	 *
+	 * This method should be overridden by a subclass to support Java 5 types.
+	 *
+	 * @return 0, subclasses must override this and return the number of generic
+	 *         arguments
+	 */
+	public int size() {
+		return 0;
+	}
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/support/table/AnsiColumn.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/support/table/AnsiColumn.java b/shell/core/src/main/java/org/apache/karaf/shell/support/table/AnsiColumn.java
new file mode 100644
index 0000000..4d5b0e7
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/support/table/AnsiColumn.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.karaf.shell.support.table;
+
+import org.fusesource.jansi.Ansi;
+
+/**
+ * Colored support for column.
+ */
+public class AnsiColumn extends Col {
+
+    private Ansi.Color color;
+    private boolean bold;
+
+    public AnsiColumn(String header, Ansi.Color color, boolean bold) {
+        super(header);
+        this.color = color;
+        this.bold = bold;
+    }
+
+    @Override
+    public String getContent(String content) {
+        String in = super.getContent(content);
+
+        Ansi ansi = Ansi.ansi();
+        ansi.fg(color);
+
+        if (bold)
+            ansi.a(Ansi.Attribute.INTENSITY_BOLD);
+
+        ansi.a(in);
+
+        if (bold)
+            ansi.a(Ansi.Attribute.INTENSITY_BOLD_OFF);
+
+        ansi.fg(Ansi.Color.DEFAULT);
+
+        return ansi.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/support/table/Col.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/support/table/Col.java b/shell/core/src/main/java/org/apache/karaf/shell/support/table/Col.java
new file mode 100644
index 0000000..e3cc51d
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/support/table/Col.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.karaf.shell.support.table;
+
+/**
+ * Column definition.
+ */
+public class Col {
+
+    /**
+     * Column header.
+     */
+    private String header;
+
+    /**
+     * Maximum size of this column. The default -1 means the column
+     * may grow indefinitely
+     */
+    int maxSize = -1;
+    
+    int size = 0;
+    
+    /**
+     * Alignment
+     */
+    private HAlign align = HAlign.left;
+
+    public Col(String header) {
+        this.header = header;
+    }
+
+    public Col align(HAlign align) {
+        this.align = align;
+        return this;
+    }
+
+    public Col alignLeft() {
+        this.align = HAlign.left;
+        return this;
+    }
+
+    public Col alignRight() {
+        this.align = HAlign.right;
+        return this;
+    }
+
+    public Col alignCenter() {
+        this.align = HAlign.center;
+        return this;
+    }
+    
+    public Col maxSize(int maxSize) {
+        this.maxSize = maxSize;
+        return this;
+    }
+
+    public int getSize() {
+        return size;
+    }
+    
+    protected void updateSize(int cellSize) {
+        if (this.size <= cellSize) {
+            this.size = getClippedSize(cellSize);
+        }
+    }
+    
+    private int getClippedSize(int cellSize) {
+        return this.maxSize == -1 ? cellSize : Math.min(cellSize, this.maxSize);
+    }
+
+    String format(Object cellData) {
+        if (cellData == null) {
+            cellData = "";
+        }
+        String fullContent = String.format("%s", cellData);
+        if (fullContent.length() == 0) {
+            return "";
+        }
+        String finalContent = fullContent.substring(0, getClippedSize(fullContent.length()));
+        updateSize(finalContent.length());
+        return finalContent;
+    }
+
+    String getHeader() {
+        return header;
+    }
+
+    String getContent(String content) {
+        return this.align.position(cut(content, this.size), this.size);
+    }
+
+    private String cut(String content, int size) {
+        if (content.length() <= size) {
+            return content;
+        } else {
+            return content.substring(0, Math.max(0, size - 1));
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/support/table/HAlign.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/support/table/HAlign.java b/shell/core/src/main/java/org/apache/karaf/shell/support/table/HAlign.java
new file mode 100644
index 0000000..9869a2c
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/support/table/HAlign.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.karaf.shell.support.table;
+
+import static org.apache.karaf.shell.support.table.StringUtil.length;
+import static org.apache.karaf.shell.support.table.StringUtil.repeat;
+
+/**
+ * Enumeration type which contains all possible horizontal alignments.
+ */
+public enum HAlign {
+
+    /**
+     * Center align.
+     */
+    center {
+        @Override
+        public String position(String text, int colWidth) {
+            int width = colWidth - length(text);
+            text = repeat(" ", width / 2) + text + repeat(" ", width / 2);
+            if (length(text) < colWidth) {
+                // if colWidth is odd we add space at the end.
+                text += " ";
+            }
+            return text;
+        }
+    },
+
+    /**
+     * Left align.
+     */
+    left {
+        @Override
+        public String position(String text, int colWidth) {
+            return text + repeat(" ", colWidth - length(text));
+        }
+    },
+
+    /**
+     * Right align.
+     */
+    right {
+        @Override
+        public String position(String text, int colWidth) {
+            return repeat(" ", colWidth - length(text)) + text;
+        }
+    };
+
+    /**
+     * Calculate text position.
+     * 
+     * @param text Text 
+     * @param colWidth
+     * @return
+     */
+    public abstract String position(String text, int colWidth);
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/support/table/Row.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/support/table/Row.java b/shell/core/src/main/java/org/apache/karaf/shell/support/table/Row.java
new file mode 100644
index 0000000..771eb25
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/support/table/Row.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.karaf.shell.support.table;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class Row {
+    private List<Object> data;
+    private List<String> content;
+    
+    Row() {
+        data = new ArrayList<Object>();
+        content = new ArrayList<String>();
+    }
+    
+    Row(List<Col> cols) {
+        this();
+        for (Col col : cols) {
+            data.add(col.getHeader());
+        }
+    }
+
+    public void addContent(Object ... cellDataAr) {
+        data.addAll(Arrays.asList(cellDataAr));
+    }
+    
+    void formatContent(List<Col> cols) {
+        content.clear();
+        int c = 0;
+        for (Col col : cols) {
+            content.add(col.format(data.get(c)));
+            c++;
+        }
+    }
+    
+    String getContent(List<Col> cols, String separator) {
+        StringBuilder st = new StringBuilder();
+        int c = 0;
+        if (cols.size() != content.size()) {
+            throw new RuntimeException("Number of columns and number of content elements do not match");
+        }
+
+        for (Col col : cols) {
+            st.append(col.getContent(content.get(c)));
+            if (c + 1 < cols.size()) {
+                st.append(separator);
+            }
+            c++;
+        }
+
+        return st.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/support/table/ShellTable.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/support/table/ShellTable.java b/shell/core/src/main/java/org/apache/karaf/shell/support/table/ShellTable.java
new file mode 100644
index 0000000..257014f
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/support/table/ShellTable.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.shell.support.table;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class ShellTable {
+
+    private List<Col> cols = new ArrayList<Col>();
+    private List<Row> rows = new ArrayList<Row>();
+    boolean showHeaders = true;
+    private String separator = " | ";
+    private int size;
+    private String emptyTableText;
+
+    public ShellTable() {
+
+    }
+
+    public ShellTable noHeaders() {
+        this.showHeaders = false;
+        return this;
+    }
+
+    public ShellTable separator(String separator) {
+        this.separator = separator;
+        return this;
+    }
+
+    public ShellTable size(int size) {
+        this.size = size;
+        return this;
+    }
+
+    public ShellTable column(Col colunmn) {
+        cols.add(colunmn);
+        return this;
+    }
+
+    public Col column(String header) {
+        Col col = new Col(header);
+        cols.add(col);
+        return col;
+    }
+
+    public Row addRow() {
+        Row row = new Row();
+        rows.add(row);
+        return row;
+    }
+
+    /**
+     * Set text to display if there are no rows in the table
+     * @param text
+     * @return
+     */
+    public ShellTable emptyTableText(String text) {
+        this.emptyTableText = text;
+        return this;
+    }
+
+    public void print(PrintStream out) {
+        print(out, true);
+    }
+
+    public void print(PrintStream out, boolean format)  {
+
+        // "normal" table rendering, with borders
+        Row headerRow = new Row(cols);
+        headerRow.formatContent(cols);
+        for (Row row : rows) {
+            row.formatContent(cols);
+        }
+
+        if (size > 0) {
+            tryGrowToMaxSize();
+        }
+
+        if (format && showHeaders) {
+            String headerLine = headerRow.getContent(cols, separator);
+            out.println(headerLine);
+            for (Col col : cols) {
+                out.print(underline(col.getSize()));
+            }
+            out.println(underline((cols.size() - 1) * 3));
+        }
+
+        for (Row row : rows) {
+            if (!format) {
+                if (separator == null || separator.equals(" | "))
+                    out.println(row.getContent(cols, "\t"));
+                else out.println(row.getContent(cols, separator));
+            } else {
+                out.println(row.getContent(cols, separator));
+            }
+        }
+
+        if (format && rows.size() == 0 && emptyTableText != null) {
+            out.println(emptyTableText);
+        }
+    }
+
+    private void tryGrowToMaxSize() {
+        int currentSize = 0;
+        for (Col col : cols) {
+            currentSize += col.size + separator.length();
+        }
+        currentSize -= separator.length();
+        int sizeToGrow = size - currentSize;
+
+        for (Col col : cols) {
+            if (col.maxSize == -1) {
+                col.size = Math.max(0, col.size + sizeToGrow);
+                return;
+            }
+        }
+
+    }
+
+    private String underline(int length) {
+        char[] exmarks = new char[length];
+        Arrays.fill(exmarks, '-');
+        return new String(exmarks);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/support/table/StringUtil.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/support/table/StringUtil.java b/shell/core/src/main/java/org/apache/karaf/shell/support/table/StringUtil.java
new file mode 100644
index 0000000..b1f3ab5
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/support/table/StringUtil.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.karaf.shell.support.table;
+
+class StringUtil {
+
+    /**
+     * Returns length of the string.
+     * 
+     * @param string String.
+     * @return Length.
+     */
+    public static int length(String string) {
+        return string == null ? 0 : string.length();
+    }
+
+    /**
+     * Utility method to repeat string.
+     * 
+     * @param string String to repeat.
+     * @param times Number of times.
+     * @return Repeat string.
+     */
+    public static String repeat(String string, int times) {
+        if (times <= 0) {
+            return "";
+        }
+        else if (times % 2 == 0) {
+            return repeat(string+string, times/2);
+        }
+        else {
+           return string + repeat(string+string, times/2);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/resources/OSGI-INF/bundle.info
----------------------------------------------------------------------
diff --git a/shell/core/src/main/resources/OSGI-INF/bundle.info b/shell/core/src/main/resources/OSGI-INF/bundle.info
new file mode 100644
index 0000000..4d4296e
--- /dev/null
+++ b/shell/core/src/main/resources/OSGI-INF/bundle.info
@@ -0,0 +1,16 @@
+h1. Synopsis
+
+${project.name}
+
+${project.description}
+
+Maven URL:
+[mvn:${project.groupId}/${project.artifactId}/${project.version}]
+
+h1. Description
+
+This bundle provides the integration of Apache Felix Gogo, shell, and console.
+
+It provides the default Karaf branding including the default welcome message.
+
+h1. See also

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/pom.xml
----------------------------------------------------------------------
diff --git a/shell/pom.xml b/shell/pom.xml
index d8dc873..1ab6361 100644
--- a/shell/pom.xml
+++ b/shell/pom.xml
@@ -35,6 +35,7 @@
 
     <modules>
         <module>commands</module>
+        <module>core</module>
         <module>console</module>
         <module>ssh</module>
         <module>help</module>


[10/10] git commit: [KARAF-2805] Migrate shell, ssh, wrapper, kar, instance, features and bundle to the new API and switch the bin/shell script to use the new console

Posted by gn...@apache.org.
[KARAF-2805] Migrate shell, ssh, wrapper, kar, instance, features and bundle to the new API and switch the bin/shell script to use the new console

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

Branch: refs/heads/master
Commit: 2e2b9324d4a65076b119239edc8920185914dc60
Parents: 2e3c3ef
Author: Guillaume Nodet <gn...@gmail.com>
Authored: Wed Mar 5 15:42:44 2014 +0100
Committer: Guillaume Nodet <gn...@gmail.com>
Committed: Wed Mar 5 15:53:17 2014 +0100

----------------------------------------------------------------------
 .../main/filtered-resources/resources/bin/shell |   4 +-
 .../filtered-resources/resources/bin/shell.bat  |   4 +-
 bundle/command/pom.xml                          |   3 +-
 .../karaf/bundle/command/BundleCommand.java     |  16 +-
 .../command/BundleCommandWithConfirmation.java  |   4 +-
 .../karaf/bundle/command/BundlesCommand.java    |  23 +-
 .../command/BundlesCommandWithConfirmation.java |   4 +-
 .../karaf/bundle/command/Capabilities.java      |   8 +-
 .../apache/karaf/bundle/command/Classes.java    |  12 +-
 .../org/apache/karaf/bundle/command/Diag.java   |   6 +-
 .../karaf/bundle/command/DynamicImport.java     |   5 +-
 .../apache/karaf/bundle/command/FindClass.java  |  29 +-
 .../apache/karaf/bundle/command/Headers.java    |  18 +-
 .../org/apache/karaf/bundle/command/Info.java   |   6 +-
 .../apache/karaf/bundle/command/Install.java    |  24 +-
 .../bundle/command/ListBundleServices.java      |  21 +-
 .../karaf/bundle/command/ListBundles.java       |  23 +-
 .../apache/karaf/bundle/command/LoadTest.java   |  29 +-
 .../apache/karaf/bundle/command/Refresh.java    |   6 +-
 .../karaf/bundle/command/Requirements.java      |   8 +-
 .../apache/karaf/bundle/command/Resolve.java    |   6 +-
 .../apache/karaf/bundle/command/Restart.java    |   6 +-
 .../karaf/bundle/command/ShowBundleTree.java    |   8 +-
 .../org/apache/karaf/bundle/command/Start.java  |   4 +-
 .../apache/karaf/bundle/command/StartLevel.java |  15 +-
 .../org/apache/karaf/bundle/command/Stop.java   |   6 +-
 .../apache/karaf/bundle/command/Uninstall.java  |   4 +-
 .../org/apache/karaf/bundle/command/Update.java |   6 +-
 .../org/apache/karaf/bundle/command/Watch.java  |  17 +-
 .../OSGI-INF/blueprint/shell-bundles.xml        |  25 --
 .../karaf/bundle/command/ListServicesTest.java  |   6 +-
 features/command/pom.xml                        |  14 +-
 .../command/FeaturesCommandSupport.java         |  11 +-
 .../features/command/InfoFeatureCommand.java    |  16 +-
 .../features/command/InstallFeatureCommand.java |  14 +-
 .../command/ListFeatureVersionsCommand.java     |  12 +-
 .../features/command/ListFeaturesCommand.java   |   6 +-
 .../karaf/features/command/RepoAddCommand.java  |  12 +-
 .../karaf/features/command/RepoListCommand.java |   8 +-
 .../features/command/RepoRefreshCommand.java    |   8 +-
 .../features/command/RepoRemoveCommand.java     |  14 +-
 .../command/UninstallFeatureCommand.java        |  18 +-
 .../command/completers/AllFeatureCompleter.java |   2 +-
 .../completers/AvailableFeatureCompleter.java   |   2 +-
 .../completers/AvailableRepoNameCompleter.java  |  15 +-
 .../completers/FeatureCompleterSupport.java     |  12 +-
 .../completers/InstalledFeatureCompleter.java   |   2 +-
 .../completers/InstalledRepoNameCompleter.java  |  14 +-
 .../completers/InstalledRepoUriCompleter.java   |  14 +-
 .../OSGI-INF/blueprint/features-command.xml     |  27 --
 instance/command/pom.xml                        |   6 +-
 .../instance/command/ChangeOptsCommand.java     |  10 +-
 .../command/ChangeRmiRegistryPortCommand.java   |  10 +-
 .../command/ChangeRmiServerPortCommand.java     |  10 +-
 .../instance/command/ChangeSshPortCommand.java  |  10 +-
 .../karaf/instance/command/CloneCommand.java    |  12 +-
 .../karaf/instance/command/ConnectCommand.java  |  17 +-
 .../karaf/instance/command/CreateCommand.java   |  14 +-
 .../karaf/instance/command/DestroyCommand.java  |  10 +-
 .../command/InstanceCommandSupport.java         |  12 +-
 .../karaf/instance/command/ListCommand.java     |   6 +-
 .../karaf/instance/command/RenameCommand.java   |  12 +-
 .../karaf/instance/command/StartCommand.java    |  12 +-
 .../karaf/instance/command/StatusCommand.java   |  11 +-
 .../karaf/instance/command/StopCommand.java     |  10 +-
 .../command/completers/InstanceCompleter.java   |  14 +-
 .../org/apache/karaf/instance/main/Execute.java |  21 +-
 .../services/org/apache/karaf/shell/commands    |  30 +++
 .../OSGI-INF/blueprint/instance-command.xml     |  26 --
 instance/core/pom.xml                           |   2 +-
 .../core/internal/InstanceServiceImpl.java      |   7 +
 kar/command/pom.xml                             |   3 +-
 .../karaf/kar/command/CreateKarCommand.java     |  25 +-
 .../karaf/kar/command/InstallKarCommand.java    |  22 +-
 .../karaf/kar/command/KarCommandSupport.java    |  38 ---
 .../karaf/kar/command/ListKarCommand.java       |  21 +-
 .../karaf/kar/command/UninstallKarCommand.java  |  23 +-
 .../kar/command/completers/KarCompleter.java    |  26 +-
 .../OSGI-INF/blueprint/kar-command.xml          |  23 --
 shell/commands/pom.xml                          |   5 +-
 .../karaf/shell/commands/InfoProvider.java      |  30 +++
 .../karaf/shell/commands/impl/AliasAction.java  |  18 +-
 .../karaf/shell/commands/impl/CatAction.java    |  19 +-
 .../karaf/shell/commands/impl/ClearAction.java  |  15 +-
 .../shell/commands/impl/CompletionAction.java   |  36 +--
 .../karaf/shell/commands/impl/DateAction.java   |  15 +-
 .../karaf/shell/commands/impl/EachAction.java   |  19 +-
 .../karaf/shell/commands/impl/EchoAction.java   |  15 +-
 .../karaf/shell/commands/impl/EditAction.java   |  40 ++-
 .../shell/commands/impl/ExecuteAction.java      |  17 +-
 .../karaf/shell/commands/impl/GrepAction.java   |  16 +-
 .../karaf/shell/commands/impl/HeadAction.java   |  19 +-
 .../shell/commands/impl/HistoryAction.java      |  30 ++-
 .../karaf/shell/commands/impl/IfAction.java     |  19 +-
 .../karaf/shell/commands/impl/InfoAction.java   |  45 +++-
 .../karaf/shell/commands/impl/JavaAction.java   |  19 +-
 .../karaf/shell/commands/impl/LogoutAction.java |  24 +-
 .../karaf/shell/commands/impl/MoreAction.java   |  56 ++--
 .../karaf/shell/commands/impl/NewAction.java    |  20 +-
 .../commands/impl/PrintStackTracesAction.java   |  21 +-
 .../karaf/shell/commands/impl/PrintfAction.java |  14 +-
 .../karaf/shell/commands/impl/SleepAction.java  |  19 +-
 .../karaf/shell/commands/impl/SortAction.java   |  20 +-
 .../karaf/shell/commands/impl/SourceAction.java |  24 +-
 .../karaf/shell/commands/impl/TacAction.java    |  18 +-
 .../karaf/shell/commands/impl/TailAction.java   |  20 +-
 .../shell/commands/impl/ThreadsAction.java      |  15 +-
 .../karaf/shell/commands/impl/WatchAction.java  |  64 ++---
 .../karaf/shell/commands/impl/WcAction.java     |  15 +-
 .../services/org/apache/karaf/shell/commands    |  10 +-
 .../OSGI-INF/blueprint/shell-commands.xml       |   4 +-
 .../karaf/shell/commands/impl/GrepTest.java     |   5 +-
 .../karaf/shell/commands/impl/ThreadsTest.java  |   8 +-
 shell/ssh/pom.xml                               |  14 +-
 .../org/apache/karaf/shell/ssh/Activator.java   | 268 +++++++++++++++++++
 .../apache/karaf/shell/ssh/ActivatorNoOsgi.java |  33 +++
 .../karaf/shell/ssh/KarafAgentFactory.java      |  18 +-
 .../karaf/shell/ssh/KarafJaasAuthenticator.java |   7 +
 .../apache/karaf/shell/ssh/ShellCommand.java    |  22 +-
 .../karaf/shell/ssh/ShellCommandFactory.java    |  10 +-
 .../karaf/shell/ssh/ShellFactoryImpl.java       |  39 +--
 .../org/apache/karaf/shell/ssh/SshAction.java   |  50 ++--
 .../apache/karaf/shell/ssh/SshServerAction.java |  33 +--
 .../karaf/shell/ssh/SshServerFactory.java       |  77 ------
 .../org/apache/karaf/shell/ssh/SshTerminal.java |  30 ++-
 .../shell/ssh/UserAuthFactoriesFactory.java     |  33 +--
 .../shell/ssh/util/SingleServiceTracker.java    | 171 ++++++++++++
 .../services/org/apache/karaf/shell/commands    |  17 ++
 .../resources/OSGI-INF/blueprint/shell-ssh.xml  | 149 -----------
 wrapper/command/pom.xml                         |  12 +-
 .../apache/karaf/wrapper/commands/Install.java  |  38 ++-
 .../services/org/apache/karaf/shell/commands    |   1 +
 .../OSGI-INF/blueprint/wrapper-commands.xml     |  26 --
 wrapper/core/pom.xml                            |   2 +-
 134 files changed, 1568 insertions(+), 1236 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/assemblies/features/framework/src/main/filtered-resources/resources/bin/shell
----------------------------------------------------------------------
diff --git a/assemblies/features/framework/src/main/filtered-resources/resources/bin/shell b/assemblies/features/framework/src/main/filtered-resources/resources/bin/shell
index 7f66eaa..bb62d23 100755
--- a/assemblies/features/framework/src/main/filtered-resources/resources/bin/shell
+++ b/assemblies/features/framework/src/main/filtered-resources/resources/bin/shell
@@ -272,7 +272,7 @@ setupDefaults() {
     fi
 
     # Setup classpath
-    CLASSPATH="$KARAF_HOME/system/org/apache/karaf/shell/org.apache.karaf.shell.console/${project.version}/org.apache.karaf.shell.console-${project.version}.jar"
+    CLASSPATH="$KARAF_HOME/system/org/apache/karaf/shell/org.apache.karaf.shell.core/${project.version}/org.apache.karaf.shell.core-${project.version}.jar"
     CLASSPATH="$CLASSPATH:$KARAF_HOME/system/org/ops4j/pax/logging/pax-logging-api/${pax.logging.version}/pax-logging-api-${pax.logging.version}.jar"
     CLASSPATH="$CLASSPATH:$KARAF_HOME/system/jline/jline/${jline.version}/jline-${jline.version}.jar"
 
@@ -331,7 +331,7 @@ run() {
         CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
     fi
 
-    exec "$JAVA" $JAVA_OPTS -Dkaraf.instances="${KARAF_HOME}/instances" -Dkaraf.home="$KARAF_HOME" -Dkaraf.base="$KARAF_BASE" -Dkaraf.etc="$KARAF_ETC" -Djava.io.tmpdir="$KARAF_DATA/tmp" -Djava.util.logging.config.file="$KARAF_BASE/etc/java.util.logging.properties" $KARAF_OPTS $OPTS -classpath "$CLASSPATH" org.apache.karaf.shell.console.impl.Main --classpath="$KARAF_HOME/system" "$@"
+    exec "$JAVA" $JAVA_OPTS -Dkaraf.instances="${KARAF_HOME}/instances" -Dkaraf.home="$KARAF_HOME" -Dkaraf.base="$KARAF_BASE" -Dkaraf.etc="$KARAF_ETC" -Djava.io.tmpdir="$KARAF_DATA/tmp" -Djava.util.logging.config.file="$KARAF_BASE/etc/java.util.logging.properties" $KARAF_OPTS $OPTS -classpath "$CLASSPATH" org.apache.karaf.shell.impl.console.standalone.Main --classpath="$KARAF_HOME/system" "$@"
 }
 
 main() {

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/assemblies/features/framework/src/main/filtered-resources/resources/bin/shell.bat
----------------------------------------------------------------------
diff --git a/assemblies/features/framework/src/main/filtered-resources/resources/bin/shell.bat b/assemblies/features/framework/src/main/filtered-resources/resources/bin/shell.bat
index c4734f8..69b7a72 100644
--- a/assemblies/features/framework/src/main/filtered-resources/resources/bin/shell.bat
+++ b/assemblies/features/framework/src/main/filtered-resources/resources/bin/shell.bat
@@ -121,7 +121,7 @@ if "%KARAF_DEBUG%" == "" goto :KARAF_DEBUG_END
     call :warn Enabling Java debug options: %JAVA_DEBUG_OPTS%
 :KARAF_DEBUG_END
 
-set CLASSPATH=%KARAF_HOME%\system\org\apache\karaf\shell\org.apache.karaf.shell.console\${project.version}\org.apache.karaf.shell.console-${project.version}.jar
+set CLASSPATH=%KARAF_HOME%\system\org\apache\karaf\shell\org.apache.karaf.shell.core\${project.version}\org.apache.karaf.shell.core-${project.version}.jar
 set CLASSPATH=%CLASSPATH%;%KARAF_HOME%\system\org\ops4j\pax\logging\pax-logging-api\${pax.logging.version}\pax-logging-api-${pax.logging.version}.jar
 set CLASSPATH=%CLASSPATH%;%KARAF_HOME%\system\jline\jline\${jline.version}\jline-${jline.version}.jar
 
@@ -129,7 +129,7 @@ set CLASSPATH=%CLASSPATH%;%KARAF_HOME%\system\jline\jline\${jline.version}\jline
     if "%SHIFT%" == "true" SET ARGS=%2 %3 %4 %5 %6 %7 %8
     if not "%SHIFT%" == "true" SET ARGS=%1 %2 %3 %4 %5 %6 %7 %8
     rem Execute the Java Virtual Machine
-    "%JAVA%" %JAVA_OPTS% %OPTS% -classpath "%CLASSPATH%" -Dkaraf.instances="%KARAF_HOME%\instances" -Dkaraf.home="%KARAF_HOME%" -Dkaraf.base="%KARAF_BASE%" -Dkaraf.etc="%KARAF_ETC%" -Djava.io.tmpdir="%KARAF_DATA%\tmp" -Djava.util.logging.config.file="%KARAF_BASE%\etc\java.util.logging.properties" %KARAF_OPTS% org.apache.karaf.shell.console.impl.Main --classpath="%KARAF_HOME%\system" %ARGS%
+    "%JAVA%" %JAVA_OPTS% %OPTS% -classpath "%CLASSPATH%" -Dkaraf.instances="%KARAF_HOME%\instances" -Dkaraf.home="%KARAF_HOME%" -Dkaraf.base="%KARAF_BASE%" -Dkaraf.etc="%KARAF_ETC%" -Djava.io.tmpdir="%KARAF_DATA%\tmp" -Djava.util.logging.config.file="%KARAF_BASE%\etc\java.util.logging.properties" %KARAF_OPTS% org.apache.karaf.shell.impl.console.standalone.Main --classpath="%KARAF_HOME%\system" %ARGS%
 
 rem # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/pom.xml
----------------------------------------------------------------------
diff --git a/bundle/command/pom.xml b/bundle/command/pom.xml
index ebfa147..d6bd26a 100644
--- a/bundle/command/pom.xml
+++ b/bundle/command/pom.xml
@@ -44,7 +44,7 @@
         </dependency>
         <dependency>
             <groupId>org.apache.karaf.shell</groupId>
-            <artifactId>org.apache.karaf.shell.console</artifactId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
         </dependency>
         <dependency>
             <groupId>org.apache.karaf.bundle</groupId>
@@ -107,6 +107,7 @@
                             org.apache.felix.utils.version;-split-package:=merge-first,
                             org.apache.felix.utils.manifest;-split-package:=merge-first
                         </Private-Package>
+                        <Karaf-Commands>org.apache.karaf.bundle.command.*</Karaf-Commands>
                     </instructions>
                 </configuration>
             </plugin>

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/java/org/apache/karaf/bundle/command/BundleCommand.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/java/org/apache/karaf/bundle/command/BundleCommand.java b/bundle/command/src/main/java/org/apache/karaf/bundle/command/BundleCommand.java
index 9aa6c89..dce0ca8 100644
--- a/bundle/command/src/main/java/org/apache/karaf/bundle/command/BundleCommand.java
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/BundleCommand.java
@@ -17,16 +17,17 @@
 package org.apache.karaf.bundle.command;
 
 import org.apache.karaf.bundle.core.BundleService;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.console.OsgiCommandSupport;
-import org.apache.karaf.shell.inject.Reference;
-import org.apache.karaf.shell.util.ShellUtil;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.support.ShellUtil;
 import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
 
 /**
  * Unique bundle command.
  */
-public abstract class BundleCommand extends OsgiCommandSupport {
+public abstract class BundleCommand implements Action {
 
     @Argument(index = 0, name = "id", description = "The bundle ID or name or name/version", required = true, multiValued = false)
     String id;
@@ -36,11 +37,14 @@ public abstract class BundleCommand extends OsgiCommandSupport {
     @Reference
     BundleService bundleService;
 
+    @Reference
+    BundleContext bundleContext;
+
     public BundleCommand(boolean defaultAllBundles) {
         this.defaultAllBundles = defaultAllBundles;
     }
 
-    protected Object doExecute() throws Exception {
+    public Object execute() throws Exception {
         return doExecute(true);
     }
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/java/org/apache/karaf/bundle/command/BundleCommandWithConfirmation.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/java/org/apache/karaf/bundle/command/BundleCommandWithConfirmation.java b/bundle/command/src/main/java/org/apache/karaf/bundle/command/BundleCommandWithConfirmation.java
index 66c324c..c07a357 100644
--- a/bundle/command/src/main/java/org/apache/karaf/bundle/command/BundleCommandWithConfirmation.java
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/BundleCommandWithConfirmation.java
@@ -16,7 +16,7 @@
  */
 package org.apache.karaf.bundle.command;
 
-import org.apache.karaf.shell.commands.Option;
+import org.apache.karaf.shell.api.action.Option;
 
 /**
  * Unique bundle command with confirmation while accessing system bundle.
@@ -30,7 +30,7 @@ public abstract class BundleCommandWithConfirmation extends BundleCommand {
         super(true);
     }
 
-    protected Object doExecute() throws Exception {
+    public Object execute() throws Exception {
         return doExecute(force);
     }
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/java/org/apache/karaf/bundle/command/BundlesCommand.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/java/org/apache/karaf/bundle/command/BundlesCommand.java b/bundle/command/src/main/java/org/apache/karaf/bundle/command/BundlesCommand.java
index 6a421fe..ff4dd53 100644
--- a/bundle/command/src/main/java/org/apache/karaf/bundle/command/BundlesCommand.java
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/BundlesCommand.java
@@ -19,13 +19,14 @@ package org.apache.karaf.bundle.command;
 import java.util.List;
 
 import org.apache.karaf.bundle.core.BundleService;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.console.OsgiCommandSupport;
-import org.apache.karaf.shell.inject.Reference;
-import org.apache.karaf.shell.util.ShellUtil;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.support.ShellUtil;
 import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
 
-public abstract class BundlesCommand extends OsgiCommandSupport {
+public abstract class BundlesCommand implements Action {
 
     @Argument(index = 0, name = "ids", description = "The list of bundle (identified by IDs or name or name/version) separated by whitespaces", required = false, multiValued = true)
     List<String> ids;
@@ -33,13 +34,21 @@ public abstract class BundlesCommand extends OsgiCommandSupport {
     boolean defaultAllBundles = true;
 
     @Reference
+    BundleContext bundleContext;
+
+    @Reference
     BundleService bundleService;
     
     public BundlesCommand(boolean defaultAllBundles) {
         this.defaultAllBundles = defaultAllBundles;
     }
-    
-    protected Object doExecute() throws Exception {
+
+    public void setBundleContext(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+    }
+
+    @Override
+    public Object execute() throws Exception {
         doExecute(true);
         return null;
     }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/java/org/apache/karaf/bundle/command/BundlesCommandWithConfirmation.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/java/org/apache/karaf/bundle/command/BundlesCommandWithConfirmation.java b/bundle/command/src/main/java/org/apache/karaf/bundle/command/BundlesCommandWithConfirmation.java
index a6cc62b..271fc42 100644
--- a/bundle/command/src/main/java/org/apache/karaf/bundle/command/BundlesCommandWithConfirmation.java
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/BundlesCommandWithConfirmation.java
@@ -19,8 +19,8 @@ package org.apache.karaf.bundle.command;
 import java.util.ArrayList;
 import java.util.List;
 
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.console.MultiException;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.support.MultiException;
 import org.osgi.framework.Bundle;
 
 /**

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/java/org/apache/karaf/bundle/command/Capabilities.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Capabilities.java b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Capabilities.java
index 4d8c694..de83a1d 100644
--- a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Capabilities.java
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Capabilities.java
@@ -22,10 +22,10 @@ import java.util.List;
 import java.util.Map;
 import java.util.regex.Pattern;
 
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.inject.Service;
-import org.apache.karaf.shell.util.ShellUtil;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.ShellUtil;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceReference;

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/java/org/apache/karaf/bundle/command/Classes.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Classes.java b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Classes.java
index 3a9dc03..b0f80ff 100644
--- a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Classes.java
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Classes.java
@@ -16,15 +16,15 @@
  */
 package org.apache.karaf.bundle.command;
 
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.inject.Service;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.wiring.BundleWiring;
-
 import java.util.Collection;
 import java.util.List;
 
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.wiring.BundleWiring;
+
 @Command(scope = "bundle", name = "classes", description = "Displays a list of classes contained in the bundle")
 @Service
 public class Classes extends BundlesCommand {

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/java/org/apache/karaf/bundle/command/Diag.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Diag.java b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Diag.java
index 0a70d72..1e8e11b 100644
--- a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Diag.java
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Diag.java
@@ -20,9 +20,9 @@ import java.util.List;
 
 import org.apache.karaf.bundle.core.BundleInfo;
 import org.apache.karaf.bundle.core.BundleState;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.inject.Service;
-import org.apache.karaf.shell.util.ShellUtil;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.ShellUtil;
 import org.osgi.framework.Bundle;
 
 @Command(scope = "bundle", name = "diag", description = "Displays diagnostic information why a bundle is not Active")

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/java/org/apache/karaf/bundle/command/DynamicImport.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/java/org/apache/karaf/bundle/command/DynamicImport.java b/bundle/command/src/main/java/org/apache/karaf/bundle/command/DynamicImport.java
index e2e4759..74fc20a 100644
--- a/bundle/command/src/main/java/org/apache/karaf/bundle/command/DynamicImport.java
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/DynamicImport.java
@@ -16,9 +16,8 @@
  */
 package org.apache.karaf.bundle.command;
 
-import org.apache.karaf.bundle.core.BundleService;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.osgi.framework.Bundle;
 
 /**

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/java/org/apache/karaf/bundle/command/FindClass.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/java/org/apache/karaf/bundle/command/FindClass.java b/bundle/command/src/main/java/org/apache/karaf/bundle/command/FindClass.java
index afafd54..4a9fc75 100644
--- a/bundle/command/src/main/java/org/apache/karaf/bundle/command/FindClass.java
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/FindClass.java
@@ -17,31 +17,36 @@ package org.apache.karaf.bundle.command;
  * limitations under the License.
  */
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.console.OsgiCommandSupport;
-import org.apache.karaf.shell.inject.Service;
-import org.apache.karaf.shell.util.ShellUtil;
-import org.osgi.framework.wiring.BundleWiring;
-import org.osgi.framework.Bundle;
-
 import java.util.Collection;
-import java.util.List;
+
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.ShellUtil;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.wiring.BundleWiring;
 
 @Command(scope = "bundle", name = "find-class", description = "Locates a specified class in any deployed bundle")
 @Service
-public class FindClass extends OsgiCommandSupport {
+public class FindClass implements Action {
 
     @Argument(index = 0, name = "className", description = "Class name or partial class name to be found", required = true, multiValued = false)
     String className;
 
-    protected Object doExecute() throws Exception {
+    @Reference
+    BundleContext bundleContext;
+
+    @Override
+    public Object execute() throws Exception {
         findResource();
         return null;
     }
 
     protected void findResource() {
-        Bundle[] bundles = getBundleContext().getBundles();
+        Bundle[] bundles = bundleContext.getBundles();
         String filter = "*" + className + "*";
         for (Bundle bundle:bundles){
             BundleWiring wiring = bundle.adapt(BundleWiring.class);

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/java/org/apache/karaf/bundle/command/Headers.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Headers.java b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Headers.java
index a173a90..16083ff 100644
--- a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Headers.java
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Headers.java
@@ -25,18 +25,18 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
-import jline.Terminal;
-
 import org.apache.felix.utils.manifest.Attribute;
 import org.apache.felix.utils.manifest.Clause;
 import org.apache.felix.utils.manifest.Directive;
 import org.apache.felix.utils.manifest.Parser;
 import org.apache.felix.utils.version.VersionRange;
 import org.apache.felix.utils.version.VersionTable;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.inject.Service;
-import org.apache.karaf.shell.util.ShellUtil;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.Terminal;
+import org.apache.karaf.shell.support.ShellUtil;
 import org.fusesource.jansi.Ansi;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.Constants;
@@ -57,6 +57,9 @@ public class Headers extends BundlesCommand {
 
     @Option(name = "--indent", description = "Indentation method")
     int indent = -1;
+
+    @Reference
+    Terminal terminal;
     
     public Headers() {
         super(true);
@@ -228,8 +231,7 @@ public class Headers extends BundlesCommand {
     }
 
     protected int getTermWidth() {
-        Terminal term = (Terminal) session.get(".jline.terminal");
-        return term != null ? term.getWidth() : 80;
+        return terminal.getWidth();
 
     }
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/java/org/apache/karaf/bundle/command/Info.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Info.java b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Info.java
index 904c9d6..451ec87 100644
--- a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Info.java
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Info.java
@@ -25,9 +25,9 @@ import java.util.List;
 import org.apache.karaf.bundle.command.wikidoc.AnsiPrintingWikiVisitor;
 import org.apache.karaf.bundle.command.wikidoc.WikiParser;
 import org.apache.karaf.bundle.command.wikidoc.WikiVisitor;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.inject.Service;
-import org.apache.karaf.shell.util.ShellUtil;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.ShellUtil;
 import org.osgi.framework.Bundle;
 
 @Command(scope = "bundle", name = "info", description = "Displays detailed information of a given bundles.")

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/java/org/apache/karaf/bundle/command/Install.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Install.java b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Install.java
index 15604cc..11fba89 100644
--- a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Install.java
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Install.java
@@ -19,17 +19,19 @@ package org.apache.karaf.bundle.command;
 import java.util.ArrayList;
 import java.util.List;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.console.MultiException;
-import org.apache.karaf.shell.console.OsgiCommandSupport;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.MultiException;
 import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
 
 @Command(scope = "bundle", name = "install", description = "Installs one or more bundles.")
 @Service
-public class Install extends OsgiCommandSupport {
+public class Install implements Action {
 
     @Argument(index = 0, name = "urls", description = "Bundle URLs separated by whitespaces", required = true, multiValued = true)
     List<String> urls;
@@ -37,12 +39,16 @@ public class Install extends OsgiCommandSupport {
     @Option(name = "-s", aliases={"--start"}, description="Starts the bundles after installation", required = false, multiValued = false)
     boolean start;
 
-    protected Object doExecute() throws Exception {
+    @Reference
+    BundleContext bundleContext;
+
+    @Override
+    public Object execute() throws Exception {
         List<Exception> exceptions = new ArrayList<Exception>();
         List<Bundle> bundles = new ArrayList<Bundle>();
         for (String url : urls) {
             try {
-                bundles.add(getBundleContext().installBundle(url, null));
+                bundles.add(bundleContext.installBundle(url, null));
             } catch (Exception e) {
                 exceptions.add(new Exception("Unable to install bundle " + url, e));
             }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/java/org/apache/karaf/bundle/command/ListBundleServices.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/java/org/apache/karaf/bundle/command/ListBundleServices.java b/bundle/command/src/main/java/org/apache/karaf/bundle/command/ListBundleServices.java
index b813c52..28f8264 100644
--- a/bundle/command/src/main/java/org/apache/karaf/bundle/command/ListBundleServices.java
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/ListBundleServices.java
@@ -16,14 +16,15 @@
  */
 package org.apache.karaf.bundle.command;
 
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
-import org.apache.felix.service.command.Function;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.console.Completer;
-import org.apache.karaf.shell.inject.Service;
-import org.apache.karaf.shell.util.ShellUtil;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.ShellUtil;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceReference;
@@ -40,6 +41,11 @@ public class ListBundleServices extends BundlesCommand {
     
     @Option(name = "-p", aliases = {}, description = "Shows the properties of the services", required = false, multiValued = false)
     boolean showProperties = false;
+
+    Set<String> hidden = new HashSet<String>(Arrays.asList(new String[] {
+        "org.apache.felix.service.command.Function",
+        "org.apache.karaf.shell.console.Completer"
+    }));
     
     public ListBundleServices() {
         super(true);
@@ -94,8 +100,7 @@ public class ListBundleServices extends BundlesCommand {
 
     private boolean isCommandOrCompleter(String[] objectClasses) {
         for (String objectClass : objectClasses) {
-            if (objectClass.equals(Function.class.getName())
-                    || objectClass.equals(Completer.class.getName())) {
+            if (hidden.contains(objectClass)) {
                 return true;
             }
         }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/java/org/apache/karaf/bundle/command/ListBundles.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/java/org/apache/karaf/bundle/command/ListBundles.java b/bundle/command/src/main/java/org/apache/karaf/bundle/command/ListBundles.java
index 1cc87ef..4ab0af9 100644
--- a/bundle/command/src/main/java/org/apache/karaf/bundle/command/ListBundles.java
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/ListBundles.java
@@ -19,18 +19,19 @@ package org.apache.karaf.bundle.command;
 import org.apache.karaf.bundle.core.BundleInfo;
 import org.apache.karaf.bundle.core.BundleService;
 import org.apache.karaf.bundle.core.BundleState;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.console.OsgiCommandSupport;
-import org.apache.karaf.shell.inject.Reference;
-import org.apache.karaf.shell.inject.Service;
 import org.apache.karaf.shell.table.ShellTable;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
 import org.osgi.framework.startlevel.FrameworkStartLevel;
 
 @Command(scope = "bundle", name = "list", description = "Lists all installed bundles.")
 @Service
-public class ListBundles extends OsgiCommandSupport {
+public class ListBundles implements Action {
 
     @Option(name = "-l", aliases = {}, description = "Show the locations", required = false, multiValued = false)
     boolean showLoc;
@@ -48,14 +49,18 @@ public class ListBundles extends OsgiCommandSupport {
     boolean noFormat;
 
     @Reference
+    BundleContext bundleContext;
+
+    @Reference
     private BundleService bundleService;
 
     public void setBundleService(BundleService bundleService) {
         this.bundleService = bundleService;
     }
 
-    protected Object doExecute() throws Exception {
-        Bundle[] bundles = getBundleContext().getBundles();
+    @Override
+    public Object execute() throws Exception {
+        Bundle[] bundles = bundleContext.getBundles();
         if (bundles == null) {
             System.out.println("There are no installed bundles.");
             return null;
@@ -64,7 +69,7 @@ public class ListBundles extends OsgiCommandSupport {
         determineBundleLevelThreshold();
         
         // Display active start level.
-        FrameworkStartLevel fsl = getBundleContext().getBundle(0).adapt(FrameworkStartLevel.class);
+        FrameworkStartLevel fsl = bundleContext.getBundle(0).adapt(FrameworkStartLevel.class);
         if (fsl != null) {
             System.out.println("START LEVEL " + fsl.getStartLevel() + " , List Threshold: " + bundleLevelThreshold);
         }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/java/org/apache/karaf/bundle/command/LoadTest.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/java/org/apache/karaf/bundle/command/LoadTest.java b/bundle/command/src/main/java/org/apache/karaf/bundle/command/LoadTest.java
index 6c8d6c3..7ca43b2 100644
--- a/bundle/command/src/main/java/org/apache/karaf/bundle/command/LoadTest.java
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/LoadTest.java
@@ -24,12 +24,12 @@ import java.util.Random;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import jline.console.ConsoleReader;
-import org.apache.felix.service.command.CommandSession;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.console.OsgiCommandSupport;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.FrameworkEvent;
@@ -38,7 +38,7 @@ import org.osgi.framework.wiring.FrameworkWiring;
 
 @Command(scope = "bundle", name = "load-test", description = "Load test bundle lifecycle")
 @Service
-public class LoadTest extends OsgiCommandSupport {
+public class LoadTest implements Action {
 
     @Option(name = "--threads", description = "number of concurrent threads")
     int threads = 2;
@@ -55,12 +55,18 @@ public class LoadTest extends OsgiCommandSupport {
     @Option(name = "--excludes", description = "List of bundles (ids or symbolic names) to exclude")
     List<String> excludes = Arrays.asList("0", "org.ops4j.pax.url.mvn", "org.ops4j.pax.logging.pax-logging-api", "org.ops4j.pax.logging.pax-logging-service");
 
+    @Reference
+    Session session;
+
+    @Reference
+    BundleContext bundleContext;
+
     @Override
-    protected Object doExecute() throws Exception {
+    public Object execute() throws Exception {
         if (!confirm(session)) {
             return null;
         }
-        final BundleContext bundleContext = getBundleContext().getBundle(0).getBundleContext();
+        final BundleContext bundleContext = this.bundleContext.getBundle(0).getBundleContext();
         final FrameworkWiring wiring = bundleContext.getBundle().adapt(FrameworkWiring.class);
         final CountDownLatch latch = new CountDownLatch(threads);
         final Bundle[] bundles = bundleContext.getBundles();
@@ -159,11 +165,10 @@ public class LoadTest extends OsgiCommandSupport {
         return null;
     }
 
-    private boolean confirm(CommandSession session) throws IOException {
+    private boolean confirm(Session session) throws IOException {
         for (;;) {
-            ConsoleReader reader = (ConsoleReader) session.get(".jline.reader");
             String msg = "You are about to perform a start/stop/refresh load test on bundles.\nDo you wish to continue (yes/no): ";
-            String str = reader.readLine(msg);
+            String str = session.readLine(msg, null);
             if ("yes".equalsIgnoreCase(str)) {
                 return true;
             }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/java/org/apache/karaf/bundle/command/Refresh.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Refresh.java b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Refresh.java
index bfd0c2d..53adaeb 100644
--- a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Refresh.java
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Refresh.java
@@ -18,8 +18,8 @@ package org.apache.karaf.bundle.command;
 
 import java.util.List;
 
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.wiring.FrameworkWiring;
 
@@ -32,7 +32,7 @@ public class Refresh extends BundlesCommandWithConfirmation {
     }
 
     protected void doExecute(List<Bundle> bundles) throws Exception {
-        FrameworkWiring wiring = getBundleContext().getBundle(0).adapt(FrameworkWiring.class);
+        FrameworkWiring wiring = bundleContext.getBundle(0).adapt(FrameworkWiring.class);
         wiring.refreshBundles(bundles == null || bundles.isEmpty() ? null : bundles);
     }
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/java/org/apache/karaf/bundle/command/Requirements.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Requirements.java b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Requirements.java
index 3fdc004..c87d4d4 100644
--- a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Requirements.java
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Requirements.java
@@ -22,10 +22,10 @@ import java.util.List;
 import java.util.Map;
 import java.util.regex.Pattern;
 
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.inject.Service;
-import org.apache.karaf.shell.util.ShellUtil;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.ShellUtil;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceReference;

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/java/org/apache/karaf/bundle/command/Resolve.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Resolve.java b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Resolve.java
index d72cfce..720f118 100644
--- a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Resolve.java
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Resolve.java
@@ -18,8 +18,8 @@ package org.apache.karaf.bundle.command;
 
 import java.util.List;
 
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.wiring.FrameworkWiring;
 
@@ -32,7 +32,7 @@ public class Resolve extends BundlesCommand {
     }
 
     protected void doExecute(List<Bundle> bundles) throws Exception {
-        FrameworkWiring wiring = getBundleContext().getBundle(0).adapt(FrameworkWiring.class);
+        FrameworkWiring wiring = bundleContext.getBundle(0).adapt(FrameworkWiring.class);
         wiring.resolveBundles(bundles == null || bundles.isEmpty() ? null : bundles);
     }
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/java/org/apache/karaf/bundle/command/Restart.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Restart.java b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Restart.java
index 16d9b02..95d5dfe 100644
--- a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Restart.java
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Restart.java
@@ -19,9 +19,9 @@ package org.apache.karaf.bundle.command;
 import java.util.ArrayList;
 import java.util.List;
 
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.console.MultiException;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.MultiException;
 import org.osgi.framework.Bundle;
 
 @Command(scope = "bundle", name = "restart", description = "Restarts bundles.")

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/java/org/apache/karaf/bundle/command/ShowBundleTree.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/java/org/apache/karaf/bundle/command/ShowBundleTree.java b/bundle/command/src/main/java/org/apache/karaf/bundle/command/ShowBundleTree.java
index 1e44a4d..a83ce39 100644
--- a/bundle/command/src/main/java/org/apache/karaf/bundle/command/ShowBundleTree.java
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/ShowBundleTree.java
@@ -16,8 +16,6 @@
  */
 package org.apache.karaf.bundle.command;
 
-import static java.lang.String.format;
-
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -31,8 +29,8 @@ import org.apache.felix.utils.version.VersionRange;
 import org.apache.felix.utils.version.VersionTable;
 import org.apache.karaf.bundle.command.bundletree.Node;
 import org.apache.karaf.bundle.command.bundletree.Tree;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.Constants;
 import org.osgi.framework.wiring.BundleCapability;
@@ -43,6 +41,8 @@ import org.osgi.framework.wiring.BundleWiring;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static java.lang.String.format;
+
 /**
  * Command for showing the full tree of bundles that have been used to resolve
  * a given bundle.

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/java/org/apache/karaf/bundle/command/Start.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Start.java b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Start.java
index c3f1882..48e90fa 100644
--- a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Start.java
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Start.java
@@ -16,8 +16,8 @@
  */
 package org.apache.karaf.bundle.command;
 
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.osgi.framework.Bundle;
 
 @Command(scope = "bundle", name = "start", description = "Starts bundles.")

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/java/org/apache/karaf/bundle/command/StartLevel.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/java/org/apache/karaf/bundle/command/StartLevel.java b/bundle/command/src/main/java/org/apache/karaf/bundle/command/StartLevel.java
index 117f21a..30bec0f 100644
--- a/bundle/command/src/main/java/org/apache/karaf/bundle/command/StartLevel.java
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/StartLevel.java
@@ -16,10 +16,11 @@
  */
 package org.apache.karaf.bundle.command;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import jline.console.ConsoleReader;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.startlevel.BundleStartLevel;
 
@@ -30,6 +31,9 @@ public class StartLevel extends BundleCommandWithConfirmation {
     @Argument(index = 1, name = "startLevel", description = "The bundle's new start level", required = false, multiValued = false)
     Integer level;
 
+    @Reference
+    Session session;
+
     protected void doExecute(Bundle bundle) throws Exception {
         // Get package instance service.
         BundleStartLevel bsl = bundle.adapt(BundleStartLevel.class);
@@ -42,9 +46,8 @@ public class StartLevel extends BundleCommandWithConfirmation {
         }
         else if ((level < 50) && (bsl.getStartLevel() > 50) && !force){
             for (;;) {
-                ConsoleReader reader = (ConsoleReader) session.get(".jline.reader");
                 String msg = "You are about to designate bundle as a system bundle.  Do you wish to continue (yes/no): ";
-                String str = reader.readLine(msg);
+                String str = session.readLine(msg, null);
                 if ("yes".equalsIgnoreCase(str)) {
                     bsl.setStartLevel(level);
                     break;

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/java/org/apache/karaf/bundle/command/Stop.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Stop.java b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Stop.java
index c5408bc..0b0fed2 100644
--- a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Stop.java
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Stop.java
@@ -16,9 +16,9 @@
  */
 package org.apache.karaf.bundle.command;
 
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.osgi.framework.Bundle;
 
 @Command(scope = "bundle", name = "stop", description = "Stop bundles.")

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/java/org/apache/karaf/bundle/command/Uninstall.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Uninstall.java b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Uninstall.java
index 04f15f2..67b5b14 100644
--- a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Uninstall.java
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Uninstall.java
@@ -16,8 +16,8 @@
  */
 package org.apache.karaf.bundle.command;
 
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.osgi.framework.Bundle;
 
 @Command(scope = "bundle", name = "uninstall", description = "Uninstall bundles.")

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/java/org/apache/karaf/bundle/command/Update.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Update.java b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Update.java
index b19bed7..71ae74d 100644
--- a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Update.java
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Update.java
@@ -19,9 +19,9 @@ package org.apache.karaf.bundle.command;
 import java.io.InputStream;
 import java.net.URL;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.osgi.framework.Bundle;
 
 @Command(scope = "bundle", name = "update", description = "Update bundle.")

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/java/org/apache/karaf/bundle/command/Watch.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Watch.java b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Watch.java
index 715455b..9203f59 100644
--- a/bundle/command/src/main/java/org/apache/karaf/bundle/command/Watch.java
+++ b/bundle/command/src/main/java/org/apache/karaf/bundle/command/Watch.java
@@ -19,19 +19,18 @@ package org.apache.karaf.bundle.command;
 import java.util.List;
 
 import org.apache.karaf.bundle.core.BundleWatcher;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.console.OsgiCommandSupport;
-import org.apache.karaf.shell.inject.Reference;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.Constants;
 
 @Command(scope = "bundle", name = "watch", description = "Watches and updates bundles", detailedDescription = "Watches the local maven repo for changes in snapshot jars and redploys changed jars")
 @Service
-public class Watch extends OsgiCommandSupport {
+public class Watch implements Action {
 
     @Argument(index = 0, name = "urls", description = "The bundle IDs or URLs", required = false, multiValued = true)
     List<String> urls;
@@ -59,7 +58,7 @@ public class Watch extends OsgiCommandSupport {
     }
 
     @Override
-    protected Object doExecute() throws Exception {
+    public Object execute() throws Exception {
         if (start && stop) {
             System.err.println("Please use only one of --start and --stop options!");
             return null;

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/main/resources/OSGI-INF/blueprint/shell-bundles.xml
----------------------------------------------------------------------
diff --git a/bundle/command/src/main/resources/OSGI-INF/blueprint/shell-bundles.xml b/bundle/command/src/main/resources/OSGI-INF/blueprint/shell-bundles.xml
deleted file mode 100644
index 327becd..0000000
--- a/bundle/command/src/main/resources/OSGI-INF/blueprint/shell-bundles.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
-    Licensed to the Apache Software Foundation (ASF) under one or more
-    contributor license agreements.  See the NOTICE file distributed with
-    this work for additional information regarding copyright ownership.
-    The ASF licenses this file to You under the Apache License, Version 2.0
-    (the "License"); you may not use this file except in compliance with
-    the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
-
--->
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
-
-    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.2.0"
-                    scan="org.apache.karaf.bundle.command.*" />
-
-</blueprint>

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/bundle/command/src/test/java/org/apache/karaf/bundle/command/ListServicesTest.java
----------------------------------------------------------------------
diff --git a/bundle/command/src/test/java/org/apache/karaf/bundle/command/ListServicesTest.java b/bundle/command/src/test/java/org/apache/karaf/bundle/command/ListServicesTest.java
index 4ddfbba..2c25a44 100644
--- a/bundle/command/src/test/java/org/apache/karaf/bundle/command/ListServicesTest.java
+++ b/bundle/command/src/test/java/org/apache/karaf/bundle/command/ListServicesTest.java
@@ -38,7 +38,7 @@ public class ListServicesTest {
     @Test
     public void listAllShort() throws Exception {
         System.out.println("listAllShort");
-        listServices.doExecute();
+        listServices.execute();
     }
 
     
@@ -46,7 +46,7 @@ public class ListServicesTest {
     public void listAllLong() throws Exception {
         System.out.println("listAllLong");
         listServices.ids = Arrays.asList(new String[]{"1", "2"});
-        listServices.doExecute();
+        listServices.execute();
     }
 
     @Test
@@ -54,7 +54,7 @@ public class ListServicesTest {
         System.out.println("listAllLongServicesUse");
         listServices.ids = Arrays.asList(new String[]{"1", "2"});
         listServices.inUse = true;
-        listServices.doExecute();
+        listServices.execute();
     }
 
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/features/command/pom.xml
----------------------------------------------------------------------
diff --git a/features/command/pom.xml b/features/command/pom.xml
index 112d988..debe123 100644
--- a/features/command/pom.xml
+++ b/features/command/pom.xml
@@ -59,7 +59,7 @@
         </dependency>
         <dependency>
             <groupId>org.apache.karaf.shell</groupId>
-            <artifactId>org.apache.karaf.shell.console</artifactId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
         </dependency>
     </dependencies>
 
@@ -85,17 +85,7 @@
                 <artifactId>maven-bundle-plugin</artifactId>
                 <configuration>
                     <instructions>
-                        <Import-Package>
-                            javax.management,
-                            javax.management.loading,
-                            org.apache.aries.blueprint,
-                            org.osgi.service.blueprint.container,
-                            org.osgi.service.blueprint.reflect,
-                            org.apache.felix.service.command,
-                            org.apache.karaf.shell.commands,
-                            org.apache.karaf.shell.console,
-                            *
-                        </Import-Package>
+                        <Karaf-Commands>org.apache.karaf.features.command.*</Karaf-Commands>
                     </instructions>
                 </configuration>
             </plugin>

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/features/command/src/main/java/org/apache/karaf/features/command/FeaturesCommandSupport.java
----------------------------------------------------------------------
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/FeaturesCommandSupport.java b/features/command/src/main/java/org/apache/karaf/features/command/FeaturesCommandSupport.java
index caa8dfa..076650d 100644
--- a/features/command/src/main/java/org/apache/karaf/features/command/FeaturesCommandSupport.java
+++ b/features/command/src/main/java/org/apache/karaf/features/command/FeaturesCommandSupport.java
@@ -17,17 +17,18 @@
 package org.apache.karaf.features.command;
 
 import org.apache.karaf.features.FeaturesService;
-import org.apache.karaf.shell.console.OsgiCommandSupport;
-import org.apache.karaf.shell.inject.Reference;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
 
-public abstract class FeaturesCommandSupport extends OsgiCommandSupport {
+public abstract class FeaturesCommandSupport implements Action {
 
     @Reference
     private FeaturesService featuresService;
 
-    protected Object doExecute() throws Exception {
+    @Override
+    public Object execute() throws Exception {
         if (featuresService == null) {
-            featuresService = getService(FeaturesService.class);
+            throw new IllegalStateException("FeaturesService not found");
         }
         doExecute(featuresService);
         return null;

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/features/command/src/main/java/org/apache/karaf/features/command/InfoFeatureCommand.java
----------------------------------------------------------------------
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/InfoFeatureCommand.java b/features/command/src/main/java/org/apache/karaf/features/command/InfoFeatureCommand.java
index c030953..7084a6e 100644
--- a/features/command/src/main/java/org/apache/karaf/features/command/InfoFeatureCommand.java
+++ b/features/command/src/main/java/org/apache/karaf/features/command/InfoFeatureCommand.java
@@ -21,18 +21,18 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
-import org.apache.karaf.features.Conditional;
-import org.apache.karaf.features.command.completers.AllFeatureCompleter;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Completer;
-import org.apache.karaf.shell.commands.Option;
 import org.apache.karaf.features.BundleInfo;
+import org.apache.karaf.features.Conditional;
 import org.apache.karaf.features.ConfigFileInfo;
 import org.apache.karaf.features.Dependency;
 import org.apache.karaf.features.Feature;
 import org.apache.karaf.features.FeaturesService;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.features.command.completers.AllFeatureCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 @Command(scope = "feature", name = "info", description = "Shows information about selected feature.")
 @Service
@@ -43,7 +43,7 @@ public class InfoFeatureCommand extends FeaturesCommandSupport {
     private static final String CONDITIONAL_CONTENT = "Conditional(%s)";
 
 	@Argument(index = 0, name = "name", description = "The name of the feature", required = true, multiValued = false)
-    @Completer(AllFeatureCompleter.class)
+    @Completion(AllFeatureCompleter.class)
     private String name;
 
     @Argument(index = 1, name = "version", description = "The version of the feature", required = false, multiValued = false)

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/features/command/src/main/java/org/apache/karaf/features/command/InstallFeatureCommand.java
----------------------------------------------------------------------
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/InstallFeatureCommand.java b/features/command/src/main/java/org/apache/karaf/features/command/InstallFeatureCommand.java
index 6323a23..e47edf0 100644
--- a/features/command/src/main/java/org/apache/karaf/features/command/InstallFeatureCommand.java
+++ b/features/command/src/main/java/org/apache/karaf/features/command/InstallFeatureCommand.java
@@ -19,13 +19,13 @@ package org.apache.karaf.features.command;
 import java.util.EnumSet;
 import java.util.List;
 
-import org.apache.karaf.features.command.completers.AvailableFeatureCompleter;
-import org.apache.karaf.shell.commands.Completer;
-import org.apache.karaf.shell.commands.Option;
 import org.apache.karaf.features.FeaturesService;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.features.command.completers.AvailableFeatureCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 @Command(scope = "feature", name = "install", description = "Installs a feature with the specified name and version.")
 @Service
@@ -34,7 +34,7 @@ public class InstallFeatureCommand extends FeaturesCommandSupport {
     private static String DEFAULT_VERSION = "0.0.0";
 
     @Argument(index = 0, name = "feature", description = "The name and version of the features to install. A feature id looks like name/version. The version is optional.", required = true, multiValued = true)
-    @Completer(AvailableFeatureCompleter.class)
+    @Completion(AvailableFeatureCompleter.class)
     List<String> features;
     @Option(name = "-c", aliases = "--no-clean", description = "Do not uninstall bundles on failure", required = false, multiValued = false)
     boolean noClean;

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/features/command/src/main/java/org/apache/karaf/features/command/ListFeatureVersionsCommand.java
----------------------------------------------------------------------
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/ListFeatureVersionsCommand.java b/features/command/src/main/java/org/apache/karaf/features/command/ListFeatureVersionsCommand.java
index f3f042e..792825c 100644
--- a/features/command/src/main/java/org/apache/karaf/features/command/ListFeatureVersionsCommand.java
+++ b/features/command/src/main/java/org/apache/karaf/features/command/ListFeatureVersionsCommand.java
@@ -22,19 +22,19 @@ import org.apache.karaf.features.Feature;
 import org.apache.karaf.features.FeaturesService;
 import org.apache.karaf.features.Repository;
 import org.apache.karaf.features.command.completers.AllFeatureCompleter;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Completer;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.inject.Service;
 import org.apache.karaf.shell.table.ShellTable;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 @Command(scope = "feature", name = "version-list", description = "Lists all versions of a feature available from the currently available repositories.")
 @Service
 public class ListFeatureVersionsCommand extends FeaturesCommandSupport {
 
 	@Argument(index = 0, name = "feature", description = "Name of feature.", required = true, multiValued = false)
-    @Completer(AllFeatureCompleter.class)
+    @Completion(AllFeatureCompleter.class)
 	String feature;
 
     @Option(name = "--no-format", description = "Disable table rendered output", required = false, multiValued = false)

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/features/command/src/main/java/org/apache/karaf/features/command/ListFeaturesCommand.java
----------------------------------------------------------------------
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/ListFeaturesCommand.java b/features/command/src/main/java/org/apache/karaf/features/command/ListFeaturesCommand.java
index 0a699c9..0cb6d3c 100644
--- a/features/command/src/main/java/org/apache/karaf/features/command/ListFeaturesCommand.java
+++ b/features/command/src/main/java/org/apache/karaf/features/command/ListFeaturesCommand.java
@@ -24,10 +24,10 @@ import java.util.List;
 import org.apache.karaf.features.Feature;
 import org.apache.karaf.features.FeaturesService;
 import org.apache.karaf.features.Repository;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.inject.Service;
 import org.apache.karaf.shell.table.ShellTable;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 @Command(scope = "feature", name = "list", description = "Lists all existing features available from the defined repositories.")
 @Service

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/features/command/src/main/java/org/apache/karaf/features/command/RepoAddCommand.java
----------------------------------------------------------------------
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/RepoAddCommand.java b/features/command/src/main/java/org/apache/karaf/features/command/RepoAddCommand.java
index 0594228..16faf42 100644
--- a/features/command/src/main/java/org/apache/karaf/features/command/RepoAddCommand.java
+++ b/features/command/src/main/java/org/apache/karaf/features/command/RepoAddCommand.java
@@ -20,18 +20,18 @@ import java.net.URI;
 
 import org.apache.karaf.features.FeaturesService;
 import org.apache.karaf.features.command.completers.AvailableRepoNameCompleter;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Completer;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 @Command(scope = "feature", name = "repo-add", description = "Add a features repository")
 @Service
 public class RepoAddCommand extends FeaturesCommandSupport {
 
     @Argument(index = 0, name = "name/url", description = "Shortcut name of the features repository or the full URL", required = true, multiValued = false)
-    @Completer(AvailableRepoNameCompleter.class)
+    @Completion(AvailableRepoNameCompleter.class)
     private String nameOrUrl;
     
     @Argument(index = 1, name = "version", description = "The version of the features repository if using features repository name as first argument. It should be empty if using the URL", required = false, multiValued = false)

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/features/command/src/main/java/org/apache/karaf/features/command/RepoListCommand.java
----------------------------------------------------------------------
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/RepoListCommand.java b/features/command/src/main/java/org/apache/karaf/features/command/RepoListCommand.java
index b71f866..af180bd 100644
--- a/features/command/src/main/java/org/apache/karaf/features/command/RepoListCommand.java
+++ b/features/command/src/main/java/org/apache/karaf/features/command/RepoListCommand.java
@@ -21,11 +21,11 @@ import java.util.List;
 
 import org.apache.karaf.features.FeaturesService;
 import org.apache.karaf.features.Repository;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.console.MultiException;
-import org.apache.karaf.shell.inject.Service;
 import org.apache.karaf.shell.table.ShellTable;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.MultiException;
 
 @Command(scope = "feature", name = "repo-list", description = "Displays a list of all defined repositories.")
 @Service

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/features/command/src/main/java/org/apache/karaf/features/command/RepoRefreshCommand.java
----------------------------------------------------------------------
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/RepoRefreshCommand.java b/features/command/src/main/java/org/apache/karaf/features/command/RepoRefreshCommand.java
index 6930a93..8c7ed79 100644
--- a/features/command/src/main/java/org/apache/karaf/features/command/RepoRefreshCommand.java
+++ b/features/command/src/main/java/org/apache/karaf/features/command/RepoRefreshCommand.java
@@ -23,14 +23,14 @@ import java.util.List;
 import org.apache.karaf.features.FeaturesService;
 import org.apache.karaf.features.Repository;
 import org.apache.karaf.features.command.completers.InstalledRepoUriCompleter;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Completer;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
 
 @Command(scope = "feature", name = "repo-refresh", description = "Refresh a features repository")
 public class RepoRefreshCommand extends FeaturesCommandSupport {
     @Argument(index = 0, name = "Feature name or uri", description = "Shortcut name of the feature repository or the full URI", required = false, multiValued = false)
-    @Completer(InstalledRepoUriCompleter.class)
+    @Completion(InstalledRepoUriCompleter.class)
     private String nameOrUrl;
     
     @Argument(index = 1, name = "Feature version", description = "The version of the feature if using the feature name. Should be empty if using the uri", required = false, multiValued = false)

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/features/command/src/main/java/org/apache/karaf/features/command/RepoRemoveCommand.java
----------------------------------------------------------------------
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/RepoRemoveCommand.java b/features/command/src/main/java/org/apache/karaf/features/command/RepoRemoveCommand.java
index 4c14f66..0710b72 100644
--- a/features/command/src/main/java/org/apache/karaf/features/command/RepoRemoveCommand.java
+++ b/features/command/src/main/java/org/apache/karaf/features/command/RepoRemoveCommand.java
@@ -18,21 +18,21 @@ package org.apache.karaf.features.command;
 
 import java.net.URI;
 
-import org.apache.karaf.features.command.completers.InstalledRepoNameCompleter;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Completer;
-import org.apache.karaf.shell.commands.Option;
 import org.apache.karaf.features.FeaturesService;
 import org.apache.karaf.features.Repository;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.features.command.completers.InstalledRepoNameCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 @Command(scope = "feature", name = "repo-remove", description = "Removes the specified repository features service.")
 @Service
 public class RepoRemoveCommand extends FeaturesCommandSupport {
 
     @Argument(index = 0, name = "repository", description = "Name or url of the repository to remove.", required = true, multiValued = false)
-    @Completer(InstalledRepoNameCompleter.class)
+    @Completion(InstalledRepoNameCompleter.class)
     private String repository;
 
     @Option(name = "-u", aliases = { "--uninstall-all" }, description = "Uninstall all features from the repository", required = false, multiValued = false)

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/features/command/src/main/java/org/apache/karaf/features/command/UninstallFeatureCommand.java
----------------------------------------------------------------------
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/UninstallFeatureCommand.java b/features/command/src/main/java/org/apache/karaf/features/command/UninstallFeatureCommand.java
index e22d2f3..b56eac6 100644
--- a/features/command/src/main/java/org/apache/karaf/features/command/UninstallFeatureCommand.java
+++ b/features/command/src/main/java/org/apache/karaf/features/command/UninstallFeatureCommand.java
@@ -16,23 +16,23 @@
  */
 package org.apache.karaf.features.command;
 
-import org.apache.karaf.features.command.completers.InstalledFeatureCompleter;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.features.FeaturesService;
-import org.apache.karaf.shell.commands.Completer;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.inject.Service;
-
 import java.util.EnumSet;
 import java.util.List;
 
+import org.apache.karaf.features.FeaturesService;
+import org.apache.karaf.features.command.completers.InstalledFeatureCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
 @Command(scope = "feature", name = "uninstall", description = "Uninstalls a feature with the specified name and version.")
 @Service
 public class UninstallFeatureCommand extends FeaturesCommandSupport {
 
     @Argument(index = 0, name = "features", description = "The name and version of the features to uninstall. A feature id looks like name/version. The version is optional.", required = true, multiValued = true)
-    @Completer(InstalledFeatureCompleter.class)
+    @Completion(InstalledFeatureCompleter.class)
     List<String> features;
 
     @Option(name = "-r", aliases = "--no-auto-refresh", description = "Do not automatically refresh bundles", required = false, multiValued = false)

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/features/command/src/main/java/org/apache/karaf/features/command/completers/AllFeatureCompleter.java
----------------------------------------------------------------------
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/completers/AllFeatureCompleter.java b/features/command/src/main/java/org/apache/karaf/features/command/completers/AllFeatureCompleter.java
index 08fba41..7444b95 100644
--- a/features/command/src/main/java/org/apache/karaf/features/command/completers/AllFeatureCompleter.java
+++ b/features/command/src/main/java/org/apache/karaf/features/command/completers/AllFeatureCompleter.java
@@ -17,7 +17,7 @@
 package org.apache.karaf.features.command.completers;
 
 import org.apache.karaf.features.Feature;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 /**
  * {@link org.apache.karaf.shell.console.Completer} for available features.

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/features/command/src/main/java/org/apache/karaf/features/command/completers/AvailableFeatureCompleter.java
----------------------------------------------------------------------
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/completers/AvailableFeatureCompleter.java b/features/command/src/main/java/org/apache/karaf/features/command/completers/AvailableFeatureCompleter.java
index 22ec700..79cd280 100644
--- a/features/command/src/main/java/org/apache/karaf/features/command/completers/AvailableFeatureCompleter.java
+++ b/features/command/src/main/java/org/apache/karaf/features/command/completers/AvailableFeatureCompleter.java
@@ -17,7 +17,7 @@
 package org.apache.karaf.features.command.completers;
 
 import org.apache.karaf.features.Feature;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 /**
  * {@link org.apache.karaf.shell.console.Completer} for features not installed yet.

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/features/command/src/main/java/org/apache/karaf/features/command/completers/AvailableRepoNameCompleter.java
----------------------------------------------------------------------
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/completers/AvailableRepoNameCompleter.java b/features/command/src/main/java/org/apache/karaf/features/command/completers/AvailableRepoNameCompleter.java
index 7797715..acefe77 100644
--- a/features/command/src/main/java/org/apache/karaf/features/command/completers/AvailableRepoNameCompleter.java
+++ b/features/command/src/main/java/org/apache/karaf/features/command/completers/AvailableRepoNameCompleter.java
@@ -20,11 +20,12 @@ import java.util.Arrays;
 import java.util.List;
 
 import org.apache.karaf.features.FeaturesService;
-import org.apache.karaf.features.internal.FeatureFinder;
-import org.apache.karaf.shell.console.Completer;
-import org.apache.karaf.shell.console.completer.StringsCompleter;
-import org.apache.karaf.shell.inject.Reference;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
 
 /**
  * Shows the list of feature repos that can be installed with their short name
@@ -39,9 +40,9 @@ public class AvailableRepoNameCompleter implements Completer {
         this.featuresService = featuresService;
     }
 
-    public int complete(final String buffer, final int cursor, @SuppressWarnings("rawtypes") final List candidates) {
+    public int complete(Session session, CommandLine commandLine, final List<String> candidates) {
         StringsCompleter delegate = new StringsCompleter(Arrays.asList(featuresService.getRepositoryNames()));
-        return delegate.complete(buffer, cursor, candidates);
+        return delegate.complete(session, commandLine, candidates);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/features/command/src/main/java/org/apache/karaf/features/command/completers/FeatureCompleterSupport.java
----------------------------------------------------------------------
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/completers/FeatureCompleterSupport.java b/features/command/src/main/java/org/apache/karaf/features/command/completers/FeatureCompleterSupport.java
index 3dd1e2b..d01e5af 100644
--- a/features/command/src/main/java/org/apache/karaf/features/command/completers/FeatureCompleterSupport.java
+++ b/features/command/src/main/java/org/apache/karaf/features/command/completers/FeatureCompleterSupport.java
@@ -20,9 +20,11 @@ import java.util.List;
 
 import org.apache.karaf.features.Feature;
 import org.apache.karaf.features.FeaturesService;
-import org.apache.karaf.shell.console.Completer;
-import org.apache.karaf.shell.console.completer.StringsCompleter;
-import org.apache.karaf.shell.inject.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
 
 /**
  * Base completer for feature commands.
@@ -39,7 +41,7 @@ public abstract class FeatureCompleterSupport implements Completer {
         this.featuresService = featuresService;
     }
 
-    public int complete(final String buffer, final int cursor, @SuppressWarnings("rawtypes") final List candidates) {
+    public int complete(Session session, final CommandLine commandLine, final List<String> candidates) {
         StringsCompleter delegate = new StringsCompleter();
         try {
             for (Feature feature : featuresService.listFeatures()) {
@@ -50,7 +52,7 @@ public abstract class FeatureCompleterSupport implements Completer {
         } catch (Exception e) {
             // Ignore
         }
-        return delegate.complete(buffer, cursor, candidates);
+        return delegate.complete(session, commandLine, candidates);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/features/command/src/main/java/org/apache/karaf/features/command/completers/InstalledFeatureCompleter.java
----------------------------------------------------------------------
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/completers/InstalledFeatureCompleter.java b/features/command/src/main/java/org/apache/karaf/features/command/completers/InstalledFeatureCompleter.java
index e61940f..4c937db 100644
--- a/features/command/src/main/java/org/apache/karaf/features/command/completers/InstalledFeatureCompleter.java
+++ b/features/command/src/main/java/org/apache/karaf/features/command/completers/InstalledFeatureCompleter.java
@@ -17,7 +17,7 @@
 package org.apache.karaf.features.command.completers;
 
 import org.apache.karaf.features.Feature;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 /**
  * {@link org.apache.karaf.shell.console.Completer} for installed features.


[07/10] [KARAF-2805] Migrate shell, ssh, wrapper, kar, instance, features and bundle to the new API and switch the bin/shell script to use the new console

Posted by gn...@apache.org.
http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshTerminal.java
----------------------------------------------------------------------
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshTerminal.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshTerminal.java
index 9961c3c..fa24169 100644
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshTerminal.java
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshTerminal.java
@@ -18,26 +18,18 @@
  */
 package org.apache.karaf.shell.ssh;
 
-import jline.TerminalSupport;
+import org.apache.karaf.shell.api.console.Terminal;
 import org.apache.sshd.server.Environment;
 
-public class SshTerminal extends TerminalSupport {
+public class SshTerminal implements Terminal {
 
     private Environment environment;
 
 
     public SshTerminal(Environment environment) {
-        super(true);
-        setAnsiSupported(true);
         this.environment = environment;
     }
 
-    public void init() throws Exception {
-    }
-
-    public void restore() throws Exception {
-    }
-
     @Override
     public int getWidth() {
         int width = 0;
@@ -46,7 +38,7 @@ public class SshTerminal extends TerminalSupport {
         } catch (Throwable t) {
             // Ignore
         }
-        return width > 0 ? width : super.getWidth();
+        return width > 0 ? width : 80;
     }
 
     @Override
@@ -57,7 +49,21 @@ public class SshTerminal extends TerminalSupport {
         } catch (Throwable t) {
             // Ignore
         }
-        return height > 0 ? height : super.getHeight();
+        return height > 0 ? height : 24;
+    }
+
+    @Override
+    public boolean isAnsiSupported() {
+        return true;
+    }
+
+    @Override
+    public boolean isEchoEnabled() {
+        return true;
     }
 
+    @Override
+    public void setEchoEnabled(boolean enabled) {
+        // TODO: how to disable echo over ssh ?
+    }
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/UserAuthFactoriesFactory.java
----------------------------------------------------------------------
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/UserAuthFactoriesFactory.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/UserAuthFactoriesFactory.java
index ae49e61..94d71a3 100644
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/UserAuthFactoriesFactory.java
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/UserAuthFactoriesFactory.java
@@ -23,8 +23,6 @@ import org.apache.sshd.server.UserAuth;
 import org.apache.sshd.server.auth.UserAuthKeyboardInteractive;
 import org.apache.sshd.server.auth.UserAuthPassword;
 import org.apache.sshd.server.auth.UserAuthPublicKey;
-import org.osgi.service.blueprint.container.ComponentDefinitionException;
-import org.osgi.service.blueprint.container.ReifiedType;
 
 import java.lang.reflect.ParameterizedType;
 import java.util.ArrayList;
@@ -34,7 +32,7 @@ import java.util.Set;
 
 /**
  * <p>A factory for user authentication factories to set on
- * {@link SshServer#setUserAuthFactories(java.util.List)} based on a
+ * {@link org.apache.sshd.SshServer#setUserAuthFactories(java.util.List)} based on a
  * comma-separated list of authentication methods.</p>
  *
  * <p>Currently, the following methods are supported:</p>
@@ -55,33 +53,6 @@ public class UserAuthFactoriesFactory {
     private Set<String> methodSet;
     private List<NamedFactory<UserAuth>> factories;
 
-    public static Converter getConverter() {
-        return new Converter();
-    }
-
-    /**
-     * This blueprint type converter silently converts instances of
-     * <code>Class X implements NameFactory</code>
-     * to the reified type <code>cNameFactory</code>
-     * and therefore helps blueprint to set the returned factories on
-     * {@link SshServerAction#setUserAuthFactories(List)} without complaining
-     * about type conversion errors.
-     */
-    public static class Converter implements org.osgi.service.blueprint.container.Converter {
-
-        public boolean canConvert(Object sourceObject, ReifiedType targetType) {
-            return NamedFactory.class.isAssignableFrom(sourceObject.getClass())
-                    && UserAuth.class.equals(((ParameterizedType) sourceObject.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0])
-                    && NamedFactory.class.equals(targetType.getRawClass())
-                    && UserAuth.class.equals(targetType.getActualTypeArgument(0).getRawClass());
-        }
-
-        public Object convert(Object sourceObject, ReifiedType targetType) throws Exception {
-            return sourceObject;
-        }
-
-    }
-
     public void setAuthMethods(String methods) {
         this.methodSet = new HashSet<String>();
         this.factories = new ArrayList<NamedFactory<UserAuth>>();
@@ -94,7 +65,7 @@ public class UserAuthFactoriesFactory {
             } else if (PUBLICKEY_METHOD.equals(am)) {
                 this.factories.add(new UserAuthPublicKey.Factory());
             } else {
-                throw new ComponentDefinitionException("Invalid authentication method " + am + " specified");
+                throw new IllegalArgumentException("Invalid authentication method " + am + " specified");
             }
             this.methodSet.add(am);
         }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/util/SingleServiceTracker.java
----------------------------------------------------------------------
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/util/SingleServiceTracker.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/util/SingleServiceTracker.java
new file mode 100644
index 0000000..c883dc0
--- /dev/null
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/util/SingleServiceTracker.java
@@ -0,0 +1,171 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+package org.apache.karaf.shell.ssh.util;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+
+//This is from aries util
+public final class SingleServiceTracker<T> {
+    public static interface SingleServiceListener {
+        public void serviceFound();
+
+        public void serviceLost();
+
+        public void serviceReplaced();
+    }
+
+    private final BundleContext ctx;
+    private final String className;
+    private final AtomicReference<T> service = new AtomicReference<T>();
+    private final AtomicReference<ServiceReference> ref = new AtomicReference<ServiceReference>();
+    private final AtomicBoolean open = new AtomicBoolean(false);
+    private final SingleServiceListener serviceListener;
+    private String filterString;
+    private Filter filter;
+
+    private final ServiceListener listener = new ServiceListener() {
+        public void serviceChanged(ServiceEvent event) {
+            if (open.get()) {
+                if (event.getType() == ServiceEvent.UNREGISTERING) {
+                    ServiceReference deadRef = event.getServiceReference();
+                    if (deadRef.equals(ref.get())) {
+                        findMatchingReference(deadRef);
+                    }
+                } else if (event.getType() == ServiceEvent.REGISTERED && ref.get() == null) {
+                    findMatchingReference(null);
+                }
+            }
+        }
+    };
+
+    public SingleServiceTracker(BundleContext context, Class<T> clazz, SingleServiceListener sl) {
+        ctx = context;
+        this.className = clazz.getName();
+        serviceListener = sl;
+    }
+
+    public SingleServiceTracker(BundleContext context, Class<T> clazz, String filterString, SingleServiceListener sl) throws InvalidSyntaxException {
+        this(context, clazz, sl);
+        this.filterString = filterString;
+        if (filterString != null) filter = context.createFilter(filterString);
+    }
+
+    public T getService() {
+        return service.get();
+    }
+
+    public ServiceReference getServiceReference() {
+        return ref.get();
+    }
+
+    public void open() {
+        if (open.compareAndSet(false, true)) {
+            try {
+                String filterString = '(' + Constants.OBJECTCLASS + '=' + className + ')';
+                if (filter != null) filterString = "(&" + filterString + filter + ')';
+                ctx.addServiceListener(listener, filterString);
+                findMatchingReference(null);
+            } catch (InvalidSyntaxException e) {
+                // this can never happen. (famous last words :)
+            }
+        }
+    }
+
+    private void findMatchingReference(ServiceReference original) {
+        boolean clear = true;
+        ServiceReference ref = ctx.getServiceReference(className);
+        if (ref != null && (filter == null || filter.match(ref))) {
+            @SuppressWarnings("unchecked")
+            T service = (T) ctx.getService(ref);
+            if (service != null) {
+                clear = false;
+
+                // We do the unget out of the lock so we don't exit this class while holding a lock.
+                if (!!!update(original, ref, service)) {
+                    ctx.ungetService(ref);
+                }
+            }
+        } else if (original == null) {
+            clear = false;
+        }
+
+        if (clear) {
+            update(original, null, null);
+        }
+    }
+
+    private boolean update(ServiceReference deadRef, ServiceReference newRef, T service) {
+        boolean result = false;
+        int foundLostReplaced = -1;
+
+        // Make sure we don't try to get a lock on null
+        Object lock;
+
+        // we have to choose our lock.
+        if (newRef != null) lock = newRef;
+        else if (deadRef != null) lock = deadRef;
+        else lock = this;
+
+        // This lock is here to ensure that no two threads can set the ref and service
+        // at the same time.
+        synchronized (lock) {
+            if (open.get()) {
+                result = this.ref.compareAndSet(deadRef, newRef);
+                if (result) {
+                    this.service.set(service);
+
+                    if (deadRef == null && newRef != null) foundLostReplaced = 0;
+                    if (deadRef != null && newRef == null) foundLostReplaced = 1;
+                    if (deadRef != null && newRef != null) foundLostReplaced = 2;
+                }
+            }
+        }
+
+        if (serviceListener != null) {
+            if (foundLostReplaced == 0) serviceListener.serviceFound();
+            else if (foundLostReplaced == 1) serviceListener.serviceLost();
+            else if (foundLostReplaced == 2) serviceListener.serviceReplaced();
+        }
+
+        return result;
+    }
+
+    public void close() {
+        if (open.compareAndSet(true, false)) {
+            ctx.removeServiceListener(listener);
+
+            synchronized (this) {
+                ServiceReference deadRef = ref.getAndSet(null);
+                service.set(null);
+                if (deadRef != null) ctx.ungetService(deadRef);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/ssh/src/main/resources/META-INF/services/org/apache/karaf/shell/commands
----------------------------------------------------------------------
diff --git a/shell/ssh/src/main/resources/META-INF/services/org/apache/karaf/shell/commands b/shell/ssh/src/main/resources/META-INF/services/org/apache/karaf/shell/commands
new file mode 100644
index 0000000..44d0683
--- /dev/null
+++ b/shell/ssh/src/main/resources/META-INF/services/org/apache/karaf/shell/commands
@@ -0,0 +1,17 @@
+##---------------------------------------------------------------------------
+##  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.
+##---------------------------------------------------------------------------
+org.apache.karaf.shell.ssh.ActivatorNoOsgi

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/ssh/src/main/resources/OSGI-INF/blueprint/shell-ssh.xml
----------------------------------------------------------------------
diff --git a/shell/ssh/src/main/resources/OSGI-INF/blueprint/shell-ssh.xml b/shell/ssh/src/main/resources/OSGI-INF/blueprint/shell-ssh.xml
deleted file mode 100644
index 7a58288..0000000
--- a/shell/ssh/src/main/resources/OSGI-INF/blueprint/shell-ssh.xml
+++ /dev/null
@@ -1,149 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
-    Licensed to the Apache Software Foundation (ASF) under one or more
-    contributor license agreements.  See the NOTICE file distributed with
-    this work for additional information regarding copyright ownership.
-    The ASF licenses this file to You under the Apache License, Version 2.0
-    (the "License"); you may not use this file except in compliance with
-    the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
-
--->
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
-           xmlns:bp="http://www.osgi.org/xmlns/blueprint/v1.0.0"
-           xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0"
-           xmlns:ext="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0"
-           default-activation="lazy">
-
-    <type-converters>
-        <bean class="org.apache.karaf.shell.ssh.ShellFactoryImpl" factory-method="getConverter" />
-        <bean class="org.apache.karaf.shell.ssh.UserAuthFactoriesFactory" factory-method="getConverter" />
-    </type-converters>
-    
-    <reference id="consoleFactory" interface="org.apache.karaf.shell.console.factory.ConsoleFactory" />
-
-    <ext:property-placeholder placeholder-prefix="$[" placeholder-suffix="]">
-        <ext:default-properties>
-            <ext:property name="karaf.startRemoteShell" value="true" />
-        </ext:default-properties>
-    </ext:property-placeholder>
-
-    <cm:property-placeholder persistent-id="org.apache.karaf.shell" update-strategy="reload">
-        <cm:default-properties>
-            <cm:property name="sshPort" value="8101"/>
-            <cm:property name="sshHost" value="0.0.0.0"/>
-            <cm:property name="sshIdleTimeout" value="1800000"/>
-            <cm:property name="sshRealm" value="karaf"/>
-            <cm:property name="hostKey" value="$[karaf.base]/etc/host.key"/>
-            <cm:property name="authorizedKeys" value="$[karaf.base]/etc/authorized_keys"/>
-            <cm:property name="authMethods" value="keyboard-interactive,password,publickey"/>
-            <cm:property name="keySize" value="1024"/>
-            <cm:property name="algorithm" value="DSA"/>
-            <cm:property name="macs" value="hmac-sha1" />
-            <cm:property name="ciphers" value="aes256-ctr,aes192-ctr,aes128-ctr,arcfour256" />
-        </cm:default-properties>
-    </cm:property-placeholder>
-
-    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
-        <command>
-            <action class="org.apache.karaf.shell.ssh.SshAction">
-                <property name="sshClientFactory" ref="sshClientFactory" />
-            </action>
-        </command>
-        <command>
-            <action class="org.apache.karaf.shell.ssh.SshServerAction">
-                <property name="sshServerId">
-                    <bp:idref component-id="sshServer"/>
-                </property>
-            </action>
-        </command>
-    </command-bundle>
-    
-    <bean id="sshClientFactory" class="org.apache.karaf.shell.ssh.SshClientFactory">
-        <argument ref="agentFactory" />
-        <argument value="$[user.home]/.sshkaraf/known_hosts"/>
-    </bean>
-
-    <bean id="userAuthFactoriesFactory" class="org.apache.karaf.shell.ssh.UserAuthFactoriesFactory">
-        <property name="authMethods" value="${authMethods}"/>
-    </bean>
-
-    <bean id="sshServer" class="org.apache.sshd.SshServer" factory-method="setUpDefaultServer" scope="prototype">
-        <property name="port" value="${sshPort}"/>
-        <property name="host" value="${sshHost}"/>
-        <property name="macFactories">
-            <bean class="org.apache.karaf.shell.ssh.SshUtils" factory-method="buildMacs">
-                <argument value="${macs}" />
-            </bean>
-        </property>
-        <property name="cipherFactories">
-            <bean class="org.apache.karaf.shell.ssh.SshUtils" factory-method="buildCiphers">
-                <argument value="${ciphers}" />
-            </bean>
-        </property>
-        <property name="shellFactory">
-            <bean class="org.apache.karaf.shell.ssh.ShellFactoryImpl">
-            	<argument ref="consoleFactory"/>
-            </bean>
-        </property>
-        <property name="commandFactory">
-            <bean class="org.apache.sshd.server.command.ScpCommandFactory">
-                <argument>
-                    <bean class="org.apache.karaf.shell.ssh.ShellCommandFactory">
-                        <property name="commandProcessor" ref="commandProcessor"/>
-                    </bean>
-                </argument>
-            </bean>
-        </property>
-        <property name="subsystemFactories">
-            <list>
-                <bean class="org.apache.sshd.server.sftp.SftpSubsystem.Factory"/>
-            </list>
-        </property>
-        <property name="keyPairProvider" ref="keyPairProvider"/>
-        <property name="passwordAuthenticator" ref="authenticator"/>
-        <property name="publickeyAuthenticator" ref="authenticator"/>
-        <property name="fileSystemFactory">
-            <bean class="org.apache.karaf.shell.ssh.KarafFileSystemFactory"/>
-        </property>
-        <property name="userAuthFactories">
-            <bean factory-ref="userAuthFactoriesFactory" factory-method="getFactories"/>
-        </property>
-        <property name="agentFactory" ref="agentFactory" />
-    </bean>
-
-    <bean id="agentFactory" class="org.apache.karaf.shell.ssh.KarafAgentFactory">
-        <property name="bundleContext" ref="blueprintBundleContext" />
-    </bean>
-    <reference-list id="commandSessions" interface="org.apache.felix.service.command.CommandSession" availability="optional" activation="eager">
-        <reference-listener ref="agentFactory" bind-method="registerCommandSession" unbind-method="unregisterCommandSession" />
-    </reference-list>
-
-    <bean id="keyPairProvider" class="org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider">
-        <property name="path" value="${hostKey}"/>
-        <property name="keySize" value="${keySize}"/>
-        <property name="algorithm" value="${algorithm}"/>
-    </bean>
-
-    <bean id="authenticator" class="org.apache.karaf.shell.ssh.KarafJaasAuthenticator">
-        <property name="realm" value="${sshRealm}"/>
-    </bean>
-
-    <bean id="sshServerFactory" class="org.apache.karaf.shell.ssh.SshServerFactory" init-method="start"
-          destroy-method="stop" activation="eager">
-        <argument ref="sshServer"/>
-        <property name="start" value="$[karaf.startRemoteShell]"/>
-        <property name="idleTimeout" value="${sshIdleTimeout}"/>
-    </bean>
-
-    <reference id="commandProcessor" interface="org.apache.felix.service.command.CommandProcessor"/>
-
-</blueprint>

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/wrapper/command/pom.xml
----------------------------------------------------------------------
diff --git a/wrapper/command/pom.xml b/wrapper/command/pom.xml
index d70ff8c..a8d2fca 100644
--- a/wrapper/command/pom.xml
+++ b/wrapper/command/pom.xml
@@ -86,17 +86,11 @@
                             org.apache.karaf.wrapper,
                             javax.management,
                             javax.management.loading,
-                            org.apache.aries.blueprint,
-                            org.osgi.service.blueprint.container,
-                            org.osgi.service.blueprint.reflect,
-                            org.apache.felix.service.command,
-                            org.apache.karaf.shell.commands,
-                            org.apache.karaf.shell.console,
                             *
                         </Import-Package>
-                        <Private-Package>
-                            org.apache.karaf.wrapper.internal
-                        </Private-Package>
+                        <Karaf-Commands>
+                            org.apache.karaf.wrapper.commands
+                        </Karaf-Commands>
                     </instructions>
                 </configuration>
             </plugin>

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/wrapper/command/src/main/java/org/apache/karaf/wrapper/commands/Install.java
----------------------------------------------------------------------
diff --git a/wrapper/command/src/main/java/org/apache/karaf/wrapper/commands/Install.java b/wrapper/command/src/main/java/org/apache/karaf/wrapper/commands/Install.java
index 14c921a..346f6f1 100644
--- a/wrapper/command/src/main/java/org/apache/karaf/wrapper/commands/Install.java
+++ b/wrapper/command/src/main/java/org/apache/karaf/wrapper/commands/Install.java
@@ -18,21 +18,22 @@ package org.apache.karaf.wrapper.commands;
 
 import java.io.File;
 
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.inject.Reference;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.apache.karaf.wrapper.WrapperService;
-import org.apache.karaf.wrapper.internal.WrapperServiceImpl;
-import org.fusesource.jansi.Ansi;
+
+import static org.apache.karaf.shell.support.ansi.SimpleAnsi.INTENSITY_BOLD;
+import static org.apache.karaf.shell.support.ansi.SimpleAnsi.INTENSITY_NORMAL;
 
 /**
  * Installs the Karaf instance as a service in your operating system.
  */
 @Command(scope = "wrapper", name = "install", description = "Install the container as a system service in the OS.")
 @Service
-public class Install extends AbstractAction {
+public class Install implements Action {
 
 	@Option(name = "-n", aliases = { "--name" }, description = "The service name that will be used when installing the service. (Default: karaf)", required = false, multiValued = false)
 	private String name = "karaf";
@@ -47,13 +48,10 @@ public class Install extends AbstractAction {
 	private String startType = "AUTO_START";
 
     @Reference
-	private WrapperService wrapperService = new WrapperServiceImpl();
-
-	public void setWrapperService(WrapperService wrapperService) {
-		this.wrapperService = wrapperService;
-	}
+	private WrapperService wrapperService;
 
-	protected Object doExecute() throws Exception {
+    @Override
+	public Object execute() throws Exception {
         File[] wrapperPaths = wrapperService.install(name, displayName, description, startType);
 
         String os = System.getProperty("os.name", "Unknown");
@@ -67,7 +65,7 @@ public class Install extends AbstractAction {
         System.out.println("");
         if (os.startsWith("Win")) {
             System.out.println("");
-            System.out.println(Ansi.ansi().a(Ansi.Attribute.INTENSITY_BOLD).a("MS Windows system detected:").a(Ansi.Attribute.RESET).toString());
+            System.out.println(INTENSITY_BOLD + "MS Windows system detected:" + INTENSITY_NORMAL);
             System.out.println("To install the service, run: ");
             System.out.println("  C:> " + serviceFile.getPath() + " install");
             System.out.println("");
@@ -82,7 +80,7 @@ public class Install extends AbstractAction {
             System.out.println("");
         } else if (os.startsWith("Mac OS X")) {
             System.out.println("");
-            System.out.println(Ansi.ansi().a(Ansi.Attribute.INTENSITY_BOLD).a("Mac OS X system detected:").a(Ansi.Attribute.RESET).toString());
+            System.out.println(INTENSITY_BOLD + "Mac OS X system detected:" + INTENSITY_NORMAL);
             System.out.println("to add bin/org.apache.karaf.KARAF as user service move this file into ~/Library/LaunchAgents/");  
             System.out.println("> mv bin/org.apache.karaf.KARAF.plist ~/Library/LaunchAgents/");
             System.out.println("");
@@ -110,7 +108,7 @@ public class Install extends AbstractAction {
 
             if (redhatRelease.exists()) {
                 System.out.println("");
-                System.out.println(Ansi.ansi().a(Ansi.Attribute.INTENSITY_BOLD).a("RedHat/Fedora/CentOS Linux system detected:").a(Ansi.Attribute.RESET).toString());
+                System.out.println(INTENSITY_BOLD + "RedHat/Fedora/CentOS Linux system detected:" + INTENSITY_NORMAL);
                 System.out.println("  To install the service:");
                 System.out.println("    $ ln -s " + serviceFile.getPath() + " /etc/init.d/");
                 System.out.println("    $ chkconfig " + serviceFile.getName() + " --add");
@@ -132,7 +130,7 @@ public class Install extends AbstractAction {
                 System.out.println("    $ rm /etc/init.d/" + serviceFile.getPath());
             } else if (debianVersion.exists()) {
                 System.out.println("");
-                System.out.println(Ansi.ansi().a(Ansi.Attribute.INTENSITY_BOLD).a("Ubuntu/Debian Linux system detected:").a(Ansi.Attribute.RESET).toString());
+                System.out.println(INTENSITY_BOLD + "Ubuntu/Debian Linux system detected:" + INTENSITY_NORMAL);
                 System.out.println("  To install the service:");
                 System.out.println("    $ ln -s " + serviceFile.getPath() + " /etc/init.d/");
                 System.out.println("");
@@ -152,7 +150,7 @@ public class Install extends AbstractAction {
                 System.out.println("    $ rm /etc/init.d/" + serviceFile.getName());
             } else {
 				System.out.println("");
-                System.out.println(Ansi.ansi().a(Ansi.Attribute.INTENSITY_BOLD).a("On Redhat/Fedora/CentOS Systems:").a(Ansi.Attribute.RESET).toString());
+                System.out.println(INTENSITY_BOLD + "On Redhat/Fedora/CentOS Systems:" + INTENSITY_NORMAL);
 				System.out.println("  To install the service:");
 				System.out.println("    $ ln -s "+serviceFile.getPath()+" /etc/init.d/");
 				System.out.println("    $ chkconfig "+serviceFile.getName()+" --add");
@@ -174,7 +172,7 @@ public class Install extends AbstractAction {
 				System.out.println("    $ rm /etc/init.d/"+serviceFile.getName());
 
 				System.out.println("");
-                System.out.println(Ansi.ansi().a(Ansi.Attribute.INTENSITY_BOLD).a("On Ubuntu/Debian Systems:").a(Ansi.Attribute.RESET).toString());
+                System.out.println(INTENSITY_BOLD + "On Ubuntu/Debian Systems:" + INTENSITY_NORMAL);
 				System.out.println("  To install the service:");
 				System.out.println("    $ ln -s "+serviceFile.getPath()+" /etc/init.d/");
 				System.out.println("");

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/wrapper/command/src/main/resources/META-INF/services/org/apache/karaf/shell/commands
----------------------------------------------------------------------
diff --git a/wrapper/command/src/main/resources/META-INF/services/org/apache/karaf/shell/commands b/wrapper/command/src/main/resources/META-INF/services/org/apache/karaf/shell/commands
index d89d5da..73b329d 100644
--- a/wrapper/command/src/main/resources/META-INF/services/org/apache/karaf/shell/commands
+++ b/wrapper/command/src/main/resources/META-INF/services/org/apache/karaf/shell/commands
@@ -14,4 +14,5 @@
 ##  See the License for the specific language governing permissions and
 ##  limitations under the License.
 ##---------------------------------------------------------------------------
+org.apache.karaf.wrapper.internal.WrapperServiceImpl
 org.apache.karaf.wrapper.commands.Install
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/wrapper/command/src/main/resources/OSGI-INF/blueprint/wrapper-commands.xml
----------------------------------------------------------------------
diff --git a/wrapper/command/src/main/resources/OSGI-INF/blueprint/wrapper-commands.xml b/wrapper/command/src/main/resources/OSGI-INF/blueprint/wrapper-commands.xml
deleted file mode 100644
index f29e8a0..0000000
--- a/wrapper/command/src/main/resources/OSGI-INF/blueprint/wrapper-commands.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
-    Licensed to the Apache Software Foundation (ASF) under one or more
-    contributor license agreements.  See the NOTICE file distributed with
-    this work for additional information regarding copyright ownership.
-    The ASF licenses this file to You under the Apache License, Version 2.0
-    (the "License"); you may not use this file except in compliance with
-    the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
-
--->
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
-        default-activation="lazy">
-
-    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.2.0"
-                    scan="org.apache.karaf.wrapper.commands.*" />
-
-</blueprint>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/wrapper/core/pom.xml
----------------------------------------------------------------------
diff --git a/wrapper/core/pom.xml b/wrapper/core/pom.xml
index 982433a..f6f51d1 100644
--- a/wrapper/core/pom.xml
+++ b/wrapper/core/pom.xml
@@ -51,7 +51,7 @@
         </dependency>
         <dependency>
             <groupId>org.apache.karaf.shell</groupId>
-            <artifactId>org.apache.karaf.shell.console</artifactId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
         </dependency>
         <dependency>
             <groupId>tanukisoft</groupId>


[09/10] [KARAF-2805] Migrate shell, ssh, wrapper, kar, instance, features and bundle to the new API and switch the bin/shell script to use the new console

Posted by gn...@apache.org.
http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/features/command/src/main/java/org/apache/karaf/features/command/completers/InstalledRepoNameCompleter.java
----------------------------------------------------------------------
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/completers/InstalledRepoNameCompleter.java b/features/command/src/main/java/org/apache/karaf/features/command/completers/InstalledRepoNameCompleter.java
index cb43813..94e4cf7 100644
--- a/features/command/src/main/java/org/apache/karaf/features/command/completers/InstalledRepoNameCompleter.java
+++ b/features/command/src/main/java/org/apache/karaf/features/command/completers/InstalledRepoNameCompleter.java
@@ -20,10 +20,12 @@ import java.util.List;
 
 import org.apache.karaf.features.FeaturesService;
 import org.apache.karaf.features.Repository;
-import org.apache.karaf.shell.console.Completer;
-import org.apache.karaf.shell.console.completer.StringsCompleter;
-import org.apache.karaf.shell.inject.Reference;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
 
 /**
  * {@link Completer} for Feature Repository URLs.
@@ -41,7 +43,7 @@ public class InstalledRepoNameCompleter implements Completer {
         this.featuresService = featuresService;
     }
 
-    public int complete(final String buffer, final int cursor, @SuppressWarnings("rawtypes") final List candidates) {
+    public int complete(Session session, final CommandLine commandLine, final List<String> candidates) {
         StringsCompleter delegate = new StringsCompleter();
         try {
             for (Repository repository : featuresService.listRepositories()) {
@@ -50,7 +52,7 @@ public class InstalledRepoNameCompleter implements Completer {
         } catch (Exception e) {
             // Ignore
         }
-        return delegate.complete(buffer, cursor, candidates);
+        return delegate.complete(session, commandLine, candidates);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/features/command/src/main/java/org/apache/karaf/features/command/completers/InstalledRepoUriCompleter.java
----------------------------------------------------------------------
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/completers/InstalledRepoUriCompleter.java b/features/command/src/main/java/org/apache/karaf/features/command/completers/InstalledRepoUriCompleter.java
index 9a80e12..7a760c2 100644
--- a/features/command/src/main/java/org/apache/karaf/features/command/completers/InstalledRepoUriCompleter.java
+++ b/features/command/src/main/java/org/apache/karaf/features/command/completers/InstalledRepoUriCompleter.java
@@ -18,12 +18,14 @@ package org.apache.karaf.features.command.completers;
 
 import java.util.List;
 
-import org.apache.karaf.shell.console.completer.StringsCompleter;
-import org.apache.karaf.shell.console.Completer;
 import org.apache.karaf.features.FeaturesService;
 import org.apache.karaf.features.Repository;
-import org.apache.karaf.shell.inject.Reference;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
 
 /**
  * {@link Completer} for Feature Repository URLs.
@@ -42,7 +44,7 @@ public class InstalledRepoUriCompleter implements Completer {
         this.featuresService = featuresService;
     }
 
-    public int complete(final String buffer, final int cursor, @SuppressWarnings("rawtypes") final List candidates) {
+    public int complete(Session session, final CommandLine commandLine, final List<String> candidates) {
         StringsCompleter delegate = new StringsCompleter();
         try {
             for (Repository repository : featuresService.listRepositories()) {
@@ -51,7 +53,7 @@ public class InstalledRepoUriCompleter implements Completer {
         } catch (Exception e) {
             // Ignore
         }
-        return delegate.complete(buffer, cursor, candidates);
+        return delegate.complete(session, commandLine, candidates);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/features/command/src/main/resources/OSGI-INF/blueprint/features-command.xml
----------------------------------------------------------------------
diff --git a/features/command/src/main/resources/OSGI-INF/blueprint/features-command.xml b/features/command/src/main/resources/OSGI-INF/blueprint/features-command.xml
deleted file mode 100644
index 8053312..0000000
--- a/features/command/src/main/resources/OSGI-INF/blueprint/features-command.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
-    Licensed to the Apache Software Foundation (ASF) under one or more
-    contributor license agreements.  See the NOTICE file distributed with
-    this work for additional information regarding copyright ownership.
-    The ASF licenses this file to You under the Apache License, Version 2.0
-    (the "License"); you may not use this file except in compliance with
-    the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
-
--->
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" default-activation="lazy">
-
-    <!-- Commands -->
-
-    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.2.0"
-                    scan="org.apache.karaf.features.command.*" />
-
-</blueprint>

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/instance/command/pom.xml
----------------------------------------------------------------------
diff --git a/instance/command/pom.xml b/instance/command/pom.xml
index 2c75801..52058cd 100644
--- a/instance/command/pom.xml
+++ b/instance/command/pom.xml
@@ -68,7 +68,7 @@
 
         <dependency>
             <groupId>org.apache.karaf.shell</groupId>
-            <artifactId>org.apache.karaf.shell.console</artifactId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
         </dependency>
 
         <dependency>
@@ -120,7 +120,8 @@
                              as Execute.java accesses the InstanceServiceImpl and jansi
                              (but only outside OSGi) -->
                         <Import-Package>
-                        	!org.apache.karaf.instance.core.internal,
+                        	!org.apache.karaf.shell.impl.action.command,
+                            !org.apache.karaf.instance.core.internal,
                             !org.fusesource.jansi,
                         	*
                         </Import-Package>
@@ -129,6 +130,7 @@
                             org.apache.karaf.instance.command,
                             org.apache.karaf.instance.command.completers
                         </Private-Package>
+                        <Karaf-Commands>org.apache.karaf.instance.command.*</Karaf-Commands>
                     </instructions>
                 </configuration>
             </plugin>

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/instance/command/src/main/java/org/apache/karaf/instance/command/ChangeOptsCommand.java
----------------------------------------------------------------------
diff --git a/instance/command/src/main/java/org/apache/karaf/instance/command/ChangeOptsCommand.java b/instance/command/src/main/java/org/apache/karaf/instance/command/ChangeOptsCommand.java
index e2faee1..c526c07 100644
--- a/instance/command/src/main/java/org/apache/karaf/instance/command/ChangeOptsCommand.java
+++ b/instance/command/src/main/java/org/apache/karaf/instance/command/ChangeOptsCommand.java
@@ -17,17 +17,17 @@
 package org.apache.karaf.instance.command;
 
 import org.apache.karaf.instance.command.completers.InstanceCompleter;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Completer;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 @Command(scope = "instance", name = "opts-change", description = "Changes the Java options of an existing container instance.")
 @Service
 public class ChangeOptsCommand extends InstanceCommandSupport {
 
     @Argument(index = 0, name = "name", description="The name of the container instance", required = true, multiValued = false)
-    @Completer(InstanceCompleter.class)
+    @Completion(InstanceCompleter.class)
     private String instance = null;
 
     @Argument(index = 1, name = "javaOpts", description = "The new Java options to set", required = true, multiValued = false)

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/instance/command/src/main/java/org/apache/karaf/instance/command/ChangeRmiRegistryPortCommand.java
----------------------------------------------------------------------
diff --git a/instance/command/src/main/java/org/apache/karaf/instance/command/ChangeRmiRegistryPortCommand.java b/instance/command/src/main/java/org/apache/karaf/instance/command/ChangeRmiRegistryPortCommand.java
index 148bc93..de5bfaf 100644
--- a/instance/command/src/main/java/org/apache/karaf/instance/command/ChangeRmiRegistryPortCommand.java
+++ b/instance/command/src/main/java/org/apache/karaf/instance/command/ChangeRmiRegistryPortCommand.java
@@ -17,17 +17,17 @@
 package org.apache.karaf.instance.command;
 
 import org.apache.karaf.instance.command.completers.InstanceCompleter;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Completer;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 @Command(scope = "instance", name = "rmi-registry-port-change", description = "Changes the RMI registry port (used by management layer) of an existing container instance.")
 @Service
 public class ChangeRmiRegistryPortCommand extends InstanceCommandSupport {
 
     @Argument(index = 0, name = "name", description = "The name of the container instance", required = true, multiValued = false)
-    @Completer(InstanceCompleter.class)
+    @Completion(InstanceCompleter.class)
     private String instance = null;
 
     @Argument(index = 1, name = "port", description = "The new RMI registry port to set", required = true, multiValued = false)

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/instance/command/src/main/java/org/apache/karaf/instance/command/ChangeRmiServerPortCommand.java
----------------------------------------------------------------------
diff --git a/instance/command/src/main/java/org/apache/karaf/instance/command/ChangeRmiServerPortCommand.java b/instance/command/src/main/java/org/apache/karaf/instance/command/ChangeRmiServerPortCommand.java
index 3b6fdc4..0ac2cf7 100644
--- a/instance/command/src/main/java/org/apache/karaf/instance/command/ChangeRmiServerPortCommand.java
+++ b/instance/command/src/main/java/org/apache/karaf/instance/command/ChangeRmiServerPortCommand.java
@@ -17,17 +17,17 @@
 package org.apache.karaf.instance.command;
 
 import org.apache.karaf.instance.command.completers.InstanceCompleter;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Completer;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 @Command(scope = "instance", name = "rmi-server-port-change", description = "Changes the RMI server port (used by management layer) of an existing instance.")
 @Service
 public class ChangeRmiServerPortCommand extends InstanceCommandSupport {
 
     @Argument(index = 0, name = "name", description = "The name of the container instance", required = true, multiValued = false)
-    @Completer(InstanceCompleter.class)
+    @Completion(InstanceCompleter.class)
     private String instance = null;
 
     @Argument(index = 1, name = "port", description = "The new RMI server port to set", required = true, multiValued = false)

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/instance/command/src/main/java/org/apache/karaf/instance/command/ChangeSshPortCommand.java
----------------------------------------------------------------------
diff --git a/instance/command/src/main/java/org/apache/karaf/instance/command/ChangeSshPortCommand.java b/instance/command/src/main/java/org/apache/karaf/instance/command/ChangeSshPortCommand.java
index 01df8ae..17ad26c 100644
--- a/instance/command/src/main/java/org/apache/karaf/instance/command/ChangeSshPortCommand.java
+++ b/instance/command/src/main/java/org/apache/karaf/instance/command/ChangeSshPortCommand.java
@@ -17,17 +17,17 @@
 package org.apache.karaf.instance.command;
 
 import org.apache.karaf.instance.command.completers.InstanceCompleter;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Completer;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 @Command(scope = "instance", name = "ssh-port-change", description = "Changes the secure shell port of an existing container instance.")
 @Service
 public class ChangeSshPortCommand extends InstanceCommandSupport {
 
     @Argument(index = 0, name = "name", description="The name of the container instance", required = true, multiValued = false)
-    @Completer(InstanceCompleter.class)
+    @Completion(InstanceCompleter.class)
     private String instance = null;
 
     @Argument(index = 1, name = "port", description = "The new secure shell port to set", required = true, multiValued = false)

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/instance/command/src/main/java/org/apache/karaf/instance/command/CloneCommand.java
----------------------------------------------------------------------
diff --git a/instance/command/src/main/java/org/apache/karaf/instance/command/CloneCommand.java b/instance/command/src/main/java/org/apache/karaf/instance/command/CloneCommand.java
index e5626e0..e4f28c5 100644
--- a/instance/command/src/main/java/org/apache/karaf/instance/command/CloneCommand.java
+++ b/instance/command/src/main/java/org/apache/karaf/instance/command/CloneCommand.java
@@ -17,12 +17,12 @@
 package org.apache.karaf.instance.command;
 
 import org.apache.karaf.instance.command.completers.InstanceCompleter;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Completer;
-import org.apache.karaf.shell.commands.Option;
 import org.apache.karaf.instance.core.InstanceSettings;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 /**
  * Clone an existing instance.
@@ -50,7 +50,7 @@ public class CloneCommand extends InstanceCommandSupport {
     boolean verbose = false;
 
     @Argument(index = 0, name = "name", description = "The name of the source container instance", required = true, multiValued = false)
-    @Completer(InstanceCompleter.class)
+    @Completion(InstanceCompleter.class)
     String name;
 
     @Argument(index = 1, name = "cloneName", description = "The name of the cloned container instance", required = true, multiValued = false)

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/instance/command/src/main/java/org/apache/karaf/instance/command/ConnectCommand.java
----------------------------------------------------------------------
diff --git a/instance/command/src/main/java/org/apache/karaf/instance/command/ConnectCommand.java b/instance/command/src/main/java/org/apache/karaf/instance/command/ConnectCommand.java
index 294c348..c81a3eb 100644
--- a/instance/command/src/main/java/org/apache/karaf/instance/command/ConnectCommand.java
+++ b/instance/command/src/main/java/org/apache/karaf/instance/command/ConnectCommand.java
@@ -21,11 +21,13 @@ package org.apache.karaf.instance.command;
 import java.util.List;
 
 import org.apache.karaf.instance.command.completers.InstanceCompleter;
-import org.apache.karaf.shell.commands.Completer;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.Session;
 
 @Command(scope = "instance", name = "connect", description = "Connects to an existing container instance.")
 @Service
@@ -38,12 +40,15 @@ public class ConnectCommand extends InstanceCommandSupport {
     private String password;
 
     @Argument(index = 0, name="name", description="The name of the container instance", required = true, multiValued = false)
-    @Completer(InstanceCompleter.class)
+    @Completion(InstanceCompleter.class)
     private String instance = null;
 
     @Argument(index = 1, name = "command", description = "Optional command to execute", required = false, multiValued = true)
     private List<String> command;
 
+    @Reference
+    Session session;
+
     protected Object doExecute() throws Exception {
         String cmdStr = "";
         if (command != null) {

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/instance/command/src/main/java/org/apache/karaf/instance/command/CreateCommand.java
----------------------------------------------------------------------
diff --git a/instance/command/src/main/java/org/apache/karaf/instance/command/CreateCommand.java b/instance/command/src/main/java/org/apache/karaf/instance/command/CreateCommand.java
index af3aebd..0dcc3ac 100644
--- a/instance/command/src/main/java/org/apache/karaf/instance/command/CreateCommand.java
+++ b/instance/command/src/main/java/org/apache/karaf/instance/command/CreateCommand.java
@@ -20,12 +20,12 @@ import java.util.List;
 
 import org.apache.karaf.features.command.completers.AllFeatureCompleter;
 import org.apache.karaf.features.command.completers.InstalledRepoUriCompleter;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Completer;
-import org.apache.karaf.shell.commands.Option;
 import org.apache.karaf.instance.core.InstanceSettings;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 /**
  * Creates a new instance.
@@ -51,12 +51,12 @@ public class CreateCommand extends InstanceCommandSupport
     
     @Option(name = "-f", aliases = {"--feature"},
             description = "Initial features. This option can be specified multiple times to enable multiple initial features", required = false, multiValued = true)
-    @Completer(AllFeatureCompleter.class)
+    @Completion(AllFeatureCompleter.class)
     List<String> features;
     
     @Option(name = "-furl", aliases = {"--featureURL"}, 
             description = "Additional feature descriptor URLs. This option can be specified multiple times to add multiple URLs", required = false, multiValued = true)
-    @Completer(InstalledRepoUriCompleter.class)
+    @Completion(InstalledRepoUriCompleter.class)
     List<String> featureURLs;
 
     @Option(name = "-v", aliases = {"--verbose"}, description = "Display actions performed by the command (disabled by default)", required = false, multiValued = false)

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/instance/command/src/main/java/org/apache/karaf/instance/command/DestroyCommand.java
----------------------------------------------------------------------
diff --git a/instance/command/src/main/java/org/apache/karaf/instance/command/DestroyCommand.java b/instance/command/src/main/java/org/apache/karaf/instance/command/DestroyCommand.java
index dbb1659..fb2964f 100644
--- a/instance/command/src/main/java/org/apache/karaf/instance/command/DestroyCommand.java
+++ b/instance/command/src/main/java/org/apache/karaf/instance/command/DestroyCommand.java
@@ -17,10 +17,10 @@
 package org.apache.karaf.instance.command;
 
 import org.apache.karaf.instance.command.completers.InstanceCompleter;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Completer;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 /**
  * Destroy an existing instance.
@@ -30,7 +30,7 @@ import org.apache.karaf.shell.inject.Service;
 public class DestroyCommand extends InstanceCommandSupport
 {
     @Argument(index = 0, name = "name", description= "The name of the container instance to destroy", required = true, multiValued = false)
-    @Completer(InstanceCompleter.class)
+    @Completion(InstanceCompleter.class)
     private String instance = null;
 
     protected Object doExecute() throws Exception {

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/instance/command/src/main/java/org/apache/karaf/instance/command/InstanceCommandSupport.java
----------------------------------------------------------------------
diff --git a/instance/command/src/main/java/org/apache/karaf/instance/command/InstanceCommandSupport.java b/instance/command/src/main/java/org/apache/karaf/instance/command/InstanceCommandSupport.java
index b320b3c..3b0b535 100644
--- a/instance/command/src/main/java/org/apache/karaf/instance/command/InstanceCommandSupport.java
+++ b/instance/command/src/main/java/org/apache/karaf/instance/command/InstanceCommandSupport.java
@@ -18,10 +18,10 @@ package org.apache.karaf.instance.command;
 
 import org.apache.karaf.instance.core.Instance;
 import org.apache.karaf.instance.core.InstanceService;
-import org.apache.karaf.shell.console.OsgiCommandSupport;
-import org.apache.karaf.shell.inject.Reference;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
 
-public abstract class InstanceCommandSupport extends OsgiCommandSupport {
+public abstract class InstanceCommandSupport implements Action {
 
     @Reference
     private InstanceService instanceService;
@@ -42,4 +42,10 @@ public abstract class InstanceCommandSupport extends OsgiCommandSupport {
         return i;
     }
 
+    @Override
+    public Object execute() throws Exception {
+        return doExecute();
+    }
+
+    protected abstract Object doExecute() throws Exception;
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/instance/command/src/main/java/org/apache/karaf/instance/command/ListCommand.java
----------------------------------------------------------------------
diff --git a/instance/command/src/main/java/org/apache/karaf/instance/command/ListCommand.java b/instance/command/src/main/java/org/apache/karaf/instance/command/ListCommand.java
index b15f8b1..368259a 100644
--- a/instance/command/src/main/java/org/apache/karaf/instance/command/ListCommand.java
+++ b/instance/command/src/main/java/org/apache/karaf/instance/command/ListCommand.java
@@ -17,10 +17,10 @@
 package org.apache.karaf.instance.command;
 
 import org.apache.karaf.instance.core.Instance;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.inject.Service;
 import org.apache.karaf.shell.table.ShellTable;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 @Command(scope = "instance", name = "list", description = "Lists all existing container instances.")
 @Service

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/instance/command/src/main/java/org/apache/karaf/instance/command/RenameCommand.java
----------------------------------------------------------------------
diff --git a/instance/command/src/main/java/org/apache/karaf/instance/command/RenameCommand.java b/instance/command/src/main/java/org/apache/karaf/instance/command/RenameCommand.java
index 872d954..ef68400 100644
--- a/instance/command/src/main/java/org/apache/karaf/instance/command/RenameCommand.java
+++ b/instance/command/src/main/java/org/apache/karaf/instance/command/RenameCommand.java
@@ -17,11 +17,11 @@
 package org.apache.karaf.instance.command;
 
 import org.apache.karaf.instance.command.completers.InstanceCompleter;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Completer;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 @Command(scope = "instance", name = "rename", description = "Rename an existing container instance.")
 @Service
@@ -31,7 +31,7 @@ public class RenameCommand extends InstanceCommandSupport {
     boolean verbose = false;
 
     @Argument(index = 0, name = "name", description = "The name of the container instance to rename", required = true, multiValued = false)
-    @Completer(InstanceCompleter.class)
+    @Completion(InstanceCompleter.class)
     String instance = null;
 
     @Argument(index = 1, name = "new-name", description = "The new name of the container instance", required = true, multiValued = false)

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/instance/command/src/main/java/org/apache/karaf/instance/command/StartCommand.java
----------------------------------------------------------------------
diff --git a/instance/command/src/main/java/org/apache/karaf/instance/command/StartCommand.java b/instance/command/src/main/java/org/apache/karaf/instance/command/StartCommand.java
index 9b6e464..8c52d5e 100644
--- a/instance/command/src/main/java/org/apache/karaf/instance/command/StartCommand.java
+++ b/instance/command/src/main/java/org/apache/karaf/instance/command/StartCommand.java
@@ -17,12 +17,12 @@
 package org.apache.karaf.instance.command;
 
 import org.apache.karaf.instance.command.completers.InstanceCompleter;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Completer;
-import org.apache.karaf.shell.commands.Option;
 import org.apache.karaf.instance.core.Instance;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 @Command(scope = "instance", name = "start", description = "Start an existing container instance.")
 @Service
@@ -38,7 +38,7 @@ public class StartCommand extends InstanceCommandSupport {
     private boolean wait;
 
     @Argument(index = 0, name = "name", description = "The name of the container instance", required = true, multiValued = false)
-    @Completer(InstanceCompleter.class)
+    @Completion(InstanceCompleter.class)
     private String instance = null;
 
     static final String DEBUG_OPTS = " -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005";

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/instance/command/src/main/java/org/apache/karaf/instance/command/StatusCommand.java
----------------------------------------------------------------------
diff --git a/instance/command/src/main/java/org/apache/karaf/instance/command/StatusCommand.java b/instance/command/src/main/java/org/apache/karaf/instance/command/StatusCommand.java
index 760dffb..82100e1 100644
--- a/instance/command/src/main/java/org/apache/karaf/instance/command/StatusCommand.java
+++ b/instance/command/src/main/java/org/apache/karaf/instance/command/StatusCommand.java
@@ -18,18 +18,17 @@ package org.apache.karaf.instance.command;
 
 import org.apache.karaf.instance.command.completers.InstanceCompleter;
 import org.apache.karaf.instance.core.Instance;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Completer;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 @Command(scope = "instance", name = "status", description = "Check the current status of an instance.")
 @Service
 public class StatusCommand extends InstanceCommandSupport {
 
     @Argument(index = 0, name = "name", description = "The name of the instance", required = true, multiValued = false)
-    @Completer(InstanceCompleter.class)
+    @Completion(InstanceCompleter.class)
     private String name;
 
     protected Object doExecute() throws Exception {

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/instance/command/src/main/java/org/apache/karaf/instance/command/StopCommand.java
----------------------------------------------------------------------
diff --git a/instance/command/src/main/java/org/apache/karaf/instance/command/StopCommand.java b/instance/command/src/main/java/org/apache/karaf/instance/command/StopCommand.java
index 4d540b2..6c8de10 100644
--- a/instance/command/src/main/java/org/apache/karaf/instance/command/StopCommand.java
+++ b/instance/command/src/main/java/org/apache/karaf/instance/command/StopCommand.java
@@ -17,17 +17,17 @@
 package org.apache.karaf.instance.command;
 
 import org.apache.karaf.instance.command.completers.InstanceCompleter;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Completer;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 @Command(scope = "instance", name = "stop", description = "Stop an existing container instance.")
 @Service
 public class StopCommand extends InstanceCommandSupport {
 
     @Argument(index = 0, name = "name", description = "The name of the container instance", required = true, multiValued = false)
-    @Completer(InstanceCompleter.class)
+    @Completion(InstanceCompleter.class)
     private String instance = null;
 
     protected Object doExecute() throws Exception {

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/instance/command/src/main/java/org/apache/karaf/instance/command/completers/InstanceCompleter.java
----------------------------------------------------------------------
diff --git a/instance/command/src/main/java/org/apache/karaf/instance/command/completers/InstanceCompleter.java b/instance/command/src/main/java/org/apache/karaf/instance/command/completers/InstanceCompleter.java
index 5d8cc71..4468374 100644
--- a/instance/command/src/main/java/org/apache/karaf/instance/command/completers/InstanceCompleter.java
+++ b/instance/command/src/main/java/org/apache/karaf/instance/command/completers/InstanceCompleter.java
@@ -20,10 +20,12 @@ import java.util.List;
 
 import org.apache.karaf.instance.core.Instance;
 import org.apache.karaf.instance.core.InstanceService;
-import org.apache.karaf.shell.console.completer.StringsCompleter;
-import org.apache.karaf.shell.console.Completer;
-import org.apache.karaf.shell.inject.Reference;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
 
 /**
  * Displays a list of configured server instances for the instance commands.
@@ -39,12 +41,12 @@ public class InstanceCompleter implements Completer {
         this.instanceService = instanceService;
     }
 
-    public int complete(String buffer, int cursor, List candidates) {
+    public int complete(Session session, CommandLine commandLine, List<String> candidates) {
         StringsCompleter delegate = new StringsCompleter();
         for (Instance instance : instanceService.getInstances()) {
             delegate.getStrings().add(instance.getName());
         }
-        return delegate.complete(buffer, cursor, candidates);
+        return delegate.complete(session, commandLine, candidates);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/instance/command/src/main/java/org/apache/karaf/instance/main/Execute.java
----------------------------------------------------------------------
diff --git a/instance/command/src/main/java/org/apache/karaf/instance/main/Execute.java b/instance/command/src/main/java/org/apache/karaf/instance/main/Execute.java
index 1556cef..591873e 100644
--- a/instance/command/src/main/java/org/apache/karaf/instance/main/Execute.java
+++ b/instance/command/src/main/java/org/apache/karaf/instance/main/Execute.java
@@ -23,12 +23,23 @@ import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
 
-import org.apache.karaf.instance.command.*;
-import org.apache.karaf.shell.commands.Action;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.basic.DefaultActionPreparator;
+import org.apache.karaf.instance.command.ChangeOptsCommand;
+import org.apache.karaf.instance.command.ChangeRmiRegistryPortCommand;
+import org.apache.karaf.instance.command.ChangeRmiServerPortCommand;
 import org.apache.karaf.instance.command.ChangeSshPortCommand;
+import org.apache.karaf.instance.command.CloneCommand;
+import org.apache.karaf.instance.command.CreateCommand;
+import org.apache.karaf.instance.command.DestroyCommand;
+import org.apache.karaf.instance.command.InstanceCommandSupport;
+import org.apache.karaf.instance.command.ListCommand;
+import org.apache.karaf.instance.command.RenameCommand;
+import org.apache.karaf.instance.command.StartCommand;
+import org.apache.karaf.instance.command.StatusCommand;
+import org.apache.karaf.instance.command.StopCommand;
 import org.apache.karaf.instance.core.internal.InstanceServiceImpl;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.impl.action.command.DefaultActionPreparator;
 import org.fusesource.jansi.AnsiConsole;
 
 public class Execute {
@@ -136,7 +147,7 @@ public class Execute {
         InstanceServiceImpl instanceService = new InstanceServiceImpl();
         instanceService.setStorageLocation(storageFile);
         command.setInstanceService(instanceService);
-        command.execute(null);
+        command.execute();
     }
 
     private static void listCommands() {

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/instance/command/src/main/resources/META-INF/services/org/apache/karaf/shell/commands
----------------------------------------------------------------------
diff --git a/instance/command/src/main/resources/META-INF/services/org/apache/karaf/shell/commands b/instance/command/src/main/resources/META-INF/services/org/apache/karaf/shell/commands
new file mode 100644
index 0000000..92245c7
--- /dev/null
+++ b/instance/command/src/main/resources/META-INF/services/org/apache/karaf/shell/commands
@@ -0,0 +1,30 @@
+##---------------------------------------------------------------------------
+##  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.
+##---------------------------------------------------------------------------
+org.apache.karaf.instance.core.internal.InstanceServiceImpl
+org.apache.karaf.instance.command.ChangeOptsCommand
+org.apache.karaf.instance.command.ChangeRmiRegistryPortCommand
+org.apache.karaf.instance.command.ChangeRmiServerPortCommand
+org.apache.karaf.instance.command.ChangeSshPortCommand
+org.apache.karaf.instance.command.CloneCommand
+org.apache.karaf.instance.command.ConnectCommand
+org.apache.karaf.instance.command.CreateCommand
+org.apache.karaf.instance.command.DestroyCommand
+org.apache.karaf.instance.command.ListCommand
+org.apache.karaf.instance.command.RenameCommand
+org.apache.karaf.instance.command.StartCommand
+org.apache.karaf.instance.command.StatusCommand
+org.apache.karaf.instance.command.StopCommand

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/instance/command/src/main/resources/OSGI-INF/blueprint/instance-command.xml
----------------------------------------------------------------------
diff --git a/instance/command/src/main/resources/OSGI-INF/blueprint/instance-command.xml b/instance/command/src/main/resources/OSGI-INF/blueprint/instance-command.xml
deleted file mode 100644
index 4bb3520..0000000
--- a/instance/command/src/main/resources/OSGI-INF/blueprint/instance-command.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
-    Licensed to the Apache Software Foundation (ASF) under one or more
-    contributor license agreements.  See the NOTICE file distributed with
-    this work for additional information regarding copyright ownership.
-    The ASF licenses this file to You under the Apache License, Version 2.0
-    (the "License"); you may not use this file except in compliance with
-    the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
-
--->
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
-           default-activation="lazy">
-
-    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.2.0"
-                    scan="org.apache.karaf.instance.command.*" />
-
-</blueprint>

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/instance/core/pom.xml
----------------------------------------------------------------------
diff --git a/instance/core/pom.xml b/instance/core/pom.xml
index 80ae6b0..be13f18 100644
--- a/instance/core/pom.xml
+++ b/instance/core/pom.xml
@@ -59,7 +59,7 @@
 
         <dependency>
             <groupId>org.apache.karaf.shell</groupId>
-            <artifactId>org.apache.karaf.shell.console</artifactId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
         </dependency>
 
         <dependency>

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/instance/core/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java
----------------------------------------------------------------------
diff --git a/instance/core/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java b/instance/core/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java
index 32326ba..ae03fd9 100644
--- a/instance/core/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java
+++ b/instance/core/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java
@@ -94,6 +94,13 @@ public class InstanceServiceImpl implements InstanceService {
         Map<String, InstanceState> instances;
     }
 
+    public InstanceServiceImpl() {
+        String prop = System.getProperty("karaf.instances");
+        if (prop != null) {
+            storageLocation = new File(prop);
+        }
+    }
+
     public File getStorageLocation() {
         return storageLocation;
     }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/kar/command/pom.xml
----------------------------------------------------------------------
diff --git a/kar/command/pom.xml b/kar/command/pom.xml
index 1830ba8..67d18f5 100644
--- a/kar/command/pom.xml
+++ b/kar/command/pom.xml
@@ -50,7 +50,7 @@
         </dependency>
         <dependency>
             <groupId>org.apache.karaf.shell</groupId>
-            <artifactId>org.apache.karaf.shell.console</artifactId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
         </dependency>
         <dependency>
             <groupId>org.apache.karaf.shell</groupId>
@@ -89,6 +89,7 @@
                 <configuration>
                     <instructions>
                         <Export-Package>!*</Export-Package>
+                        <Karaf-Commands>org.apache.karaf.kar.command.*</Karaf-Commands>
                     </instructions>
                 </configuration>
             </plugin>

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/kar/command/src/main/java/org/apache/karaf/kar/command/CreateKarCommand.java
----------------------------------------------------------------------
diff --git a/kar/command/src/main/java/org/apache/karaf/kar/command/CreateKarCommand.java b/kar/command/src/main/java/org/apache/karaf/kar/command/CreateKarCommand.java
index dbc34a6..a93706c 100644
--- a/kar/command/src/main/java/org/apache/karaf/kar/command/CreateKarCommand.java
+++ b/kar/command/src/main/java/org/apache/karaf/kar/command/CreateKarCommand.java
@@ -19,24 +19,31 @@ package org.apache.karaf.kar.command;
 import java.util.List;
 
 import org.apache.karaf.features.command.completers.InstalledRepoNameCompleter;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Completer;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.kar.KarService;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 @Command(scope = "kar", name = "create", description = "Create a kar file for a list of feature repos")
 @Service
-public class CreateKarCommand extends KarCommandSupport {
+public class CreateKarCommand implements Action {
     
     @Argument(index = 0, name = "repoName", description = "Repository name. The kar will contain all features of the named repository by default", required = true, multiValued = false)
-    @Completer(InstalledRepoNameCompleter.class)
+    @Completion(InstalledRepoNameCompleter.class)
     private String repoName;
     
     @Argument(index = 1, name = "features", description = "Names of the features to include. If set then only these features will be added", required = false, multiValued = true)
     private List<String> features;
-    
-    public Object doExecute() throws Exception {
-        this.getKarService().create(repoName, features, System.out);
+
+    @Reference
+    private KarService karService;
+
+    @Override
+    public Object execute() throws Exception {
+        karService.create(repoName, features, System.out);
         return null;
     }
     

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/kar/command/src/main/java/org/apache/karaf/kar/command/InstallKarCommand.java
----------------------------------------------------------------------
diff --git a/kar/command/src/main/java/org/apache/karaf/kar/command/InstallKarCommand.java b/kar/command/src/main/java/org/apache/karaf/kar/command/InstallKarCommand.java
index 1053e74..806367a 100644
--- a/kar/command/src/main/java/org/apache/karaf/kar/command/InstallKarCommand.java
+++ b/kar/command/src/main/java/org/apache/karaf/kar/command/InstallKarCommand.java
@@ -16,21 +16,27 @@
  */
 package org.apache.karaf.kar.command;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.kar.KarService;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.net.URI;
 
 @Command(scope = "kar", name = "install", description = "Installs a KAR file.")
 @Service
-public class InstallKarCommand extends KarCommandSupport {
-    
+public class InstallKarCommand implements Action {
+
     @Argument(index = 0, name = "url", description = "The URL of the KAR file to install.", required = true, multiValued = false)
     private String url;
-    
-    public Object doExecute() throws Exception {
-        this.getKarService().install(new URI(url));
+
+    @Reference
+    private KarService karService;
+
+    public Object execute() throws Exception {
+        karService.install(new URI(url));
         return null;
     }
     

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/kar/command/src/main/java/org/apache/karaf/kar/command/KarCommandSupport.java
----------------------------------------------------------------------
diff --git a/kar/command/src/main/java/org/apache/karaf/kar/command/KarCommandSupport.java b/kar/command/src/main/java/org/apache/karaf/kar/command/KarCommandSupport.java
deleted file mode 100644
index 3af6622..0000000
--- a/kar/command/src/main/java/org/apache/karaf/kar/command/KarCommandSupport.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.karaf.kar.command;
-
-import org.apache.karaf.kar.KarService;
-import org.apache.karaf.shell.console.OsgiCommandSupport;
-import org.apache.karaf.shell.inject.Reference;
-
-public abstract class KarCommandSupport extends OsgiCommandSupport {
-
-    @Reference
-    private KarService karService;
-    
-    public KarService getKarService() {
-        return this.karService;
-    }
-    
-    public void setKarService(KarService karService) {
-        this.karService = karService;
-    }
-    
-    public abstract Object doExecute() throws Exception;
-
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/kar/command/src/main/java/org/apache/karaf/kar/command/ListKarCommand.java
----------------------------------------------------------------------
diff --git a/kar/command/src/main/java/org/apache/karaf/kar/command/ListKarCommand.java b/kar/command/src/main/java/org/apache/karaf/kar/command/ListKarCommand.java
index e1b59bb..853a743 100644
--- a/kar/command/src/main/java/org/apache/karaf/kar/command/ListKarCommand.java
+++ b/kar/command/src/main/java/org/apache/karaf/kar/command/ListKarCommand.java
@@ -16,24 +16,31 @@
  */
 package org.apache.karaf.kar.command;
 
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.kar.KarService;
 import org.apache.karaf.shell.table.ShellTable;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 @Command(scope = "kar", name = "list", description = "List the installed KAR files.")
 @Service
-public class ListKarCommand extends KarCommandSupport {
+public class ListKarCommand implements Action {
 
     @Option(name = "--no-format", description = "Disable table rendered output", required = false, multiValued = false)
     boolean noFormat;
-    
-    public Object doExecute() throws Exception {
+
+    @Reference
+    private KarService karService;
+
+    @Override
+    public Object execute() throws Exception {
 
         ShellTable table = new ShellTable();
         table.column("KAR Name");
 
-        for (String karName : this.getKarService().list()) {
+        for (String karName : karService.list()) {
             table.addRow().addContent(karName);
         }
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/kar/command/src/main/java/org/apache/karaf/kar/command/UninstallKarCommand.java
----------------------------------------------------------------------
diff --git a/kar/command/src/main/java/org/apache/karaf/kar/command/UninstallKarCommand.java b/kar/command/src/main/java/org/apache/karaf/kar/command/UninstallKarCommand.java
index ce5664d..506bdb3 100644
--- a/kar/command/src/main/java/org/apache/karaf/kar/command/UninstallKarCommand.java
+++ b/kar/command/src/main/java/org/apache/karaf/kar/command/UninstallKarCommand.java
@@ -16,22 +16,29 @@
  */
 package org.apache.karaf.kar.command;
 
+import org.apache.karaf.kar.KarService;
 import org.apache.karaf.kar.command.completers.KarCompleter;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Completer;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 @Command(scope = "kar", name = "uninstall", description = "Uninstall a KAR file.")
 @Service
-public class UninstallKarCommand extends KarCommandSupport {
+public class UninstallKarCommand implements Action {
 
     @Argument(index = 0, name = "name", description = "The name of the KAR file to uninstall.", required = true, multiValued = false)
-    @Completer(KarCompleter.class)
+    @Completion(KarCompleter.class)
     private String name;
 
-    public Object doExecute() throws Exception {
-        this.getKarService().uninstall(name);
+    @Reference
+    private KarService karService;
+
+    @Override
+    public Object execute() throws Exception {
+        karService.uninstall(name);
         return null;
     }
     

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/kar/command/src/main/java/org/apache/karaf/kar/command/completers/KarCompleter.java
----------------------------------------------------------------------
diff --git a/kar/command/src/main/java/org/apache/karaf/kar/command/completers/KarCompleter.java b/kar/command/src/main/java/org/apache/karaf/kar/command/completers/KarCompleter.java
index 460a21b..539cfaf 100644
--- a/kar/command/src/main/java/org/apache/karaf/kar/command/completers/KarCompleter.java
+++ b/kar/command/src/main/java/org/apache/karaf/kar/command/completers/KarCompleter.java
@@ -16,14 +16,16 @@
  */
 package org.apache.karaf.kar.command.completers;
 
-import org.apache.karaf.kar.KarService;
-import org.apache.karaf.shell.console.Completer;
-import org.apache.karaf.shell.console.completer.StringsCompleter;
-import org.apache.karaf.shell.inject.Reference;
-import org.apache.karaf.shell.inject.Service;
-
 import java.util.List;
 
+import org.apache.karaf.kar.KarService;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
+
 /**
  * Completer on all installed KAR files.
  */
@@ -33,7 +35,7 @@ public class KarCompleter implements Completer {
     @Reference
     private KarService karService;
     
-    public int complete(String buffer, int cursor, @SuppressWarnings("rawtypes") List candidates) {
+    public int complete(Session session, CommandLine commandLine, List<String> candidates) {
         StringsCompleter delegate = new StringsCompleter();
         try {
             for (String karName : karService.list()) {
@@ -42,15 +44,7 @@ public class KarCompleter implements Completer {
         } catch (Exception e) {
             // ignore
         }
-        return delegate.complete(buffer, cursor, candidates);
-    }
-    
-    public void setKarService(KarService karService) {
-        this.karService = karService;
-    }
-    
-    public KarService getKarService() {
-        return this.karService;
+        return delegate.complete(session, commandLine, candidates);
     }
     
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/kar/command/src/main/resources/OSGI-INF/blueprint/kar-command.xml
----------------------------------------------------------------------
diff --git a/kar/command/src/main/resources/OSGI-INF/blueprint/kar-command.xml b/kar/command/src/main/resources/OSGI-INF/blueprint/kar-command.xml
deleted file mode 100644
index 53d56d7..0000000
--- a/kar/command/src/main/resources/OSGI-INF/blueprint/kar-command.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
-        Licensed to the Apache Software Foundation (ASF) under one or more
-        contributor license agreements. See the NOTICE file distributed with
-        this work for additional information regarding copyright ownership.
-        The ASF licenses this file to You under the Apache License, Version
-        2.0 (the "License"); you may not use this file except in compliance
-        with the License. You may obtain a copy of the License at
-
-        http://www.apache.org/licenses/LICENSE-2.0 Unless required by
-        applicable law or agreed to in writing, software distributed under the
-        License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
-        CONDITIONS OF ANY KIND, either express or implied. See the License for
-        the specific language governing permissions and limitations under the
-        License.
-    -->
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" default-activation="lazy">
-
-    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.2.0"
-                    scan="org.apache.karaf.kar.command.*" />
-
-</blueprint>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/pom.xml
----------------------------------------------------------------------
diff --git a/shell/commands/pom.xml b/shell/commands/pom.xml
index 15fb8c7..6609cd5 100644
--- a/shell/commands/pom.xml
+++ b/shell/commands/pom.xml
@@ -40,7 +40,7 @@
     <dependencies>
         <dependency>
             <groupId>org.apache.karaf.shell</groupId>
-            <artifactId>org.apache.karaf.shell.console</artifactId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
         </dependency>
         <dependency>
             <groupId>org.apache.karaf.shell</groupId>
@@ -113,6 +113,9 @@
                             org.apache.karaf.util;-split-package:=merge-first,
                             org.apache.karaf.shell.commands.impl*
                         </Private-Package>
+                        <Karaf-Commands>
+                            org.apache.karaf.shell.commands.impl
+                        </Karaf-Commands>
                     </instructions>
                 </configuration>
             </plugin>

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/InfoProvider.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/InfoProvider.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/InfoProvider.java
new file mode 100644
index 0000000..a69f824
--- /dev/null
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/InfoProvider.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.shell.commands;
+
+import java.util.Properties;
+
+/**
+ * A bundle can publish a service with this interface to offer some informations for the shell:info command
+ */
+public interface InfoProvider {
+
+    public String getName();
+
+    public Properties getProperties();
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/AliasAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/AliasAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/AliasAction.java
index 298c98d..21db405 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/AliasAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/AliasAction.java
@@ -16,19 +16,25 @@
  */
 package org.apache.karaf.shell.commands.impl;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 @Command(scope = "shell", name = "alias", description = "Create an alias to a command")
 @Service
-public class AliasAction extends AbstractAction {
+public class AliasAction implements Action {
 
     @Argument(index = 0, name = "command", description = "The command to alias, e.g. 'ldn = { log:display -n $args }'", required = true, multiValued = false)
     private String alias;
 
-    protected Object doExecute() throws Exception {
+    @Reference
+    private Session session;
+
+    @Override
+    public Object execute() throws Exception {
         session.execute(alias);
         return null;
     }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/CatAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/CatAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/CatAction.java
index 18ca32b..41ec0a6 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/CatAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/CatAction.java
@@ -28,18 +28,22 @@ import java.net.MalformedURLException;
 import java.util.Collections;
 import java.util.List;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Concatenate and print files and/or URLs.
  */
 @Command(scope = "shell", name = "cat", description = "Displays the content of a file or URL.")
 @Service
-public class CatAction extends AbstractAction {
+public class CatAction implements Action {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
 
     @Option(name = "-n", aliases = {}, description = "Number the output lines, starting at 1.", required = false, multiValued = false)
     private boolean displayLineNumbers;
@@ -50,7 +54,8 @@ public class CatAction extends AbstractAction {
     @Argument(index = 0, name = "paths or urls", description = "A list of file paths or urls to display separated by whitespace (use - for STDIN)", required = false, multiValued = true)
     private List<String> paths;
 
-    protected Object doExecute() throws Exception {
+    @Override
+    public Object execute() throws Exception {
         if (stdin) {
             paths = Collections.singletonList("-");
         }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/ClearAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/ClearAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/ClearAction.java
index ac660eb..805ec25 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/ClearAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/ClearAction.java
@@ -16,23 +16,24 @@
  */
 package org.apache.karaf.shell.commands.impl;
 
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 /**
  * A command to clear the console buffer
  */
 @Command(scope = "shell", name = "clear", description = "Clears the console buffer.")
 @Service
-public class ClearAction extends AbstractAction {
+public class ClearAction implements Action {
 
-	protected Object doExecute() throws Exception {
+    @Override
+    public Object execute() throws Exception {
 		System.out.print("\33[2J");
 		System.out.flush();
 		System.out.print("\33[1;1H");
 		System.out.flush();
-		return null;
-	}	
+        return null;
+	}
 
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/CompletionAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/CompletionAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/CompletionAction.java
index 2639039..614d2b1 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/CompletionAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/CompletionAction.java
@@ -16,36 +16,38 @@
  */
 package org.apache.karaf.shell.commands.impl;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.console.SessionProperties;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
 
 /**
  * Command to change the completion mode while using the shell console.
  */
 @Command(scope = "shell", name = "completion", description = "Display or change the completion mode on the current console session.")
 @Service
-public class CompletionAction extends AbstractAction {
+public class CompletionAction implements Action {
 
     @Argument(index = 0, name = "mode", description = "The completion mode to set. The valid completion modes are: global, first, subshell.", required = false, multiValued = false)
+    @Completion(value = StringsCompleter.class, values = { "global", "first", "subshell" })
     String mode;
 
-    protected Object doExecute() throws Exception {
-        if (mode == null) {
-            System.out.println(session.get(SessionProperties.COMPLETION_MODE));
-            return null;
-        }
+    @Reference
+    Session session;
 
-        if (!mode.equalsIgnoreCase("global") && !mode.equalsIgnoreCase("first") && !mode.equalsIgnoreCase("subshell")) {
+    @Override
+    public Object execute() throws Exception {
+        if (mode == null) {
+            System.out.println(session.get(Session.COMPLETION_MODE));
+        } else if (!mode.equalsIgnoreCase("global") && !mode.equalsIgnoreCase("first") && !mode.equalsIgnoreCase("subshell")) {
             System.err.println("The completion mode is not correct. The valid modes are: global, first, subshell. See documentation for details.");
-            return null;
+        } else {
+            session.put(Session.COMPLETION_MODE, mode.toLowerCase());
         }
-
-        mode = mode.toUpperCase();
-
-        session.put(SessionProperties.COMPLETION_MODE, mode);
         return null;
     }
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/DateAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/DateAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/DateAction.java
index e448fd7..c201ab0 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/DateAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/DateAction.java
@@ -16,11 +16,11 @@
  */
 package org.apache.karaf.shell.commands.impl;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
@@ -28,7 +28,7 @@ import java.util.Date;
 
 @Command(scope = "shell", name = "date", description = "Display the current time in the given FORMAT")
 @Service
-public class DateAction extends AbstractAction {
+public class DateAction implements Action {
 
     @Option(name = "-d", aliases = { "--date" }, description = "Display time described, not now", multiValued = false, required = false)
     private String date;
@@ -36,7 +36,8 @@ public class DateAction extends AbstractAction {
     @Argument(index = 0, name = "format", description = "Output format", multiValued = false, required = false)
     private String format;
 
-    protected Object doExecute() throws Exception {
+    @Override
+    public Object execute() throws Exception {
         Date d;
         if (date == null || date.equalsIgnoreCase("now")) {
             d = new Date();

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/EachAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/EachAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/EachAction.java
index a115939..0de344e 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/EachAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/EachAction.java
@@ -19,18 +19,20 @@ package org.apache.karaf.shell.commands.impl;
 import java.util.Collection;
 import java.util.Collections;
 
-import org.apache.felix.service.command.Function;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.Function;
+import org.apache.karaf.shell.api.console.Session;
 
 /**
  * Execute a closure on a list of arguments.
  */
 @Command(scope = "shell", name = "each", description = "Execute a closure on a list of arguments.")
 @Service
-public class EachAction extends AbstractAction {
+public class EachAction implements Action {
 
     @Argument(name = "values", index = 0, multiValued = false, required = true, description = "The collection of arguments to iterate on")
     Collection<Object> values;
@@ -38,8 +40,11 @@ public class EachAction extends AbstractAction {
     @Argument(name = "function", index = 1, multiValued = false, required = true, description = "The function to execute")
     Function function;
 
+    @Reference
+    Session session;
+
     @Override
-    protected Object doExecute() throws Exception {
+    public Object execute() throws Exception {
         for (Object v : values) {
             function.execute(session, Collections.singletonList(v));
         }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/EchoAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/EchoAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/EchoAction.java
index 4eda5d3..433e2a9 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/EchoAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/EchoAction.java
@@ -18,15 +18,15 @@ package org.apache.karaf.shell.commands.impl;
 
 import java.util.List;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 @Command(scope = "shell", name = "echo", description="Echoes or prints arguments to STDOUT.")
 @Service
-public class EchoAction extends AbstractAction
+public class EchoAction implements Action
 {
     @Option(name = "-n", aliases = {}, description = "Do not print the trailing newline character", required = false, multiValued = false)
     private boolean noTrailingNewline = false;
@@ -34,7 +34,8 @@ public class EchoAction extends AbstractAction
     @Argument(index = 0, name = "arguments", description="Arguments to display separated by whitespaces", required = false, multiValued = true)
     private List<String> args;
 
-    protected Object doExecute() throws Exception {
+    @Override
+    public Object execute() throws Exception {
         if (args != null) {
             boolean first = true;
             for (String arg : args) {

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/EditAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/EditAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/EditAction.java
index dc2d90c..77b9a09 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/EditAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/EditAction.java
@@ -27,19 +27,20 @@ import java.net.URLConnection;
 import java.util.UUID;
 import java.util.regex.Pattern;
 
-import jline.Terminal;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.inject.Reference;
-import org.apache.karaf.shell.inject.Service;
+import jline.TerminalSupport;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.console.Terminal;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.apache.karaf.util.StreamUtils;
 import org.jledit.ConsoleEditor;
 import org.jledit.EditorFactory;
 
 @Command(scope = "shell", name = "edit", description = "Calls a text editor.")
 @Service
-public class EditAction extends AbstractAction {
+public class EditAction implements Action {
 
     private final Pattern URL_PATTERN = Pattern.compile("[^: ]+:[^ ]+");
 
@@ -49,8 +50,11 @@ public class EditAction extends AbstractAction {
     @Reference
     private EditorFactory editorFactory;
 
+    @Reference
+    Terminal terminal;
+
     @Override
-    protected Object doExecute() throws Exception {
+    public Object execute() throws Exception {
         URLConnection connection = null;
         InputStream is = null;
         OutputStream os = null;
@@ -137,6 +141,7 @@ public class EditAction extends AbstractAction {
         if (os != null) {
             StreamUtils.close(os);
         }
+
         return null;
     }
 
@@ -146,13 +151,22 @@ public class EditAction extends AbstractAction {
      * @return
      * @throws Exception
      */
-    private Terminal getTerminal() throws Exception {
-        Object terminalObject = session.get(".jline.terminal");
-        if (terminalObject instanceof Terminal) {
-            return (Terminal) terminalObject;
+    private jline.Terminal getTerminal() throws Exception {
+        try {
+            return (jline.Terminal) terminal.getClass().getMethod("getTerminal").invoke(terminal);
+        } catch (Throwable t) {
+            return new TerminalSupport(true) {
+                @Override
+                public int getWidth() {
+                    return terminal.getWidth();
+                }
 
+                @Override
+                public int getHeight() {
+                    return terminal.getHeight();
+                }
+            };
         }
-        throw new IllegalStateException("Could not get Terminal from CommandSession.");
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/ExecuteAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/ExecuteAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/ExecuteAction.java
index 677f3de..0afe73d 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/ExecuteAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/ExecuteAction.java
@@ -18,23 +18,28 @@ package org.apache.karaf.shell.commands.impl;
 
 import java.util.List;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.apache.karaf.util.process.PumpStreamHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Execute system processes.
  */
 @Command(scope = "shell", name = "exec", description = "Executes system processes.")
 @Service
-public class ExecuteAction extends AbstractAction {
+public class ExecuteAction implements Action {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
 
     @Argument(index = 0, name = "command", description = "Execution command with arguments", required = true, multiValued = true)
     private List<String> args;
 
-    protected Object doExecute() throws Exception {
+    @Override
+    public Object execute() throws Exception {
         ProcessBuilder builder = new ProcessBuilder(args);
 
         PumpStreamHandler handler = new PumpStreamHandler(System.in, System.out, System.err, "Command" + args.toString());

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/GrepAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/GrepAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/GrepAction.java
index 8ed0f76..573714e 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/GrepAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/GrepAction.java
@@ -26,17 +26,17 @@ import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.fusesource.jansi.Ansi;
 
 
 @Command(scope = "shell", name="grep", description="Prints lines matching the given pattern.", detailedDescription="classpath:grep.txt")
 @Service
-public class GrepAction extends AbstractAction {
+public class GrepAction implements Action {
 
     public static enum ColorOption {
         never,
@@ -82,8 +82,8 @@ public class GrepAction extends AbstractAction {
     @Option(name = "-C", aliases = { "--context" }, description = "Print NUM lines of output context.  Places a line containing -- between contiguous groups of matches.", required = false, multiValued = false)
     private int context = 0;
 
-
-    protected Object doExecute() throws Exception {
+    @Override
+    public Object execute() throws Exception {
         if (after < 0) {
             after = context;
         }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/HeadAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/HeadAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/HeadAction.java
index 9a05935..ad144d4 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/HeadAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/HeadAction.java
@@ -25,25 +25,30 @@ import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.List;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 @Command(scope = "shell", name = "head", description = "Displays the first lines of a file.")
 @Service
-public class HeadAction extends AbstractAction {
+public class HeadAction implements Action {
 
     private static final int DEFAULT_NUMBER_OF_LINES = 10;
 
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
     @Option(name = "-n", aliases = {}, description = "The number of lines to display, starting at 1.", required = false, multiValued = false)
     private int numberOfLines;
 
     @Argument(index = 0, name = "paths or urls", description = "A list of file paths or urls to display separated by whitespaces.", required = false, multiValued = true)
     private List<String> paths;
 
-    protected Object doExecute() throws Exception {
+    @Override
+    public Object execute() throws Exception {
         //If no paths provided assume standar input
         if (paths == null || paths.size() == 0) {
             if (log.isDebugEnabled()) {

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/HistoryAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/HistoryAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/HistoryAction.java
index 6c7dae6..09ddeb1 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/HistoryAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/HistoryAction.java
@@ -16,11 +16,12 @@
  */
 package org.apache.karaf.shell.commands.impl;
 
-import jline.console.history.History;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.console.History;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.fusesource.jansi.Ansi;
 
 /**
@@ -28,28 +29,29 @@ import org.fusesource.jansi.Ansi;
  */
 @Command(scope = "shell", name="history", description="Prints command history.")
 @Service
-public class HistoryAction extends AbstractAction {
+public class HistoryAction implements Action {
 
     @Option(name = "-c", aliases = { "--clear" }, description = "Clears the shell command history.", required = false, multiValued = false)
     private boolean clear;
-    
-    @Override
-    protected Object doExecute() throws Exception {
-        History history = (History) session.get(".jline.history");       
 
+    @Reference
+    History history;
+
+    @Override
+    public Object execute() throws Exception {
         if (history != null && clear) {
             history.clear();
         }
-        
-        for (History.Entry element : history) {
+        for (int index = history.first(); index <= history.last(); index++) {
             System.out.println(
                     Ansi.ansi()
                         .a("  ")
-                        .a(Ansi.Attribute.INTENSITY_BOLD).render("%3d", element.index()).a(Ansi.Attribute.INTENSITY_BOLD_OFF)
+                        .a(Ansi.Attribute.INTENSITY_BOLD).render("%3d", index).a(Ansi.Attribute.INTENSITY_BOLD_OFF)
                         .a("  ")
-                        .a(element.value())
+                        .a(history.get(index))
                         .toString());
         }
         return null;
     }
+
 }


[04/10] [KARAF-2805] Clean console and commands model

Posted by gn...@apache.org.
http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/DefaultActionPreparator.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/DefaultActionPreparator.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/DefaultActionPreparator.java
new file mode 100644
index 0000000..9cd0343
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/DefaultActionPreparator.java
@@ -0,0 +1,481 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.action.command;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.converter.DefaultConverter;
+import org.apache.karaf.shell.support.converter.GenericType;
+import org.apache.karaf.shell.support.CommandException;
+import org.apache.karaf.shell.support.NameScoping;
+
+import static org.apache.karaf.shell.support.ansi.SimpleAnsi.COLOR_DEFAULT;
+import static org.apache.karaf.shell.support.ansi.SimpleAnsi.COLOR_RED;
+import static org.apache.karaf.shell.support.ansi.SimpleAnsi.INTENSITY_BOLD;
+import static org.apache.karaf.shell.support.ansi.SimpleAnsi.INTENSITY_NORMAL;
+
+public class DefaultActionPreparator {
+
+    public boolean prepare(Action action, Session session, List<Object> params) throws Exception {
+
+        Command command = action.getClass().getAnnotation(Command.class);
+        Map<Option, Field> options = new HashMap<Option, Field>();
+        Map<Argument, Field> arguments = new HashMap<Argument, Field>();
+        List<Argument> orderedArguments = new ArrayList<Argument>();
+
+        for (Class<?> type = action.getClass(); type != null; type = type.getSuperclass()) {
+            for (Field field : type.getDeclaredFields()) {
+                Option option = field.getAnnotation(Option.class);
+                if (option != null) {
+                    options.put(option, field);
+                }
+
+                Argument argument = field.getAnnotation(Argument.class);
+                if (argument != null) {
+                    argument = replaceDefaultArgument(field, argument);
+                    arguments.put(argument, field);
+                    int index = argument.index();
+                    while (orderedArguments.size() <= index) {
+                        orderedArguments.add(null);
+                    }
+                    if (orderedArguments.get(index) != null) {
+                        throw new IllegalArgumentException("Duplicate argument index: " + index + " on Action " + action.getClass().getName());
+                    }
+                    orderedArguments.set(index, argument);
+                }
+            }
+        }
+        assertIndexesAreCorrect(action.getClass(), orderedArguments);
+
+        String commandErrorSt = COLOR_RED + "Error executing command " + command.scope() + ":" + INTENSITY_BOLD + command.name() + INTENSITY_NORMAL + COLOR_DEFAULT + ": ";
+        for (Iterator<Object> it = params.iterator(); it.hasNext(); ) {
+            Object param = it.next();
+            if (HelpOption.HELP.name().equals(param)) {
+                int termWidth = session.getTerminal() != null ? session.getTerminal().getWidth() : 80;
+                boolean globalScope = NameScoping.isGlobalScope(session, command.scope());
+                printUsage(action, options, arguments, System.out, globalScope, termWidth);
+                return false;
+            }
+        }
+        
+        // Populate
+        Map<Option, Object> optionValues = new HashMap<Option, Object>();
+        Map<Argument, Object> argumentValues = new HashMap<Argument, Object>();
+        boolean processOptions = true;
+        int argIndex = 0;
+        for (Iterator<Object> it = params.iterator(); it.hasNext(); ) {
+            Object param = it.next();
+
+            if (processOptions && param instanceof String && ((String) param).startsWith("-")) {
+                boolean isKeyValuePair = ((String) param).indexOf('=') != -1;
+                String name;
+                Object value = null;
+                if (isKeyValuePair) {
+                    name = ((String) param).substring(0, ((String) param).indexOf('='));
+                    value = ((String) param).substring(((String) param).indexOf('=') + 1);
+                } else {
+                    name = (String) param;
+                }
+                Option option = null;
+                for (Option opt : options.keySet()) {
+                    if (name.equals(opt.name()) || Arrays.asList(opt.aliases()).contains(name)) {
+                        option = opt;
+                        break;
+                    }
+                }
+                if (option == null) {
+                    throw new CommandException(commandErrorSt
+                                + "undefined option " + INTENSITY_BOLD + param + INTENSITY_NORMAL + "\n"
+                                + "Try <command> --help' for more information.",
+                                        "Undefined option: " + param);
+                }
+                Field field = options.get(option);
+                if (value == null && (field.getType() == boolean.class || field.getType() == Boolean.class)) {
+                    value = Boolean.TRUE;
+                }
+                if (value == null && it.hasNext()) {
+                    value = it.next();
+                }
+                if (value == null) {
+                        throw new CommandException(commandErrorSt
+                                + "missing value for option " + INTENSITY_BOLD + param + INTENSITY_NORMAL,
+                                "Missing value for option: " + param
+                        );
+                }
+                if (option.multiValued()) {
+                    @SuppressWarnings("unchecked")
+                    List<Object> l = (List<Object>) optionValues.get(option);
+                    if (l == null) {
+                        l = new ArrayList<Object>();
+                        optionValues.put(option, l);
+                    }
+                    l.add(value);
+                } else {
+                    optionValues.put(option, value);
+                }
+            } else {
+                processOptions = false;
+                if (argIndex >= orderedArguments.size()) {
+                        throw new CommandException(commandErrorSt +
+                                "too many arguments specified",
+                                "Too many arguments specified"
+                        );
+                }
+                Argument argument = orderedArguments.get(argIndex);
+                if (!argument.multiValued()) {
+                    argIndex++;
+                }
+                if (argument.multiValued()) {
+                    @SuppressWarnings("unchecked")
+                    List<Object> l = (List<Object>) argumentValues.get(argument);
+                    if (l == null) {
+                        l = new ArrayList<Object>();
+                        argumentValues.put(argument, l);
+                    }
+                    l.add(param);
+                } else {
+                    argumentValues.put(argument, param);
+                }
+            }
+        }
+        // Check required arguments / options
+        for (Option option : options.keySet()) {
+            if (option.required() && optionValues.get(option) == null) {
+                    throw new CommandException(commandErrorSt +
+                            "option " + INTENSITY_BOLD + option.name() + INTENSITY_NORMAL + " is required",
+                            "Option " + option.name() + " is required"
+                    );
+            }
+        }
+        for (Argument argument : orderedArguments) {
+            if (argument.required() && argumentValues.get(argument) == null) {
+                    throw new CommandException(commandErrorSt +
+                            "argument " + INTENSITY_BOLD + argument.name() + INTENSITY_NORMAL + " is required",
+                            "Argument " + argument.name() + " is required"
+                    );
+            }
+        }
+            
+        // Convert and inject values
+        for (Map.Entry<Option, Object> entry : optionValues.entrySet()) {
+            Field field = options.get(entry.getKey());
+            Object value;
+            try {
+                value = convert(action, entry.getValue(), field.getGenericType());
+            } catch (Exception e) {
+                    throw new CommandException(commandErrorSt +
+                            "unable to convert option " + INTENSITY_BOLD + entry.getKey().name() + INTENSITY_NORMAL + " with value '"
+                            + entry.getValue() + "' to type " + new GenericType(field.getGenericType()).toString(),
+                            "Unable to convert option " + entry.getKey().name() + " with value '"
+                                    + entry.getValue() + "' to type " + new GenericType(field.getGenericType()).toString(),
+                            e
+                    );
+            }
+            field.setAccessible(true);
+            field.set(action, value);
+        }
+        for (Map.Entry<Argument, Object> entry : argumentValues.entrySet()) {
+            Field field = arguments.get(entry.getKey());
+            Object value;
+            try {
+                value = convert(action, entry.getValue(), field.getGenericType());
+            } catch (Exception e) {
+                    throw new CommandException(commandErrorSt +
+                            "unable to convert argument " + INTENSITY_BOLD + entry.getKey().name() + INTENSITY_NORMAL + " with value '"
+                            + entry.getValue() + "' to type " + new GenericType(field.getGenericType()).toString(),
+                            "Unable to convert argument " + entry.getKey().name() + " with value '"
+                                    + entry.getValue() + "' to type " + new GenericType(field.getGenericType()).toString(),
+                            e
+                    );
+            }
+            field.setAccessible(true);
+            field.set(action, value);
+        }
+        return true;
+    }
+
+    protected Object convert(Action action, Object value, Type toType) throws Exception {
+        if (toType == String.class) {
+            return value != null ? value.toString() : null;
+        }
+        return new DefaultConverter(action.getClass().getClassLoader()).convert(value, toType);
+    }
+
+    private Argument replaceDefaultArgument(Field field, Argument argument) {
+        if (Argument.DEFAULT.equals(argument.name())) {
+            final Argument delegate = argument;
+            final String name = field.getName();
+            argument = new Argument() {
+                public String name() {
+                    return name;
+                }
+
+                public String description() {
+                    return delegate.description();
+                }
+
+                public boolean required() {
+                    return delegate.required();
+                }
+
+                public int index() {
+                    return delegate.index();
+                }
+
+                public boolean multiValued() {
+                    return delegate.multiValued();
+                }
+
+                public String valueToShowInHelp() {
+                    return delegate.valueToShowInHelp();
+                }
+
+                public Class<? extends Annotation> annotationType() {
+                    return delegate.annotationType();
+                }
+            };
+        }
+        return argument;
+    }
+
+    private void assertIndexesAreCorrect(Class<? extends Action> actionClass, List<Argument> orderedArguments) {
+        for (int i = 0; i < orderedArguments.size(); i++) {
+            if (orderedArguments.get(i) == null) {
+                throw new IllegalArgumentException("Missing argument for index: " + i + " on Action " + actionClass.getName());
+            }
+        }
+    }
+
+    public void printUsage(Action action, Map<Option, Field> options, Map<Argument, Field> arguments, PrintStream out, boolean globalScope, int termWidth) {
+        Command command = action.getClass().getAnnotation(Command.class);
+        if (command != null) {
+            List<Argument> argumentsSet = new ArrayList<Argument>(arguments.keySet());
+            Collections.sort(argumentsSet, new Comparator<Argument>() {
+                public int compare(Argument o1, Argument o2) {
+                    return Integer.valueOf(o1.index()).compareTo(Integer.valueOf(o2.index()));
+                }
+            });
+            Set<Option> optionsSet = new HashSet<Option>(options.keySet());
+            optionsSet.add(HelpOption.HELP);
+            if (command != null && (command.description() != null || command.name() != null)) {
+                out.println(INTENSITY_BOLD + "DESCRIPTION" + INTENSITY_NORMAL);
+                out.print("        ");
+                if (command.name() != null) {
+                    if (globalScope) {
+                        out.println(INTENSITY_BOLD + command.name() + INTENSITY_NORMAL);
+                    } else {
+                        out.println(command.scope() + ":" + INTENSITY_BOLD + command.name() + INTENSITY_NORMAL);
+                    }
+                    out.println();
+                }
+                out.print("\t");
+                out.println(command.description());
+                out.println();
+            }
+            StringBuffer syntax = new StringBuffer();
+            if (command != null) {
+                if (globalScope) {
+                    syntax.append(command.name());
+                } else {
+                    syntax.append(String.format("%s:%s", command.scope(), command.name()));
+                }
+            }
+            if (options.size() > 0) {
+                syntax.append(" [options]");
+            }
+            if (arguments.size() > 0) {
+                syntax.append(' ');
+                for (Argument argument : argumentsSet) {
+                    if (!argument.required()) {
+                        syntax.append(String.format("[%s] ", argument.name()));
+                    } else {
+                        syntax.append(String.format("%s ", argument.name()));
+                    }
+                }
+            }
+
+            out.println(INTENSITY_BOLD + "SYNTAX" + INTENSITY_NORMAL);
+            out.print("        ");
+            out.println(syntax.toString());
+            out.println();
+            if (arguments.size() > 0) {
+                out.println(INTENSITY_BOLD + "ARGUMENTS" + INTENSITY_NORMAL);
+                for (Argument argument : argumentsSet) {
+                    out.print("        ");
+                    out.println(INTENSITY_BOLD + argument.name() + INTENSITY_NORMAL);
+                    printFormatted("                ", argument.description(), termWidth, out, true);
+                    if (!argument.required()) {
+                        if (argument.valueToShowInHelp() != null && argument.valueToShowInHelp().length() != 0) {
+                            if (Argument.DEFAULT_STRING.equals(argument.valueToShowInHelp())) {
+                                Object o = getDefaultValue(action, arguments.get(argument));
+                                String defaultValue = getDefaultValueString(o);
+                                if (defaultValue != null) {
+                                    printDefaultsTo(out, defaultValue);
+                                }
+                            } else {
+                                printDefaultsTo(out, argument.valueToShowInHelp());
+                            }
+                        }
+                    }
+                }
+                out.println();
+            }
+            if (options.size() > 0) {
+                out.println(INTENSITY_BOLD + "OPTIONS" + INTENSITY_NORMAL);
+                for (Option option : optionsSet) {
+                    String opt = option.name();
+                    for (String alias : option.aliases()) {
+                        opt += ", " + alias;
+                    }
+                    out.print("        ");
+                    out.println(INTENSITY_BOLD + opt + INTENSITY_NORMAL);
+                    printFormatted("                ", option.description(), termWidth, out, true);
+                    if (option.valueToShowInHelp() != null && option.valueToShowInHelp().length() != 0) {
+                        if (Option.DEFAULT_STRING.equals(option.valueToShowInHelp())) {
+                            Object o = getDefaultValue(action, options.get(option));
+                            String defaultValue = getDefaultValueString(o);
+                            if (defaultValue != null) {
+                                printDefaultsTo(out, defaultValue);
+                            }
+                        } else {
+                            printDefaultsTo(out, option.valueToShowInHelp());
+                        }
+                    }
+                }
+                out.println();
+            }
+            if (command.detailedDescription().length() > 0) {
+                out.println(INTENSITY_BOLD + "DETAILS" + INTENSITY_NORMAL);
+                String desc = loadDescription(action.getClass(), command.detailedDescription());
+                printFormatted("        ", desc, termWidth, out, true);
+            }
+        }
+    }
+
+    public Object getDefaultValue(Action action, Field field) {
+        try {
+            field.setAccessible(true);
+            return field.get(action);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    private String loadDescription(Class<?> clazz, String desc) {
+        if (desc != null && desc.startsWith("classpath:")) {
+            desc = loadClassPathResource(clazz, desc.substring("classpath:".length()));
+        }
+        return desc;
+    }
+
+    public String getDefaultValueString(Object o) {
+        if (o != null
+                && (!(o instanceof Boolean) || ((Boolean) o))
+                && (!(o instanceof Number) || ((Number) o).doubleValue() != 0.0)) {
+            return o.toString();
+        } else {
+            return null;
+        }
+    }
+
+    private void printDefaultsTo(PrintStream out, String value) {
+        out.println("                (defaults to " + value + ")");
+    }
+
+    static void printFormatted(String prefix, String str, int termWidth, PrintStream out, boolean prefixFirstLine) {
+        int pfxLen = prefix.length();
+        int maxwidth = termWidth - pfxLen;
+        Pattern wrap = Pattern.compile("(\\S\\S{" + maxwidth + ",}|.{1," + maxwidth + "})(\\s+|$)");
+        int cur = 0;
+        while (cur >= 0) {
+            int lst = str.indexOf('\n', cur);
+            String s = (lst >= 0) ? str.substring(cur, lst) : str.substring(cur);
+            if (s.length() == 0) {
+                out.println();
+            } else {
+                Matcher m = wrap.matcher(s);
+                while (m.find()) {
+                    if (cur > 0 || prefixFirstLine) {
+                        out.print(prefix);
+                    }
+                    out.println(m.group());
+                }
+            }
+            if (lst >= 0) {
+                cur = lst + 1;
+            } else {
+                break;
+            }
+        }
+    }
+
+    private String loadClassPathResource(Class<?> clazz, String path) {
+        InputStream is = clazz.getResourceAsStream(path);
+        if (is == null) {
+            is = clazz.getClassLoader().getResourceAsStream(path);
+        }
+        if (is == null) {
+            return "Unable to load description from " + path;
+        }
+
+        try {
+            Reader r = new InputStreamReader(is);
+            StringWriter sw = new StringWriter();
+            int c;
+            while ((c = r.read()) != -1) {
+                sw.append((char) c);
+            }
+            return sw.toString();
+        } catch (IOException e) {
+            return "Unable to load description from " + path;
+        } finally {
+            try {
+                is.close();
+            } catch (IOException e) {
+                // Ignore
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/HelpOption.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/HelpOption.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/HelpOption.java
new file mode 100644
index 0000000..ec0b1a8
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/HelpOption.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.action.command;
+
+import java.lang.annotation.Annotation;
+
+import org.apache.karaf.shell.api.action.Option;
+
+
+public class HelpOption {
+
+    public static final Option HELP = new Option() {
+        public String name() {
+            return "--help";
+        }
+
+        public String[] aliases() {
+            return new String[]{};
+        }
+
+        public String description() {
+            return "Display this help message";
+        }
+
+        public boolean required() {
+            return false;
+        }
+
+        public boolean multiValued() {
+            return false;
+        }
+
+        public String valueToShowInHelp() {
+            return Option.DEFAULT_STRING;
+        }
+
+        public Class<? extends Annotation> annotationType() {
+            return Option.class;
+        }
+    };
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/ManagerImpl.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/ManagerImpl.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/ManagerImpl.java
new file mode 100644
index 0000000..611ecab
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/ManagerImpl.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.action.command;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Destroy;
+import org.apache.karaf.shell.api.action.lifecycle.Init;
+import org.apache.karaf.shell.api.action.lifecycle.Manager;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Registry;
+import org.apache.karaf.shell.support.converter.GenericType;
+
+public class ManagerImpl implements Manager {
+
+    private final Registry dependencies;
+    private final Registry registrations;
+    private final Map<Class<?>, Object> instances = new HashMap<Class<?>, Object>();
+    private final boolean allowCustomServices;
+
+    public ManagerImpl(Registry dependencies, Registry registrations) {
+        this(dependencies, registrations, false);
+    }
+
+    public ManagerImpl(Registry dependencies, Registry registrations, boolean allowCustomServices) {
+        this.dependencies = dependencies;
+        this.registrations = registrations;
+        this.allowCustomServices = allowCustomServices;
+    }
+
+    public <T> T instantiate(Class<? extends T> clazz) throws Exception {
+        return instantiate(clazz, dependencies);
+    }
+
+    public <T> T instantiate(Class<? extends T> clazz, Registry registry) throws Exception {
+        if (!allowCustomServices) {
+            Service reg = clazz.getAnnotation(Service.class);
+            if (reg == null) {
+                throw new IllegalArgumentException("Class " + clazz.getName() + " is not annotated with @Service");
+            }
+        }
+        T instance = clazz.newInstance();
+        // Inject services
+        for (Class<?> cl = clazz; cl != Object.class; cl = cl.getSuperclass()) {
+            for (Field field : cl.getDeclaredFields()) {
+                if (field.getAnnotation(Reference.class) != null) {
+                    GenericType type = new GenericType(field.getGenericType());
+                    Object value;
+                    if (type.getRawClass() == List.class) {
+                        value = registry.getServices(type.getActualTypeArgument(0).getRawClass());
+                        if (value == null && registry != this.dependencies) {
+                            value = this.dependencies.getServices(type.getActualTypeArgument(0).getRawClass());
+                        }
+                    } else {
+                        value = registry.getService(type.getRawClass());
+                        if (value == null && registry != this.dependencies) {
+                            value = this.dependencies.getService(type.getRawClass());
+                        }
+                    }
+                    if (!allowCustomServices && value == null) {
+                        throw new RuntimeException("No service matching " + field.getType().getName());
+                    }
+                    field.setAccessible(true);
+                    field.set(instance, value);
+                }
+            }
+        }
+        for (Method method : clazz.getDeclaredMethods()) {
+            Init ann = method.getAnnotation(Init.class);
+            if (ann != null && method.getParameterTypes().length == 0 && method.getReturnType() == void.class) {
+                method.setAccessible(true);
+                method.invoke(instance);
+            }
+        }
+        return instance;
+    }
+
+    public void release(Object instance) throws Exception {
+        Class<?> clazz = instance.getClass();
+        if (!allowCustomServices) {
+            Service reg = clazz.getAnnotation(Service.class);
+            if (reg == null) {
+                throw new IllegalArgumentException("Class " + clazz.getName() + " is not annotated with @Service");
+            }
+        }
+        for (Method method : clazz.getDeclaredMethods()) {
+            Destroy ann = method.getAnnotation(Destroy.class);
+            if (ann != null && method.getParameterTypes().length == 0 && method.getReturnType() == void.class) {
+                method.setAccessible(true);
+                method.invoke(instance);
+            }
+        }
+    }
+
+    @Override
+    public void register(Class<?> clazz) {
+        if (!allowCustomServices) {
+            Service reg = clazz.getAnnotation(Service.class);
+            if (reg == null ) {
+                throw new IllegalArgumentException("Class " + clazz.getName() + " is not annotated with @Service");
+            }
+        }
+        if (Action.class.isAssignableFrom(clazz)) {
+            final Command cmd = clazz.getAnnotation(Command.class);
+            if (cmd == null) {
+                throw new IllegalArgumentException("Command " + clazz.getName() + " is not annotated with @Command");
+            }
+            Object command = new ActionCommand(this, (Class<? extends Action>) clazz);
+            registrations.register(command);
+        }
+        if (allowCustomServices || Completer.class.isAssignableFrom(clazz)) {
+            try {
+                // Create completer
+                Object completer = instantiate(clazz);
+                synchronized (instances) {
+                    instances.put(clazz, completer);
+                }
+                registrations.register(completer);
+            } catch (RuntimeException e) {
+                throw e;
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    @Override
+    public void unregister(Class<?> clazz) {
+        Object object;
+        synchronized (instances) {
+            object = instances.remove(clazz);
+        }
+        if (object != null) {
+            registrations.unregister(object);
+            if (object instanceof Completer) {
+                try {
+                    release(object);
+                } catch (Exception e) {
+                    // TODO: log exception
+                }
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/CommandExtender.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/CommandExtender.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/CommandExtender.java
new file mode 100644
index 0000000..25bcae7
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/CommandExtender.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.action.osgi;
+
+import org.apache.felix.utils.extender.AbstractExtender;
+import org.apache.felix.utils.extender.Extension;
+import org.apache.karaf.shell.api.console.Registry;
+import org.apache.karaf.shell.impl.action.command.ManagerImpl;
+import org.osgi.framework.Bundle;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Bundle extender scanning for command classes.
+ */
+public class CommandExtender extends AbstractExtender {
+
+    public static final String KARAF_COMMANDS = "Karaf-Commands";
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(CommandExtender.class);
+
+    //
+    // Adapt BundleActivator to make it blueprint friendly
+    //
+
+    private Registry registry;
+
+    public CommandExtender(Registry registry) {
+        this.registry = registry;
+        this.registry.register(new ManagerImpl(this.registry, this.registry));
+    }
+
+    //
+    // Extender implementation
+    //
+
+    @Override
+    protected Extension doCreateExtension(Bundle bundle) throws Exception {
+        if (bundle.getHeaders().get(KARAF_COMMANDS) != null) {
+            return new CommandExtension(bundle, registry);
+        }
+        return null;
+    }
+
+    @Override
+    protected void debug(Bundle bundle, String msg) {
+        StringBuilder buf = new StringBuilder();
+        if ( bundle != null )
+        {
+            buf.append( bundle.getSymbolicName() );
+            buf.append( " (" );
+            buf.append( bundle.getBundleId() );
+            buf.append( "): " );
+        }
+        buf.append(msg);
+        LOGGER.debug(buf.toString());
+    }
+
+    @Override
+    protected void warn(Bundle bundle, String msg, Throwable t) {
+        StringBuilder buf = new StringBuilder();
+        if ( bundle != null )
+        {
+            buf.append( bundle.getSymbolicName() );
+            buf.append( " (" );
+            buf.append( bundle.getBundleId() );
+            buf.append( "): " );
+        }
+        buf.append(msg);
+        LOGGER.warn(buf.toString(), t);
+    }
+
+    @Override
+    protected void error(String msg, Throwable t) {
+        LOGGER.error(msg, t);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/CommandExtension.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/CommandExtension.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/CommandExtension.java
new file mode 100644
index 0000000..b1a953b
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/CommandExtension.java
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.action.osgi;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.felix.utils.extender.Extension;
+import org.apache.felix.utils.manifest.Clause;
+import org.apache.felix.utils.manifest.Parser;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.History;
+import org.apache.karaf.shell.api.console.Registry;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.console.SessionFactory;
+import org.apache.karaf.shell.api.console.Terminal;
+import org.apache.karaf.shell.impl.action.command.ManagerImpl;
+import org.apache.karaf.shell.support.converter.GenericType;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.wiring.BundleWiring;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Commands extension
+ */
+public class CommandExtension implements Extension, Satisfiable {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(CommandExtension.class);
+
+    private final Bundle bundle;
+    private final ManagerImpl manager;
+    private final Registry registry;
+    private final CountDownLatch started;
+    private final MultiServiceTracker tracker;
+    private final List<Satisfiable> satisfiables = new ArrayList<Satisfiable>();
+
+
+    public CommandExtension(Bundle bundle, Registry registry) {
+        this.bundle = bundle;
+        this.registry = new RegistryImpl(registry);
+        this.registry.register(bundle.getBundleContext());
+        this.manager = new ManagerImpl(this.registry, registry);
+        this.registry.register(this.manager);
+        this.started = new CountDownLatch(1);
+        this.tracker = new MultiServiceTracker(bundle.getBundleContext(), this);
+    }
+
+    @Override
+    public void found() {
+        for (Satisfiable s : satisfiables) {
+            s.found();
+        }
+    }
+
+    @Override
+    public void updated() {
+        for (Satisfiable s : satisfiables) {
+            s.updated();
+        }
+    }
+
+    @Override
+    public void lost() {
+        for (Satisfiable s : satisfiables) {
+            s.lost();
+        }
+    }
+
+    public void start() throws Exception {
+        try {
+            String header = bundle.getHeaders().get(CommandExtender.KARAF_COMMANDS);
+            Clause[] clauses = Parser.parseHeader(header);
+            BundleWiring wiring = bundle.adapt(BundleWiring.class);
+            for (Clause clause : clauses) {
+                String name = clause.getName();
+                int options = BundleWiring.LISTRESOURCES_LOCAL;
+                name = name.replace('.', '/');
+                if (name.endsWith("*")) {
+                    options |= BundleWiring.LISTRESOURCES_RECURSE;
+                    name = name.substring(0, name.length() - 1);
+                }
+                if (!name.startsWith("/")) {
+                    name = "/" + name;
+                }
+                if (name.endsWith("/")) {
+                    name = name.substring(0, name.length() - 1);
+                }
+                Collection<String> classes = wiring.listResources(name, "*.class", options);
+                for (String className : classes) {
+                    className = className.replace('/', '.').replace(".class", "");
+                    inspectClass(bundle.loadClass(className));
+                }
+            }
+            tracker.open();
+            if (!tracker.isSatisfied()) {
+                LOGGER.info("Command registration delayed. Missing dependencies: " + tracker.getMissingServices());
+            }
+        } finally {
+            started.countDown();
+        }
+    }
+
+    public void destroy() {
+        try {
+            started.await(5000, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            LOGGER.warn("The wait for bundle being started before destruction has been interrupted.", e);
+        }
+        tracker.close();
+    }
+
+    private void inspectClass(final Class<?> clazz) throws Exception {
+        Service reg = clazz.getAnnotation(Service.class);
+        if (reg == null) {
+            return;
+        }
+        // Create trackers
+        for (Class<?> cl = clazz; cl != Object.class; cl = cl.getSuperclass()) {
+            for (Field field : cl.getDeclaredFields()) {
+                if (field.getAnnotation(Reference.class) != null) {
+                    GenericType type = new GenericType(field.getType());
+                    Class clazzRef = type.getRawClass() == List.class ? type.getActualTypeArgument(0).getRawClass() : type.getRawClass();
+                    if (clazzRef != BundleContext.class
+                            && clazzRef != Session.class
+                            && clazzRef != Terminal.class
+                            && clazzRef != History.class
+                            && clazzRef != Registry.class
+                            && clazzRef != SessionFactory.class
+                            && !registry.hasService(clazzRef)) {
+                        track(clazzRef);
+                    }
+                }
+            }
+        }
+        satisfiables.add(new AutoRegister(clazz));
+    }
+
+    protected void track(final Class clazzRef) {
+        tracker.track(clazzRef);
+        registry.register(new Callable() {
+            @Override
+            public Object call() throws Exception {
+                return tracker.getService(clazzRef);
+            }
+        }, clazzRef);
+    }
+
+    public class AutoRegister implements Satisfiable {
+
+        private final Class<?> clazz;
+
+        public AutoRegister(Class<?> clazz) {
+            this.clazz = clazz;
+        }
+
+        @Override
+        public void found() {
+            try {
+                manager.register(clazz);
+            } catch (Exception e) {
+                throw new RuntimeException("Unable to create service " + clazz.getName(), e);
+            }
+        }
+
+        @Override
+        public void updated() {
+            lost();
+            found();
+        }
+
+        @Override
+        public void lost() {
+            manager.unregister(clazz);
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/MultiServiceTracker.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/MultiServiceTracker.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/MultiServiceTracker.java
new file mode 100644
index 0000000..a762957
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/MultiServiceTracker.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.action.osgi;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.osgi.framework.BundleContext;
+
+/**
+ * Track multiple services by their type
+ */
+public class MultiServiceTracker implements Satisfiable {
+
+    private final BundleContext bundleContext;
+    private final Satisfiable satisfiable;
+    private final ConcurrentMap<Class, SingleServiceTracker> trackers = new ConcurrentHashMap<Class, SingleServiceTracker>();
+    private final AtomicInteger count = new AtomicInteger(-1);
+
+    public MultiServiceTracker(BundleContext bundleContext, Satisfiable satisfiable) {
+        this.bundleContext = bundleContext;
+        this.satisfiable = satisfiable;
+    }
+
+    @SuppressWarnings("unchecked")
+    public void track(Class service) {
+        if (trackers.get(service) == null) {
+            SingleServiceTracker tracker = new SingleServiceTracker(bundleContext, service, this);
+            trackers.putIfAbsent(service, tracker);
+        }
+    }
+
+    public <T> T getService(Class<T> clazz) {
+        SingleServiceTracker tracker = trackers.get(clazz);
+        return tracker != null ? clazz.cast(tracker.getService()) : null;
+    }
+
+    public void open() {
+        for (SingleServiceTracker tracker : trackers.values()) {
+            tracker.open();
+        }
+        found();
+    }
+
+    public void close() {
+        lost();
+        for (SingleServiceTracker tracker : trackers.values()) {
+            tracker.close();
+        }
+    }
+
+    public boolean isSatisfied() {
+        return count.get() == trackers.size();
+    }
+
+    public List<String> getMissingServices() {
+        List<String> missing = new ArrayList<String>();
+        for (SingleServiceTracker tracker : trackers.values()) {
+            if (!tracker.isSatisfied()) {
+                missing.add(tracker.getClassName());
+            }
+        }
+        return missing;
+    }
+
+    @Override
+    public void found() {
+        if (count.incrementAndGet() == trackers.size()) {
+            satisfiable.found();
+        }
+    }
+
+    @Override
+    public void updated() {
+        if (count.get() == trackers.size()) {
+            satisfiable.updated();
+        }
+    }
+
+    @Override
+    public void lost() {
+        if (count.getAndDecrement() == trackers.size()) {
+            satisfiable.lost();
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/RegistryImpl.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/RegistryImpl.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/RegistryImpl.java
new file mode 100644
index 0000000..38b56ed
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/RegistryImpl.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.action.osgi;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+import org.apache.karaf.shell.api.console.Command;
+import org.apache.karaf.shell.api.console.Registry;
+
+public class RegistryImpl implements Registry {
+
+    private final Registry parent;
+    private final Map<Object, Object> services = new LinkedHashMap<Object, Object>();
+
+    public RegistryImpl(Registry parent) {
+        this.parent = parent;
+    }
+
+    @Override
+    public List<Command> getCommands() {
+        return getServices(Command.class);
+    }
+
+    @Override
+    public <T> void register(Callable<T> factory, Class<T> clazz) {
+        synchronized (services) {
+            services.put(factory, new Factory<T>(clazz, factory));
+        }
+    }
+
+    @Override
+    public void register(Object service) {
+        synchronized (services) {
+            services.put(service, service);
+        }
+    }
+
+    @Override
+    public void unregister(Object service) {
+        synchronized (services) {
+            services.remove(service);
+        }
+    }
+
+    @Override
+    public <T> T getService(Class<T> clazz) {
+        synchronized (services) {
+            for (Object service : services.values()) {
+                if (service instanceof Factory) {
+                    if (clazz.isAssignableFrom(((Factory) service).clazz)) {
+                        if (isVisible(service)) {
+                            try {
+                                return clazz.cast(((Factory) service).callable.call());
+                            } catch (Exception e) {
+                                // TODO: log exception
+                            }
+                        }
+                    }
+                } else if (clazz.isInstance(service)) {
+                    if (isVisible(service)) {
+                        return clazz.cast(service);
+                    }
+                }
+            }
+        }
+        if (parent != null) {
+            return parent.getService(clazz);
+        }
+        return null;
+    }
+
+    @Override
+    public <T> List<T> getServices(Class<T> clazz) {
+        List<T> list = new ArrayList<T>();
+        synchronized (services) {
+            for (Object service : services.values()) {
+                if (service instanceof Factory) {
+                    if (clazz.isAssignableFrom(((Factory) service).clazz)) {
+                        if (isVisible(service)) {
+                            try {
+                                list.add(clazz.cast(((Factory) service).callable.call()));
+                            } catch (Exception e) {
+                                // TODO: log exception
+                            }
+                        }
+                    }
+                } else if (clazz.isInstance(service)) {
+                    if (isVisible(service)) {
+                        list.add(clazz.cast(service));
+                    }
+                }
+            }
+        }
+        if (parent != null) {
+            list.addAll(parent.getServices(clazz));
+        }
+        return list;
+    }
+
+    @Override
+    public boolean hasService(Class<?> clazz) {
+        synchronized (services) {
+            for (Object service : services.values()) {
+                if (service instanceof Factory) {
+                    if (clazz.isAssignableFrom(((Factory) service).clazz)) {
+                        if (isVisible(service)) {
+                            return true;
+                        }
+                    }
+                } else if (clazz.isInstance(service)) {
+                    if (isVisible(service)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        if (parent != null) {
+            return parent.hasService(clazz);
+        }
+        return false;
+    }
+
+    protected boolean isVisible(Object service) {
+        return true;
+    }
+
+    static class Factory<T> {
+
+        final Class<T> clazz;
+        final Callable<T> callable;
+
+        Factory(Class<T> clazz, Callable<T> callable) {
+            this.clazz = clazz;
+            this.callable = callable;
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/Satisfiable.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/Satisfiable.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/Satisfiable.java
new file mode 100644
index 0000000..90545aa
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/Satisfiable.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.action.osgi;
+
+/**
+ * Interface to be called with a boolean satisfaction status.
+ */
+public interface Satisfiable {
+
+    void found();
+    void updated();
+    void lost();
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/SingleServiceTracker.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/SingleServiceTracker.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/SingleServiceTracker.java
new file mode 100644
index 0000000..039fe48
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/osgi/SingleServiceTracker.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.action.osgi;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * Track a single service by its type.
+ *
+ * @param <T>
+ */
+public final class SingleServiceTracker<T> {
+
+    private final BundleContext ctx;
+    private final String className;
+    private final AtomicReference<T> service = new AtomicReference<T>();
+    private final AtomicReference<ServiceReference> ref = new AtomicReference<ServiceReference>();
+    private final AtomicBoolean open = new AtomicBoolean(false);
+    private final Satisfiable serviceListener;
+    private Filter filter;
+
+    private final ServiceListener listener = new ServiceListener() {
+        public void serviceChanged(ServiceEvent event) {
+            if (open.get()) {
+                if (event.getType() == ServiceEvent.UNREGISTERING) {
+                    ServiceReference deadRef = event.getServiceReference();
+                    if (deadRef.equals(ref.get())) {
+                        findMatchingReference(deadRef);
+                    }
+                } else if (event.getType() == ServiceEvent.REGISTERED && ref.get() == null) {
+                    findMatchingReference(null);
+                }
+            }
+        }
+    };
+
+    public SingleServiceTracker(BundleContext context, Class<T> clazz, Satisfiable sl) {
+        ctx = context;
+        this.className = clazz.getName();
+        serviceListener = sl;
+    }
+
+    public T getService() {
+        return service.get();
+    }
+
+    public ServiceReference getServiceReference() {
+        return ref.get();
+    }
+
+    public void open() {
+        if (open.compareAndSet(false, true)) {
+            try {
+                String filterString = '(' + Constants.OBJECTCLASS + '=' + className + ')';
+                if (filter != null) filterString = "(&" + filterString + filter + ')';
+                ctx.addServiceListener(listener, filterString);
+                findMatchingReference(null);
+            } catch (InvalidSyntaxException e) {
+                // this can never happen. (famous last words :)
+            }
+        }
+    }
+
+    private void findMatchingReference(ServiceReference original) {
+        boolean clear = true;
+        ServiceReference ref = ctx.getServiceReference(className);
+        if (ref != null && (filter == null || filter.match(ref))) {
+            @SuppressWarnings("unchecked")
+            T service = (T) ctx.getService(ref);
+            if (service != null) {
+                clear = false;
+
+                // We do the unget out of the lock so we don't exit this class while holding a lock.
+                if (!!!update(original, ref, service)) {
+                    ctx.ungetService(ref);
+                }
+            }
+        } else if (original == null) {
+            clear = false;
+        }
+
+        if (clear) {
+            update(original, null, null);
+        }
+    }
+
+    private boolean update(ServiceReference deadRef, ServiceReference newRef, T service) {
+        boolean result = false;
+        int foundLostReplaced = -1;
+
+        // Make sure we don't try to get a lock on null
+        Object lock;
+
+        // we have to choose our lock.
+        if (newRef != null) lock = newRef;
+        else if (deadRef != null) lock = deadRef;
+        else lock = this;
+
+        // This lock is here to ensure that no two threads can set the ref and service
+        // at the same time.
+        synchronized (lock) {
+            if (open.get()) {
+                result = this.ref.compareAndSet(deadRef, newRef);
+                if (result) {
+                    this.service.set(service);
+
+                    if (deadRef == null && newRef != null) foundLostReplaced = 0;
+                    if (deadRef != null && newRef == null) foundLostReplaced = 1;
+                    if (deadRef != null && newRef != null) foundLostReplaced = 2;
+                }
+            }
+        }
+
+        if (serviceListener != null) {
+            if (foundLostReplaced == 0) serviceListener.found();
+            else if (foundLostReplaced == 1) serviceListener.lost();
+            else if (foundLostReplaced == 2) serviceListener.updated();
+        }
+
+        return result;
+    }
+
+    public void close() {
+        if (open.compareAndSet(true, false)) {
+            ctx.removeServiceListener(listener);
+
+            synchronized (this) {
+                ServiceReference deadRef = ref.getAndSet(null);
+                service.set(null);
+                if (deadRef != null) ctx.ungetService(deadRef);
+            }
+        }
+    }
+
+    public boolean isSatisfied() {
+        return service.get() != null;
+    }
+
+    public String getClassName() {
+        return className;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/Branding.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/Branding.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/Branding.java
new file mode 100644
index 0000000..bae8f2a
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/Branding.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.karaf.shell.impl.console;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+import org.apache.karaf.shell.api.console.Terminal;
+
+
+public final class Branding {
+    
+    private Branding() { }
+
+    public static Properties loadBrandingProperties() {
+        Properties props = new Properties();
+        loadProps(props, "org/apache/karaf/shell/console/branding.properties");
+        loadProps(props, "org/apache/karaf/branding/branding.properties");
+        return props;
+    }
+
+    public static Properties loadBrandingProperties(Terminal terminal) {
+        Properties props = new Properties();
+        if (terminal != null && terminal.getClass().getName().endsWith("SshTerminal")) {
+            //it's a ssh client, so load branding seperately
+            loadProps(props, "org/apache/karaf/shell/console/branding-ssh.properties");
+        } else {
+            loadProps(props, "org/apache/karaf/shell/console/branding.properties");
+        }
+
+        loadProps(props, "org/apache/karaf/branding/branding.properties");
+        return props;
+    }
+    
+    protected static void loadProps(Properties props, String resource) {
+        InputStream is = null;
+        try {
+            is = Branding.class.getClassLoader().getResourceAsStream(resource);
+            if (is != null) {
+                props.load(is);
+            }
+        } catch (IOException e) {
+            // ignore
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/CommandNamesCompleter.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/CommandNamesCompleter.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/CommandNamesCompleter.java
new file mode 100644
index 0000000..377bdcc
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/CommandNamesCompleter.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.karaf.shell.impl.console;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.karaf.shell.api.console.Command;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
+
+public class CommandNamesCompleter extends org.apache.karaf.shell.support.completers.CommandNamesCompleter {
+
+    @Override
+    public int complete(Session session, CommandLine commandLine, List<String> candidates) {
+        // TODO: optimize
+        List<Command> list = session.getRegistry().getCommands();
+        Set<String> names = new HashSet<String>();
+        for (Command command : list) {
+            names.add(command.getScope() + ":" + command.getName());
+            names.add(command.getName());
+        }
+        int res = new StringsCompleter(names).complete(session, commandLine, candidates);
+        Collections.sort(candidates);
+        return res;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/CommandWrapper.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/CommandWrapper.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/CommandWrapper.java
new file mode 100644
index 0000000..9488d8f
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/CommandWrapper.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.console;
+
+import java.util.List;
+
+import org.apache.felix.gogo.runtime.Closure;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.felix.service.command.Function;
+import org.apache.karaf.shell.api.console.Command;
+import org.apache.karaf.shell.api.console.Session;
+
+public class CommandWrapper implements Function {
+
+    private final Command command;
+
+    public CommandWrapper(Command command) {
+        this.command = command;
+    }
+
+    public Command getCommand() {
+        return command;
+    }
+
+    @Override
+    public Object execute(final CommandSession commandSession, List<Object> arguments) throws Exception {
+        // TODO: remove the hack for .session
+        Session session = (Session) commandSession.get(".session");
+        // When need to translate closures to a compatible type for the command
+        for (int i = 0; i < arguments.size(); i++) {
+            Object v = arguments.get(i);
+            if (v instanceof Closure) {
+                final Closure closure = (Closure) v;
+                arguments.set(i, new org.apache.karaf.shell.api.console.Function() {
+                    @Override
+                    public Object execute(Session session, List<Object> arguments) throws Exception {
+                        return closure.execute(commandSession, arguments);
+                    }
+                });
+            }
+        }
+        return command.execute(session, arguments);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/CommandsCompleter.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/CommandsCompleter.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/CommandsCompleter.java
new file mode 100644
index 0000000..d73af34
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/CommandsCompleter.java
@@ -0,0 +1,256 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.console;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.apache.karaf.shell.api.console.Command;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.console.SessionFactory;
+import org.apache.karaf.shell.support.completers.AggregateCompleter;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
+
+/**
+ * Overall command line completer.
+ */
+public class CommandsCompleter extends org.apache.karaf.shell.support.completers.CommandsCompleter {
+
+    private final SessionFactory factory;
+    private final Map<String, Completer> globalCompleters = new HashMap<String, Completer>();
+    private final Map<String, Completer> localCompleters = new HashMap<String, Completer>();
+    private final List<Command> commands = new ArrayList<Command>();
+
+    public CommandsCompleter(SessionFactory factory) {
+        this.factory = factory;
+    }
+
+    public int complete(Session session, CommandLine commandLine, List<String> candidates) {
+        Map<String, Completer>[] allCompleters = checkData();
+
+        List<String> scopes = getCurrentScopes(session);
+        sort(allCompleters, scopes);
+
+        String subShell = getCurrentSubShell(session);
+        String completion = getCompletionType(session);
+
+        // SUBSHELL mode
+        if (Session.COMPLETION_MODE_SUBSHELL.equalsIgnoreCase(completion)) {
+            if (subShell.isEmpty()) {
+                subShell = Session.SCOPE_GLOBAL;
+            }
+            List<Completer> completers = new ArrayList<Completer>();
+            for (String name : allCompleters[1].keySet()) {
+                if (name.startsWith(subShell)) {
+                    completers.add(allCompleters[1].get(name));
+                }
+            }
+            if (!subShell.equals(Session.SCOPE_GLOBAL)) {
+                completers.add(new StringsCompleter(new String[] { "exit" }));
+            }
+            int res = new AggregateCompleter(completers).complete(session, commandLine, candidates);
+            Collections.sort(candidates);
+            return res;
+        }
+
+        if (Session.COMPLETION_MODE_FIRST.equalsIgnoreCase(completion)) {
+            if (!subShell.isEmpty()) {
+                List<Completer> completers = new ArrayList<Completer>();
+                for (String name : allCompleters[1].keySet()) {
+                    if (name.startsWith(subShell)) {
+                        completers.add(allCompleters[1].get(name));
+                    }
+                }
+                int res = new AggregateCompleter(completers).complete(session, commandLine, candidates);
+                if (!candidates.isEmpty()) {
+                    Collections.sort(candidates);
+                    return res;
+                }
+            }
+            List<Completer> compl = new ArrayList<Completer>();
+            compl.add(new StringsCompleter(getAliases(session)));
+            compl.addAll(allCompleters[0].values());
+            int res = new AggregateCompleter(compl).complete(session, commandLine, candidates);
+            Collections.sort(candidates);
+            return res;
+        }
+
+        List<Completer> compl = new ArrayList<Completer>();
+        compl.add(new StringsCompleter(getAliases(session)));
+        compl.addAll(allCompleters[0].values());
+        int res = new AggregateCompleter(compl).complete(session, commandLine, candidates);
+        Collections.sort(candidates);
+        return res;
+    }
+
+    protected void sort(Map<String, Completer>[] completers, List<String> scopes) {
+        ScopeComparator comparator = new ScopeComparator(scopes);
+        for (int i = 0; i < completers.length; i++) {
+            Map<String, Completer> map = new TreeMap<String, Completer>(comparator);
+            map.putAll(completers[i]);
+            completers[i] = map;
+        }
+    }
+
+    protected static class ScopeComparator implements Comparator<String> {
+        private final List<String> scopes;
+        public ScopeComparator(List<String> scopes) {
+            this.scopes = scopes;
+        }
+        @Override
+        public int compare(String o1, String o2) {
+            String[] p1 = o1.split(":");
+            String[] p2 = o2.split(":");
+            int p = 0;
+            while (p < p1.length && p < p2.length) {
+                int i1 = scopes.indexOf(p1[p]);
+                int i2 = scopes.indexOf(p2[p]);
+                if (i1 < 0) {
+                    if (i2 < 0) {
+                        int c = p1[p].compareTo(p2[p]);
+                        if (c != 0) {
+                            return c;
+                        } else {
+                            p++;
+                        }
+                    } else {
+                        return +1;
+                    }
+                } else if (i2 < 0) {
+                    return -1;
+                } else if (i1 < i2) {
+                    return -1;
+                } else if (i1 > i2) {
+                    return +1;
+                } else {
+                    p++;
+                }
+            }
+            return 0;
+        }
+    }
+
+    protected List<String> getCurrentScopes(Session session) {
+        String scopes = (String) session.get(Session.SCOPE);
+        return Arrays.asList(scopes.split(":"));
+    }
+
+    protected String getCurrentSubShell(Session session) {
+        String s = (String) session.get(Session.SUBSHELL);
+        if (s == null) {
+            s = "";
+        }
+        return s;
+    }
+
+    protected String getCompletionType(Session session) {
+        String completion = (String) session.get(Session.COMPLETION_MODE);
+        if (completion == null) {
+            completion = Session.COMPLETION_MODE_GLOBAL;
+        }
+        return completion;
+    }
+
+    protected String stripScope(String name) {
+        int index = name.indexOf(":");
+        return index > 0 ? name.substring(index + 1) : name;
+    }
+
+    @SuppressWarnings("unchecked")
+    protected Map<String, Completer>[] checkData() {
+        // Copy the set to avoid concurrent modification exceptions
+        // TODO: fix that in gogo instead
+        Collection<Command> commands;
+        boolean update;
+        synchronized (this) {
+            commands = factory.getRegistry().getCommands();
+            update = !commands.equals(this.commands);
+        }
+        if (update) {
+            // get command aliases
+            Map<String, Completer> global = new HashMap<String, Completer>();
+            Map<String, Completer> local = new HashMap<String, Completer>();
+
+            // add argument completers for each command
+            for (Command command : commands) {
+                String key = command.getScope() + ":" + command.getName();
+                Completer cg = command.getCompleter(false);
+                Completer cl = command.getCompleter(true);
+                if (cg == null) {
+                    if (Session.SCOPE_GLOBAL.equals(command.getScope())) {
+                        cg = new StringsCompleter(new String[] { command.getName() });
+                    } else {
+                        cg = new StringsCompleter(new String[] { key, command.getName() });
+                    }
+                }
+                if (cl == null) {
+                    cl = new StringsCompleter(new String[] { command.getName() });
+                }
+                global.put(key, cg);
+                local.put(key, cl);
+            }
+
+            synchronized (this) {
+                this.commands.clear();
+                this.globalCompleters.clear();
+                this.localCompleters.clear();
+                this.commands.addAll(commands);
+                this.globalCompleters.putAll(global);
+                this.localCompleters.putAll(local);
+            }
+        }
+        synchronized (this) {
+            return new Map[] {
+                    new HashMap<String, Completer>(this.globalCompleters),
+                    new HashMap<String, Completer>(this.localCompleters)
+            };
+        }
+    }
+
+    /**
+     * Get the aliases defined in the console session.
+     *
+     * @return the aliases set
+     */
+    @SuppressWarnings("unchecked")
+    private Set<String> getAliases(Session session) {
+        Set<String> vars = ((Set<String>) session.get(null));
+        Set<String> aliases = new HashSet<String>();
+        for (String var : vars) {
+            Object content = session.get(var);
+            if (content != null && "org.apache.felix.gogo.runtime.Closure".equals(content.getClass().getName())) {
+                aliases.add(var);
+            }
+        }
+        return aliases;
+    }
+
+}
+

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/CompleterAsCompletor.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/CompleterAsCompletor.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/CompleterAsCompletor.java
new file mode 100644
index 0000000..fb8ebfb
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/CompleterAsCompletor.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.console;
+
+import java.util.List;
+
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.impl.console.parsing.CommandLineImpl;
+
+public class CompleterAsCompletor implements jline.console.completer.Completer {
+
+    private final Session session;
+    private final Completer completer;
+
+    public CompleterAsCompletor(Session session, Completer completer) {
+        this.session = session;
+        this.completer = completer;
+    }
+
+    public int complete(String buffer, int cursor, List candidates) {
+        return completer.complete(session, CommandLineImpl.build(buffer, cursor), candidates);
+    }
+
+}


[03/10] [KARAF-2805] Clean console and commands model

Posted by gn...@apache.org.
http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/ConsoleSessionImpl.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/ConsoleSessionImpl.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/ConsoleSessionImpl.java
new file mode 100644
index 0000000..7988035
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/ConsoleSessionImpl.java
@@ -0,0 +1,583 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.console;
+
+import java.io.CharArrayWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.InterruptedIOException;
+import java.io.PrintStream;
+import java.io.Reader;
+import java.lang.management.ManagementFactory;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import jline.UnsupportedTerminal;
+import jline.console.ConsoleReader;
+import jline.console.history.MemoryHistory;
+import jline.console.history.PersistentHistory;
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.felix.service.command.Converter;
+import org.apache.felix.service.command.Function;
+import org.apache.felix.service.threadio.ThreadIO;
+import org.apache.karaf.shell.api.console.Command;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.History;
+import org.apache.karaf.shell.api.console.Registry;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.console.SessionFactory;
+import org.apache.karaf.shell.api.console.Terminal;
+import org.apache.karaf.shell.impl.console.parsing.Parser;
+import org.apache.karaf.shell.support.ShellUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ConsoleSessionImpl implements Session {
+
+    public static final String SHELL_INIT_SCRIPT = "karaf.shell.init.script";
+    public static final String SHELL_HISTORY_MAXSIZE = "karaf.shell.history.maxSize";
+    public static final String PROMPT = "PROMPT";
+    public static final String DEFAULT_PROMPT = "\u001B[1m${USER}\u001B[0m@${APPLICATION}(${SUBSHELL})> ";
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(ConsoleSessionImpl.class);
+
+    // Input stream
+    final BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(1024);
+    final ConsoleInputStream console = new ConsoleInputStream();
+    final Pipe pipe = new Pipe();
+    volatile boolean running;
+    volatile boolean eof;
+
+    final SessionFactory factory;
+    final ThreadIO threadIO;
+    final InputStream in;
+    final PrintStream out;
+    final PrintStream err;
+    private Runnable closeCallback;
+
+    final CommandSession session;
+    final Registry registry;
+    final Terminal terminal;
+    final History history;
+    final ConsoleReader reader;
+
+    private boolean interrupt;
+    private Thread thread;
+
+    public ConsoleSessionImpl(SessionFactory factory,
+                              CommandProcessor processor,
+                              ThreadIO threadIO,
+                              InputStream in,
+                              PrintStream out,
+                              PrintStream err,
+                              Terminal term,
+                              String encoding,
+                              Runnable closeCallback) {
+        // Arguments
+        this.factory = factory;
+        this.threadIO = threadIO;
+        this.in = in;
+        this.out = out;
+        this.err = err;
+        this.closeCallback = closeCallback;
+
+        // Terminal
+        terminal = term == null ? new JLineTerminal(new UnsupportedTerminal()) : term;
+
+
+        // Console reader
+        try {
+            reader = new ConsoleReader(null,
+                    in != null ? console : null,
+                    out,
+                    terminal instanceof JLineTerminal ? ((JLineTerminal) terminal).getTerminal() : new KarafTerminal(terminal),
+                    encoding);
+        } catch (IOException e) {
+            throw new RuntimeException("Error opening console reader", e);
+        }
+
+        // History
+        final File file = getHistoryFile();
+        try {
+            file.getParentFile().mkdirs();
+            reader.setHistory(new KarafFileHistory(file));
+        } catch (Exception e) {
+            LOGGER.error("Can not read history from file " + file + ". Using in memory history", e);
+        }
+        if (reader.getHistory() instanceof MemoryHistory) {
+            String maxSizeStr = System.getProperty(SHELL_HISTORY_MAXSIZE);
+            if (maxSizeStr != null) {
+                ((MemoryHistory) this.reader.getHistory()).setMaxSize(Integer.parseInt(maxSizeStr));
+            }
+        }
+        history = new HistoryWrapper(reader.getHistory());
+
+        // Registry
+        registry = new RegistryImpl(factory.getRegistry());
+        registry.register(factory);
+        registry.register(this);
+        registry.register(registry);
+        registry.register(terminal);
+        registry.register(history);
+
+        // Completers
+        Completer completer = new CommandsCompleter(factory);
+        reader.addCompleter(new CompleterAsCompletor(this, completer));
+        registry.register(completer);
+        registry.register(new CommandNamesCompleter());
+
+        // Session
+        session = processor.createSession(in != null ? console : null, out, err);
+        Properties sysProps = System.getProperties();
+        for (Object key : sysProps.keySet()) {
+            session.put(key.toString(), sysProps.get(key));
+        }
+        session.put(".session", this);
+        session.put(".commandSession", session);
+        session.put(".jline.reader", reader);
+        session.put(".jline.terminal", reader.getTerminal());
+        session.put(".jline.history", reader.getHistory());
+        session.put(Session.SCOPE, "shell:bundle:*");
+        session.put(Session.SUBSHELL, "");
+        session.put(Session.COMPLETION_MODE, loadCompletionMode());
+        session.put("USER", ShellUtil.getCurrentUserName());
+        session.put("APPLICATION", System.getProperty("karaf.name", "root"));
+        session.put("#LINES", new Function() {
+            public Object execute(CommandSession session, List<Object> arguments) throws Exception {
+                return Integer.toString(terminal.getHeight());
+            }
+        });
+        session.put("#COLUMNS", new Function() {
+            public Object execute(CommandSession session, List<Object> arguments) throws Exception {
+                return Integer.toString(terminal.getWidth());
+            }
+        });
+        session.put("pid", getPid());
+    }
+
+    /**
+     * Subclasses can override to use a different history file.
+     *
+     * @return
+     */
+    protected File getHistoryFile() {
+        String defaultHistoryPath = new File(System.getProperty("user.home"), ".karaf/karaf.history").toString();
+        return new File(System.getProperty("karaf.history", defaultHistoryPath));
+    }
+
+    @Override
+    public Terminal getTerminal() {
+        return terminal;
+    }
+
+    public History getHistory() {
+        return history;
+    }
+
+    @Override
+    public Registry getRegistry() {
+        return registry;
+    }
+
+    @Override
+    public SessionFactory getFactory() {
+        return factory;
+    }
+
+    public void close() {
+        if (!running) {
+            return;
+        }
+        if (reader.getHistory() instanceof PersistentHistory) {
+            try {
+                ((PersistentHistory) reader.getHistory()).flush();
+            } catch (IOException e) {
+                // ignore
+            }
+        }
+        running = false;
+        pipe.interrupt();
+        if (closeCallback != null) {
+            closeCallback.run();
+        }
+    }
+
+    public void run() {
+        try {
+            threadIO.setStreams(session.getKeyboard(), out, err);
+            thread = Thread.currentThread();
+            running = true;
+            pipe.start();
+            Properties brandingProps = Branding.loadBrandingProperties(terminal);
+            welcome(brandingProps);
+            setSessionProperties(brandingProps);
+            String scriptFileName = System.getProperty(SHELL_INIT_SCRIPT);
+            executeScript(scriptFileName);
+            while (running) {
+                try {
+                    String command = readAndParseCommand();
+                    if (command == null) {
+                        break;
+                    }
+                    //session.getConsole().println("Executing: " + line);
+                    Object result = session.execute(command);
+                    if (result != null) {
+                        session.getConsole().println(session.format(result, Converter.INSPECT));
+                    }
+                } catch (InterruptedIOException e) {
+                    //System.err.println("^C");
+                    // TODO: interrupt current thread
+                } catch (InterruptedException e) {
+                    //interrupt current thread
+                } catch (Throwable t) {
+                    ShellUtil.logException(this, t);
+                }
+            }
+            close();
+        } finally {
+            try {
+                threadIO.close();
+            } catch (Throwable t) {
+                // Ignore
+            }
+        }
+    }
+
+    @Override
+    public Object execute(CharSequence commandline) throws Exception {
+        return session.execute(commandline);
+    }
+
+    @Override
+    public Object get(String name) {
+        return session.get(name);
+    }
+
+    @Override
+    public void put(String name, Object value) {
+        session.put(name, value);
+    }
+
+    @Override
+    public InputStream getKeyboard() {
+        return session.getKeyboard();
+    }
+
+    @Override
+    public PrintStream getConsole() {
+        return session.getConsole();
+    }
+
+    @Override
+    public String resolveCommand(String name) {
+        // TODO: optimize
+        if (!name.contains(":")) {
+            String[] scopes = ((String) get(Session.SCOPE)).split(":");
+            List<Command> commands = registry.getCommands();
+            for (String scope : scopes) {
+                for (Command command : commands) {
+                    if ((Session.SCOPE_GLOBAL.equals(scope) || command.getScope().equals(scope)) && command.getName().equals(name)) {
+                        return command.getScope() + ":" + name;
+                    }
+                }
+            }
+        }
+        return name;
+    }
+
+    @Override
+    public String readLine(String prompt, Character mask) throws IOException {
+        return reader.readLine(prompt, mask);
+    }
+
+    private String loadCompletionMode() {
+        String mode;
+        try {
+            File shellCfg = new File(System.getProperty("karaf.etc"), "/org.apache.karaf.shell.cfg");
+            Properties properties = new Properties();
+            properties.load(new FileInputStream(shellCfg));
+            mode = (String) properties.get("completionMode");
+            if (mode == null) {
+                LOGGER.debug("completionMode property is not defined in etc/org.apache.karaf.shell.cfg file. Using default completion mode.");
+                mode = Session.COMPLETION_MODE_GLOBAL;
+            }
+        } catch (Exception e) {
+            LOGGER.warn("Can't read {}/org.apache.karaf.shell.cfg file. The completion is set to default.", System.getProperty("karaf.etc"));
+            mode = Session.COMPLETION_MODE_GLOBAL;
+        }
+        return mode;
+    }
+
+    private String readAndParseCommand() throws IOException {
+        String command = null;
+        boolean loop = true;
+        boolean first = true;
+        while (loop) {
+            checkInterrupt();
+            String line = reader.readLine(first ? getPrompt() : "> ");
+            if (line == null) {
+                break;
+            }
+            if (command == null) {
+                command = line;
+            } else {
+                if (command.charAt(command.length() - 1) == '\\') {
+                    command = command.substring(0, command.length() - 1) + line;
+                } else {
+                    command += "\n" + line;
+                }
+            }
+            if (reader.getHistory().size() == 0) {
+                reader.getHistory().add(command);
+            } else {
+                // jline doesn't add blank lines to the history so we don't
+                // need to replace the command in jline's console history with
+                // an indented one
+                if (command.length() > 0 && !" ".equals(command)) {
+                    reader.getHistory().replace(command);
+                }
+            }
+            if (command.length() > 0 && command.charAt(command.length() - 1) == '\\') {
+                loop = true;
+                first = false;
+            } else {
+                try {
+                    Class<?> cl = CommandSession.class.getClassLoader().loadClass("org.apache.felix.gogo.runtime.Parser");
+                    Object parser = cl.getConstructor(CharSequence.class).newInstance(command);
+                    cl.getMethod("program").invoke(parser);
+                    loop = false;
+                } catch (Exception e) {
+                    loop = true;
+                    first = false;
+                } catch (Throwable t) {
+                    // Reflection problem ? just quit
+                    loop = false;
+                }
+            }
+        }
+        return command;
+    }
+
+    private void executeScript(String scriptFileName) {
+        if (scriptFileName != null) {
+            Reader r = null;
+            try {
+                File scriptFile = new File(scriptFileName);
+                r = new InputStreamReader(new FileInputStream(scriptFile));
+                CharArrayWriter w = new CharArrayWriter();
+                int n;
+                char[] buf = new char[8192];
+                while ((n = r.read(buf)) > 0) {
+                    w.write(buf, 0, n);
+                }
+                session.execute(new String(w.toCharArray()));
+            } catch (Exception e) {
+                LOGGER.debug("Error in initialization script", e);
+                System.err.println("Error in initialization script: " + e.getMessage());
+            } finally {
+                if (r != null) {
+                    try {
+                        r.close();
+                    } catch (IOException e) {
+                        // Ignore
+                    }
+                }
+            }
+        }
+    }
+
+    protected void welcome(Properties brandingProps) {
+        String welcome = brandingProps.getProperty("welcome");
+        if (welcome != null && welcome.length() > 0) {
+            session.getConsole().println(welcome);
+        }
+    }
+
+    protected void setSessionProperties(Properties brandingProps) {
+        for (Map.Entry<Object, Object> entry : brandingProps.entrySet()) {
+            String key = (String) entry.getKey();
+            if (key.startsWith("session.")) {
+                session.put(key.substring("session.".length()), entry.getValue());
+            }
+        }
+    }
+
+    protected String getPrompt() {
+        try {
+            String prompt;
+            try {
+                Object p = session.get(PROMPT);
+                if (p != null) {
+                    prompt = p.toString();
+                } else {
+                    Properties properties = Branding.loadBrandingProperties(terminal);
+                    if (properties.getProperty("prompt") != null) {
+                        prompt = properties.getProperty("prompt");
+                        // we put the PROMPT in ConsoleSession to avoid to read
+                        // the properties file each time.
+                        session.put(PROMPT, prompt);
+                    } else {
+                        prompt = DEFAULT_PROMPT;
+                    }
+                }
+            } catch (Throwable t) {
+                prompt = DEFAULT_PROMPT;
+            }
+            Matcher matcher = Pattern.compile("\\$\\{([^}]+)\\}").matcher(prompt);
+            while (matcher.find()) {
+                Object rep = session.get(matcher.group(1));
+                if (rep != null) {
+                    prompt = prompt.replace(matcher.group(0), rep.toString());
+                    matcher.reset(prompt);
+                }
+            }
+            return prompt;
+        } catch (Throwable t) {
+            return "$ ";
+        }
+    }
+
+    private void checkInterrupt() throws IOException {
+        if (Thread.interrupted() || interrupt) {
+            interrupt = false;
+            throw new InterruptedIOException("Keyboard interruption");
+        }
+    }
+
+    private void interrupt() {
+        interrupt = true;
+        thread.interrupt();
+    }
+
+    private String getPid() {
+        String name = ManagementFactory.getRuntimeMXBean().getName();
+        String[] parts = name.split("@");
+        return parts[0];
+    }
+
+    private class ConsoleInputStream extends InputStream {
+        private int read(boolean wait) throws IOException {
+            if (!running) {
+                return -1;
+            }
+            checkInterrupt();
+            if (eof && queue.isEmpty()) {
+                return -1;
+            }
+            Integer i;
+            if (wait) {
+                try {
+                    i = queue.take();
+                } catch (InterruptedException e) {
+                    throw new InterruptedIOException();
+                }
+                checkInterrupt();
+            } else {
+                i = queue.poll();
+            }
+            if (i == null) {
+                return -1;
+            }
+            return i;
+        }
+
+        @Override
+        public int read() throws IOException {
+            return read(true);
+        }
+
+        @Override
+        public int read(byte b[], int off, int len) throws IOException {
+            if (b == null) {
+                throw new NullPointerException();
+            } else if (off < 0 || len < 0 || len > b.length - off) {
+                throw new IndexOutOfBoundsException();
+            } else if (len == 0) {
+                return 0;
+            }
+
+            int nb = 1;
+            int i = read(true);
+            if (i < 0) {
+                return -1;
+            }
+            b[off++] = (byte) i;
+            while (nb < len) {
+                i = read(false);
+                if (i < 0) {
+                    return nb;
+                }
+                b[off++] = (byte) i;
+                nb++;
+            }
+            return nb;
+        }
+
+        @Override
+        public int available() throws IOException {
+            return queue.size();
+        }
+    }
+
+    private class Pipe extends Thread {
+        public Pipe() {
+            super("Karaf shell pipe thread");
+            setDaemon(true);
+        }
+
+        public void run() {
+            try {
+                while (running) {
+                    try {
+                        int c = in.read();
+                        if (c == -1) {
+                            return;
+                        } else if (c == 4 && !ShellUtil.getBoolean(ConsoleSessionImpl.this, Session.IGNORE_INTERRUPTS)) {
+                            err.println("^D");
+                            return;
+                        } else if (c == 3 && !ShellUtil.getBoolean(ConsoleSessionImpl.this, Session.IGNORE_INTERRUPTS)) {
+                            err.println("^C");
+                            reader.getCursorBuffer().clear();
+                            ConsoleSessionImpl.this.interrupt();
+                        }
+                        queue.put(c);
+                    } catch (Throwable t) {
+                        return;
+                    }
+                }
+            } finally {
+                eof = true;
+                try {
+                    queue.put(-1);
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/HeadlessSessionImpl.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/HeadlessSessionImpl.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/HeadlessSessionImpl.java
new file mode 100644
index 0000000..16e73ca
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/HeadlessSessionImpl.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.console;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.List;
+import java.util.Properties;
+
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.karaf.shell.api.console.Command;
+import org.apache.karaf.shell.api.console.History;
+import org.apache.karaf.shell.api.console.Registry;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.console.SessionFactory;
+import org.apache.karaf.shell.api.console.Terminal;
+import org.apache.karaf.shell.support.ShellUtil;
+
+public class HeadlessSessionImpl implements Session {
+
+    final SessionFactory factory;
+    final CommandSession session;
+    final Registry registry;
+
+    public HeadlessSessionImpl(SessionFactory factory, CommandProcessor processor, InputStream in, PrintStream out, PrintStream err) {
+        // Factory
+        this.factory = factory;
+        // Registry
+        registry = new RegistryImpl(factory.getRegistry());
+        registry.register(factory);
+        registry.register(this);
+        registry.register(registry);
+        // Session
+        session = processor.createSession(in, out, err);
+        Properties sysProps = System.getProperties();
+        for (Object key : sysProps.keySet()) {
+            session.put(key.toString(), sysProps.get(key));
+        }
+        session.put(".session", this);
+        session.put(".commandSession", session);
+        session.put(Session.SCOPE, "shell:bundle:*");
+        session.put(Session.SUBSHELL, "");
+        session.put("USER", ShellUtil.getCurrentUserName());
+        session.put("APPLICATION", System.getProperty("karaf.name", "root"));
+    }
+
+    public CommandSession getSession() {
+        return session;
+    }
+
+    @Override
+    public Object execute(CharSequence commandline) throws Exception {
+        return session.execute(commandline);
+    }
+
+    @Override
+    public Object get(String name) {
+        return session.get(name);
+    }
+
+    @Override
+    public void put(String name, Object value) {
+        session.put(name, value);
+    }
+
+    @Override
+    public InputStream getKeyboard() {
+        return session.getKeyboard();
+    }
+
+    @Override
+    public PrintStream getConsole() {
+        return session.getConsole();
+    }
+
+    @Override
+    public String readLine(String prompt, Character mask) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Terminal getTerminal() {
+        return null;
+    }
+
+    @Override
+    public History getHistory() {
+        return null;
+    }
+
+    @Override
+    public Registry getRegistry() {
+        return registry;
+    }
+
+    @Override
+    public SessionFactory getFactory() {
+        return factory;
+    }
+
+    @Override
+    public void run() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String resolveCommand(String name) {
+        // TODO: optimize
+        if (!name.contains(":")) {
+            String[] scopes = ((String) get(Session.SCOPE)).split(":");
+            List<Command> commands = registry.getCommands();
+            for (String scope : scopes) {
+                for (Command command : commands) {
+                    if ((Session.SCOPE_GLOBAL.equals(scope) || command.getScope().equals(scope)) && command.getName().equals(name)) {
+                        return command.getScope() + ":" + name;
+                    }
+                }
+            }
+        }
+        return name;
+    }
+
+    @Override
+    public void close() {
+        session.close();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/HistoryWrapper.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/HistoryWrapper.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/HistoryWrapper.java
new file mode 100644
index 0000000..6f69991
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/HistoryWrapper.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.console;
+
+import org.apache.karaf.shell.api.console.History;
+
+public class HistoryWrapper implements History {
+
+    private final jline.console.history.History history;
+
+    public HistoryWrapper(jline.console.history.History history) {
+        this.history = history;
+    }
+
+    @Override
+    public void clear() {
+        history.clear();
+    }
+
+    public int first() {
+        return history.iterator().next().index() + 1;
+    }
+
+    public int last() {
+        return first() + history.size() - 1;
+    }
+
+    @Override
+    public CharSequence get(int index) {
+        return history.get(index - 1);
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/JLineTerminal.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/JLineTerminal.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/JLineTerminal.java
new file mode 100644
index 0000000..d000885
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/JLineTerminal.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.console;
+
+import org.apache.karaf.shell.api.console.Terminal;
+
+/**
+* Created by gnodet on 27/02/14.
+*/
+public class JLineTerminal implements Terminal {
+
+    private final jline.Terminal terminal;
+
+    public JLineTerminal(jline.Terminal terminal) {
+        this.terminal = terminal;
+    }
+
+    public jline.Terminal getTerminal() {
+        return terminal;
+    }
+
+    @Override
+    public int getWidth() {
+        return terminal.getWidth();
+    }
+
+    @Override
+    public int getHeight() {
+        return terminal.getHeight();
+    }
+
+    @Override
+    public boolean isAnsiSupported() {
+        return terminal.isAnsiSupported();
+    }
+
+    @Override
+    public boolean isEchoEnabled() {
+        return terminal.isEchoEnabled();
+    }
+
+    @Override
+    public void setEchoEnabled(boolean enabled) {
+        terminal.setEchoEnabled(enabled);
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/KarafFileHistory.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/KarafFileHistory.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/KarafFileHistory.java
new file mode 100644
index 0000000..aa4d2e4
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/KarafFileHistory.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.console;
+
+import java.io.File;
+import java.io.IOException;
+
+import jline.console.history.FileHistory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Override the FileHistory impl to trap failures due to the
+ * user does not having write access to the history file.
+ */
+public final class KarafFileHistory extends FileHistory {
+
+	static final Logger LOGGER = LoggerFactory.getLogger(KarafFileHistory.class);
+	boolean failed = false;
+
+	public KarafFileHistory(File file) throws IOException {
+		super(file);
+	}
+
+	@Override
+	public void flush() throws IOException {
+	    if( !failed ) {
+	        try {
+	            super.flush();
+	        } catch (IOException e) {
+	            failed = true;
+	            LOGGER.debug("Could not write history file: "+ getFile(), e);
+	        }
+	    }
+	}
+
+	@Override
+	public void purge() throws IOException {
+	    if( !failed ) {
+	        try {
+	            super.purge();
+	        } catch (IOException e) {
+	            failed = true;
+	            LOGGER.debug("Could not delete history file: "+ getFile(), e);
+	        }
+	    }
+	}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/KarafTerminal.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/KarafTerminal.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/KarafTerminal.java
new file mode 100644
index 0000000..0c8b0fe
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/KarafTerminal.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.console;
+
+import jline.TerminalSupport;
+import org.apache.karaf.shell.api.console.Terminal;
+
+public class KarafTerminal extends TerminalSupport {
+
+    private final Terminal terminal;
+
+    public KarafTerminal(Terminal terminal) {
+        super(true);
+        this.terminal = terminal;
+    }
+
+    @Override
+    public synchronized boolean isAnsiSupported() {
+        return terminal.isAnsiSupported();
+    }
+
+    @Override
+    public int getWidth() {
+        return terminal.getWidth();
+    }
+
+    @Override
+    public int getHeight() {
+        return terminal.getHeight();
+    }
+
+    @Override
+    public synchronized boolean isEchoEnabled() {
+        return terminal.isEchoEnabled();
+    }
+
+    @Override
+    public synchronized void setEchoEnabled(boolean enabled) {
+        terminal.setEchoEnabled(enabled);
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/RegistryImpl.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/RegistryImpl.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/RegistryImpl.java
new file mode 100644
index 0000000..43ed27e
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/RegistryImpl.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.console;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+import org.apache.karaf.shell.api.console.Command;
+import org.apache.karaf.shell.api.console.Registry;
+
+public class RegistryImpl implements Registry {
+
+    private final Registry parent;
+    private final Map<Object, Object> services = new LinkedHashMap<Object, Object>();
+
+    public RegistryImpl(Registry parent) {
+        this.parent = parent;
+    }
+
+    @Override
+    public List<Command> getCommands() {
+        return getServices(Command.class);
+    }
+
+    @Override
+    public <T> void register(Callable<T> factory, Class<T> clazz) {
+        synchronized (services) {
+            services.put(factory, new Factory<T>(clazz, factory));
+        }
+    }
+
+    @Override
+    public void register(Object service) {
+        synchronized (services) {
+            services.put(service, service);
+        }
+    }
+
+    @Override
+    public void unregister(Object service) {
+        synchronized (services) {
+            services.remove(service);
+        }
+    }
+
+    @Override
+    public <T> T getService(Class<T> clazz) {
+        synchronized (services) {
+            for (Object service : services.values()) {
+                if (service instanceof Factory) {
+                    if (clazz.isAssignableFrom(((Factory) service).clazz)) {
+                        if (isVisible(service)) {
+                            try {
+                                return clazz.cast(((Factory) service).callable.call());
+                            } catch (Exception e) {
+                                // TODO: log exception
+                            }
+                        }
+                    }
+                } else if (clazz.isInstance(service)) {
+                    if (isVisible(service)) {
+                        return clazz.cast(service);
+                    }
+                }
+            }
+        }
+        if (parent != null) {
+            return parent.getService(clazz);
+        }
+        return null;
+    }
+
+    @Override
+    public <T> List<T> getServices(Class<T> clazz) {
+        List<T> list = new ArrayList<T>();
+        synchronized (services) {
+            for (Object service : services.values()) {
+                if (service instanceof Factory) {
+                    if (clazz.isAssignableFrom(((Factory) service).clazz)) {
+                        if (isVisible(service)) {
+                            try {
+                                list.add(clazz.cast(((Factory) service).callable.call()));
+                            } catch (Exception e) {
+                                // TODO: log exception
+                            }
+                        }
+                    }
+                } else if (clazz.isInstance(service)) {
+                    if (isVisible(service)) {
+                        list.add(clazz.cast(service));
+                    }
+                }
+            }
+        }
+        if (parent != null) {
+            list.addAll(parent.getServices(clazz));
+        }
+        return list;
+    }
+
+    @Override
+    public boolean hasService(Class<?> clazz) {
+        synchronized (services) {
+            for (Object service : services.values()) {
+                if (service instanceof Factory) {
+                    if (clazz.isAssignableFrom(((Factory) service).clazz)) {
+                        if (isVisible(service)) {
+                            return true;
+                        }
+                    }
+                } else if (clazz.isInstance(service)) {
+                    if (isVisible(service)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        if (parent != null) {
+            return parent.hasService(clazz);
+        }
+        return false;
+    }
+
+    protected boolean isVisible(Object service) {
+        return true;
+    }
+
+    static class Factory<T> {
+
+        final Class<T> clazz;
+        final Callable<T> callable;
+
+        Factory(Class<T> clazz, Callable<T> callable) {
+            this.clazz = clazz;
+            this.callable = callable;
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/SessionFactoryImpl.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/SessionFactoryImpl.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/SessionFactoryImpl.java
new file mode 100644
index 0000000..2f19499
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/SessionFactoryImpl.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.console;
+
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.lang.management.ManagementFactory;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.felix.gogo.runtime.CommandProcessorImpl;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.felix.service.command.Function;
+import org.apache.felix.service.threadio.ThreadIO;
+import org.apache.karaf.shell.api.console.Command;
+import org.apache.karaf.shell.api.console.Registry;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.console.SessionFactory;
+import org.apache.karaf.shell.api.console.Terminal;
+import org.apache.karaf.shell.impl.console.commands.ExitCommand;
+import org.apache.karaf.shell.impl.console.commands.SubShellCommand;
+import org.apache.karaf.shell.impl.console.commands.help.HelpCommand;
+import org.apache.karaf.shell.support.ShellUtil;
+
+public class SessionFactoryImpl extends RegistryImpl implements SessionFactory, Registry {
+
+    final CommandProcessorImpl commandProcessor;
+    final ThreadIO threadIO;
+    final List<Session> sessions = new ArrayList<Session>();
+    final Map<String, SubShellCommand> subshells = new HashMap<String, SubShellCommand>();
+    boolean closed;
+
+    public SessionFactoryImpl(ThreadIO threadIO) {
+        super(null);
+        this.threadIO = threadIO;
+        commandProcessor = new CommandProcessorImpl(threadIO);
+        register(new ExitCommand());
+        new HelpCommand(this);
+    }
+
+    public CommandProcessorImpl getCommandProcessor() {
+        return commandProcessor;
+    }
+
+    @Override
+    public Registry getRegistry() {
+        return this;
+    }
+
+    @Override
+    public void register(Object service) {
+        if (service instanceof Command) {
+            Command command = (Command) service;
+            String scope = command.getScope();
+            String name = command.getName();
+            if (!Session.SCOPE_GLOBAL.equals(scope)) {
+                if (!subshells.containsKey(scope)) {
+                    SubShellCommand subShell = new SubShellCommand(scope);
+                    subshells.put(scope, subShell);
+                    register(subShell);
+                }
+                subshells.get(scope).increment();
+            }
+            commandProcessor.addCommand(scope, wrap(command), name);
+        }
+        super.register(service);
+    }
+
+    protected Function wrap(Command command) {
+        return new CommandWrapper(command);
+    }
+
+    @Override
+    public void unregister(Object service) {
+        super.unregister(service);
+        if (service instanceof Command) {
+            Command command = (Command) service;
+            String scope = command.getScope();
+            String name = command.getName();
+            commandProcessor.removeCommand(scope, name);
+            if (!Session.SCOPE_GLOBAL.equals(scope)) {
+                if (subshells.get(scope).decrement() == 0) {
+                    SubShellCommand subShell = subshells.remove(scope);
+                    unregister(subShell);
+                }
+            }
+        }
+    }
+
+    @Override
+    public Session create(InputStream in, PrintStream out, PrintStream err, Terminal term, String encoding, Runnable closeCallback) {
+        synchronized (this) {
+            if (closed) {
+                throw new IllegalStateException("SessionFactory has been closed");
+            }
+            final Session session = new ConsoleSessionImpl(this, commandProcessor, threadIO, in, out, err, term, encoding, closeCallback);
+            sessions.add(session);
+            return session;
+        }
+    }
+
+    @Override
+    public Session create(InputStream in, PrintStream out, PrintStream err) {
+        synchronized (this) {
+            if (closed) {
+                throw new IllegalStateException("SessionFactory has been closed");
+            }
+            final Session session = new HeadlessSessionImpl(this, commandProcessor, in, out, err);
+            sessions.add(session);
+            return session;
+        }
+    }
+
+    public void stop() {
+        synchronized (this) {
+            closed = true;
+            for (Session session : sessions) {
+                session.close();
+            }
+            commandProcessor.stop();
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/TerminalFactory.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/TerminalFactory.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/TerminalFactory.java
new file mode 100644
index 0000000..bae3a25
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/TerminalFactory.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.karaf.shell.impl.console;
+
+import jline.NoInterruptUnixTerminal;
+import jline.Terminal;
+
+public class TerminalFactory {
+
+    private Terminal term;
+
+    public synchronized Terminal getTerminal() throws Exception {
+        if (term == null) {
+            init();
+        }
+        return term;
+    }
+
+    public void init() throws Exception {
+        jline.TerminalFactory.registerFlavor(jline.TerminalFactory.Flavor.UNIX, NoInterruptUnixTerminal.class);
+        term = jline.TerminalFactory.create();
+    }
+
+    public synchronized void destroy() throws Exception {
+        if (term != null) {
+            term.restore();
+            term = null;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/ExitCommand.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/ExitCommand.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/ExitCommand.java
new file mode 100644
index 0000000..147d403
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/ExitCommand.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.console.commands;
+
+import org.apache.karaf.shell.api.console.Session;
+
+public class ExitCommand extends TopLevelCommand {
+
+    @Override
+    public String getName() {
+        return "exit";
+    }
+
+    @Override
+    public String getDescription() {
+        return "Exit from the current shell";
+    }
+
+    @Override
+    protected void doExecute(Session session) throws Exception {
+        // get the current sub-shell
+        String currentSubShell = (String) session.get(Session.SUBSHELL);
+        if (!currentSubShell.isEmpty()) {
+            if (currentSubShell.contains(":")) {
+                int index = currentSubShell.lastIndexOf(":");
+                session.put(Session.SUBSHELL, currentSubShell.substring(0, index));
+            } else {
+                session.put(Session.SUBSHELL, "");
+            }
+            String currentScope = (String) session.get(Session.SCOPE);
+            int index = currentScope.indexOf(":");
+            session.put(Session.SCOPE, currentScope.substring(index + 1));
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/SubShellCommand.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/SubShellCommand.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/SubShellCommand.java
new file mode 100644
index 0000000..d2826f3
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/SubShellCommand.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.console.commands;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.karaf.shell.api.console.Session;
+
+public class SubShellCommand extends TopLevelCommand {
+
+    private final String name;
+    private final AtomicInteger references = new AtomicInteger();
+
+    public SubShellCommand(String name) {
+        this.name = name;
+    }
+
+    public void increment() {
+        references.incrementAndGet();
+    }
+
+    public int decrement() {
+        return references.decrementAndGet();
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String getDescription() {
+        return "Enter the subshell";
+    }
+
+    @Override
+    protected void doExecute(Session session) throws Exception {
+        session.put(Session.SUBSHELL, name);
+        session.put(Session.SCOPE, name + ":" + session.get(Session.SCOPE));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/TopLevelCommand.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/TopLevelCommand.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/TopLevelCommand.java
new file mode 100644
index 0000000..ad2c729
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/TopLevelCommand.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.console.commands;
+
+import java.io.PrintStream;
+import java.util.List;
+
+import org.apache.karaf.shell.api.console.Command;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.CommandException;
+
+import static org.apache.karaf.shell.support.ansi.SimpleAnsi.COLOR_DEFAULT;
+import static org.apache.karaf.shell.support.ansi.SimpleAnsi.COLOR_RED;
+import static org.apache.karaf.shell.support.ansi.SimpleAnsi.INTENSITY_BOLD;
+import static org.apache.karaf.shell.support.ansi.SimpleAnsi.INTENSITY_NORMAL;
+
+public abstract class TopLevelCommand implements Command {
+
+    @Override
+    public String getScope() {
+        return Session.SCOPE_GLOBAL;
+    }
+
+    @Override
+    public Completer getCompleter(boolean scoped) {
+        return null;
+//        return new StringsCompleter(new String[] { getName() });
+    }
+
+    @Override
+    public Object execute(Session session, List<Object> arguments) throws Exception {
+        if (arguments.contains("--help")) {
+            printHelp(System.out);
+            return null;
+        }
+        if (!arguments.isEmpty()) {
+            String msg = COLOR_RED
+                    + "Error executing command "
+                    + INTENSITY_BOLD + getName() + INTENSITY_NORMAL
+                    + COLOR_DEFAULT + ": " + "too many arguments specified";
+            throw new CommandException(msg);
+        }
+        doExecute(session);
+        return null;
+    }
+
+    protected void printHelp(PrintStream out) {
+        out.println(INTENSITY_BOLD + "DESCRIPTION" + INTENSITY_NORMAL);
+        out.print("        ");
+        out.println(INTENSITY_BOLD + getName() + INTENSITY_NORMAL);
+        out.println();
+        out.print("\t");
+        out.println(getDescription());
+        out.println();
+        out.println(INTENSITY_BOLD + "SYNTAX" + INTENSITY_NORMAL);
+        out.print("        ");
+        out.println(getName() + " [options]");
+        out.println();
+        out.println(INTENSITY_BOLD + "OPTIONS" + INTENSITY_NORMAL);
+        out.print("        ");
+        out.println(INTENSITY_BOLD + "--help" + INTENSITY_NORMAL);
+        out.print("                ");
+        out.println("Display this help message");
+        out.println();
+    }
+
+    protected abstract void doExecute(Session session) throws Exception;
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/help/CommandListHelpProvider.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/help/CommandListHelpProvider.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/help/CommandListHelpProvider.java
new file mode 100644
index 0000000..9101520
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/help/CommandListHelpProvider.java
@@ -0,0 +1,117 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.shell.impl.console.commands.help;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.apache.karaf.shell.api.console.Command;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.console.Terminal;
+import org.apache.karaf.shell.support.NameScoping;
+import org.apache.karaf.shell.support.ansi.SimpleAnsi;
+import org.apache.karaf.shell.support.table.Col;
+import org.apache.karaf.shell.support.table.ShellTable;
+
+public class CommandListHelpProvider implements HelpProvider {
+
+    public String getHelp(Session session, String path) {
+        if (path.indexOf('|') > 0) {
+            if (path.startsWith("command-list|")) {
+                path = path.substring("command-list|".length());
+            } else {
+                return null;
+            }
+        }
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        SortedMap<String, String> commands = getCommandDescriptions(session, path);
+        if (commands.isEmpty()) {
+            return null;
+        } else if (commands.size() == 1 && commands.containsKey(path)) {
+            return null;
+        } else {
+            printMethodList(session, new PrintStream(baos), commands);
+            return baos.toString();
+        }
+    }
+
+    private SortedMap<String, String> getCommandDescriptions(Session session, String path) {
+        // TODO: this is not really clean
+
+        List<Command> commands = session.getRegistry().getCommands();
+
+        String subshell = (String) session.get(Session.SUBSHELL);
+        String completionMode = (String) session.get(Session.COMPLETION_MODE);
+
+        SortedMap<String,String> descriptions = new TreeMap<String,String>();
+        for (Command command : commands) {
+
+            String name = command.getScope() + ":" + command.getName();
+
+            if (command != null && !name.startsWith(path)) {
+                continue;
+            }
+
+            if (completionMode != null && completionMode.equalsIgnoreCase(Session.COMPLETION_MODE_SUBSHELL)) {
+                // filter the help only for "global" commands
+                if (subshell == null || subshell.trim().isEmpty()) {
+                    if (!name.startsWith(Session.SCOPE_GLOBAL)) {
+                        continue;
+                    }
+                }
+            }
+
+            if (completionMode != null && (completionMode.equalsIgnoreCase(Session.COMPLETION_MODE_SUBSHELL)
+                                                || completionMode.equalsIgnoreCase(Session.COMPLETION_MODE_FIRST))) {
+                // filter the help only for commands local to the subshell
+                if (!name.startsWith(subshell)) {
+                    continue;
+                }
+            }
+
+            String description = command.getDescription();
+            if (name.startsWith("*:")) {
+                name = name.substring(2);
+            }
+            if (subshell != null && !subshell.trim().isEmpty() && name.startsWith(subshell + ":")) {
+                name = name.substring(subshell.length() + 1);
+            }
+            descriptions.put(name, description);
+        }
+        return descriptions;
+    }
+
+    protected void printMethodList(Session session, PrintStream out, SortedMap<String, String> commands) {
+        Terminal term = session.getTerminal();
+        int termWidth = term != null ? term.getWidth() : 80;
+        out.println(SimpleAnsi.INTENSITY_BOLD + "COMMANDS" + SimpleAnsi.INTENSITY_NORMAL);
+        ShellTable table = new ShellTable().noHeaders().separator(" ").size(termWidth);
+        table.column(new Col("Command").maxSize(35));
+        table.column(new Col("Description"));
+        for (Map.Entry<String,String> entry : commands.entrySet()) {
+            String key = NameScoping.getCommandNameWithoutGlobalPrefix(session, entry.getKey());
+            table.addRow().addContent(key, entry.getValue());
+        }
+        table.print(out, true);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/help/HelpCommand.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/help/HelpCommand.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/help/HelpCommand.java
new file mode 100644
index 0000000..086b83b
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/help/HelpCommand.java
@@ -0,0 +1,225 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.console.commands.help;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.karaf.util.properties.InterpolationHelper;
+import org.apache.karaf.shell.api.console.Command;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Registry;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.console.SessionFactory;
+import org.apache.karaf.shell.support.CommandException;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
+
+import static org.apache.karaf.shell.support.ansi.SimpleAnsi.COLOR_DEFAULT;
+import static org.apache.karaf.shell.support.ansi.SimpleAnsi.COLOR_RED;
+import static org.apache.karaf.shell.support.ansi.SimpleAnsi.INTENSITY_BOLD;
+import static org.apache.karaf.shell.support.ansi.SimpleAnsi.INTENSITY_NORMAL;
+
+public class HelpCommand implements Command {
+
+    public HelpCommand(SessionFactory factory) {
+        Registry registry = factory.getRegistry();
+        registry.register(this);
+        registry.register(new SimpleHelpProvider());
+        registry.register(new CommandListHelpProvider());
+        registry.register(new SingleCommandHelpProvider());
+    }
+
+    @Override
+    public String getScope() {
+        return Session.SCOPE_GLOBAL;
+    }
+
+    @Override
+    public String getName() {
+        return "help";
+    }
+
+    @Override
+    public String getDescription() {
+        return "Displays this help or help about a command";
+    }
+
+    @Override
+    public Object execute(Session session, List<Object> arguments) throws Exception {
+        if (arguments.contains("--help")) {
+            printHelp(System.out);
+            return null;
+        }
+        if (arguments.size() > 1) {
+            String msg = COLOR_RED
+                    + "Error executing command "
+                    + INTENSITY_BOLD + getName() + INTENSITY_NORMAL
+                    + COLOR_DEFAULT + ": " + "too many arguments specified";
+            throw new CommandException(msg);
+        }
+        String path = arguments.isEmpty() ? null : arguments.get(0) == null ? null : arguments.get(0).toString();
+        String help = getHelp(session, path);
+        if (help != null) {
+            System.out.println(help);
+        }
+        return null;
+    }
+
+    @Override
+    public Completer getCompleter(final boolean scoped) {
+        return new Completer() {
+            @Override
+            public int complete(Session session, CommandLine commandLine, List<String> candidates) {
+                String[] args = commandLine.getArguments();
+                int argIndex = commandLine.getCursorArgumentIndex();
+                StringsCompleter completer = new StringsCompleter(Collections.singletonList(getName()));
+                if (argIndex == 0) {
+                    return completer.complete(session, new ArgumentCommandLine(args[argIndex], commandLine.getArgumentPosition()), candidates);
+                } else if (!verifyCompleter(session, completer, args[0])) {
+                    return -1;
+                }
+                // TODO: use CommandNamesCompleter and better completion wrt parsing etc...
+                completer = new StringsCompleter();
+                for (Command command : session.getRegistry().getCommands()) {
+                    if (!Session.SCOPE_GLOBAL.equals(command.getScope())) {
+                        completer.getStrings().add(command.getScope() + ":" + command.getName());
+                    }
+                    completer.getStrings().add(command.getName());
+                }
+                completer.getStrings().add("--help");
+                if (argIndex == 1) {
+                    int res;
+                    if (argIndex < args.length) {
+                        res = completer.complete(session, new ArgumentCommandLine(args[argIndex], commandLine.getArgumentPosition()), candidates);
+                    } else {
+                        res = completer.complete(session, new ArgumentCommandLine("", 0), candidates);
+                    }
+                    return res + (commandLine.getBufferPosition() - commandLine.getArgumentPosition());
+                } else if (!verifyCompleter(session, completer, args[1])) {
+                    return -1;
+                }
+                return -1;
+            }
+            protected boolean verifyCompleter(Session session, Completer completer, String argument) {
+                List<String> candidates = new ArrayList<String>();
+                return completer.complete(session, new ArgumentCommandLine(argument, argument.length()), candidates) != -1 && !candidates.isEmpty();
+            }
+        };
+    }
+
+    protected void printHelp(PrintStream out) {
+        out.println(INTENSITY_BOLD + "DESCRIPTION" + INTENSITY_NORMAL);
+        out.print("        ");
+        out.println(INTENSITY_BOLD + getName() + INTENSITY_NORMAL);
+        out.println();
+        out.print("\t");
+        out.println(getDescription());
+        out.println();
+        out.println(INTENSITY_BOLD + "SYNTAX" + INTENSITY_NORMAL);
+        out.print("        ");
+        out.println(getName() + " [options] [command]");
+        out.println();
+        out.println(INTENSITY_BOLD + "ARGUMENTS" + INTENSITY_NORMAL);
+        out.print("        ");
+        out.println(INTENSITY_BOLD + "command" + INTENSITY_NORMAL);
+        out.print("                ");
+        out.println("Command to display help for");
+        out.println();
+        out.println(INTENSITY_BOLD + "OPTIONS" + INTENSITY_NORMAL);
+        out.print("        ");
+        out.println(INTENSITY_BOLD + "--help" + INTENSITY_NORMAL);
+        out.print("                ");
+        out.println("Display this help message");
+        out.println();
+    }
+
+    public String getHelp(final Session session, String path) {
+        if (path == null) {
+            path = "%root%";
+        }
+        Map<String,String> props = new HashMap<String,String>();
+        props.put("data", "${" + path + "}");
+        final List<HelpProvider> providers = session.getRegistry().getServices(HelpProvider.class);
+        InterpolationHelper.performSubstitution(props, new InterpolationHelper.SubstitutionCallback() {
+            public String getValue(final String key) {
+                for (HelpProvider hp : providers) {
+                    String result = hp.getHelp(session, key);
+                    if (result != null) {
+                        return removeNewLine(result);
+                    }
+                }
+                return null;
+            }
+        });
+        return props.get("data");
+    }
+
+    private String removeNewLine(String help) {
+        if (help != null && help.endsWith("\n")) {
+            help = help.substring(0, help.length()  -1);
+        }
+        return help;
+    }
+
+    static class ArgumentCommandLine implements CommandLine {
+        private final String argument;
+        private final int position;
+
+        ArgumentCommandLine(String argument, int position) {
+            this.argument = argument;
+            this.position = position;
+        }
+
+        @Override
+        public int getCursorArgumentIndex() {
+            return 0;
+        }
+
+        @Override
+        public String getCursorArgument() {
+            return argument;
+        }
+
+        @Override
+        public int getArgumentPosition() {
+            return position;
+        }
+
+        @Override
+        public String[] getArguments() {
+            return new String[] { argument };
+        }
+
+        @Override
+        public int getBufferPosition() {
+            return position;
+        }
+
+        @Override
+        public String getBuffer() {
+            return argument;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/help/HelpProvider.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/help/HelpProvider.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/help/HelpProvider.java
new file mode 100644
index 0000000..ad5e215
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/help/HelpProvider.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.console.commands.help;
+
+import org.apache.karaf.shell.api.console.Session;
+
+public interface HelpProvider {
+
+    String getHelp(Session session, String path);
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/help/SimpleHelpProvider.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/help/SimpleHelpProvider.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/help/SimpleHelpProvider.java
new file mode 100644
index 0000000..90ad506
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/help/SimpleHelpProvider.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.karaf.shell.impl.console.commands.help;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.karaf.shell.api.console.Session;
+
+
+public class SimpleHelpProvider implements HelpProvider {
+
+    private final Map<String, String> help;
+
+    public SimpleHelpProvider() {
+        help = new HashMap<String, String>();
+        help.put("%root%", "${command-list|}");
+        help.put("all", "${command-list|}");
+    }
+
+    public String getHelp(Session session, String path) {
+        if (path.indexOf('|') > 0) {
+            if (path.startsWith("simple|")) {
+                path = path.substring("simple|".length());
+            } else {
+                return null;
+            }
+        }
+        return help.get(path);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/help/SingleCommandHelpProvider.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/help/SingleCommandHelpProvider.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/help/SingleCommandHelpProvider.java
new file mode 100644
index 0000000..dafab98
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/commands/help/SingleCommandHelpProvider.java
@@ -0,0 +1,54 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.shell.impl.console.commands.help;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import org.apache.karaf.shell.api.console.Session;
+
+public class SingleCommandHelpProvider implements HelpProvider {
+
+    public String getHelp(Session session, String path) {
+        if (path.indexOf('|') > 0) {
+            if (path.startsWith("command|")) {
+                path = path.substring("command|".length());
+            } else {
+                return null;
+            }
+        }
+
+        String resolved = session.resolveCommand(path);
+
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        PrintStream ps = new PrintStream(baos, true);
+        Session s = session.getFactory().create(null, ps, ps);
+        s.put(Session.SCOPE, session.get(Session.SCOPE));
+        s.put(Session.SUBSHELL, session.get(Session.SUBSHELL));
+        try {
+            s.execute(path + " --help");
+        } catch (Throwable t) {
+            return null;
+        } finally {
+            s.close();
+        }
+        return baos.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/Activator.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/Activator.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/Activator.java
new file mode 100644
index 0000000..921dca3
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/Activator.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.console.osgi;
+
+import org.apache.felix.gogo.runtime.threadio.ThreadIOImpl;
+import org.apache.karaf.shell.api.console.SessionFactory;
+import org.apache.karaf.shell.impl.action.osgi.CommandExtender;
+import org.apache.karaf.shell.impl.action.command.ManagerImpl;
+import org.apache.karaf.shell.impl.console.SessionFactoryImpl;
+import org.apache.karaf.shell.impl.console.TerminalFactory;
+import org.apache.karaf.shell.impl.console.osgi.secured.SecuredSessionFactoryImpl;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+public class Activator implements BundleActivator {
+
+    private static final String START_CONSOLE = "karaf.startLocalConsole";
+
+    private ThreadIOImpl threadIO;
+
+    private SessionFactoryImpl sessionFactory;
+    private ServiceRegistration sessionFactoryRegistration;
+
+    private CommandExtender actionExtender;
+
+    private TerminalFactory terminalFactory;
+    private LocalConsoleManager localConsoleManager;
+
+    @Override
+    public void start(BundleContext context) throws Exception {
+        threadIO = new ThreadIOImpl();
+        threadIO.start();
+
+        sessionFactory = new SecuredSessionFactoryImpl(context, threadIO);
+        sessionFactory.getCommandProcessor().addConverter(new Converters(context));
+        sessionFactory.getCommandProcessor().addConstant(".context", context.getBundle(0).getBundleContext());
+
+        sessionFactory.register(new ManagerImpl(sessionFactory, sessionFactory));
+
+        sessionFactoryRegistration = context.registerService(SessionFactory.class.getName(), sessionFactory, null);
+
+        actionExtender = new CommandExtender(sessionFactory);
+        actionExtender.start(context);
+
+        if (Boolean.parseBoolean(context.getProperty(START_CONSOLE))) {
+            terminalFactory = new TerminalFactory();
+            localConsoleManager = new LocalConsoleManager(context, terminalFactory, sessionFactory);
+            localConsoleManager.start();
+        }
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception {
+        sessionFactoryRegistration.unregister();
+        sessionFactory.stop();
+        localConsoleManager.stop();
+        actionExtender.stop(context);
+        threadIO.stop();
+        terminalFactory.destroy();
+    }
+}


[08/10] [KARAF-2805] Migrate shell, ssh, wrapper, kar, instance, features and bundle to the new API and switch the bin/shell script to use the new console

Posted by gn...@apache.org.
http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/IfAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/IfAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/IfAction.java
index 6a01233..b7d4585 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/IfAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/IfAction.java
@@ -16,18 +16,20 @@
  */
 package org.apache.karaf.shell.commands.impl;
 
-import org.apache.felix.service.command.Function;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.Function;
+import org.apache.karaf.shell.api.console.Session;
 
 /**
  * Execute a closure on a list of arguments.
  */
 @Command(scope = "shell", name = "if", description = "If/Then/Else block.")
 @Service
-public class IfAction extends AbstractAction {
+public class IfAction implements Action {
 
     @Argument(name = "condition", index = 0, multiValued = false, required = true, description = "The condition")
     Function condition;
@@ -38,8 +40,11 @@ public class IfAction extends AbstractAction {
     @Argument(name = "ifFalse", index = 2, multiValued = false, required = false, description = "The function to execute if the condition is false")
     Function ifFalse;
 
+    @Reference
+    Session session;
+
     @Override
-    protected Object doExecute() throws Exception {
+    public Object execute() throws Exception {
         Object result = condition.execute(session, null);
         if (isTrue(result)) {
             return ifTrue.execute(session, null);

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/InfoAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/InfoAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/InfoAction.java
index 90cf2e8..dfa811d 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/InfoAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/InfoAction.java
@@ -31,26 +31,33 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Properties;
+import java.util.concurrent.Callable;
 
-import org.apache.karaf.shell.commands.Command;
 import org.apache.karaf.shell.commands.InfoProvider;
-import org.apache.karaf.shell.console.OsgiCommandSupport;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.fusesource.jansi.Ansi;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
 
 @Command(scope = "shell", name = "info", description = "Prints system information.")
 @Service
-public class InfoAction extends OsgiCommandSupport {
+public class InfoAction implements Action {
 
     private NumberFormat fmtI = new DecimalFormat("###,###", new DecimalFormatSymbols(Locale.ENGLISH));
     private NumberFormat fmtD = new DecimalFormat("###,##0.000", new DecimalFormatSymbols(Locale.ENGLISH));
 
-    protected Object doExecute() throws Exception {
+//    @Reference
+    List<InfoProvider> infoProviders;
+
+    @Override
+    public Object execute() throws Exception {
         int maxNameLen;
 
         RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
@@ -67,8 +74,10 @@ public class InfoAction extends OsgiCommandSupport {
         printValue("Karaf version", maxNameLen, System.getProperty("karaf.version"));
         printValue("Karaf home", maxNameLen, System.getProperty("karaf.home"));
         printValue("Karaf base", maxNameLen, System.getProperty("karaf.base"));
-        printValue("OSGi Framework", maxNameLen, bundleContext.getBundle(0).getSymbolicName() + " - " +
-                bundleContext.getBundle(0).getVersion());
+        String osgi = getOsgiFramework();
+        if (osgi != null) {
+            printValue("OSGi Framework", maxNameLen, osgi);
+        }
         System.out.println();
 
         System.out.println("JVM");
@@ -119,9 +128,7 @@ public class InfoAction extends OsgiCommandSupport {
 
         //Display Information from external information providers.
         Map<String, Map<Object, Object>> properties = new HashMap<String, Map<Object, Object>>();
-        List<InfoProvider> infoProviders = getAllServices(InfoProvider.class);
         if (infoProviders != null) {
-
             // dump all properties to Map, KARAF-425
             for (InfoProvider provider : infoProviders) {
                 if (!properties.containsKey(provider.getName())) {
@@ -149,7 +156,6 @@ public class InfoAction extends OsgiCommandSupport {
                 }
             }
         }
-
         return null;
     }
 
@@ -221,4 +227,21 @@ public class InfoAction extends OsgiCommandSupport {
         return sb.toString();
     }
 
+    String getOsgiFramework() {
+        try {
+            Callable<String> call = new Callable<String>() {
+                @Override
+                public String call() throws Exception {
+                    BundleContext context = FrameworkUtil.getBundle(getClass()).getBundleContext();
+                    Bundle sysBundle = context.getBundle(0);
+                    return sysBundle.getSymbolicName() + "-" + sysBundle.getVersion();
+                }
+            };
+            return call.call();
+        } catch (Throwable t) {
+            // We're not in OSGi, just safely return null
+            return null;
+        }
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/JavaAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/JavaAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/JavaAction.java
index 84e4d62..99f201d 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/JavaAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/JavaAction.java
@@ -19,11 +19,13 @@ package org.apache.karaf.shell.commands.impl;
 import java.lang.reflect.Method;
 import java.util.List;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Execute a Java standard application.
@@ -34,7 +36,9 @@ import org.apache.karaf.shell.inject.Service;
  */
 @Command(scope = "shell", name = "java", description = "Executes a Java standard application.")
 @Service
-public class JavaAction extends AbstractAction {
+public class JavaAction implements Action {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
 
     @Option(name = "-m", aliases = {"--method"}, description = "Invoke a named method", required = false, multiValued = false)
     private String methodName = "main";
@@ -45,7 +49,8 @@ public class JavaAction extends AbstractAction {
     @Argument(index = 1, name = "arguments", description="Arguments to pass to the method of the given class", required = false, multiValued = false)
     private List<String> args;
 
-    protected Object doExecute() throws Exception {
+    @Override
+    public Object execute() throws Exception {
         boolean info = log.isInfoEnabled();
 
         Class type = Thread.currentThread().getContextClassLoader().loadClass(className);

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/LogoutAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/LogoutAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/LogoutAction.java
index f11437d..3654225 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/LogoutAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/LogoutAction.java
@@ -16,18 +16,28 @@
  */
 package org.apache.karaf.shell.commands.impl;
 
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.console.CloseShellException;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.Session;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 @Command(scope = "shell", name = "logout", description = "Disconnects shell from current session.")
 @Service
-public class LogoutAction extends AbstractAction {
+public class LogoutAction implements Action {
 
-    protected Object doExecute() throws Exception {
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Reference
+    Session session;
+
+    @Override
+    public Object execute() throws Exception {
         log.info("Disconnecting from current session...");
-        throw new CloseShellException();
+        session.close();
+        return null;
     }
 
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/MoreAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/MoreAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/MoreAction.java
index ebe1d86..5db87f6 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/MoreAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/MoreAction.java
@@ -24,23 +24,34 @@ import java.lang.reflect.Method;
 import java.util.LinkedList;
 import java.util.List;
 
-import jline.Terminal;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.console.Terminal;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 @Command(scope = "shell", name = "more", description = "File pager.")
 @Service
-public class MoreAction extends AbstractAction {
+public class MoreAction implements Action {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
 
     @Option(name = "--lines", description = "stop after N lines")
     int lines;
 
+    @Reference
+    Terminal terminal;
+
+    @Reference
+    Session session;
+
     @Override
-    protected Object doExecute() throws Exception {
-        Terminal term = (Terminal) session.get(".jline.terminal");
-        if (term == null || !isTty(System.out)) {
+    public Object execute() throws Exception {
+        if (terminal == null || !isTty(System.out)) {
             BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
             String line;
             while ((line = reader.readLine()) != null) {
@@ -49,13 +60,13 @@ public class MoreAction extends AbstractAction {
             }
             return null;
         } else {
-            boolean echo = term.isEchoEnabled();
-            term.setEchoEnabled(false);
+            boolean echo = terminal.isEchoEnabled();
+            terminal.setEchoEnabled(false);
             try {
                 if (lines == 0) {
-                    lines = term.getHeight();
+                    lines = terminal.getHeight();
                 }
-                LineSplitter reader = new LineSplitter(new BufferedReader(new InputStreamReader(System.in)), term.getWidth());
+                LineSplitter reader = new LineSplitter(new BufferedReader(new InputStreamReader(System.in)), terminal.getWidth());
                 int count = 0;
                 int c;
                 do {
@@ -100,14 +111,13 @@ public class MoreAction extends AbstractAction {
                         }
                     }
                 } while (c != 'q');
-                return null;
             } catch (InterruptedException ie) {
-            	log.debug("Interupted by user");
-            	return null;
+            	log.debug("Interrupted by user");
             } finally {
-                term.setEchoEnabled(echo);
+                terminal.setEchoEnabled(echo);
             }
         }
+        return null;
     }
 
     public static class LineSplitter {
@@ -148,4 +158,16 @@ public class MoreAction extends AbstractAction {
         }
     }
 
+    /**
+     * This is for long running commands to be interrupted by ctrl-c
+     *
+     * @throws InterruptedException
+     */
+    public static void checkInterrupted() throws InterruptedException {
+        Thread.yield();
+        if (Thread.currentThread().isInterrupted()) {
+            throw new InterruptedException();
+        }
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/NewAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/NewAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/NewAction.java
index 990eee5..c2d2a86 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/NewAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/NewAction.java
@@ -16,14 +16,6 @@
  */
 package org.apache.karaf.shell.commands.impl;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.converter.DefaultConverter;
-import org.apache.karaf.shell.commands.converter.GenericType;
-import org.apache.karaf.shell.commands.converter.ReifiedType;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.inject.Service;
-
 import java.lang.reflect.Array;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
@@ -36,12 +28,20 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.converter.DefaultConverter;
+import org.apache.karaf.shell.support.converter.GenericType;
+import org.apache.karaf.shell.support.converter.ReifiedType;
+
 /**
  * Instantiate a new object
  */
 @Command(scope = "shell", name = "new", description = "Creates a new java object.")
 @Service
-public class NewAction extends AbstractAction {
+public class NewAction implements Action {
 
     @Argument(name = "class", index = 0, multiValued = false, required = true, description = "The object class")
     Class clazz;
@@ -54,7 +54,7 @@ public class NewAction extends AbstractAction {
     protected DefaultConverter converter = new DefaultConverter(getClass().getClassLoader());
 
     @Override
-    protected Object doExecute() throws Exception {
+    public Object execute() throws Exception {
         if (args == null) {
             args = Collections.emptyList();
         }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/PrintStackTracesAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/PrintStackTracesAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/PrintStackTracesAction.java
index a07850c..38ca8b5 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/PrintStackTracesAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/PrintStackTracesAction.java
@@ -16,11 +16,12 @@
  */
 package org.apache.karaf.shell.commands.impl;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.console.SessionProperties;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 /**
  * Command for showing the full tree of bundles that have been used to resolve
@@ -28,14 +29,18 @@ import org.apache.karaf.shell.inject.Service;
  */
 @Command(scope = "shell", name = "stack-traces-print", description = "Prints the full stack trace in the console when the execution of a command throws an exception.")
 @Service
-public class PrintStackTracesAction extends AbstractAction {
+public class PrintStackTracesAction implements Action {
 
     @Argument(name = "print", description="Print stack traces or not", required = false, multiValued = false)
     boolean print = true;
 
-    protected Object doExecute() throws Exception {
+    @Reference
+    Session session;
+
+    @Override
+    public Object execute() throws Exception {
         System.out.println("Printing of stacktraces set to " + print);
-        session.put(SessionProperties.PRINT_STACK_TRACES, Boolean.valueOf(print));
+        session.put(Session.PRINT_STACK_TRACES, print);
         return null;
     }
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/PrintfAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/PrintfAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/PrintfAction.java
index 3761a20..51b02bb 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/PrintfAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/PrintfAction.java
@@ -18,14 +18,14 @@ package org.apache.karaf.shell.commands.impl;
 
 import java.util.Collection;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 @Command(scope = "shell", name = "printf", description = "Formats and prints arguments.")
 @Service
-public class PrintfAction extends AbstractAction {
+public class PrintfAction implements Action {
 
     @Argument(index = 0, name = "format", description = "The format pattern to use", required = true, multiValued = false)
     private String format;
@@ -33,8 +33,10 @@ public class PrintfAction extends AbstractAction {
     @Argument(index = 1, name = "arguments", description = "The arguments for the given format pattern", required = true, multiValued = true)
     private Collection<Object> arguments = null;
 
-    protected Object doExecute() throws Exception {
+    @Override
+    public Object execute() throws Exception {
         System.out.printf(format, arguments.toArray());
         return null;
     }
+
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/SleepAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/SleepAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/SleepAction.java
index 4f4f0e5..e8b1103 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/SleepAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/SleepAction.java
@@ -16,15 +16,19 @@
  */
 package org.apache.karaf.shell.commands.impl;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 @Command(scope = "shell", name = "sleep", description = "Sleeps for a bit then wakes up.")
 @Service
-public class SleepAction extends AbstractAction {
+public class SleepAction implements Action {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
 
     @Argument(index = 0, name = "duration", description = "The amount of time to sleep. The default time unit is millisecond, use -s option to use second instead.", required = true, multiValued = false)
     private long time = -1;
@@ -32,7 +36,8 @@ public class SleepAction extends AbstractAction {
     @Option(name = "-s", aliases = { "--second" }, description = "Use a duration time in seconds instead of milliseconds.", required = false, multiValued = false)
     private boolean second = false;
 
-    protected Object doExecute() throws Exception {
+    @Override
+    public Object execute() throws Exception {
         if (second) {
             log.info("Sleeping for {} second(s)", time);
             time = time * 1000;

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/SortAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/SortAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/SortAction.java
index 6b383f7..4b9d165 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/SortAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/SortAction.java
@@ -34,18 +34,22 @@ import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Sort lines of text
  */
 @Command(scope = "shell", name = "sort", description = "Writes sorted concatenation of all files to standard output.")
 @Service
-public class SortAction extends AbstractAction {
+public class SortAction implements Action {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
 
     @Option(name = "-f", aliases = { "-ignore-case" }, description = "fold lower case to upper case characters", required = false, multiValued = false)
     private boolean caseInsensitive;
@@ -71,8 +75,8 @@ public class SortAction extends AbstractAction {
     @Argument(index = 0, name = "files", description = "A list of files separated by whitespaces", required = false, multiValued = true)
     private List<String> paths;
 
-
-    public Object doExecute() throws Exception {
+    @Override
+    public Object execute() throws Exception {
         if (paths != null && paths.size() > 0) {
             List<String> lines = new ArrayList<String>();
             for (String filename : paths) {

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/SourceAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/SourceAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/SourceAction.java
index 8eaaf18..c8bae6f 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/SourceAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/SourceAction.java
@@ -28,17 +28,23 @@ import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.List;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * TODO
  */
 @Command(scope = "shell", name = "source", description = "Run a script")
 @Service
-public class SourceAction extends AbstractAction {
+public class SourceAction implements Action {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
 
     @Argument(index = 0, name = "script", description = "A URI pointing to the script", required = true, multiValued = false)
     private String script;
@@ -46,8 +52,11 @@ public class SourceAction extends AbstractAction {
     @Argument(index = 1, name = "args", description = "Arguments for the script", required = false, multiValued = true)
     private List<Object> args;
 
+    @Reference
+    Session session;
+
     @Override
-    protected Object doExecute() throws Exception {
+    public Object execute() throws Exception {
         BufferedReader reader = null;
         Object arg0 = session.get("0");
         try {
@@ -75,7 +84,7 @@ public class SourceAction extends AbstractAction {
                 session.put( Integer.toString(i+1), args.get(i) );
             }
 
-            return session.execute(w.toString());
+            session.execute(w.toString());
         } finally {
             for (int i = 0; args != null && i < args.size(); i++) {
                 session.put( Integer.toString(i+1), null );
@@ -89,5 +98,6 @@ public class SourceAction extends AbstractAction {
                 }
             }
         }
+        return null;
     }
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/TacAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/TacAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/TacAction.java
index 89fde75..5cf1b20 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/TacAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/TacAction.java
@@ -25,10 +25,12 @@ import java.io.FileOutputStream;
 import java.io.OutputStreamWriter;
 import java.io.Writer;
 
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 /**
  * Grab the text from the standard input and return it as a string.
@@ -36,12 +38,16 @@ import org.apache.karaf.shell.inject.Service;
  */
 @Command(scope = "shell", name = "tac", description = "Captures the STDIN and returns it as a string. Optionally writes the content to a file.")
 @Service
-public class TacAction extends AbstractAction {
+public class TacAction implements Action {
 
     @Option(name = "-f", aliases = {}, description = "Outputs the content to the given file", required = false, multiValued = false)
     private File file;
 
-    protected Object doExecute() throws Exception {
+    @Reference
+    Session session;
+
+    @Override
+    public Object execute() throws Exception {
         StringWriter sw = new StringWriter();
         Writer[] writers;
         if (file != null) {

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/TailAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/TailAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/TailAction.java
index d9486e2..435816c 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/TailAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/TailAction.java
@@ -25,15 +25,19 @@ import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.LinkedList;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 @Command(scope = "shell", name = "tail", description = "Displays the last lines of a file.")
 @Service
-public class TailAction extends AbstractAction {
+public class TailAction implements Action {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
 
     private static final int DEFAULT_NUMBER_OF_LINES = 10;
 
@@ -51,7 +55,8 @@ public class TailAction extends AbstractAction {
     @Argument(index = 0, name = "path or url", description = "A file path or url to display.", required = false, multiValued = false)
     private String path;
 
-    protected Object doExecute() throws Exception {
+    @Override
+    public Object execute() throws Exception {
         //If no paths provided assume standar input
         if (path == null || path.trim().length() == 0) {
             if (log.isDebugEnabled()) {
@@ -89,7 +94,6 @@ public class TailAction extends AbstractAction {
                 }
             }
         }
-
         return null;
     }
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/ThreadsAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/ThreadsAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/ThreadsAction.java
index e6c77ea..fbf6692 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/ThreadsAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/ThreadsAction.java
@@ -27,11 +27,11 @@ import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.apache.karaf.shell.table.ShellTable;
 
 /**
@@ -40,7 +40,7 @@ import org.apache.karaf.shell.table.ShellTable;
  */
 @Command(scope = "shell", name = "threads", description = "Prints the current threads (optionally with stacktraces)")
 @Service
-public class ThreadsAction extends AbstractAction {
+public class ThreadsAction implements Action {
 
     @Option(name = "--tree" , description = "Display threads as a tree")
     boolean tree = false;
@@ -70,7 +70,7 @@ public class ThreadsAction extends AbstractAction {
     boolean noFormat;
 
     @Override
-    protected Object doExecute() throws Exception {
+    public Object execute() throws Exception {
         Map<Long, ThreadInfo> threadInfos = new TreeMap<Long, ThreadInfo>();
         ThreadMXBean threadsBean = ManagementFactory.getThreadMXBean();
         ThreadInfo[] infos;
@@ -118,7 +118,6 @@ public class ThreadsAction extends AbstractAction {
             ThreadGroupData data = new ThreadGroupData(group, threadInfos);
             data.print();
         }
-
         return null;
     }
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/WatchAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/WatchAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/WatchAction.java
index 4ec7763..fa75134 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/WatchAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/WatchAction.java
@@ -24,18 +24,22 @@ import java.io.PrintStream;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
-import org.apache.felix.service.command.CommandProcessor;
-import org.apache.felix.service.command.CommandSession;
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Completer;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.console.completer.CommandsCompleter;
-import org.apache.karaf.shell.inject.Reference;
+
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.console.SessionFactory;
+import org.apache.karaf.shell.support.ShellUtil;
+import org.apache.karaf.shell.support.completers.CommandsCompleter;
 
 @Command(scope = "shell", name = "watch", description = "Watches & refreshes the output of a command")
-public class WatchAction extends AbstractAction {
+@Service
+public class WatchAction implements Action {
 
     @Option(name = "-n", aliases = {"--interval"}, description = "The interval between executions of the command in seconds", required = false, multiValued = false)
     private long interval = 1;
@@ -44,25 +48,27 @@ public class WatchAction extends AbstractAction {
     private boolean append = false;
 
     @Argument(index = 0, name = "command", description = "The command to watch / refresh", required = true, multiValued = true)
-    @Completer(CommandsCompleter.class)
+    @Completion(CommandsCompleter.class)
     private String[] arguments;
 
     @Reference
-    CommandProcessor commandProcessor;
+    Session session;
+
+    @Reference
+    SessionFactory sessionFactory;
 
     private ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
 
     @Override
-    protected Object doExecute() throws Exception {
+    public Object execute() throws Exception {
         if (arguments == null || arguments.length == 0) {
             System.err.println("Argument expected");
-            return null;
         } else {
             StringBuilder command = new StringBuilder();
-            for (String arg:arguments) {
+            for (String arg : arguments) {
                 command.append(arg).append(" ");
             }
-            WatchTask watchTask = new WatchTask(commandProcessor, command.toString().trim());
+            WatchTask watchTask = new WatchTask(command.toString().trim());
             executorService.scheduleAtFixedRate(watchTask, 0, interval, TimeUnit.SECONDS);
             try {
                 session.getKeyboard().read();
@@ -70,23 +76,21 @@ public class WatchAction extends AbstractAction {
             } finally {
                 executorService.shutdownNow();
                 watchTask.close();
-                return null;
             }
         }
+        return null;
     }
 
     public class WatchTask implements Runnable {
 
-        private final CommandProcessor processor;
         private final String command;
 
-        CommandSession session;
+        Session session;
         ByteArrayOutputStream byteArrayOutputStream = null;
         PrintStream printStream = null;
         boolean doDisplay = true;
 
-        public WatchTask(CommandProcessor processor, String command) {
-            this.processor = processor;
+        public WatchTask(String command) {
             this.command = command;
         }
 
@@ -94,10 +98,15 @@ public class WatchAction extends AbstractAction {
             try {
                 byteArrayOutputStream = new ByteArrayOutputStream();
                 printStream = new PrintStream(byteArrayOutputStream);
-                session = commandProcessor.createSession(null, printStream, printStream);
-                session.put("SCOPE", "shell:bundle:*");
+                session = sessionFactory.create(null, printStream, printStream);
+                session.put(Session.SCOPE, WatchAction.this.session.get(Session.SCOPE));
+                session.put(Session.SUBSHELL, WatchAction.this.session.get(Session.SUBSHELL));
                 String output = "";
-                session.execute(command);
+                try {
+                    session.execute(command);
+                } catch (Exception e) {
+                    ShellUtil.logException(session, e);
+                }
                 output = byteArrayOutputStream.toString();
                 if (doDisplay) {
                     if (!append) {
@@ -130,11 +139,4 @@ public class WatchAction extends AbstractAction {
         }
     }
 
-    public CommandProcessor getCommandProcessor() {
-        return commandProcessor;
-    }
-
-    public void setCommandProcessor(CommandProcessor commandProcessor) {
-        this.commandProcessor = commandProcessor;
-    }
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/WcAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/WcAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/WcAction.java
index f87fe48..93e00bc 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/WcAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/WcAction.java
@@ -16,18 +16,18 @@
  */
 package org.apache.karaf.shell.commands.impl;
 
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.console.AbstractAction;
-import org.apache.karaf.shell.inject.Service;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 
 import java.io.*;
 import java.util.List;
 
 @Command(scope = "shell", name = "wc", description = "Print newline, word, and byte counts for each file.")
 @Service
-public class WcAction extends AbstractAction {
+public class WcAction implements Action {
 
     @Option(name = "-l", aliases = { "--lines" }, description = "Print the newline counts.", required = false, multiValued = false)
     private boolean lines;
@@ -45,7 +45,7 @@ public class WcAction extends AbstractAction {
     private List<File> files;
 
     @Override
-    protected Object doExecute() throws Exception {
+    public Object execute() throws Exception {
         this.setDefaultOptions();
 
         String outputString;
@@ -58,7 +58,6 @@ public class WcAction extends AbstractAction {
         }
 
         System.out.println(outputString);
-
         return null;
     }
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/resources/META-INF/services/org/apache/karaf/shell/commands
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/resources/META-INF/services/org/apache/karaf/shell/commands b/shell/commands/src/main/resources/META-INF/services/org/apache/karaf/shell/commands
index a94c585..8149c3d 100644
--- a/shell/commands/src/main/resources/META-INF/services/org/apache/karaf/shell/commands
+++ b/shell/commands/src/main/resources/META-INF/services/org/apache/karaf/shell/commands
@@ -14,24 +14,32 @@
 ##  See the License for the specific language governing permissions and
 ##  limitations under the License.
 ##---------------------------------------------------------------------------
+org.jledit.ConcreteEditorFactory
 org.apache.karaf.shell.commands.impl.AliasAction
 org.apache.karaf.shell.commands.impl.CatAction
 org.apache.karaf.shell.commands.impl.ClearAction
+org.apache.karaf.shell.commands.impl.CompletionAction
 org.apache.karaf.shell.commands.impl.DateAction
 org.apache.karaf.shell.commands.impl.EachAction
 org.apache.karaf.shell.commands.impl.EchoAction
+org.apache.karaf.shell.commands.impl.EditAction
 org.apache.karaf.shell.commands.impl.ExecuteAction
 org.apache.karaf.shell.commands.impl.GrepAction
-org.apache.karaf.shell.commands.impl.HistoryAction
 org.apache.karaf.shell.commands.impl.HeadAction
+org.apache.karaf.shell.commands.impl.HistoryAction
 org.apache.karaf.shell.commands.impl.IfAction
+org.apache.karaf.shell.commands.impl.InfoAction
 org.apache.karaf.shell.commands.impl.JavaAction
 org.apache.karaf.shell.commands.impl.LogoutAction
 org.apache.karaf.shell.commands.impl.MoreAction
 org.apache.karaf.shell.commands.impl.NewAction
 org.apache.karaf.shell.commands.impl.PrintfAction
+org.apache.karaf.shell.commands.impl.PrintStackTracesAction
 org.apache.karaf.shell.commands.impl.SleepAction
 org.apache.karaf.shell.commands.impl.SortAction
+org.apache.karaf.shell.commands.impl.SourceAction
 org.apache.karaf.shell.commands.impl.TacAction
 org.apache.karaf.shell.commands.impl.TailAction
+org.apache.karaf.shell.commands.impl.ThreadsAction
+org.apache.karaf.shell.commands.impl.WatchAction
 org.apache.karaf.shell.commands.impl.WcAction

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/main/resources/OSGI-INF/blueprint/shell-commands.xml
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/resources/OSGI-INF/blueprint/shell-commands.xml b/shell/commands/src/main/resources/OSGI-INF/blueprint/shell-commands.xml
index 36c1bae..af2747c 100644
--- a/shell/commands/src/main/resources/OSGI-INF/blueprint/shell-commands.xml
+++ b/shell/commands/src/main/resources/OSGI-INF/blueprint/shell-commands.xml
@@ -19,13 +19,15 @@
 -->
 <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" default-activation="lazy">
 
+    <!--
     <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.2.0"
                     scan="org.apache.karaf.shell.commands.impl" />
 
    <bean id="commandCompleter" class="org.apache.karaf.shell.console.completer.CommandsCompleter"/>
    <service ref="commandCompleter" auto-export="all-classes"/>
+    -->
 
-   <bean class="org.osgi.util.tracker.BundleTracker" init-method="open"
+    <bean class="org.osgi.util.tracker.BundleTracker" init-method="open"
         destroy-method="close">
         <argument ref="blueprintBundleContext" />
         <argument value="32" />

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/test/java/org/apache/karaf/shell/commands/impl/GrepTest.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/test/java/org/apache/karaf/shell/commands/impl/GrepTest.java b/shell/commands/src/test/java/org/apache/karaf/shell/commands/impl/GrepTest.java
index eb61a7b..3845b9d 100644
--- a/shell/commands/src/test/java/org/apache/karaf/shell/commands/impl/GrepTest.java
+++ b/shell/commands/src/test/java/org/apache/karaf/shell/commands/impl/GrepTest.java
@@ -23,8 +23,7 @@ import java.io.InputStream;
 import java.util.Arrays;
 
 import junit.framework.TestCase;
-import org.apache.karaf.shell.commands.basic.DefaultActionPreparator;
-import org.apache.karaf.shell.commands.impl.GrepAction;
+import org.apache.karaf.shell.impl.action.command.DefaultActionPreparator;
 
 public class GrepTest extends TestCase {
 
@@ -37,7 +36,7 @@ public class GrepTest extends TestCase {
             GrepAction grep = new GrepAction();
             DefaultActionPreparator preparator = new DefaultActionPreparator();
             preparator.prepare(grep, null, Arrays.<Object>asList("-C", "100", "2"));
-            grep.doExecute();
+            grep.execute();
         } finally {
             System.setIn(input);
         }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/commands/src/test/java/org/apache/karaf/shell/commands/impl/ThreadsTest.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/test/java/org/apache/karaf/shell/commands/impl/ThreadsTest.java b/shell/commands/src/test/java/org/apache/karaf/shell/commands/impl/ThreadsTest.java
index 5daf61c..98d9aed 100644
--- a/shell/commands/src/test/java/org/apache/karaf/shell/commands/impl/ThreadsTest.java
+++ b/shell/commands/src/test/java/org/apache/karaf/shell/commands/impl/ThreadsTest.java
@@ -26,26 +26,26 @@ public class ThreadsTest {
     public void testThreadlist() throws Exception {
         ThreadsAction action = new ThreadsAction();
         action.list = true;
-        action.doExecute();
+        action.execute();
     }
     
     @Test
     public void testThreadInfo() throws Exception {
         ThreadsAction action = new ThreadsAction();
         action.id = 1L;
-        action.doExecute();
+        action.execute();
     }
 
     @Test
     public void testThreadTree() throws Exception {
         ThreadsAction action = new ThreadsAction();
         action.tree = true;
-        action.doExecute();
+        action.execute();
     }
 
     @Test
     public void testThreadDump() throws Exception {
         ThreadsAction action = new ThreadsAction();
-        action.doExecute();
+        action.execute();
     }
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/ssh/pom.xml
----------------------------------------------------------------------
diff --git a/shell/ssh/pom.xml b/shell/ssh/pom.xml
index 749e5ca..84135eb 100644
--- a/shell/ssh/pom.xml
+++ b/shell/ssh/pom.xml
@@ -40,7 +40,7 @@
     <dependencies>
         <dependency>
             <groupId>org.apache.karaf.shell</groupId>
-            <artifactId>org.apache.karaf.shell.console</artifactId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
         </dependency>
 
         <dependency>
@@ -101,22 +101,12 @@
                 <configuration>
                     <instructions>
                         <Import-Package>
-                            org.apache.aries.blueprint,
-                            org.osgi.service.blueprint.container,
-                            org.osgi.service.blueprint.reflect,
-                            org.apache.felix.service.command,
-                            org.apache.karaf.shell.commands,
-                            org.apache.karaf.shell.console,
-                            org.apache.mina.util,
-                            org.apache.sshd.server.keyprovider,
-                            org.apache.sshd.server.jaas,
-                            org.apache.sshd.server.sftp,
-                            !org.apache.sshd.server.sftp.SftpSubsystem,
                             *
                         </Import-Package>
                         <Private-Package>
                             org.apache.karaf.util
                         </Private-Package>
+                        <Bundle-Activator>org.apache.karaf.shell.ssh.Activator</Bundle-Activator>
                     </instructions>
                 </configuration>
             </plugin>

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/Activator.java
----------------------------------------------------------------------
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/Activator.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/Activator.java
new file mode 100644
index 0000000..a61babc
--- /dev/null
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/Activator.java
@@ -0,0 +1,268 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.ssh;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.karaf.shell.api.action.lifecycle.Manager;
+import org.apache.karaf.shell.ssh.util.SingleServiceTracker;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.console.SessionFactory;
+import org.apache.sshd.SshServer;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.server.command.ScpCommandFactory;
+import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
+import org.apache.sshd.server.sftp.SftpSubsystem;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * Activate this bundle
+ */
+public class Activator implements BundleActivator, ManagedService {
+
+    ServiceRegistration registration;
+
+    List<Session> sessions = new CopyOnWriteArrayList<Session>();
+
+    BundleContext bundleContext;
+    SingleServiceTracker<SessionFactory> sessionFactoryTracker;
+    ServiceTracker<Session, Session> sessionTracker;
+    Dictionary<String, ?> configuration;
+
+    final KarafAgentFactory agentFactory = new KarafAgentFactory();
+
+    SessionFactory sessionFactory;
+    SshClientFactory sshClientFactory;
+    final Callable<SshServer> sshServerFactory = new Callable<SshServer>() {
+        @Override
+        public SshServer call() throws Exception {
+            return createSshServer(sessionFactory);
+        }
+    };
+    SshServer server;
+    final List<SshServer> servers = new ArrayList<SshServer>();
+
+
+    public void start(BundleContext context) throws Exception {
+        bundleContext = context;
+
+        Hashtable<String, Object> props = new Hashtable<String, Object>();
+        props.put(Constants.SERVICE_PID, "org.apache.karaf.shell");
+        registration = bundleContext.registerService(ManagedService.class, this, props);
+
+        sshClientFactory = new SshClientFactory(agentFactory, new File(context.getProperty("user.home"), ".sshkaraf/known_hosts"));
+
+        sessionFactoryTracker = new SingleServiceTracker<SessionFactory>(bundleContext, SessionFactory.class, new SingleServiceTracker.SingleServiceListener() {
+            @Override
+            public void serviceFound() {
+                bindSessionFactory(sessionFactoryTracker.getService());
+            }
+            @Override
+            public void serviceLost() {
+                unbindSessionFactory();
+            }
+            @Override
+            public void serviceReplaced() {
+                serviceLost();
+                serviceFound();
+            }
+        });
+        sessionFactoryTracker.open();
+
+        sessionTracker = new ServiceTracker<Session, Session>(bundleContext, Session.class, null) {
+            @Override
+            public Session addingService(ServiceReference<Session> reference) {
+                Session session = super.addingService(reference);
+                agentFactory.registerSession(session);
+                return session;
+            }
+            @Override
+            public void removedService(ServiceReference<Session> reference, Session session) {
+                agentFactory.unregisterSession(session);
+                super.removedService(reference, session);
+            }
+        };
+        sessionTracker.open();
+
+    }
+
+    private void bindSessionFactory(final SessionFactory sessionFactory) {
+        this.sessionFactory = sessionFactory;
+        this.sessionFactory.getRegistry().register(sshServerFactory, SshServer.class);
+        this.sessionFactory.getRegistry().register(sshClientFactory);
+        this.sessionFactory.getRegistry().getService(Manager.class).register(SshServerAction.class);
+        this.sessionFactory.getRegistry().getService(Manager.class).register(SshAction.class);
+        if (Boolean.parseBoolean(Activator.this.bundleContext.getProperty("karaf.startRemoteShell"))) {
+            server = createSshServer(sessionFactory);
+            try {
+                server.start();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private void unbindSessionFactory() {
+        this.sessionFactory.getRegistry().getService(Manager.class).unregister(SshAction.class);
+        this.sessionFactory.getRegistry().getService(Manager.class).unregister(SshServerAction.class);
+        this.sessionFactory.getRegistry().unregister(sshClientFactory);
+        this.sessionFactory.getRegistry().unregister(sshServerFactory);
+        SshServer srv = server;
+        server = null;
+        if (srv != null) {
+            try {
+                srv.stop();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    public void stop(BundleContext context) {
+        registration.unregister();
+        sessionTracker.close();
+        sessionFactoryTracker.close();
+        synchronized (servers) {
+            for (SshServer server : servers) {
+                try {
+                    server.stop();
+                } catch (InterruptedException e) {
+                    // TODO: log exception
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void updated(Dictionary<String, ?> configuration) throws ConfigurationException {
+        this.configuration = configuration;
+    }
+
+    private int getInt(String key, int def) {
+        Dictionary<String, ?> config = this.configuration;
+        if (config != null) {
+            Object val = config.get(key);
+            if (val instanceof Number) {
+                return ((Number) val).intValue();
+            } else if (val != null) {
+                return Integer.parseInt(val.toString());
+            }
+        }
+        return def;
+    }
+
+    private long getLong(String key, long def) {
+        Dictionary<String, ?> config = this.configuration;
+        if (config != null) {
+            Object val = config.get(key);
+            if (val instanceof Number) {
+                return ((Number) val).longValue();
+            } else if (val != null) {
+                return Long.parseLong(val.toString());
+            }
+        }
+        return def;
+    }
+
+    private String getString(String key, String def) {
+        Dictionary<String, ?> config = this.configuration;
+        if (config != null) {
+            Object val = config.get(key);
+            if (val != null) {
+                return val.toString();
+            }
+        }
+        return def;
+    }
+
+    protected SshServer createSshServer(SessionFactory sessionFactory) {
+        int sshPort           = getInt("sshPort", 8181);
+        String sshHost        = getString("sshHost", "0.0.0.0");
+        long sshIdleTimeout   = getLong("sshIdleTimeout", 1800000);
+        String sshRealm       = getString("sshRealm", "karaf");
+        String hostKey        = getString("hostKey", System.getProperty("karaf.base") + "/etc/host.key");
+        String authMethods    = getString("authMethods", "keyboard-interactive,password,publickey");
+        int keySize           = getInt("keySize", 1024);
+        String algorithm      = getString("algorithm", "DSA");
+        String macs           = getString("macs", "hmac-sha1");
+        String ciphers        = getString("ciphers", "aes256-ctr,aes192-ctr,aes128-ctr,arcfour256");
+
+        SimpleGeneratorHostKeyProvider keyPairProvider = new SimpleGeneratorHostKeyProvider();
+        keyPairProvider.setPath(hostKey);
+        keyPairProvider.setKeySize(keySize);
+        keyPairProvider.setAlgorithm(algorithm);
+
+        KarafJaasAuthenticator authenticator = new KarafJaasAuthenticator(sshRealm);
+
+        UserAuthFactoriesFactory authFactoriesFactory = new UserAuthFactoriesFactory();
+        authFactoriesFactory.setAuthMethods(authMethods);
+
+        SshServer server = SshServer.setUpDefaultServer();
+        server.setPort(sshPort);
+        server.setHost(sshHost);
+        server.setMacFactories(SshUtils.buildMacs(macs));
+        server.setCipherFactories(SshUtils.buildCiphers(ciphers));
+        server.setShellFactory(new ShellFactoryImpl(sessionFactory));
+        server.setCommandFactory(new ScpCommandFactory(new ShellCommandFactory(sessionFactory)));
+        server.setSubsystemFactories(Arrays.<NamedFactory<org.apache.sshd.server.Command>>asList(new SftpSubsystem.Factory()));
+        server.setKeyPairProvider(keyPairProvider);
+        server.setPasswordAuthenticator(authenticator);
+        server.setPublickeyAuthenticator(authenticator);
+        server.setFileSystemFactory(new KarafFileSystemFactory());
+        server.setUserAuthFactories(authFactoriesFactory.getFactories());
+        server.setAgentFactory(agentFactory);
+        server.getProperties().put(SshServer.IDLE_TIMEOUT, Long.toString(sshIdleTimeout));
+
+        synchronized (servers) {
+            servers.add(server);
+        }
+        return server;
+    }
+
+    public void bindCommandSession(Session session) {
+        sessions.add(session);
+        if (agentFactory != null) {
+            agentFactory.registerSession(session);
+        }
+    }
+
+    public void unbindCommandSession(Session session) {
+        sessions.remove(session);
+        if (agentFactory != null) {
+            agentFactory.unregisterSession(session);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ActivatorNoOsgi.java
----------------------------------------------------------------------
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ActivatorNoOsgi.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ActivatorNoOsgi.java
new file mode 100644
index 0000000..f71c5ef
--- /dev/null
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ActivatorNoOsgi.java
@@ -0,0 +1,33 @@
+package org.apache.karaf.shell.ssh;
+
+import java.io.File;
+
+import org.apache.karaf.shell.api.action.lifecycle.Destroy;
+import org.apache.karaf.shell.api.action.lifecycle.Init;
+import org.apache.karaf.shell.api.action.lifecycle.Manager;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.console.SessionFactory;
+
+public class ActivatorNoOsgi {
+
+    @Reference
+    SessionFactory sessionFactory;
+
+    KarafAgentFactory agentFactory;
+    SshClientFactory sshClientFactory;
+
+    @Init
+    public void init() {
+        agentFactory = new KarafAgentFactory();
+        sshClientFactory = new SshClientFactory(agentFactory, new File(System.getProperty("user.home"), ".sshkaraf/known_hosts"));
+        sessionFactory.getRegistry().register(sshClientFactory);
+        sessionFactory.getRegistry().getService(Manager.class).register(SshAction.class);
+    }
+
+    @Destroy
+    public void destroy() {
+        sessionFactory.getRegistry().register(sshClientFactory);
+        sessionFactory.getRegistry().getService(Manager.class).register(SshAction.class);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/KarafAgentFactory.java
----------------------------------------------------------------------
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/KarafAgentFactory.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/KarafAgentFactory.java
index 45d1f36..da4d43a 100644
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/KarafAgentFactory.java
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/KarafAgentFactory.java
@@ -26,7 +26,6 @@ import java.security.KeyPair;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
-import org.apache.felix.service.command.CommandSession;
 import org.apache.sshd.agent.SshAgent;
 import org.apache.sshd.agent.SshAgentFactory;
 import org.apache.sshd.agent.SshAgentServer;
@@ -38,7 +37,6 @@ import org.apache.sshd.common.Channel;
 import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.Session;
 import org.apache.sshd.server.session.ServerSession;
-import org.osgi.framework.BundleContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -49,16 +47,6 @@ public class KarafAgentFactory implements SshAgentFactory {
     private final Map<String, AgentServerProxy> proxies = new ConcurrentHashMap<String, AgentServerProxy>();
     private final Map<String, SshAgent> locals = new ConcurrentHashMap<String, SshAgent>();
 
-    private BundleContext bundleContext;
-
-    public BundleContext getBundleContext() {
-        return bundleContext;
-    }
-
-    public void setBundleContext(BundleContext bundleContext) {
-        this.bundleContext = bundleContext;
-    }
-
     public NamedFactory<Channel> getChannelForwardingFactory() {
         return new ChannelAgentForwarding.Factory();
     }
@@ -97,11 +85,11 @@ public class KarafAgentFactory implements SshAgentFactory {
         };
     }
 
-    public void registerCommandSession(CommandSession session) {
+    public void registerSession(org.apache.karaf.shell.api.console.Session session) {
         try {
             String user = (String) session.get("USER");
             SshAgent agent = new AgentImpl();
-            URL url = bundleContext.getBundle().getResource("karaf.key");
+            URL url = getClass().getClassLoader().getResource("karaf.key");
             InputStream is = url.openStream();
             ObjectInputStream r = new ObjectInputStream(is);
             KeyPair keyPair = (KeyPair) r.readObject();
@@ -114,7 +102,7 @@ public class KarafAgentFactory implements SshAgentFactory {
         }
     }
 
-    public void unregisterCommandSession(CommandSession session) {
+    public void unregisterSession(org.apache.karaf.shell.api.console.Session session) {
         try {
             if (session != null && session.get(SshAgent.SSH_AUTHSOCKET_ENV_NAME) != null) {
                 String agentId = (String) session.get(SshAgent.SSH_AUTHSOCKET_ENV_NAME);

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/KarafJaasAuthenticator.java
----------------------------------------------------------------------
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/KarafJaasAuthenticator.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/KarafJaasAuthenticator.java
index 0562bb2..6df591a 100644
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/KarafJaasAuthenticator.java
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/KarafJaasAuthenticator.java
@@ -47,6 +47,13 @@ public class KarafJaasAuthenticator implements PasswordAuthenticator, PublickeyA
 
     private String realm;
 
+    public KarafJaasAuthenticator() {
+    }
+
+    public KarafJaasAuthenticator(String realm) {
+        this.realm = realm;
+    }
+
     public String getRealm() {
         return realm;
     }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellCommand.java
----------------------------------------------------------------------
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellCommand.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellCommand.java
index a825f07..62d8ba6 100644
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellCommand.java
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellCommand.java
@@ -25,12 +25,11 @@ import java.util.Map;
 
 import javax.security.auth.Subject;
 
-import org.apache.felix.service.command.CommandProcessor;
-import org.apache.felix.service.command.CommandSession;
-import org.apache.felix.service.command.Converter;
 import org.apache.karaf.jaas.modules.JaasHelper;
-import org.apache.karaf.shell.util.ShellUtil;
 import org.apache.karaf.util.StreamUtils;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.console.SessionFactory;
+import org.apache.karaf.shell.support.ShellUtil;
 import org.apache.sshd.server.Command;
 import org.apache.sshd.server.Environment;
 import org.apache.sshd.server.ExitCallback;
@@ -57,10 +56,10 @@ public class ShellCommand implements Command, SessionAware {
     private OutputStream err;
     private ExitCallback callback;
     private ServerSession session;
-    private CommandProcessor commandProcessor;
+    private SessionFactory sessionFactory;
 
-    public ShellCommand(CommandProcessor commandProcessor, String command) {
-        this.commandProcessor = commandProcessor;
+    public ShellCommand(SessionFactory sessionFactory, String command) {
+        this.sessionFactory = sessionFactory;
         this.command = command;
     }
 
@@ -86,9 +85,7 @@ public class ShellCommand implements Command, SessionAware {
 
     public void start(final Environment env) throws IOException {
         try {
-            final CommandSession session = commandProcessor.createSession(in, new PrintStream(out), new PrintStream(err));
-            session.put("SCOPE", "shell:osgi:*");
-            session.put("APPLICATION", System.getProperty("karaf.name", "root"));
+            final Session session = sessionFactory.create(in, new PrintStream(out), new PrintStream(err));
             for (Map.Entry<String,String> e : env.getEnv().entrySet()) {
                 session.put(e.getKey(), e.getValue());
             }
@@ -114,7 +111,8 @@ public class ShellCommand implements Command, SessionAware {
                 }
                 if (result != null)
                 {
-                    session.getConsole().println(session.format(result, Converter.INSPECT));
+                    // TODO: print the result of the command ?
+//                    session.getConsole().println(session.format(result, Converter.INSPECT));
                 }
             } catch (Throwable t) {
                 ShellUtil.logException(session, t);
@@ -130,7 +128,7 @@ public class ShellCommand implements Command, SessionAware {
     public void destroy() {
 	}
 
-    private void executeScript(String scriptFileName, CommandSession session) {
+    private void executeScript(String scriptFileName, Session session) {
         if (scriptFileName != null) {
             Reader r = null;
             try {

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellCommandFactory.java
----------------------------------------------------------------------
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellCommandFactory.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellCommandFactory.java
index a1de13d..dc1f04e 100644
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellCommandFactory.java
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellCommandFactory.java
@@ -18,20 +18,20 @@
  */
 package org.apache.karaf.shell.ssh;
 
-import org.apache.felix.service.command.CommandProcessor;
+import org.apache.karaf.shell.api.console.SessionFactory;
 import org.apache.sshd.server.Command;
 import org.apache.sshd.server.CommandFactory;
 
 public class ShellCommandFactory implements CommandFactory {
 
-    private CommandProcessor commandProcessor;
+    private SessionFactory sessionFactory;
 
-    public void setCommandProcessor(CommandProcessor commandProcessor) {
-        this.commandProcessor = commandProcessor;
+    public ShellCommandFactory(SessionFactory sessionFactory) {
+        this.sessionFactory = sessionFactory;
     }
 
     public Command createCommand(String command) {
-        return new ShellCommand(commandProcessor, command);
+        return new ShellCommand(sessionFactory, command);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellFactoryImpl.java
----------------------------------------------------------------------
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellFactoryImpl.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellFactoryImpl.java
index 657193b..a6defed 100644
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellFactoryImpl.java
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellFactoryImpl.java
@@ -29,30 +29,27 @@ import java.util.Map;
 
 import javax.security.auth.Subject;
 
-import jline.Terminal;
-
-import org.apache.felix.service.command.CommandSession;
 import org.apache.karaf.jaas.modules.JaasHelper;
-import org.apache.karaf.shell.console.Console;
-import org.apache.karaf.shell.console.factory.ConsoleFactory;
-import org.apache.karaf.shell.util.ShellUtil;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.console.SessionFactory;
+import org.apache.karaf.shell.api.console.Terminal;
+import org.apache.karaf.shell.support.ShellUtil;
 import org.apache.sshd.common.Factory;
 import org.apache.sshd.server.Command;
 import org.apache.sshd.server.Environment;
 import org.apache.sshd.server.ExitCallback;
 import org.apache.sshd.server.SessionAware;
 import org.apache.sshd.server.session.ServerSession;
-import org.osgi.service.blueprint.container.ReifiedType;
 
 /**
  * SSHD {@link org.apache.sshd.server.Command} factory which provides access to
  * Shell.
  */
 public class ShellFactoryImpl implements Factory<Command> {
-    private ConsoleFactory consoleFactory;
+    private SessionFactory sessionFactory;
 
-    public ShellFactoryImpl(ConsoleFactory consoleFactory) {
-        this.consoleFactory = consoleFactory;
+    public ShellFactoryImpl(SessionFactory sessionFactory) {
+        this.sessionFactory = sessionFactory;
     }
 
     public Command create() {
@@ -106,15 +103,14 @@ public class ShellFactoryImpl implements Factory<Command> {
                 if (encoding != null && encoding.indexOf('.') > 0) {
                     encoding = encoding.substring(encoding.indexOf('.') + 1);
                 }
-                final Console console = consoleFactory.create(in,
+                final Session session = sessionFactory.create(in,
                         lfToCrLfPrintStream(out), lfToCrLfPrintStream(err), terminal, encoding, destroyCallback);
-                final CommandSession session = console.getSession();
                 for (Map.Entry<String, String> e : env.getEnv().entrySet()) {
                     session.put(e.getKey(), e.getValue());
                 }
                 JaasHelper.doAs(subject, new PrivilegedAction<Object>() {
                     public Object run() {
-                        new Thread(console, "Karaf ssh console user " + ShellUtil.getCurrentUserName()).start();
+                        new Thread(session, "Karaf ssh console user " + ShellUtil.getCurrentUserName()).start();
                         return null;
                     }
                 });
@@ -158,23 +154,6 @@ public class ShellFactoryImpl implements Factory<Command> {
         }
     }
 
-    public static Converter getConverter() {
-        return new Converter();
-    }
-
-    public static class Converter implements org.osgi.service.blueprint.container.Converter {
-
-        public boolean canConvert(Object sourceObject, ReifiedType targetType) {
-            return ShellFactoryImpl.class.isAssignableFrom(sourceObject.getClass())
-                    && Factory.class.equals(targetType.getRawClass())
-                    && Command.class.equals(targetType.getActualTypeArgument(0).getRawClass());
-        }
-
-        public Object convert(Object sourceObject, ReifiedType targetType) throws Exception {
-            return sourceObject;
-        }
-    }
-
     // TODO: remove this class when sshd use lf->crlf conversion by default
     public class LfToCrLfFilterOutputStream extends FilterOutputStream {
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshAction.java
----------------------------------------------------------------------
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshAction.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshAction.java
index ed59460..0f2ae21 100644
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshAction.java
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshAction.java
@@ -22,14 +22,14 @@ import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.util.List;
 
-import jline.Terminal;
-
-import org.apache.karaf.shell.commands.Argument;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import jline.console.ConsoleReader;
-import org.apache.karaf.shell.console.OsgiCommandSupport;
-import org.apache.karaf.shell.console.SessionProperties;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.console.Terminal;
 import org.apache.sshd.ClientChannel;
 import org.apache.sshd.ClientSession;
 import org.apache.sshd.SshClient;
@@ -42,7 +42,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 @Command(scope = "ssh", name = "ssh", description = "Connects to a remote SSH server")
-public class SshAction extends OsgiCommandSupport {
+@Service
+public class SshAction implements Action {
     private final Logger log = LoggerFactory.getLogger(getClass());
 
     @Option(name="-l", aliases={"--username"}, description = "The user name for remote login", required = false, multiValued = false)
@@ -63,9 +64,11 @@ public class SshAction extends OsgiCommandSupport {
     @Argument(index = 1, name = "command", description = "Optional command to execute", required = false, multiValued = true)
     private List<String> command;
 
-	private ClientSession sshSession;
+    @Reference
+    private Session session;
 
-	private SshClientFactory sshClientFactory;
+    @Reference
+    private SshClientFactory sshClientFactory;
 
     private final static String keyChangedMessage =
             " @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ \n" +
@@ -85,7 +88,7 @@ public class SshAction extends OsgiCommandSupport {
 	}
 
 	@Override
-    protected Object doExecute() throws Exception {
+    public Object execute() throws Exception {
 
         if (hostname.indexOf('@') >= 0) {
             if (username == null) {
@@ -104,7 +107,7 @@ public class SshAction extends OsgiCommandSupport {
         if (username == null) {
             log.debug("Prompting user for login");
             if (username == null) {
-                username = readLine("Login: ");
+                username = session.readLine("Login: ", null);
             }
         }
 
@@ -121,9 +124,9 @@ public class SshAction extends OsgiCommandSupport {
         try {
             ConnectFuture future = client.connect(hostname, port);
             future.await();
-            sshSession = future.getSession();
+            ClientSession sshSession = future.getSession();
 
-            Object oldIgnoreInterrupts = this.session.get(SessionProperties.IGNORE_INTERRUPTS);
+            Object oldIgnoreInterrupts = this.session.get(Session.IGNORE_INTERRUPTS);
 
             try {
 
@@ -145,7 +148,7 @@ public class SshAction extends OsgiCommandSupport {
                 if (!authed) {
                     if (password == null) {
                         log.debug("Prompting user for password");
-                        password = readLine("Password: ");
+                        password = session.readLine("Password: ", '*');
                     } else {
                         log.debug("Password provided using command line option");
                     }
@@ -167,7 +170,7 @@ public class SshAction extends OsgiCommandSupport {
                 }
 
                 System.out.println("Connected");
-                this.session.put( SessionProperties.IGNORE_INTERRUPTS, Boolean.TRUE );
+                this.session.put( Session.IGNORE_INTERRUPTS, Boolean.TRUE );
 
                 StringBuilder sb = new StringBuilder();
                 if (command != null) {
@@ -199,7 +202,7 @@ public class SshAction extends OsgiCommandSupport {
                 channel.open();
                 channel.waitFor(ClientChannel.CLOSED, 0);
             } finally {
-                session.put( SessionProperties.IGNORE_INTERRUPTS, oldIgnoreInterrupts );
+                session.put( Session.IGNORE_INTERRUPTS, oldIgnoreInterrupts );
                 sshSession.close(false);
             }
         } finally {
@@ -210,17 +213,8 @@ public class SshAction extends OsgiCommandSupport {
     }
 
     private int getTermWidth() {
-        Terminal term = (Terminal) session.get(".jline.terminal");
+        Terminal term = session.getTerminal();
         return term != null ? term.getWidth() : 80;
     }
 
-    public String readLine(String msg) throws IOException {
-        return readLine(msg, null);
-    }
-
-    public String readLine(String msg, Character mask) throws IOException {
-        ConsoleReader reader = (ConsoleReader) session.get(".jline.reader");
-        return reader.readLine(msg, mask);
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshServerAction.java
----------------------------------------------------------------------
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshServerAction.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshServerAction.java
index e92db70..6b45a0a 100644
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshServerAction.java
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshServerAction.java
@@ -18,17 +18,18 @@
  */
 package org.apache.karaf.shell.ssh;
 
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.apache.sshd.SshServer;
-import org.apache.karaf.shell.console.BlueprintContainerAware;
-import org.apache.karaf.shell.console.OsgiCommandSupport;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.osgi.service.blueprint.container.BlueprintContainer;
 
 @Command(scope = "ssh", name = "sshd", description = "Creates a SSH server")
-public class SshServerAction extends OsgiCommandSupport implements BlueprintContainerAware
+@Service
+public class SshServerAction implements Action
 {
     private final Logger log = LoggerFactory.getLogger(getClass());
 
@@ -41,29 +42,21 @@ public class SshServerAction extends OsgiCommandSupport implements BlueprintCont
     @Option(name = "-i", aliases = { "--idle-timeout" }, description = "The session idle timeout (Default: 1800000ms)", required = false, multiValued = false)
     private long idleTimeout = 1800000;
 
-    private BlueprintContainer container;
+    @Reference
+    private SshServer server;
 
-    private String sshServerId;
-
-    public void setBlueprintContainer(final BlueprintContainer container) {
-        assert container != null;
-        this.container = container;
-    }
-
-    public void setSshServerId(String sshServerId) {
-        this.sshServerId = sshServerId;
+    public void setServer(SshServer server) {
+        this.server = server;
     }
 
-    protected Object doExecute() throws Exception {
-        SshServer server = (SshServer) container.getComponentInstance(sshServerId);
-
+    public Object execute() throws Exception {
         log.debug("Created server: {}", server);
 
         // port number
         server.setPort(port);
 
         // idle timeout
-        server.getProperties().put(SshServer.IDLE_TIMEOUT, new Long(idleTimeout).toString());
+        server.getProperties().put(SshServer.IDLE_TIMEOUT, Long.toString(idleTimeout));
 
         // starting the SSHd server
         server.start();

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e2b9324/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshServerFactory.java
----------------------------------------------------------------------
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshServerFactory.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshServerFactory.java
deleted file mode 100644
index 4789669..0000000
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshServerFactory.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *  http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.karaf.shell.ssh;
-
-import org.apache.sshd.SshServer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class SshServerFactory {
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(SshServerFactory.class);
-
-    private long idleTimeout;
-    private boolean start;
-
-    private SshServer server;
-
-    public SshServerFactory(SshServer server) {
-        this.server = server;
-    }
-
-    public boolean isStart() {
-        return start;
-    }
-
-    public void setStart(boolean start) {
-        this.start = start;
-    }
-
-    public long getIdleTimeout() {
-        return idleTimeout;
-    }
-
-    public void setIdleTimeout(long idleTimeout) {
-        this.idleTimeout = idleTimeout;
-    }
-
-    public void start() {
-        if (start) {
-            try {
-                server.getProperties().put(SshServer.IDLE_TIMEOUT, new Long(idleTimeout).toString());
-                server.start();
-            } catch (Exception e) {
-                LOGGER.info("Error updating SSH server", e);
-            }
-        }
-    }
-
-    public void stop() {
-        if (start && server != null) {
-            try {
-                server.stop();
-            } catch (Exception e) {
-                LOGGER.info("Error stopping SSH server", e);
-            } finally {
-                server = null;
-            }
-        }
-    }
-
-}


[05/10] git commit: [KARAF-2805] Clean console and commands model

Posted by gn...@apache.org.
[KARAF-2805] Clean console and commands model

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

Branch: refs/heads/master
Commit: e7d23bef3856780ab07aa813f8feea56572eb2f2
Parents: bd56b49
Author: Guillaume Nodet <gn...@gmail.com>
Authored: Wed Mar 5 14:39:38 2014 +0100
Committer: Guillaume Nodet <gn...@gmail.com>
Committed: Wed Mar 5 15:10:37 2014 +0100

----------------------------------------------------------------------
 pom.xml                                         |   5 +
 shell/core/NOTICE                               |  71 +++
 shell/core/pom.xml                              | 179 ++++++
 .../karaf/shell/console/branding-ssh.properties |  34 ++
 .../karaf/shell/console/branding.properties     |  33 ++
 .../apache/karaf/shell/api/action/Action.java   |  56 ++
 .../apache/karaf/shell/api/action/Argument.java |  73 +++
 .../apache/karaf/shell/api/action/Command.java  |  61 ++
 .../karaf/shell/api/action/Completion.java      |  59 ++
 .../apache/karaf/shell/api/action/Option.java   |  71 +++
 .../shell/api/action/lifecycle/Destroy.java     |  34 ++
 .../karaf/shell/api/action/lifecycle/Init.java  |  34 ++
 .../shell/api/action/lifecycle/Manager.java     |  49 ++
 .../shell/api/action/lifecycle/Reference.java   |  39 ++
 .../shell/api/action/lifecycle/Service.java     |  36 ++
 .../apache/karaf/shell/api/console/Command.java |  51 ++
 .../karaf/shell/api/console/CommandLine.java    |  61 ++
 .../karaf/shell/api/console/Completer.java      |  38 ++
 .../karaf/shell/api/console/Function.java       |  38 ++
 .../apache/karaf/shell/api/console/History.java |  45 ++
 .../karaf/shell/api/console/Registry.java       |  77 +++
 .../apache/karaf/shell/api/console/Session.java | 153 +++++
 .../karaf/shell/api/console/SessionFactory.java |  65 +++
 .../karaf/shell/api/console/Terminal.java       |  51 ++
 .../impl/action/command/ActionCommand.java      | 109 ++++
 .../impl/action/command/ArgumentCompleter.java  | 385 ++++++++++++
 .../action/command/DefaultActionPreparator.java | 481 +++++++++++++++
 .../shell/impl/action/command/HelpOption.java   |  57 ++
 .../shell/impl/action/command/ManagerImpl.java  | 168 ++++++
 .../shell/impl/action/osgi/CommandExtender.java |  94 +++
 .../impl/action/osgi/CommandExtension.java      | 202 +++++++
 .../impl/action/osgi/MultiServiceTracker.java   | 106 ++++
 .../shell/impl/action/osgi/RegistryImpl.java    | 159 +++++
 .../shell/impl/action/osgi/Satisfiable.java     |  30 +
 .../impl/action/osgi/SingleServiceTracker.java  | 168 ++++++
 .../karaf/shell/impl/console/Branding.java      |  72 +++
 .../impl/console/CommandNamesCompleter.java     |  47 ++
 .../shell/impl/console/CommandWrapper.java      |  61 ++
 .../shell/impl/console/CommandsCompleter.java   | 256 ++++++++
 .../impl/console/CompleterAsCompletor.java      |  41 ++
 .../shell/impl/console/ConsoleSessionImpl.java  | 583 +++++++++++++++++++
 .../shell/impl/console/HeadlessSessionImpl.java | 146 +++++
 .../shell/impl/console/HistoryWrapper.java      |  48 ++
 .../karaf/shell/impl/console/JLineTerminal.java |  62 ++
 .../shell/impl/console/KarafFileHistory.java    |  64 ++
 .../karaf/shell/impl/console/KarafTerminal.java |  57 ++
 .../karaf/shell/impl/console/RegistryImpl.java  | 159 +++++
 .../shell/impl/console/SessionFactoryImpl.java  | 144 +++++
 .../shell/impl/console/TerminalFactory.java     |  47 ++
 .../impl/console/commands/ExitCommand.java      |  51 ++
 .../impl/console/commands/SubShellCommand.java  |  58 ++
 .../impl/console/commands/TopLevelCommand.java  |  86 +++
 .../commands/help/CommandListHelpProvider.java  | 117 ++++
 .../impl/console/commands/help/HelpCommand.java | 225 +++++++
 .../console/commands/help/HelpProvider.java     |  27 +
 .../commands/help/SimpleHelpProvider.java       |  47 ++
 .../help/SingleCommandHelpProvider.java         |  54 ++
 .../shell/impl/console/osgi/Activator.java      |  78 +++
 .../shell/impl/console/osgi/Converters.java     | 279 +++++++++
 .../shell/impl/console/osgi/DelayedStarted.java |  87 +++
 .../impl/console/osgi/LocalConsoleManager.java  | 133 +++++
 .../shell/impl/console/osgi/StreamWrapUtil.java |  91 +++
 .../console/osgi/secured/SecuredCommand.java    |  85 +++
 .../osgi/secured/SecuredSessionFactoryImpl.java | 239 ++++++++
 .../osgi/secured/SingleServiceTracker.java      | 171 ++++++
 .../impl/console/parsing/CommandLineImpl.java   |  91 +++
 .../shell/impl/console/parsing/Parser.java      | 396 +++++++++++++
 .../shell/impl/console/standalone/Main.java     | 278 +++++++++
 .../karaf/shell/support/CommandException.java   |  64 ++
 .../karaf/shell/support/MultiException.java     |  95 +++
 .../apache/karaf/shell/support/NameScoping.java |  79 +++
 .../apache/karaf/shell/support/ShellUtil.java   | 214 +++++++
 .../karaf/shell/support/ansi/SimpleAnsi.java    |  30 +
 .../support/completers/AggregateCompleter.java  |  96 +++
 .../completers/CommandNamesCompleter.java       |  27 +
 .../support/completers/CommandsCompleter.java   |  27 +
 .../shell/support/completers/FileCompleter.java | 147 +++++
 .../shell/support/completers/NullCompleter.java |  34 ++
 .../support/completers/StringsCompleter.java    | 113 ++++
 .../support/converter/DefaultConverter.java     | 403 +++++++++++++
 .../shell/support/converter/GenericType.java    | 195 +++++++
 .../shell/support/converter/ReifiedType.java    | 116 ++++
 .../karaf/shell/support/table/AnsiColumn.java   |  54 ++
 .../apache/karaf/shell/support/table/Col.java   | 113 ++++
 .../karaf/shell/support/table/HAlign.java       |  71 +++
 .../apache/karaf/shell/support/table/Row.java   |  69 +++
 .../karaf/shell/support/table/ShellTable.java   | 143 +++++
 .../karaf/shell/support/table/StringUtil.java   |  48 ++
 .../src/main/resources/OSGI-INF/bundle.info     |  16 +
 shell/pom.xml                                   |   1 +
 90 files changed, 9907 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index f505153..032442a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -659,6 +659,11 @@
                 <artifactId>org.apache.karaf.shell.table</artifactId>
                 <version>${project.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.apache.karaf.shell</groupId>
+                <artifactId>org.apache.karaf.shell.core</artifactId>
+                <version>${project.version}</version>
+            </dependency>
 
             <dependency>
                 <groupId>org.apache.karaf.jaas</groupId>

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/NOTICE
----------------------------------------------------------------------
diff --git a/shell/core/NOTICE b/shell/core/NOTICE
new file mode 100644
index 0000000..b70f1f9
--- /dev/null
+++ b/shell/core/NOTICE
@@ -0,0 +1,71 @@
+Apache Karaf
+Copyright 2010-2014 The Apache Software Foundation
+
+
+I. Included Software
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+Licensed under the Apache License 2.0.
+
+This product uses software developed at
+The OSGi Alliance (http://www.osgi.org/).
+Copyright (c) OSGi Alliance (2000, 2010).
+Licensed under the Apache License 2.0.
+
+This product includes software developed at
+OW2 (http://www.ow2.org/).
+Licensed under the BSD License.
+
+This product includes software developed at
+OPS4J (http://www.ops4j.org/).
+Licensed under the Apache License 2.0.
+
+This product includes software developed at
+Eclipse Foundation (http://www.eclipse.org/).
+Licensed under the EPL.
+
+This product includes software written by
+Antony Lesuisse.
+Licensed under Public Domain.
+
+
+II. Used Software
+
+This product uses software developed at
+FUSE Source (http://www.fusesource.org/).
+Licensed under the Apache License 2.0.
+
+This product uses software developed at
+AOP Alliance (http://aopalliance.sourceforge.net/).
+Licensed under the Public Domain.
+
+This product uses software developed at
+Tanuki Software (http://www.tanukisoftware.com/).
+Licensed under the Apache License 2.0.
+
+This product uses software developed at
+Jasypt (http://jasypt.sourceforge.net/).
+Licensed under the Apache License 2.0.
+
+This product uses software developed at
+JLine (http://jline.sourceforge.net).
+Licensed under the BSD License.
+
+This product uses software developed at
+SLF4J (http://www.slf4j.org/).
+Licensed under the MIT License.
+
+This product uses software developed at
+SpringSource (http://www.springsource.org/).
+Licensed under the Apache License 2.0.
+
+This product includes software from http://www.json.org.
+Copyright (c) 2002 JSON.org
+
+
+III. License Summary
+- Apache License 2.0
+- BSD License
+- EPL License
+- MIT License

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/pom.xml
----------------------------------------------------------------------
diff --git a/shell/core/pom.xml b/shell/core/pom.xml
new file mode 100644
index 0000000..c41b0b2
--- /dev/null
+++ b/shell/core/pom.xml
@@ -0,0 +1,179 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <!--
+
+        Licensed to the Apache Software Foundation (ASF) under one or more
+        contributor license agreements.  See the NOTICE file distributed with
+        this work for additional information regarding copyright ownership.
+        The ASF licenses this file to You under the Apache License, Version 2.0
+        (the "License"); you may not use this file except in compliance with
+        the License.  You may obtain a copy of the License at
+
+           http://www.apache.org/licenses/LICENSE-2.0
+
+        Unless required by applicable law or agreed to in writing, software
+        distributed under the License is distributed on an "AS IS" BASIS,
+        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+        See the License for the specific language governing permissions and
+        limitations under the License.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.karaf.shell</groupId>
+        <artifactId>shell</artifactId>
+        <version>3.1.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>org.apache.karaf.shell.core</artifactId>
+    <packaging>bundle</packaging>
+    <name>Apache Karaf :: Shell :: Core</name>
+    <description>This bundle provides OSGi shell integration and console support.</description>
+
+    <properties>
+        <appendedResourcesDirectory>${basedir}/../../etc/appended-resources</appendedResourcesDirectory>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>jline</groupId>
+            <artifactId>jline</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.jaas</groupId>
+            <artifactId>org.apache.karaf.jaas.modules</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.jaas</groupId>
+            <artifactId>org.apache.karaf.jaas.boot</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.aries.blueprint</groupId>
+            <artifactId>org.apache.aries.blueprint.api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.aries.blueprint</groupId>
+            <artifactId>org.apache.aries.blueprint.core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.gogo.runtime</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.utils</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.util</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sshd</groupId>
+            <artifactId>sshd-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.service</groupId>
+            <artifactId>org.apache.karaf.service.guard</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <resources>
+            <resource>
+                <directory>${project.basedir}/../../client/src/main/key</directory>
+                <filtering>false</filtering>
+            </resource>
+            <resource>
+                <directory>${project.basedir}/src/main/resources</directory>
+                <includes>
+                    <include>**/*</include>
+                </includes>
+            </resource>
+            <resource>
+                <directory>${project.basedir}/src/main/resources</directory>
+                <filtering>true</filtering>
+                <includes>
+                    <include>**/*.info</include>
+                </includes>
+            </resource>
+            <resource>
+                <directory>${project.basedir}/src/main/filtered-resources</directory>
+                <filtering>true</filtering>
+                <includes>
+                    <include>**/*</include>
+                </includes>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>exec-maven-plugin</artifactId>
+                <configuration>
+                    <mainClass>Main</mainClass>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <configuration>
+                    <instructions>
+                        <Import-Package>
+                            org.osgi.service.event;resolution:=optional,
+                            org.apache.karaf.branding;resolution:=optional,
+                            *
+                        </Import-Package>
+                        <Export-Package>
+                            org.apache.karaf.shell.api.*;version=${project.version},
+                            org.apache.karaf.shell.support.*;version=${project.version},
+                        </Export-Package>
+                        <Private-Package>
+                            org.apache.karaf.service.guard.tools,
+                            org.apache.karaf.shell.impl.*,
+                            org.apache.karaf.util.properties,
+                            org.apache.felix.utils.extender,
+                            org.apache.felix.utils.manifest,
+                            org.apache.felix.gogo.api,
+                            org.apache.felix.gogo.runtime,
+                            org.apache.felix.gogo.runtime.threadio,
+                            org.apache.felix.service.command,
+                            org.apache.felix.service.threadio,
+                        </Private-Package>
+                        <Bundle-Activator>
+                            org.apache.karaf.shell.impl.console.osgi.Activator
+                        </Bundle-Activator>
+                        <Main-Class>
+                            org.apache.karaf.shell.impl.console.standalone.Main
+                        </Main-Class>
+                    </instructions>
+                    <unpackBundle>true</unpackBundle>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/filtered-resources/org/apache/karaf/shell/console/branding-ssh.properties
----------------------------------------------------------------------
diff --git a/shell/core/src/main/filtered-resources/org/apache/karaf/shell/console/branding-ssh.properties b/shell/core/src/main/filtered-resources/org/apache/karaf/shell/console/branding-ssh.properties
new file mode 100644
index 0000000..978484b
--- /dev/null
+++ b/shell/core/src/main/filtered-resources/org/apache/karaf/shell/console/branding-ssh.properties
@@ -0,0 +1,34 @@
+##
+## 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.
+##
+
+welcome = \
+\u001B[36m        __ __                  ____      \u001B[0m\r\n\
+\u001B[36m       / //_/____ __________ _/ __/      \u001B[0m\r\n\
+\u001B[36m      / ,<  / __ `/ ___/ __ `/ /_        \u001B[0m\r\n\
+\u001B[36m     / /| |/ /_/ / /  / /_/ / __/        \u001B[0m\r\n\
+\u001B[36m    /_/ |_|\\__,_/_/   \\__,_/_/         \u001B[0m\r\n\
+\r\n\
+\u001B[1m  Apache Karaf\u001B[0m (${project.version})\r\n\
+\r\n\
+Hit '\u001B[1m<tab>\u001B[0m' for a list of available commands\r\n\
+   and '\u001B[1m[cmd] --help\u001B[0m' for help on a specific command.\r\n\
+Hit '\u001B[1msystem:shutdown\u001B[0m' to shutdown Karaf.\r\n\
+Hit '\u001B[1m<ctrl-d>\u001B[0m' or type '\u001B[1mlogout\u001B[0m' to disconnect shell from current session.\r\n
+
+

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/filtered-resources/org/apache/karaf/shell/console/branding.properties
----------------------------------------------------------------------
diff --git a/shell/core/src/main/filtered-resources/org/apache/karaf/shell/console/branding.properties b/shell/core/src/main/filtered-resources/org/apache/karaf/shell/console/branding.properties
new file mode 100644
index 0000000..3fbcb0a
--- /dev/null
+++ b/shell/core/src/main/filtered-resources/org/apache/karaf/shell/console/branding.properties
@@ -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.
+##
+
+welcome = \
+\u001B[36m        __ __                  ____      \u001B[0m\r\n\
+\u001B[36m       / //_/____ __________ _/ __/      \u001B[0m\r\n\
+\u001B[36m      / ,<  / __ `/ ___/ __ `/ /_        \u001B[0m\r\n\
+\u001B[36m     / /| |/ /_/ / /  / /_/ / __/        \u001B[0m\r\n\
+\u001B[36m    /_/ |_|\\__,_/_/   \\__,_/_/         \u001B[0m\r\n\
+\r\n\
+\u001B[1m  Apache Karaf\u001B[0m (${project.version})\r\n\
+\r\n\
+Hit '\u001B[1m<tab>\u001B[0m' for a list of available commands\r\n\
+   and '\u001B[1m[cmd] --help\u001B[0m' for help on a specific command.\r\n\
+Hit '\u001B[1m<ctrl-d>\u001B[0m' or type '\u001B[1msystem:shutdown\u001B[0m' or '\u001B[1mlogout\u001B[0m' to shutdown Karaf.\r\n
+
+

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/api/action/Action.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/api/action/Action.java b/shell/core/src/main/java/org/apache/karaf/shell/api/action/Action.java
new file mode 100644
index 0000000..955c837
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/api/action/Action.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.api.action;
+
+/**
+ * An action is the default implementation of the commands in karaf.
+ * In OSGi, Actions are discovered using an extender and a new instance
+ * of the class is created when the command is invoked, so that the
+ * implementation does not need to be thread safe.
+ *
+ * Before the call to the execute method the action is checked for
+ * fields annotated with @Reference and injected with services coming
+ * from the SessionFactory's Registry or from the OSGi registry.
+ * Methods annotated with @Init are then called.  The next step is to
+ * inject command line parameters into fields annotated with @Option
+ * and @Argument and then call the execute method.
+ * 
+ * Any class implementing Action must have a no argument constructor. This
+ * is necessary so the help generator can instantiate the class and get the 
+ * default values.
+ *
+ * In order to make commands available from the non-OSGi shell,
+ * the commands must be listed in a file available at
+ * META-INF/services/org/apache/karaf/shell/commmands.
+ *
+ * @see org.apache.karaf.shell.api.action.Command
+ * @see org.apache.karaf.shell.api.action.lifecycle.Service
+ */
+public interface Action {
+
+    /**
+     * Execute the action which has been injected with services from the
+     * registry, options and arguments from the command line.
+     *
+     * @return <code>null</code> or the result of the action execution
+     * @throws Exception
+     */
+    Object execute() throws Exception;
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/api/action/Argument.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/api/action/Argument.java b/shell/core/src/main/java/org/apache/karaf/shell/api/action/Argument.java
new file mode 100644
index 0000000..8cb1e51
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/api/action/Argument.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.karaf.shell.api.action;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Represents a positional argument on a command line (as opposed to an optional named {@link Option}
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+public @interface Argument
+{
+    public static final String DEFAULT_STRING= "DEFAULT";
+
+    String DEFAULT = "##default";
+
+    /**
+     * Name of the argument.
+     * By default, the field name will be used.
+     */
+    String name() default DEFAULT;
+
+    /**
+     * A textual description of the argument.
+     */
+    String description() default "";
+
+    /**
+     * Whether this argument is mandatory or not.
+     */
+    boolean required() default false;
+
+    /**
+     * Position of the argument in the command line.
+     * When using multiple arguments, the indices must be
+     * starting from 0 and incrementing without any holes.
+     */
+    int index() default 0;
+
+    /**
+     * The last argument can be multi-valued in which case
+     * the field type must be a List.
+     */
+    boolean multiValued() default false;
+
+    /**
+     * The generated help displays default values for arguments.
+     * In case the value displayed in the help to the user should
+     * be different that the default value of the field, one
+     * can use this property to specify the value to display.
+     */
+    String valueToShowInHelp() default DEFAULT_STRING;
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/api/action/Command.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/api/action/Command.java b/shell/core/src/main/java/org/apache/karaf/shell/api/action/Command.java
new file mode 100644
index 0000000..7622b33
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/api/action/Command.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.api.action;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Used to denote a class represents a command which is executable
+ * within a shell/scope or as a command line process.
+ *
+ * All classes annotated with @Command should implement the
+ * {@link org.apache.karaf.shell.api.action.Action} interface.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface Command
+{
+    /**
+     * Returns the scope or sub shell of the command
+     */
+    String scope();
+
+    /**
+     * Returns the name of the command if used inside a shell
+     */
+    String name();
+
+    /**
+     * Returns the description of the command which is used to generate command line help
+     */
+    String description() default "";
+
+    /**
+     * Returns a detailed description of the command.
+     * This description will be shown in the help for the command.
+     * Longer descriptions can be externalized using a
+     * <code>classpath:[location]</code> url, in which case the
+     * descrition will be loaded from the bundle at the given location,
+     * relatively to the implementation of the command.
+     */
+    String detailedDescription() default "";
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/api/action/Completion.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/api/action/Completion.java b/shell/core/src/main/java/org/apache/karaf/shell/api/action/Completion.java
new file mode 100644
index 0000000..d9a3dca
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/api/action/Completion.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.shell.api.action;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The @Completion annotation can be used on a field annotated with
+ * {@link Option} or {@link Argument} to specify the completion
+ * method to use for this field.
+ *
+ * @see org.apache.karaf.shell.api.console.Completer
+ * @see org.apache.karaf.shell.support.completers.StringsCompleter
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+public @interface Completion {
+
+    /**
+     * The completer class to use for this field.
+     * The console registry will be used to look for
+     * a completer of this class.
+     *
+     * A special case for simple static completions is to use
+     * {@link org.apache.karaf.shell.support.completers.StringsCompleter},
+     * in which case, the <code>values</code> property will be used
+     * as the list of possible completions.
+     */
+    Class<?> value();
+
+    /**
+     * When using a static completer, returns the possible values.
+     */
+    String[] values() default { };
+
+    /**
+     * When using a static completer, indicates if completion
+     * should be done case sensitive or not.
+     */
+    boolean caseSensitive() default false;
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/api/action/Option.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/api/action/Option.java b/shell/core/src/main/java/org/apache/karaf/shell/api/action/Option.java
new file mode 100644
index 0000000..97e7557
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/api/action/Option.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.api.action;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Used to mark an optional named command line option who's name typically starts with "--" or "-".
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+public @interface Option
+{
+    public static final String DEFAULT_STRING= "DEFAULT";
+
+    /**
+     * The name of this option.  Usually starting with a '-'.
+     */
+    String name();
+
+    /**
+     * Specify a list of aliases for this option.
+     * Useful when using an option with short or long names.
+     */
+    String[] aliases() default {};
+
+    /**
+     * A textual description of the option.
+     */
+    String description() default "";
+
+    /**
+     * Whether this argument is mandatory or not.
+     */
+    boolean required() default false;
+
+    /**
+     * The last argument can be multi-valued in which case
+     * the field type must be a List.  On the command line,
+     * multi-valued options are used with specifying the option
+     * multiple times with different values.
+     */
+    boolean multiValued() default false;
+
+    /**
+     * The generated help displays default values for arguments.
+     * In case the value displayed in the help to the user should
+     * be different that the default value of the field, one
+     * can use this property to specify the value to display.
+     */
+    String valueToShowInHelp() default DEFAULT_STRING;
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/api/action/lifecycle/Destroy.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/api/action/lifecycle/Destroy.java b/shell/core/src/main/java/org/apache/karaf/shell/api/action/lifecycle/Destroy.java
new file mode 100644
index 0000000..86199c0
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/api/action/lifecycle/Destroy.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.api.action.lifecycle;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A class annotated with {@link @Service} can have a method
+ * annotation with <code>@Destroy</code> in which case the annotated
+ * method will be called when the object is destroyed.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+public @interface Destroy {
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/api/action/lifecycle/Init.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/api/action/lifecycle/Init.java b/shell/core/src/main/java/org/apache/karaf/shell/api/action/lifecycle/Init.java
new file mode 100644
index 0000000..d0cd3c0
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/api/action/lifecycle/Init.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.api.action.lifecycle;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A class annotated with {@link @Service} can have a method
+ * annotation with <code>@Init</code> in which case the annotated
+ * method will be called after a successful injection.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+public @interface Init {
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/api/action/lifecycle/Manager.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/api/action/lifecycle/Manager.java b/shell/core/src/main/java/org/apache/karaf/shell/api/action/lifecycle/Manager.java
new file mode 100644
index 0000000..2b26e81
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/api/action/lifecycle/Manager.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.karaf.shell.api.action.lifecycle;
+
+/**
+ * The <code>Manager</code> service can be used to programmatically
+ * register {@link org.apache.karaf.shell.api.action.Action}s or
+ * {@link org.apache.karaf.shell.api.console.Completer}s.
+ *
+ * Registered objects must be annotated with the {@link Service} annotation.
+ *
+ * Objects will be registered in the {@link org.apache.karaf.shell.api.console.Registry}
+ * associated with this <code>Manager</code>.
+ *
+ * @see org.apache.karaf.shell.api.console.Registry
+ * @see org.apache.karaf.shell.api.action.lifecycle.Service
+ */
+public interface Manager {
+
+    /**
+     * Register a service.
+     * If the given class is an {@link org.apache.karaf.shell.api.action.Action},
+     * a {@link org.apache.karaf.shell.api.console.Command} will be created and registered,
+     * else, an instance of the class will be created, injected and registered.
+     */
+    void register(Class<?> clazz);
+
+    /**
+     * Unregister a previously registered class.
+     */
+    void unregister(Class<?> clazz);
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/api/action/lifecycle/Reference.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/api/action/lifecycle/Reference.java b/shell/core/src/main/java/org/apache/karaf/shell/api/action/lifecycle/Reference.java
new file mode 100644
index 0000000..4d8a73c
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/api/action/lifecycle/Reference.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.karaf.shell.api.action.lifecycle;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A class annotated with {@link @Service} can have fields
+ * annotated with <code>@Service</code> in which case matching
+ * services will be retrieved from the
+ * {@link org.apache.karaf.shell.api.console.Registry} and
+ * injected.
+ *
+ * If a field has a {@link java.util.List} type, it will be injected
+ * with a list containing all matching services.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+public @interface Reference {
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/api/action/lifecycle/Service.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/api/action/lifecycle/Service.java b/shell/core/src/main/java/org/apache/karaf/shell/api/action/lifecycle/Service.java
new file mode 100644
index 0000000..d5d62a3
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/api/action/lifecycle/Service.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.api.action.lifecycle;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Classes that need to be managed must be annotated
+ * with <code>@Service</code> annotation.
+ *
+ * Services can either implement {@link org.apache.karaf.shell.api.action.Action}
+ * or {@link org.apache.karaf.shell.api.console.Completer}.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface Service {
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/api/console/Command.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/api/console/Command.java b/shell/core/src/main/java/org/apache/karaf/shell/api/console/Command.java
new file mode 100644
index 0000000..4bb1db0
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/api/console/Command.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.shell.api.console;
+
+/**
+ * A <code>Command</code> is a named
+ * {@link org.apache.karaf.shell.api.console.Function}
+ * which also provides completion.
+ */
+public interface Command extends Function {
+
+    /**
+     * Retrieve the scope of this command.
+     */
+    String getScope();
+
+    /**
+     * Retrieve the name of this command.
+     */
+    String getName();
+
+    /**
+     * Retrieve the description of this command.
+     * This short command description will be printed
+     * when using the <code>help</code> command.
+     */
+    String getDescription();
+
+    /**
+     * Retrieve the completer associated with this command.
+     *
+     * @param scoped whether the command is invoked from a subshell or not
+     * @return the {@link Completer} to use
+     */
+    Completer getCompleter(boolean scoped);
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/api/console/CommandLine.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/api/console/CommandLine.java b/shell/core/src/main/java/org/apache/karaf/shell/api/console/CommandLine.java
new file mode 100644
index 0000000..2ed7697
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/api/console/CommandLine.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.api.console;
+
+/**
+ * A <code>CommandLine</code> object will be created and
+ * given the {@link org.apache.karaf.shell.api.console.Completer}s to ease
+ * their work.  Arguments are separated and the cursor position within the
+ * current argument is given.
+ */
+public interface CommandLine {
+
+    /**
+     * Retrieve the argument index for the cursor position
+     */
+    int getCursorArgumentIndex();
+
+    /**
+     * Retrieve the argument for the cursor position
+     */
+    String getCursorArgument();
+
+    /**
+     * Retrieve the position of the cursor within the argument
+     */
+    int getArgumentPosition();
+
+    /**
+     * List of arguments on the current command.
+     * If the command line contains multiple commands, only the command corresponding
+     * to the cursor position is available.
+     */
+    String[] getArguments();
+
+    /**
+     * Retrieve the position of the cursor within the command line.
+     */
+    int getBufferPosition();
+
+    /**
+     * Retrieve the full buffer.
+     */
+    String getBuffer();
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/api/console/Completer.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/api/console/Completer.java b/shell/core/src/main/java/org/apache/karaf/shell/api/console/Completer.java
new file mode 100644
index 0000000..4f9f3d1
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/api/console/Completer.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.karaf.shell.api.console;
+
+import java.util.List;
+
+/**
+ * A <code>Completer</code> is used by the console to complete the command line.
+ */
+public interface Completer {
+
+    /**
+     * populate possible completion candidates.
+     *
+     * @param session the current {@link Session}
+     * @param commandLine the pre-parsed {@link CommandLine}
+     * @param candidates a list to fill with possible completion candidates
+     * @return the index of the{@link CommandLine} for which the completion will be relative
+     */
+    int complete(Session session, CommandLine commandLine, List<String> candidates);
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/api/console/Function.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/api/console/Function.java b/shell/core/src/main/java/org/apache/karaf/shell/api/console/Function.java
new file mode 100644
index 0000000..209039f
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/api/console/Function.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.karaf.shell.api.console;
+
+import java.util.List;
+
+/**
+ * This interface represents some code that can be executed in the
+ * {@link Session}.
+ */
+public interface Function {
+
+    /**
+     * Execute this function within the given Session and with the given
+     * arguments.
+     *
+     * @param session the current session
+     * @param arguments the arguments of this function
+     * @return the result
+     * @throws Exception if any exception occurs
+     */
+    Object execute(Session session, List<Object> arguments) throws Exception;
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/api/console/History.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/api/console/History.java b/shell/core/src/main/java/org/apache/karaf/shell/api/console/History.java
new file mode 100644
index 0000000..10b1b11
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/api/console/History.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.shell.api.console;
+
+/**
+ * Session history.
+ */
+public interface History {
+
+    /**
+     * First available index.
+     */
+    int first();
+
+    /**
+     * Last available index.
+     */
+    int last();
+
+    /**
+     * Command at the given index.
+     * Indices can range from <code>first()</code> to <code>last()</code>.
+     */
+    CharSequence get(int index);
+
+    /**
+     * Clear the history.
+     */
+    void clear();
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/api/console/Registry.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/api/console/Registry.java b/shell/core/src/main/java/org/apache/karaf/shell/api/console/Registry.java
new file mode 100644
index 0000000..b768d69
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/api/console/Registry.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.karaf.shell.api.console;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+
+/**
+ * Service registry.
+ *
+ * The registry can be used to register various services used during injection along
+ * with {@link Command}s.
+ *
+ * @see org.apache.karaf.shell.api.console.SessionFactory
+ * @see org.apache.karaf.shell.api.console.Session
+ */
+public interface Registry {
+
+    /**
+     * Return a list of available commands.
+     */
+    List<Command> getCommands();
+
+    /**
+     * Register a delayed service (or factory).
+     * In cases where instances must be created for each injection,
+     * a {@link Callable} can be registered and each injection will
+     * call it to obtain the actual service implementation.
+     *
+     * @param factory
+     * @param clazz
+     * @param <T>
+     */
+    <T> void register(Callable<T> factory, Class<T> clazz);
+
+    /**
+     * Register a service.
+     */
+    void register(Object service);
+
+    /**
+     * Unregister a service.
+     * If the registration has been done using a factory, the same
+     * factory should be used to unregister.
+     */
+    void unregister(Object service);
+
+    /**
+     * Obtain a service implementing the given class.
+     */
+    <T> T getService(Class<T> clazz);
+
+    /**
+     * Obtain a list of services implementing the given class.
+     */
+    <T> List<T> getServices(Class<T> clazz);
+
+    /**
+     * Check whether the registry has a service of the given class.
+     */
+    boolean hasService(Class<?> clazz);
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/api/console/Session.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/api/console/Session.java b/shell/core/src/main/java/org/apache/karaf/shell/api/console/Session.java
new file mode 100644
index 0000000..cf7f795
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/api/console/Session.java
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.api.console;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+
+/**
+ * A <code>Session</code> can be used to execute commands.
+ *
+ * The {@link org.apache.karaf.shell.api.console.Registry} associated
+ * with this <code>Session</code> will contain: <ul>
+ *     <li>{@link SessionFactory}</li>
+ *     <li>{@link Command}s</li>
+ *     <li>{@link Session}</li>
+ *     <li>{@link Registry}</li>
+ *     <li>{@link History}</li>
+ *     <li>{@link Terminal}</li>
+ * </ul>
+ */
+public interface Session extends Runnable {
+
+    //
+    // Session properties
+    //
+    // Property names starting with "karaf." are reserved for karaf
+    //
+
+    String SCOPE = "SCOPE";
+    String SUBSHELL = "SUBSHELL";
+
+    String PRINT_STACK_TRACES = "karaf.printStackTraces";
+    String LAST_EXCEPTION = "karaf.lastException";
+    String IGNORE_INTERRUPTS = "karaf.ignoreInterrupts";
+    String COMPLETION_MODE = "karaf.completionMode";
+
+    String COMPLETION_MODE_GLOBAL = "global";
+    String COMPLETION_MODE_SUBSHELL = "subshell";
+    String COMPLETION_MODE_FIRST = "first";
+
+    String SCOPE_GLOBAL = "*";
+
+    /**
+     * Execute a program in this session.
+     *
+     * @param commandline
+     * @return the result of the execution
+     */
+    Object execute(CharSequence commandline) throws Exception;
+
+    /**
+     * Get the value of a variable.
+     *
+     * @param name
+     * @return
+     */
+    Object get(String name);
+
+    /**
+     * Set the value of a variable.
+     *
+     * @param name  Name of the variable.
+     * @param value Value of the variable
+     */
+    void put(String name, Object value);
+
+    /**
+     * Return the input stream that is the first of the pipeline. This stream is
+     * sometimes necessary to communicate directly to the end user. For example,
+     * a "less" or "more" command needs direct input from the keyboard to
+     * control the paging.
+     *
+     * @return InputStream used closest to the user or null if input is from a
+     *         file.
+     */
+    InputStream getKeyboard();
+
+    /**
+     * Return the PrintStream for the console. This must always be the stream
+     * "closest" to the user. This stream can be used to post messages that
+     * bypass the piping. If the output is piped to a file, then the object
+     * returned must be null.
+     *
+     * @return
+     */
+    PrintStream getConsole();
+
+    /**
+     * Prompt the user for a line.
+     *
+     * @param prompt
+     * @param mask
+     * @return
+     * @throws java.io.IOException
+     */
+    String readLine(String prompt, final Character mask) throws IOException;
+
+    /**
+     * Retrieve the {@link org.apache.karaf.shell.api.console.Terminal} associated
+     * with this <code>Session</code> or <code>null</code> if this <code>Session</code>
+     * is headless.
+     */
+    Terminal getTerminal();
+
+    /**
+     * Retrieve the {@link org.apache.karaf.shell.api.console.History} associated
+     * with this <code>Session</code> or <code>null</code> if this <code>Session</code>
+     * is headless.
+     */
+    History getHistory();
+
+    /**
+     * Retrieve the {@link org.apache.karaf.shell.api.console.Registry} associated
+     * with this <code>Session</code>.
+     */
+    Registry getRegistry();
+
+    /**
+     * Retrieve the {@link org.apache.karaf.shell.api.console.SessionFactory} associated
+     * with this <code>Session</code>.
+     */
+    SessionFactory getFactory();
+
+    /**
+     * Resolve a command name.  If the command name has no specified scope, the fully
+     * qualified command name will be returned, depending on the scopes and current
+     * subshell.
+     */
+    String resolveCommand(String name);
+
+    /**
+     * Close this session. After the session is closed, it will throw
+     * IllegalStateException when it is used.
+     */
+    void close();
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/api/console/SessionFactory.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/api/console/SessionFactory.java b/shell/core/src/main/java/org/apache/karaf/shell/api/console/SessionFactory.java
new file mode 100644
index 0000000..7b02fd6
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/api/console/SessionFactory.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.shell.api.console;
+
+import java.io.InputStream;
+import java.io.PrintStream;
+
+/**
+ * The <code>SessionFactory</code> can be used to create
+ * {@link Session} to execute commands.
+ *
+ * The {@link org.apache.karaf.shell.api.console.Registry} associated
+ * with this <code>SessionFactory</code> will contain: <ul>
+ *     <li>{@link SessionFactory}</li>
+ *     <li>{@link Registry}</li>
+ *     <li>{@link Command}s</li>
+ * </ul>
+ */
+public interface SessionFactory {
+
+    /**
+     * Retrieve the {@link Registry} used by this <code>SessionFactory</code>.
+     */
+    Registry getRegistry();
+
+    /**
+     * Create new interactive session.
+     *
+     * @param in the input stream, can be <code>null</code> if the session is only used to execute a command using {@link Session#execute(CharSequence)}
+     * @param out the output stream
+     * @param err the error stream
+     * @param term the {@link Terminal} to use, may be <code>null</code>
+     * @param encoding the encoding to use for the input stream, may be <code>null</code>
+     * @param closeCallback a callback to be called when the session is closed, may be <code>null</code>
+     * @return the new session
+     */
+    Session create(InputStream in, PrintStream out, PrintStream err, Terminal term, String encoding, Runnable closeCallback);
+
+    /**
+     * Create a new headless session.
+     * Headless session can only be used to execute commands, so that
+     * {@link org.apache.karaf.shell.api.console.Session#run()} can not be used.
+     *
+     * @param in the input stream, can be <code>null</code> if the session is only used to execute a command using {@link Session#execute(CharSequence)}
+     * @param out the output stream
+     * @param err the error stream
+     * @return the new session
+     */
+    Session create(InputStream in, PrintStream out, PrintStream err);
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/api/console/Terminal.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/api/console/Terminal.java b/shell/core/src/main/java/org/apache/karaf/shell/api/console/Terminal.java
new file mode 100644
index 0000000..3549ec7
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/api/console/Terminal.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.api.console;
+
+/**
+ * Session terminal.
+ */
+public interface Terminal {
+
+    /**
+     * Width of the terminal.
+     */
+    int getWidth();
+
+    /**
+     * Height of the terminal.
+     */
+    int getHeight();
+
+    /**
+     * Whether ansi sequences are supported or not.
+     */
+    boolean isAnsiSupported();
+
+    /**
+     * Whether echo is enabled or not.
+     */
+    boolean isEchoEnabled();
+
+    /**
+     * Enable or disable echo.
+     */
+    void setEchoEnabled(boolean enabled);
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/ActionCommand.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/ActionCommand.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/ActionCommand.java
new file mode 100644
index 0000000..4cd58ca
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/ActionCommand.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.action.command;
+
+import java.util.List;
+
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+
+public class ActionCommand implements org.apache.karaf.shell.api.console.Command {
+
+    private final ManagerImpl manager;
+    private final Class<? extends Action> actionClass;
+
+    public ActionCommand(ManagerImpl manager, Class<? extends Action> actionClass) {
+        this.manager = manager;
+        this.actionClass = actionClass;
+    }
+
+    public Class<? extends Action> getActionClass() {
+        return actionClass;
+    }
+
+    @Override
+    public String getScope() {
+        return actionClass.getAnnotation(Command.class).scope();
+    }
+
+    @Override
+    public String getName() {
+        return actionClass.getAnnotation(Command.class).name();
+    }
+
+    @Override
+    public String getDescription() {
+        return actionClass.getAnnotation(Command.class).description();
+    }
+
+    @Override
+    public Completer getCompleter(boolean scoped) {
+        return new ArgumentCompleter(this, scoped);
+    }
+
+    protected Completer getCompleter(Class<?> clazz) {
+        return new DelayedCompleter(clazz);
+    }
+
+    @Override
+    public Object execute(Session session, List<Object> arguments) throws Exception {
+        Action action = createNewAction(session);
+        try {
+            if (new DefaultActionPreparator().prepare(action, session, arguments)) {
+                return action.execute();
+            }
+        } finally {
+            releaseAction(action);
+        }
+        return null;
+    }
+
+    protected Action createNewAction(Session session) {
+        try {
+            return manager.instantiate(actionClass, session.getRegistry());
+        } catch (Exception e) {
+            throw new RuntimeException("Unable to creation command action " + actionClass.getName(), e);
+        }
+    }
+
+    protected void releaseAction(Action action) throws Exception {
+        manager.release(action);
+    }
+
+    public static class DelayedCompleter implements Completer {
+        private final Class<?> clazz;
+
+        public DelayedCompleter(Class<?> clazz) {
+            this.clazz = clazz;
+        }
+
+        @Override
+        public int complete(Session session, CommandLine commandLine, List<String> candidates) {
+            Object service = session.getRegistry().getService(clazz);
+            if (service instanceof Completer) {
+                return ((Completer) service).complete(session, commandLine, candidates);
+            }
+            return -1;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/ArgumentCompleter.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/ArgumentCompleter.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/ArgumentCompleter.java
new file mode 100644
index 0000000..2868e6f
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/ArgumentCompleter.java
@@ -0,0 +1,385 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.action.command;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.FileCompleter;
+import org.apache.karaf.shell.support.completers.NullCompleter;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ArgumentCompleter implements Completer {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(ArgumentCompleter.class);
+
+    final ActionCommand command;
+    final Completer commandCompleter;
+    final Completer optionsCompleter;
+    final List<Completer> argsCompleters;
+    final Map<String, Completer> optionalCompleters;
+    final Map<Option, Field> fields = new HashMap<Option, Field>();
+    final Map<String, Option> options = new HashMap<String, Option>();
+    final Map<Integer, Field> arguments = new HashMap<Integer, Field>();
+    boolean strict = true;
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public ArgumentCompleter(ActionCommand command, boolean scoped) {
+        this.command = command;
+        Class<?> actionClass = command.getActionClass();
+        // Command name completer
+        Command cmd = actionClass.getAnnotation(Command.class);
+        String[] names = scoped || Session.SCOPE_GLOBAL.equals(cmd.scope()) ? new String[] { cmd.name() } : new String[] { cmd.name(), cmd.scope() + ":" + cmd.name() };
+        commandCompleter = new StringsCompleter(names);
+        // Build options completer
+        for (Class<?> type = actionClass; type != null; type = type.getSuperclass()) {
+            for (Field field : type.getDeclaredFields()) {
+                Option option = field.getAnnotation(Option.class);
+                if (option != null) {
+                    fields.put(option, field);
+                    options.put(option.name(), option);
+                    String[] aliases = option.aliases();
+                    if (aliases != null) {
+                        for (String alias : aliases) {
+                            options.put(alias, option);
+                        }
+                    }
+                }
+                Argument argument = field.getAnnotation(Argument.class);
+                if (argument != null) {
+                    Integer key = argument.index();
+                    if (arguments.containsKey(key)) {
+                        LOGGER.warn("Duplicate @Argument annotations on class " + type.getName() + " for index: " + key + " see: " + field);
+                    } else {
+                        arguments.put(key, field);
+                    }
+                }
+            }
+        }
+        options.put(HelpOption.HELP.name(), HelpOption.HELP);
+
+        argsCompleters = new ArrayList<Completer>();
+        optionsCompleter = new StringsCompleter(options.keySet());
+
+        boolean multi = false;
+        for (int key = 0; key < arguments.size(); key++) {
+            Completer completer = null;
+            Field field = arguments.get(key);
+            if (field != null) {
+                Argument argument = field.getAnnotation(Argument.class);
+                multi = (argument != null && argument.multiValued());
+                Completion ann = field.getAnnotation(Completion.class);
+                if (ann != null) {
+                    Class<?> clazz = ann.value();
+                    String[] value = ann.values();
+                    if (clazz != null) {
+                        if (value.length > 0 && clazz == StringsCompleter.class) {
+                            completer = new StringsCompleter(value, ann.caseSensitive());
+                        } else {
+                            completer = command.getCompleter(clazz);
+                        }
+                    }
+                } else {
+                    completer = getDefaultCompleter(field);
+                }
+            }
+            if (completer == null) {
+                completer = NullCompleter.INSTANCE;
+            }
+            argsCompleters.add(completer);
+        }
+        if (argsCompleters.isEmpty() || !multi) {
+            argsCompleters.add(NullCompleter.INSTANCE);
+        }
+        optionalCompleters = new HashMap<String, Completer>();
+        for (Option option : fields.keySet()) {
+            Completer completer = null;
+            Field field = fields.get(option);
+            if (field != null) {
+                Completion ann = field.getAnnotation(Completion.class);
+                if (ann != null) {
+                    Class clazz = ann.value();
+                    String[] value = ann.values();
+                    if (clazz != null) {
+                        if (clazz == StringsCompleter.class) {
+                            completer = new StringsCompleter(value, ann.caseSensitive());
+                        } else {
+                            completer = command.getCompleter(clazz);
+                        }
+                    }
+                }
+            }
+            if (completer == null) {
+                completer = NullCompleter.INSTANCE;
+            }
+            optionalCompleters.put(option.name(), completer);
+            if (option.aliases() != null) {
+                for (String alias : option.aliases()) {
+                    optionalCompleters.put(alias, completer);
+                }
+            }
+        }
+    }
+
+    private Completer getDefaultCompleter(Field field) {
+        Completer completer = null;
+        Class<?> type = field.getType();
+        if (type.isAssignableFrom(File.class)) {
+            completer = new FileCompleter();
+        } else if (type.isAssignableFrom(Boolean.class) || type.isAssignableFrom(boolean.class)) {
+            completer = new StringsCompleter(new String[] {"false", "true"}, false);
+        } else if (type.isAssignableFrom(Enum.class)) {
+            Set<String> values = new HashSet<String>();
+            for (Object o : EnumSet.allOf((Class<Enum>) type)) {
+                values.add(o.toString());
+            }
+            completer = new StringsCompleter(values, false);
+        } else {
+            // TODO any other completers we can add?
+        }
+        return completer;
+    }
+
+    /**
+     *  If true, a completion at argument index N will only succeed
+     *  if all the completions from 0-(N-1) also succeed.
+     */
+    public void setStrict(final boolean strict) {
+        this.strict = strict;
+    }
+
+    /**
+     *  Returns whether a completion at argument index N will succees
+     *  if all the completions from arguments 0-(N-1) also succeed.
+     */
+    public boolean getStrict() {
+        return this.strict;
+    }
+
+    public int complete(Session session, final CommandLine list, final List<String> candidates) {
+        int argpos = list.getArgumentPosition();
+        int argIndex = list.getCursorArgumentIndex();
+
+        Completer comp = null;
+        String[] args = list.getArguments();
+        int index = 0;
+        // First argument is command name
+        if (index < argIndex) {
+            // Verify scope
+            if (!Session.SCOPE_GLOBAL.equals(command.getScope()) && !session.resolveCommand(args[index]).equals(command.getScope() + ":" + command.getName())) {
+                return -1;
+            }
+            // Verify command name
+            if (!verifyCompleter(session, commandCompleter, args[index])) {
+                return -1;
+            }
+            index++;
+        } else {
+            comp = commandCompleter;
+        }
+        // Now, check options
+        if (comp == null) {
+            while (index < argIndex && args[index].startsWith("-")) {
+                if (!verifyCompleter(session, optionsCompleter, args[index])) {
+                    return -1;
+                }
+                Option option = options.get(args[index]);
+                if (option == null) {
+                    return -1;
+                }
+                Field field = fields.get(option);
+                if (field != null && field.getType() != boolean.class && field.getType() != Boolean.class) {
+                    if (++index == argIndex) {
+                        comp = NullCompleter.INSTANCE;
+                    }
+                }
+                index++;
+            }
+            if (comp == null && index >= argIndex && index < args.length && args[index].startsWith("-")) {
+                comp = optionsCompleter;
+            }
+        }
+        //Now check for if last Option has a completer
+        int lastAgurmentIndex = argIndex - 1;
+        if (lastAgurmentIndex >= 1) {
+            Option lastOption = options.get(args[lastAgurmentIndex]);
+            if (lastOption != null) {
+                Field lastField = fields.get(lastOption);
+                if (lastField != null && lastField.getType() != boolean.class && lastField.getType() != Boolean.class) {
+                    Option option = lastField.getAnnotation(Option.class);
+                    if (option != null) {
+                        Completer optionValueCompleter = null;
+                        String name = option.name();
+                        if (name != null) {
+                            optionValueCompleter = optionalCompleters.get(name);
+                            if (optionValueCompleter == null) {
+                                String[] aliases = option.aliases();
+                                if (aliases.length > 0) {
+                                    for (int i = 0; i < aliases.length && optionValueCompleter == null; i++) {
+                                        optionValueCompleter = optionalCompleters.get(option.aliases()[i]);
+                                    }
+                                }
+                            }
+                        }
+                        if(optionValueCompleter != null) {
+                            comp = optionValueCompleter;
+                        }
+                    }
+                }
+            }
+        }
+
+        // Check arguments
+        if (comp == null) {
+            int indexArg = 0;
+            while (index < argIndex) {
+                Completer sub = argsCompleters.get(indexArg >= argsCompleters.size() ? argsCompleters.size() - 1 : indexArg);
+                if (!verifyCompleter(session, sub, args[index])) {
+                    return -1;
+                }
+                index++;
+                indexArg++;
+            }
+            comp = argsCompleters.get(indexArg >= argsCompleters.size() ? argsCompleters.size() - 1 : indexArg);
+        }
+
+        int ret = comp.complete(session, new ArgumentCommandLine(list.getCursorArgument(), argpos), candidates);
+
+        if (ret == -1) {
+            return -1;
+        }
+
+        int pos = ret + (list.getBufferPosition() - argpos);
+
+        /**
+         *  Special case: when completing in the middle of a line, and the
+         *  area under the cursor is a delimiter, then trim any delimiters
+         *  from the candidates, since we do not need to have an extra
+         *  delimiter.
+         *
+         *  E.g., if we have a completion for "foo", and we
+         *  enter "f bar" into the buffer, and move to after the "f"
+         *  and hit TAB, we want "foo bar" instead of "foo  bar".
+         */
+
+        String buffer = list.getBuffer();
+        int cursor = list.getBufferPosition();
+        if ((buffer != null) && (cursor != buffer.length()) && isDelimiter(buffer, cursor)) {
+            for (int i = 0; i < candidates.size(); i++) {
+                String val = candidates.get(i);
+
+                while ((val.length() > 0)
+                    && isDelimiter(val, val.length() - 1)) {
+                    val = val.substring(0, val.length() - 1);
+                }
+
+                candidates.set(i, val);
+            }
+        }
+
+        return pos;
+    }
+
+    protected boolean verifyCompleter(Session session, Completer completer, String argument) {
+        List<String> candidates = new ArrayList<String>();
+        return completer.complete(session, new ArgumentCommandLine(argument, argument.length()), candidates) != -1 && !candidates.isEmpty();
+    }
+
+    /**
+     *  Returns true if the specified character is a whitespace
+     *  parameter. Check to ensure that the character is not
+     *  escaped and returns true from
+     *  {@link #isDelimiterChar}.
+     *
+     *  @param  buffer the complete command buffer
+     *  @param  pos    the index of the character in the buffer
+     *  @return        true if the character should be a delimiter
+     */
+    public boolean isDelimiter(final String buffer, final int pos) {
+        return !isEscaped(buffer, pos) && isDelimiterChar(buffer, pos);
+    }
+
+    public boolean isEscaped(final String buffer, final int pos) {
+        return pos > 0 && buffer.charAt(pos) == '\\' && !isEscaped(buffer, pos - 1);
+    }
+
+    /**
+     *  The character is a delimiter if it is whitespace, and the
+     *  preceeding character is not an escape character.
+     */
+    public boolean isDelimiterChar(String buffer, int pos) {
+        return Character.isWhitespace(buffer.charAt(pos));
+    }
+
+    static class ArgumentCommandLine implements CommandLine {
+        private final String argument;
+        private final int position;
+
+        ArgumentCommandLine(String argument, int position) {
+            this.argument = argument;
+            this.position = position;
+        }
+
+        @Override
+        public int getCursorArgumentIndex() {
+            return 0;
+        }
+
+        @Override
+        public String getCursorArgument() {
+            return argument;
+        }
+
+        @Override
+        public int getArgumentPosition() {
+            return position;
+        }
+
+        @Override
+        public String[] getArguments() {
+            return new String[] { argument };
+        }
+
+        @Override
+        public int getBufferPosition() {
+            return position;
+        }
+
+        @Override
+        public String getBuffer() {
+            return argument;
+        }
+    }
+}


[02/10] [KARAF-2805] Clean console and commands model

Posted by gn...@apache.org.
http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/Converters.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/Converters.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/Converters.java
new file mode 100644
index 0000000..87544e7
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/Converters.java
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.console.osgi;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.Formatter;
+
+import org.apache.felix.service.command.Converter;
+import org.apache.felix.service.command.Function;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.startlevel.BundleStartLevel;
+
+public class Converters implements Converter
+{
+    private final BundleContext context;
+
+    public Converters(BundleContext context)
+    {
+        this.context = context;
+    }
+
+    private CharSequence print(Bundle bundle)
+    {
+        // [ ID ] [STATE      ] [ SL ] symname
+        int level = bundle.adapt(BundleStartLevel.class).getStartLevel();
+
+        return String.format("%5d|%-11s|%5d|%s (%s)", bundle.getBundleId(),
+            getState(bundle), level, bundle.getSymbolicName(), bundle.getVersion());
+    }
+
+    private CharSequence print(ServiceReference ref)
+    {
+        StringBuilder sb = new StringBuilder();
+        Formatter f = new Formatter(sb);
+
+        String spid = "";
+        Object pid = ref.getProperty("service.pid");
+        if (pid != null)
+        {
+            spid = pid.toString();
+        }
+
+        f.format("%06d %3s %-40s %s", ref.getProperty("service.id"),
+            ref.getBundle().getBundleId(),
+            getShortNames((String[]) ref.getProperty("objectclass")), spid);
+        return sb;
+    }
+
+    private CharSequence getShortNames(String[] list)
+    {
+        StringBuilder sb = new StringBuilder();
+        String del = "";
+        for (String s : list)
+        {
+            sb.append(del + getShortName(s));
+            del = " | ";
+        }
+        return sb;
+    }
+
+    private CharSequence getShortName(String name)
+    {
+        int n = name.lastIndexOf('.');
+        if (n < 0)
+        {
+            n = 0;
+        }
+        else
+        {
+            n++;
+        }
+        return name.subSequence(n, name.length());
+    }
+
+    private String getState(Bundle bundle)
+    {
+        switch (bundle.getState())
+        {
+            case Bundle.ACTIVE:
+                return "Active";
+
+            case Bundle.INSTALLED:
+                return "Installed";
+
+            case Bundle.RESOLVED:
+                return "Resolved";
+
+            case Bundle.STARTING:
+                return "Starting";
+
+            case Bundle.STOPPING:
+                return "Stopping";
+
+            case Bundle.UNINSTALLED:
+                return "Uninstalled ";
+        }
+        return null;
+    }
+
+    public Bundle bundle(Bundle i)
+    {
+        return i;
+    }
+
+    public Object convert(Class<?> desiredType, final Object in) throws Exception
+    {
+        if (desiredType == Bundle.class)
+        {
+            return convertBundle(in);
+        }
+
+        if (desiredType == ServiceReference.class)
+        {
+            return convertServiceReference(in);
+        }
+
+        if (desiredType == Class.class)
+        {
+            try
+            {
+                return Class.forName(in.toString());
+            }
+            catch (ClassNotFoundException e)
+            {
+                return null;
+            }
+        }
+
+        if (desiredType.isAssignableFrom(String.class) && in instanceof InputStream)
+        {
+            return read(((InputStream) in));
+        }
+
+        if (in instanceof Function && desiredType.isInterface()
+            && desiredType.getDeclaredMethods().length == 1)
+        {
+            return Proxy.newProxyInstance(desiredType.getClassLoader(),
+                new Class[] { desiredType }, new InvocationHandler()
+                {
+                    Function command = ((Function) in);
+
+                    public Object invoke(Object proxy, Method method, Object[] args)
+                        throws Throwable
+                    {
+                        return command.execute(null, Arrays.asList(args));
+                    }
+                });
+        }
+
+        return null;
+    }
+
+    private Object convertServiceReference(Object in) throws InvalidSyntaxException
+    {
+        String s = in.toString();
+        if (s.startsWith("(") && s.endsWith(")"))
+        {
+            ServiceReference refs[] = context.getServiceReferences((String) null, String.format(
+                "(|(service.id=%s)(service.pid=%s))", in, in));
+            if (refs != null && refs.length > 0)
+            {
+                return refs[0];
+            }
+        }
+
+        ServiceReference refs[] = context.getServiceReferences((String) null, String.format(
+            "(|(service.id=%s)(service.pid=%s))", in, in));
+        if (refs != null && refs.length > 0)
+        {
+            return refs[0];
+        }
+        return null;
+    }
+
+    private Object convertBundle(Object in)
+    {
+        String s = in.toString();
+        try
+        {
+            long id = Long.parseLong(s);
+            return context.getBundle(id);
+        }
+        catch (NumberFormatException nfe)
+        {
+            // Ignore
+        }
+
+        Bundle bundles[] = context.getBundles();
+        for (Bundle b : bundles)
+        {
+            if (b.getLocation().equals(s))
+            {
+                return b;
+            }
+
+            if (b.getSymbolicName().equals(s))
+            {
+                return b;
+            }
+        }
+
+        return null;
+    }
+
+    public CharSequence format(Object target, int level, Converter converter)
+        throws IOException
+    {
+        if (level == INSPECT && target instanceof InputStream)
+        {
+            return read(((InputStream) target));
+        }
+        if (level == LINE && target instanceof Bundle)
+        {
+            return print((Bundle) target);
+        }
+        if (level == LINE && target instanceof ServiceReference)
+        {
+            return print((ServiceReference) target);
+        }
+        if (level == PART && target instanceof Bundle)
+        {
+            return ((Bundle) target).getSymbolicName();
+        }
+        if (level == PART && target instanceof ServiceReference)
+        {
+            return getShortNames((String[]) ((ServiceReference) target).getProperty("objectclass"));
+        }
+        return null;
+    }
+
+    private CharSequence read(InputStream in) throws IOException
+    {
+        int c;
+        StringBuffer sb = new StringBuffer();
+        while ((c = in.read()) > 0)
+        {
+            if (c >= 32 && c <= 0x7F || c == '\n' || c == '\r')
+            {
+                sb.append((char) c);
+            }
+            else
+            {
+                String s = Integer.toHexString(c).toUpperCase();
+                sb.append("\\");
+                if (s.length() < 1)
+                {
+                    sb.append(0);
+                }
+                sb.append(s);
+            }
+        }
+        return sb;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/DelayedStarted.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/DelayedStarted.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/DelayedStarted.java
new file mode 100644
index 0000000..0277e4e
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/DelayedStarted.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.shell.impl.console.osgi;
+
+import java.io.InputStream;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.startlevel.FrameworkStartLevel;
+
+/**
+ * Delay the start of the console until the desired start level is reached or enter is pressed
+ */
+class DelayedStarted extends Thread implements FrameworkListener {
+    private static final String SYSTEM_PROP_KARAF_CONSOLE_STARTED = "karaf.console.started";
+
+	private final AtomicBoolean started = new AtomicBoolean(false);
+    private final InputStream in;
+	private final Runnable console;
+	private final BundleContext bundleContext;
+
+    DelayedStarted(Runnable console, String name, BundleContext bundleContext, InputStream in) {
+        super(name);
+        this.console = console;
+        this.bundleContext = bundleContext;
+        this.in = in;
+        int defaultStartLevel = Integer.parseInt(System.getProperty(Constants.FRAMEWORK_BEGINNING_STARTLEVEL));
+        int startLevel = bundleContext.getBundle(0).adapt(FrameworkStartLevel.class).getStartLevel();
+        if (startLevel >= defaultStartLevel) {
+            started.set(true);
+        } else {
+            bundleContext.addFrameworkListener(this);
+            frameworkEvent(new FrameworkEvent(FrameworkEvent.STARTLEVEL_CHANGED, bundleContext.getBundle(), null));
+        }
+    }
+
+    public void run() {
+        try {
+            while (!started.get()) {
+                if (in.available() == 0) {
+                    Thread.sleep(10);
+                }
+                while (in.available() > 0) {
+                    char ch = (char) in.read();
+                    if (ch == '\r' || ch == '\n') {
+                        started.set(true);
+                        break;
+                    }
+                }
+            }
+        } catch (Throwable t) {
+            // Ignore
+        }
+
+        // Signal to the main module that it can stop displaying the startup progress
+        System.setProperty(SYSTEM_PROP_KARAF_CONSOLE_STARTED, "true");
+        this.bundleContext.removeFrameworkListener(this);
+        console.run();
+    }
+
+    public void frameworkEvent(FrameworkEvent event) {
+        if (event.getType() == FrameworkEvent.STARTLEVEL_CHANGED) {
+            int defaultStartLevel = Integer.parseInt(System.getProperty(Constants.FRAMEWORK_BEGINNING_STARTLEVEL));
+            int startLevel = this.bundleContext.getBundle(0).adapt(FrameworkStartLevel.class).getStartLevel();
+            if (startLevel >= defaultStartLevel) {
+                started.set(true);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/LocalConsoleManager.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/LocalConsoleManager.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/LocalConsoleManager.java
new file mode 100644
index 0000000..0814d1e
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/LocalConsoleManager.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.console.osgi;
+
+import java.nio.charset.Charset;
+import java.security.PrivilegedAction;
+
+import javax.security.auth.Subject;
+
+import org.apache.karaf.jaas.boot.principal.RolePrincipal;
+import org.apache.karaf.jaas.boot.principal.UserPrincipal;
+import org.apache.karaf.jaas.modules.JaasHelper;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.console.SessionFactory;
+import org.apache.karaf.shell.impl.console.JLineTerminal;
+import org.apache.karaf.shell.impl.console.TerminalFactory;
+import org.apache.karaf.shell.support.ShellUtil;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+public class LocalConsoleManager {
+
+    private SessionFactory sessionFactory;
+    private BundleContext bundleContext;
+    private TerminalFactory terminalFactory;
+    private Session session;
+    private ServiceRegistration<?> registration;
+    private boolean closing;
+
+    public LocalConsoleManager(BundleContext bundleContext,
+                               TerminalFactory terminalFactory,
+                               SessionFactory sessionFactory) throws Exception {
+        this.bundleContext = bundleContext;
+        this.terminalFactory = terminalFactory;
+        this.sessionFactory = sessionFactory;
+    }
+
+    public void start() throws Exception {
+        final jline.Terminal terminal = terminalFactory.getTerminal();
+        final Runnable callback = new Runnable() {
+            public void run() {
+                try {
+                    if (!closing) {
+                        bundleContext.getBundle(0).stop();
+                    }
+                } catch (Exception e) {
+                    // Ignore
+                }
+            }
+        };
+
+        
+        final Subject subject = createLocalKarafSubject();    
+        this.session = JaasHelper.<Session>doAs(subject, new PrivilegedAction<Session>() {
+            public Session run() {
+                String encoding = getEncoding();
+                session = sessionFactory.create(
+                                      StreamWrapUtil.reWrapIn(terminal, System.in),
+                                      StreamWrapUtil.reWrap(System.out), 
+                                      StreamWrapUtil.reWrap(System.err),
+                                      new JLineTerminal(terminal),
+                                      encoding, 
+                                      callback);
+                String name = "Karaf local console user " + ShellUtil.getCurrentUserName();
+                boolean delayconsole = Boolean.parseBoolean(System.getProperty("karaf.delay.console"));
+                if (delayconsole) {
+                    DelayedStarted watcher = new DelayedStarted(session, name, bundleContext, System.in);
+                    new Thread(watcher).start();
+                } else {
+                    new Thread(session, name).start();
+                }
+                return session;
+            }
+        });
+        // TODO: register the local session so that ssh can add the agent
+//        registration = bundleContext.register(CommandSession.class, console.getSession(), null);
+
+    }
+
+    private String getEncoding() {
+        String ctype = System.getenv("LC_CTYPE");
+        String encoding = ctype;
+        if (encoding != null && encoding.indexOf('.') > 0) {
+            encoding = encoding.substring(encoding.indexOf('.') + 1);
+        } else {
+            encoding = System.getProperty("input.encoding", Charset.defaultCharset().name());
+        }
+        return encoding;
+    }
+
+    private Subject createLocalKarafSubject() {
+        final Subject subject = new Subject();
+        subject.getPrincipals().add(new UserPrincipal("karaf"));
+
+        String roles = System.getProperty("karaf.local.roles");
+        if (roles != null) {
+            for (String role : roles.split("[,]")) {
+                subject.getPrincipals().add(new RolePrincipal(role.trim()));
+            }
+        }
+        return subject;
+    }
+
+    public void stop() throws Exception {
+        closing = true;
+        if (registration != null) {
+            registration.unregister();
+        }
+        // The bundle is stopped
+        // so close the console and remove the callback so that the
+        // osgi framework isn't stopped
+        if (session != null) {
+            session.close();
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/StreamWrapUtil.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/StreamWrapUtil.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/StreamWrapUtil.java
new file mode 100644
index 0000000..2b3afcd
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/StreamWrapUtil.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.console.osgi;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.lang.reflect.Method;
+
+import jline.Terminal;
+import org.fusesource.jansi.AnsiConsole;
+
+final class StreamWrapUtil {
+    private StreamWrapUtil() {
+    }
+
+    private static Object invokePrivateMethod(Object o, String methodName, Object[] params) throws Exception {
+        final Method methods[] = o.getClass().getDeclaredMethods();
+        for (int i = 0; i < methods.length; ++i) {
+            if (methodName.equals(methods[i].getName())) {
+                methods[i].setAccessible(true);
+                return methods[i].invoke(o, params);
+            }
+        }
+        return o;
+    }
+
+    /**
+     * unwrap stream so it can be recognized by the terminal and wrapped to get
+     * special keys in windows
+     * 
+     * @param stream
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    private static <T> T unwrapBIS(T stream) {
+        try {
+            return (T) invokePrivateMethod(stream, "getInIfOpen", null);
+        } catch (Throwable t) {
+            return stream;
+        }
+    }
+
+    private static PrintStream wrap(PrintStream stream) {
+        OutputStream o = AnsiConsole.wrapOutputStream(stream);
+        if (o instanceof PrintStream) {
+            return ((PrintStream) o);
+        } else {
+            return new PrintStream(o);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T> T unwrap(T stream) {
+        try {
+            Method mth = stream.getClass().getMethod("getRoot");
+            return (T) mth.invoke(stream);
+        } catch (Throwable t) {
+            return stream;
+        }
+    }
+
+    static InputStream reWrapIn(Terminal terminal, InputStream stream) {
+        try {
+            return terminal.wrapInIfNeeded(unwrapBIS(unwrap(stream)));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    static PrintStream reWrap(PrintStream stream) {
+        return wrap(unwrap(stream));
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/secured/SecuredCommand.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/secured/SecuredCommand.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/secured/SecuredCommand.java
new file mode 100644
index 0000000..0fc6cde
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/secured/SecuredCommand.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.console.osgi.secured;
+
+import java.util.List;
+
+import org.apache.felix.gogo.runtime.Closure;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.felix.service.command.Function;
+import org.apache.karaf.shell.api.console.Command;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+
+public class SecuredCommand implements Command, Function {
+
+    private final SecuredSessionFactoryImpl factory;
+    private final Command command;
+
+    public SecuredCommand(SecuredSessionFactoryImpl factory, Command command) {
+        this.command = command;
+        this.factory = factory;
+    }
+
+    public String getScope() {
+        return command.getScope();
+    }
+
+    public String getName() {
+        return command.getName();
+    }
+
+    @Override
+    public String getDescription() {
+        return command.getDescription();
+    }
+
+    @Override
+    public Completer getCompleter(boolean scoped) {
+        return command.getCompleter(scoped);
+    }
+
+    @Override
+    public Object execute(Session session, List<Object> arguments) throws Exception {
+        factory.checkSecurity(this, session, arguments);
+        return command.execute(session, arguments);
+    }
+
+    @Override
+    public Object execute(final CommandSession commandSession, List<Object> arguments) throws Exception {
+        // TODO: remove the hack for .session
+        Session session = (Session) commandSession.get(".session");
+        // When need to translate closures to a compatible type for the command
+        for (int i = 0; i < arguments.size(); i++) {
+            Object v = arguments.get(i);
+            if (v instanceof Closure) {
+                final Closure closure = (Closure) v;
+                arguments.set(i, new org.apache.karaf.shell.api.console.Function() {
+                    @Override
+                    public Object execute(Session session, List<Object> arguments) throws Exception {
+                        return closure.execute(commandSession, arguments);
+                    }
+                });
+            }
+        }
+        return execute(session, arguments);
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/secured/SecuredSessionFactoryImpl.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/secured/SecuredSessionFactoryImpl.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/secured/SecuredSessionFactoryImpl.java
new file mode 100644
index 0000000..38b542e
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/secured/SecuredSessionFactoryImpl.java
@@ -0,0 +1,239 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.console.osgi.secured;
+
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+
+import org.apache.felix.gogo.runtime.CommandNotFoundException;
+import org.apache.felix.service.command.Function;
+import org.apache.felix.service.threadio.ThreadIO;
+import org.apache.karaf.jaas.boot.principal.RolePrincipal;
+import org.apache.karaf.service.guard.tools.ACLConfigurationParser;
+import org.apache.karaf.shell.api.console.Command;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.impl.console.SessionFactoryImpl;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationEvent;
+import org.osgi.service.cm.ConfigurationListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SecuredSessionFactoryImpl extends SessionFactoryImpl implements ConfigurationListener, SingleServiceTracker.SingleServiceListener {
+
+    private static final String PROXY_COMMAND_ACL_PID_PREFIX = "org.apache.karaf.command.acl.";
+    private static final String CONFIGURATION_FILTER =
+            "(" + Constants.SERVICE_PID + "=" + PROXY_COMMAND_ACL_PID_PREFIX + "*)";
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(SecuredSessionFactoryImpl.class);
+
+    private BundleContext bundleContext;
+    private Map<String, Dictionary<String, Object>> scopes = new HashMap<String, Dictionary<String, Object>>();
+    private SingleServiceTracker<ConfigurationAdmin> configAdminTracker;
+    private ServiceRegistration registration;
+
+    public SecuredSessionFactoryImpl(BundleContext bundleContext, ThreadIO threadIO) {
+        super(threadIO);
+        this.bundleContext = bundleContext;
+        this.registration = bundleContext.registerService(ConfigurationListener.class, this, null);
+        this.configAdminTracker = new SingleServiceTracker<ConfigurationAdmin>(bundleContext, ConfigurationAdmin.class, this);
+        this.configAdminTracker.open();
+    }
+
+    public void stop() {
+        this.registration.unregister();
+        this.configAdminTracker.close();
+        super.stop();
+    }
+
+    @Override
+    protected Function wrap(Command command) {
+        return new SecuredCommand(this, command);
+    }
+
+    @Override
+    protected boolean isVisible(Object service) {
+        if (service instanceof Command) {
+            return isVisible((Command) service);
+        } else {
+            return super.isVisible(service);
+        }
+    }
+
+    protected boolean isVisible(Command command) {
+        Dictionary<String, Object> config = getScopeConfig(command.getScope());
+        if (config != null) {
+            List<String> roles = new ArrayList<String>();
+            ACLConfigurationParser.getRolesForInvocation(command.getName(), null, null, config, roles);
+            if (roles.isEmpty()) {
+                return true;
+            } else {
+                for (String role : roles) {
+                    if (currentUserHasRole(role)) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+        }
+        return true;
+    }
+
+    void checkSecurity(SecuredCommand command, Session session, List<Object> arguments) {
+        Dictionary<String, Object> config = getScopeConfig(command.getScope());
+        if (config != null) {
+            if (!isVisible(command)) {
+                throw new CommandNotFoundException(command.getScope() + ":" + command.getName());
+            }
+            List<String> roles = new ArrayList<String>();
+            ACLConfigurationParser.Specificity s = ACLConfigurationParser.getRolesForInvocation(command.getName(), new Object[] { arguments.toString() }, null, config, roles);
+            if (s == ACLConfigurationParser.Specificity.NO_MATCH) {
+                return;
+            }
+            for (String role : roles) {
+                if (currentUserHasRole(role)) {
+                    return;
+                }
+            }
+            throw new SecurityException("Insufficient credentials.");
+        }
+    }
+
+    static boolean currentUserHasRole(String requestedRole) {
+        String clazz;
+        String role;
+        int index = requestedRole.indexOf(':');
+        if (index > 0) {
+            clazz = requestedRole.substring(0, index);
+            role = requestedRole.substring(index + 1);
+        } else {
+            clazz = RolePrincipal.class.getName();
+            role = requestedRole;
+        }
+
+        AccessControlContext acc = AccessController.getContext();
+        if (acc == null) {
+            return false;
+        }
+        Subject subject = Subject.getSubject(acc);
+
+        if (subject == null) {
+            return false;
+        }
+
+        for (Principal p : subject.getPrincipals()) {
+            if (clazz.equals(p.getClass().getName()) && role.equals(p.getName())) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public void configurationEvent(ConfigurationEvent event) {
+        if (!event.getPid().startsWith(PROXY_COMMAND_ACL_PID_PREFIX))
+            return;
+
+        try {
+            switch (event.getType()) {
+                case ConfigurationEvent.CM_DELETED:
+                    removeScopeConfig(event.getPid().substring(PROXY_COMMAND_ACL_PID_PREFIX.length()));
+                    break;
+                case ConfigurationEvent.CM_UPDATED:
+                    ConfigurationAdmin configAdmin = bundleContext.getService(event.getReference());
+                    try {
+                        addScopeConfig(configAdmin.getConfiguration(event.getPid()));
+                    } finally {
+                        bundleContext.ungetService(event.getReference());
+                    }
+                    break;
+            }
+        } catch (Exception e) {
+            LOGGER.error("Problem processing Configuration Event {}", event, e);
+        }
+    }
+
+    private void addScopeConfig(Configuration config) {
+        if (!config.getPid().startsWith(PROXY_COMMAND_ACL_PID_PREFIX)) {
+            // not a command scope configuration file
+            return;
+        }
+        String scope = config.getPid().substring(PROXY_COMMAND_ACL_PID_PREFIX.length());
+        if (scope.indexOf('.') >= 0) {
+            // scopes don't contains dots, not a command scope
+            return;
+        }
+        scope = scope.trim();
+        synchronized (scopes) {
+            scopes.put(scope, config.getProperties());
+        }
+    }
+
+    private void removeScopeConfig(String scope) {
+        synchronized (scopes) {
+            scopes.remove(scope);
+        }
+    }
+
+    private Dictionary<String, Object> getScopeConfig(String scope) {
+        synchronized (scopes) {
+            return scopes.get(scope);
+        }
+    }
+
+    @Override
+    public void serviceFound() {
+        try {
+            ConfigurationAdmin configAdmin = this.configAdminTracker.getService();
+            Configuration[] configs = configAdmin.listConfigurations(CONFIGURATION_FILTER);
+            if (configs != null) {
+                for (Configuration config : configs) {
+                    addScopeConfig(config);
+                }
+            }
+        } catch (Exception e) {
+            // Ignore, should never happen
+        }
+    }
+
+    @Override
+    public void serviceLost() {
+    }
+
+    @Override
+    public void serviceReplaced() {
+        serviceFound();
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/secured/SingleServiceTracker.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/secured/SingleServiceTracker.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/secured/SingleServiceTracker.java
new file mode 100644
index 0000000..fb57ee1
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/secured/SingleServiceTracker.java
@@ -0,0 +1,171 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+package org.apache.karaf.shell.impl.console.osgi.secured;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+
+//This is from aries util
+public final class SingleServiceTracker<T> {
+    public static interface SingleServiceListener {
+        public void serviceFound();
+
+        public void serviceLost();
+
+        public void serviceReplaced();
+    }
+
+    private final BundleContext ctx;
+    private final String className;
+    private final AtomicReference<T> service = new AtomicReference<T>();
+    private final AtomicReference<ServiceReference> ref = new AtomicReference<ServiceReference>();
+    private final AtomicBoolean open = new AtomicBoolean(false);
+    private final SingleServiceListener serviceListener;
+    private String filterString;
+    private Filter filter;
+
+    private final ServiceListener listener = new ServiceListener() {
+        public void serviceChanged(ServiceEvent event) {
+            if (open.get()) {
+                if (event.getType() == ServiceEvent.UNREGISTERING) {
+                    ServiceReference deadRef = event.getServiceReference();
+                    if (deadRef.equals(ref.get())) {
+                        findMatchingReference(deadRef);
+                    }
+                } else if (event.getType() == ServiceEvent.REGISTERED && ref.get() == null) {
+                    findMatchingReference(null);
+                }
+            }
+        }
+    };
+
+    public SingleServiceTracker(BundleContext context, Class<T> clazz, SingleServiceListener sl) {
+        ctx = context;
+        this.className = clazz.getName();
+        serviceListener = sl;
+    }
+
+    public SingleServiceTracker(BundleContext context, Class<T> clazz, String filterString, SingleServiceListener sl) throws InvalidSyntaxException {
+        this(context, clazz, sl);
+        this.filterString = filterString;
+        if (filterString != null) filter = context.createFilter(filterString);
+    }
+
+    public T getService() {
+        return service.get();
+    }
+
+    public ServiceReference getServiceReference() {
+        return ref.get();
+    }
+
+    public void open() {
+        if (open.compareAndSet(false, true)) {
+            try {
+                String filterString = '(' + Constants.OBJECTCLASS + '=' + className + ')';
+                if (filter != null) filterString = "(&" + filterString + filter + ')';
+                ctx.addServiceListener(listener, filterString);
+                findMatchingReference(null);
+            } catch (InvalidSyntaxException e) {
+                // this can never happen. (famous last words :)
+            }
+        }
+    }
+
+    private void findMatchingReference(ServiceReference original) {
+        boolean clear = true;
+        ServiceReference ref = ctx.getServiceReference(className);
+        if (ref != null && (filter == null || filter.match(ref))) {
+            @SuppressWarnings("unchecked")
+            T service = (T) ctx.getService(ref);
+            if (service != null) {
+                clear = false;
+
+                // We do the unget out of the lock so we don't exit this class while holding a lock.
+                if (!!!update(original, ref, service)) {
+                    ctx.ungetService(ref);
+                }
+            }
+        } else if (original == null) {
+            clear = false;
+        }
+
+        if (clear) {
+            update(original, null, null);
+        }
+    }
+
+    private boolean update(ServiceReference deadRef, ServiceReference newRef, T service) {
+        boolean result = false;
+        int foundLostReplaced = -1;
+
+        // Make sure we don't try to get a lock on null
+        Object lock;
+
+        // we have to choose our lock.
+        if (newRef != null) lock = newRef;
+        else if (deadRef != null) lock = deadRef;
+        else lock = this;
+
+        // This lock is here to ensure that no two threads can set the ref and service
+        // at the same time.
+        synchronized (lock) {
+            if (open.get()) {
+                result = this.ref.compareAndSet(deadRef, newRef);
+                if (result) {
+                    this.service.set(service);
+
+                    if (deadRef == null && newRef != null) foundLostReplaced = 0;
+                    if (deadRef != null && newRef == null) foundLostReplaced = 1;
+                    if (deadRef != null && newRef != null) foundLostReplaced = 2;
+                }
+            }
+        }
+
+        if (serviceListener != null) {
+            if (foundLostReplaced == 0) serviceListener.serviceFound();
+            else if (foundLostReplaced == 1) serviceListener.serviceLost();
+            else if (foundLostReplaced == 2) serviceListener.serviceReplaced();
+        }
+
+        return result;
+    }
+
+    public void close() {
+        if (open.compareAndSet(true, false)) {
+            ctx.removeServiceListener(listener);
+
+            synchronized (this) {
+                ServiceReference deadRef = ref.getAndSet(null);
+                service.set(null);
+                if (deadRef != null) ctx.ungetService(deadRef);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/parsing/CommandLineImpl.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/parsing/CommandLineImpl.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/parsing/CommandLineImpl.java
new file mode 100644
index 0000000..6a603d0
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/parsing/CommandLineImpl.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.console.parsing;
+
+import java.util.List;
+
+import org.apache.karaf.shell.api.console.CommandLine;
+
+/**
+ *  The result of a delimited buffer.
+ */
+public class CommandLineImpl implements CommandLine {
+
+    private final String[] arguments;
+    private final int cursorArgumentIndex;
+    private final int argumentPosition;
+    private final int bufferPosition;
+    private final String buffer;
+
+    public static CommandLine build(String buffer, int cursor) {
+        Parser parser = new Parser(buffer, cursor);
+        try {
+            List<List<List<String>>> program = parser.program();
+            List<String> pipe = program.get(parser.c0).get(parser.c1);
+            return new CommandLineImpl(pipe.toArray(new String[pipe.size()]), parser.c2, parser.c3, cursor, buffer);
+        } catch (Throwable t) {
+            return new CommandLineImpl(new String[] { buffer }, 0, cursor, cursor, buffer);
+        }
+    }
+
+    /**
+     *  @param  arguments           the array of tokens
+     *  @param  cursorArgumentIndex the token index of the cursor
+     *  @param  argumentPosition    the position of the cursor in the
+     *                              current token
+     *  @param  bufferPosition      the position of the cursor in the whole buffer
+     *  @param buffer               the whole buffer
+     */
+    public CommandLineImpl(String[] arguments, int cursorArgumentIndex, int argumentPosition, int bufferPosition, String buffer) {
+        this.arguments = arguments;
+        this.cursorArgumentIndex = cursorArgumentIndex;
+        this.argumentPosition = argumentPosition;
+        this.bufferPosition = bufferPosition;
+        this.buffer = buffer;
+    }
+
+    public int getCursorArgumentIndex() {
+        return this.cursorArgumentIndex;
+    }
+
+    public String getCursorArgument() {
+        if ((cursorArgumentIndex < 0)
+            || (cursorArgumentIndex >= arguments.length)) {
+            return null;
+        }
+
+        return arguments[cursorArgumentIndex];
+    }
+
+    public int getArgumentPosition() {
+        return this.argumentPosition;
+    }
+
+    public String[] getArguments() {
+        return this.arguments;
+    }
+
+    public int getBufferPosition() {
+        return this.bufferPosition;
+    }
+
+    public String getBuffer() {
+        return this.buffer;
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/parsing/Parser.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/parsing/Parser.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/parsing/Parser.java
new file mode 100644
index 0000000..16f6a21
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/parsing/Parser.java
@@ -0,0 +1,396 @@
+/*
+ * 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.
+ */
+// DWB14: parser loops if // comment at start of program
+// DWB15: allow program to have trailing ';'
+package org.apache.karaf.shell.impl.console.parsing;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Parser {
+
+    int current = 0;
+    String text;
+    boolean escaped;
+    static final String SPECIAL = "<;|{[\"'$`(=";
+
+    List<List<List<String>>> program;
+    List<List<String>> statements;
+    List<String> statement;
+    int cursor;
+    int start = -1;
+    int c0;
+    int c1;
+    int c2;
+    int c3;
+
+    public Parser(String text, int cursor) {
+        this.text = text;
+        this.cursor = cursor;
+    }
+
+    void ws() {
+        // derek: BUGFIX: loop if comment  at beginning of input
+        //while (!eof() && Character.isWhitespace(peek())) {
+        while (!eof() && (!escaped && Character.isWhitespace(peek()) || current == 0)) {
+            if (current != 0 || !escaped && Character.isWhitespace(peek())) {
+                current++;
+            }
+            if (peek() == '/' && current < text.length() - 2
+                && text.charAt(current + 1) == '/') {
+                comment();
+            }
+            if (current == 0) {
+                break;
+            }
+        }
+    }
+
+    private void comment() {
+        while (!eof() && peek() != '\n' && peek() != '\r') {
+            next();
+        }
+    }
+
+    boolean eof() {
+        return current >= text.length();
+    }
+
+    char peek() {
+        return peek(false);
+    }
+
+    char peek(boolean increment) {
+        escaped = false;
+        if (eof()) {
+            return 0;
+        }
+
+        int last = current;
+        char c = text.charAt(current++);
+
+        if (c == '\\') {
+            escaped = true;
+            if (eof()) {
+                throw new RuntimeException("Eof found after \\");
+            }
+
+            c = text.charAt(current++);
+
+            switch (c) {
+                case 't':
+                    c = '\t';
+                    break;
+                case '\r':
+                case '\n':
+                    c = ' ';
+                    break;
+                case 'b':
+                    c = '\b';
+                    break;
+                case 'f':
+                    c = '\f';
+                    break;
+                case 'n':
+                    c = '\n';
+                    break;
+                case 'r':
+                    c = '\r';
+                    break;
+                case 'u':
+                    c = unicode();
+                    current += 4;
+                    break;
+                default:
+                    // We just take the next character literally
+                    // but have the escaped flag set, important for {},[] etc
+            }
+        }
+        if (cursor > last && cursor <= current) {
+            c0 = program != null ? program.size() : 0;
+            c1 = statements != null ? statements.size() : 0;
+            c2 = statement != null ? statement.size() : 0;
+            c3 = (start >= 0) ? current - start : 0;
+        }
+        if (!increment) {
+            current = last;
+        }
+        return c;
+    }
+
+    public List<List<List<String>>> program() {
+        program = new ArrayList<List<List<String>>>();
+        ws();
+        if (!eof()) {
+            program.add(pipeline());
+            while (peek() == ';') {
+                current++;
+                List<List<String>> pipeline = pipeline();
+                program.add(pipeline);
+            }
+        }
+        if (!eof()) {
+            throw new RuntimeException("Program has trailing text: " + context(current));
+        }
+
+        List<List<List<String>>> p = program;
+        program = null;
+        return p;
+    }
+
+    CharSequence context(int around) {
+        return text.subSequence(Math.max(0, current - 20), Math.min(text.length(),
+            current + 4));
+    }
+
+    public List<List<String>> pipeline() {
+        statements = new ArrayList<List<String>>();
+        statements.add(statement());
+        while (peek() == '|') {
+            current++;
+            ws();
+            if (!eof()) {
+                statements.add(statement());
+            }
+            else {
+                statements.add(new ArrayList<String>());
+                break;
+            }
+        }
+        List<List<String>> s = statements;
+        statements = null;
+        return s;
+    }
+
+    public List<String> statement() {
+        statement = new ArrayList<String>();
+        statement.add(value());
+        while (!eof()) {
+            ws();
+            if (peek() == '|' || peek() == ';') {
+                break;
+            }
+
+            if (!eof()) {
+                statement.add(messy());
+            }
+        }
+        List<String> s = statement;
+        statement = null;
+        return s;
+    }
+
+    public String messy()
+    {
+        start = current;
+        char c = peek();
+        if (c > 0 && SPECIAL.indexOf(c) < 0) {
+            current++;
+            try {
+                while (!eof()) {
+                    c = peek();
+                    if (!escaped && (c == ';' || c == '|' || Character.isWhitespace(c))) {
+                        break;
+                    }
+                    next();
+                }
+                return text.substring(start, current);
+            } finally {
+                start = -1;
+            }
+        }
+        else {
+            return value();
+        }
+    }
+
+    String value() {
+        ws();
+
+        start = current;
+        try {
+            char c = next();
+            if (!escaped) {
+                switch (c) {
+                    case '{':
+                        return text.substring(start, find('}', '{'));
+                    case '(':
+                        return text.substring(start, find(')', '('));
+                    case '[':
+                        return text.substring(start, find(']', '['));
+                    case '<':
+                        return text.substring(start, find('>', '<'));
+                    case '=':
+                        return text.substring(start, current);
+                    case '"':
+                    case '\'':
+                        quote(c);
+                        break;
+                }
+            }
+
+            // Some identifier or number
+            while (!eof()) {
+                c = peek();
+                if (!escaped) {
+                    if (Character.isWhitespace(c) || c == ';' || c == '|' || c == '=') {
+                        break;
+                    }
+                    else if (c == '{') {
+                        next();
+                        find('}', '{');
+                    }
+                    else if (c == '(') {
+                        next();
+                        find(')', '(');
+                    }
+                    else if (c == '<') {
+                        next();
+                        find('>', '<');
+                    }
+                    else if (c == '[') {
+                        next();
+                        find(']', '[');
+                    }
+                    else if (c == '\'' || c == '"') {
+                        next();
+                        quote(c);
+                        next();
+                    }
+                    else {
+                        next();
+                    }
+                }
+                else {
+                    next();
+                }
+            }
+            return text.substring(start, current);
+        } finally {
+            start = -1;
+        }
+    }
+
+    boolean escaped() {
+        return escaped;
+    }
+
+    char next() {
+        return peek(true);
+    }
+
+    char unicode() {
+        if (current + 4 > text.length()) {
+            throw new IllegalArgumentException("Unicode \\u escape at eof at pos ..."
+                + context(current) + "...");
+        }
+
+        String s = text.subSequence(current, current + 4).toString();
+        int n = Integer.parseInt(s, 16);
+        return (char) n;
+    }
+
+    int find(char target, char deeper) {
+        int start = current;
+        int level = 1;
+
+        while (level != 0) {
+            if (eof()) {
+                throw new RuntimeException("Eof found in the middle of a compound for '"
+                    + target + deeper + "', begins at " + context(start));
+            }
+
+            char c = next();
+            if (!escaped) {
+                if (c == target) {
+                    level--;
+                } else {
+                    if (c == deeper) {
+                        level++;
+                    } else {
+                        if (c == '"') {
+                            quote('"');
+                        } else {
+                            if (c == '\'') {
+                                quote('\'');
+                            }
+                            else {
+                                if (c == '`') {
+                                    quote('`');
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return current;
+    }
+
+    int quote(char which) {
+        while (!eof() && (peek() != which || escaped)) {
+            next();
+        }
+
+        return current++;
+    }
+
+    CharSequence findVar() {
+        int start = current;
+        char c = peek();
+
+        if (c == '{') {
+            next();
+            int end = find('}', '{');
+            return text.subSequence(start, end);
+        }
+        if (c == '(') {
+            next();
+            int end = find(')', '(');
+            return text.subSequence(start, end);
+        }
+
+        if (Character.isJavaIdentifierPart(c)) {
+            while (c == '$') {
+                c = next();
+            }
+            while (!eof() && (Character.isJavaIdentifierPart(c) || c == '.') && c != '$') {
+                next();
+                c = peek();
+            }
+            return text.subSequence(start, current);
+        }
+        throw new IllegalArgumentException(
+            "Reference to variable does not match syntax of a variable: "
+                + context(start));
+    }
+
+    public String toString() {
+        return "..." + context(current) + "...";
+    }
+
+    public String unescape() {
+        StringBuilder sb = new StringBuilder();
+        while (!eof()) {
+            sb.append(next());
+        }
+        return sb.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/impl/console/standalone/Main.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/standalone/Main.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/standalone/Main.java
new file mode 100644
index 0000000..842a248
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/standalone/Main.java
@@ -0,0 +1,278 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.impl.console.standalone;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.Reader;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+import org.apache.felix.gogo.runtime.threadio.ThreadIOImpl;
+import org.apache.felix.service.threadio.ThreadIO;
+import org.apache.karaf.shell.api.action.lifecycle.Manager;
+import org.apache.karaf.shell.api.console.Registry;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.console.SessionFactory;
+import org.apache.karaf.shell.api.console.Terminal;
+import org.apache.karaf.shell.impl.action.command.ManagerImpl;
+import org.apache.karaf.shell.support.NameScoping;
+import org.apache.karaf.shell.impl.console.JLineTerminal;
+import org.apache.karaf.shell.impl.console.SessionFactoryImpl;
+import org.apache.karaf.shell.impl.console.TerminalFactory;
+import org.apache.karaf.shell.support.ShellUtil;
+import org.fusesource.jansi.AnsiConsole;
+
+public class Main {
+
+    private String application = System.getProperty("karaf.name", "root");
+    private String user = "karaf";
+
+    public static void main(String args[]) throws Exception {
+        Main main = new Main();
+        main.run(args);
+    }
+
+    /**
+     * Use this method when the shell is being executed as a top level shell.
+     *
+     * @param args
+     * @throws Exception
+     */
+    public void run(String args[]) throws Exception {
+
+        ThreadIOImpl threadio = new ThreadIOImpl();
+        threadio.start();
+
+        InputStream in = unwrap(System.in);
+        PrintStream out = wrap(unwrap(System.out));
+        PrintStream err = wrap(unwrap(System.err));
+        run(threadio, args, in, out, err);
+
+        // TODO: do we need to stop the threadio that was started?
+        // threadio.stop();
+    }
+
+    private void run(ThreadIO threadio, String[] args, InputStream in, PrintStream out, PrintStream err) throws Exception {
+        StringBuilder sb = new StringBuilder();
+        String classpath = null;
+        boolean batch = false;
+        String file = null;
+
+        for (int i = 0; i < args.length; i++) {
+            String arg = args[i];
+            if (arg.startsWith("--classpath=")) {
+                classpath = arg.substring("--classpath=".length());
+            } else if (arg.startsWith("-c=")) {
+                classpath = arg.substring("-c=".length());
+            } else if (arg.equals("--classpath") || arg.equals("-c")) {
+                classpath = args[++i];
+            } else if (arg.equals("-b") || arg.equals("--batch")) {
+                batch = true;
+            } else if (arg.startsWith("--file=")) {
+                file = arg.substring("--file=".length());
+            } else if (arg.startsWith("-f=")) {
+                file = arg.substring("-f=".length());
+            } else if (arg.equals("--file") || arg.equals("-f")) {
+                file = args[++i];
+            } else {
+                sb.append(arg);
+                sb.append(' ');
+            }
+        }
+
+        if (file != null) {
+            Reader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
+            try {
+                sb.setLength(0);
+                for (int c = reader.read(); c >= 0; c = reader.read()) {
+                    sb.append((char) c);
+                }
+            } finally {
+                reader.close();
+            }
+        } else if (batch) {
+            Reader reader = new BufferedReader(new InputStreamReader(System.in));
+            sb.setLength(0);
+            for (int c = reader.read(); c >= 0; reader.read()) {
+                sb.append((char) c);
+            }
+        }
+
+        ClassLoader cl = Main.class.getClassLoader();
+        if (classpath != null) {
+            List<URL> urls = getFiles(new File(classpath));
+            cl = new URLClassLoader(urls.toArray(new URL[urls.size()]), cl);
+        }
+
+        SessionFactory sessionFactory = createSessionFactory(threadio);
+
+        run(sessionFactory, sb.toString(), in, out, err, cl);
+    }
+
+    private void run(final SessionFactory sessionFactory, String command, final InputStream in, final PrintStream out, final PrintStream err, ClassLoader cl) throws Exception {
+
+        final TerminalFactory terminalFactory = new TerminalFactory();
+        try {
+            final Terminal terminal = new JLineTerminal(terminalFactory.getTerminal());
+            Session session = createSession(sessionFactory, command.length() > 0 ? null : in, out, err, terminal);
+            session.put("USER", user);
+            session.put("APPLICATION", application);
+
+            discoverCommands(session, cl, getDiscoveryResource());
+
+            if (command.length() > 0) {
+                // Shell is directly executing a sub/command, we don't setup a console
+                // in this case, this avoids us reading from stdin un-necessarily.
+                session.put(NameScoping.MULTI_SCOPE_MODE_KEY, Boolean.toString(isMultiScopeMode()));
+                session.put(Session.PRINT_STACK_TRACES, "execution");
+                try {
+                    session.execute(command);
+                } catch (Throwable t) {
+                    ShellUtil.logException(session, t);
+                }
+
+            } else {
+                // We are going into full blown interactive shell mode.
+                session.run();
+            }
+        } finally {
+            terminalFactory.destroy();
+        }
+    }
+
+    /**
+     * Allow sub classes of main to change the ConsoleImpl implementation used.
+     *
+     * @param sessionFactory
+     * @param in
+     * @param out
+     * @param err
+     * @param terminal
+     * @return
+     * @throws Exception
+     */
+    protected Session createSession(SessionFactory sessionFactory, InputStream in, PrintStream out, PrintStream err, Terminal terminal) throws Exception {
+        return sessionFactory.create(in, out, err, terminal, null, null);
+    }
+
+    protected SessionFactory createSessionFactory(ThreadIO threadio) {
+        SessionFactoryImpl sessionFactory = new SessionFactoryImpl(threadio);
+        sessionFactory.register(new ManagerImpl(sessionFactory, sessionFactory));
+        return sessionFactory;
+    }
+
+    /**
+     * Sub classes can override so that their registered commands do not conflict with the default shell
+     * implementation.
+     */
+    public String getDiscoveryResource() {
+        return "META-INF/services/org/apache/karaf/shell/commands";
+    }
+
+    protected void discoverCommands(Session session, ClassLoader cl, String resource) throws IOException, ClassNotFoundException {
+        Manager manager = new ManagerImpl(session.getRegistry(), session.getFactory().getRegistry(), true);
+        Enumeration<URL> urls = cl.getResources(resource);
+        while (urls.hasMoreElements()) {
+            URL url = urls.nextElement();
+            BufferedReader r = new BufferedReader(new InputStreamReader(url.openStream()));
+            String line = r.readLine();
+            while (line != null) {
+                line = line.trim();
+                if (line.length() > 0 && line.charAt(0) != '#') {
+                    final Class<?> actionClass = cl.loadClass(line);
+                    manager.register(actionClass);
+                }
+                line = r.readLine();
+            }
+            r.close();
+        }
+    }
+
+    public String getApplication() {
+        return application;
+    }
+
+    public void setApplication(String application) {
+        this.application = application;
+    }
+
+    public String getUser() {
+        return user;
+    }
+
+    public void setUser(String user) {
+        this.user = user;
+    }
+
+    /**
+     * Returns whether or not we are in multi-scope mode.
+     * <p/>
+     * The default mode is multi-scoped where we prefix commands by their scope. If we are in single
+     * scoped mode then we don't use scope prefixes when registering or tab completing commands.
+     */
+    public boolean isMultiScopeMode() {
+        return true;
+    }
+
+    private static PrintStream wrap(PrintStream stream) {
+        OutputStream o = AnsiConsole.wrapOutputStream(stream);
+        if (o instanceof PrintStream) {
+            return ((PrintStream) o);
+        } else {
+            return new PrintStream(o);
+        }
+    }
+
+    private static <T> T unwrap(T stream) {
+        try {
+            Method mth = stream.getClass().getMethod("getRoot");
+            return (T) mth.invoke(stream);
+        } catch (Throwable t) {
+            return stream;
+        }
+    }
+
+    private static List<URL> getFiles(File base) throws MalformedURLException {
+        List<URL> urls = new ArrayList<URL>();
+        getFiles(base, urls);
+        return urls;
+    }
+
+    private static void getFiles(File base, List<URL> urls) throws MalformedURLException {
+        for (File f : base.listFiles()) {
+            if (f.isDirectory()) {
+                getFiles(f, urls);
+            } else if (f.getName().endsWith(".jar")) {
+                urls.add(f.toURI().toURL());
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/support/CommandException.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/support/CommandException.java b/shell/core/src/main/java/org/apache/karaf/shell/support/CommandException.java
new file mode 100644
index 0000000..15fb4e7
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/support/CommandException.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.support;
+
+import org.apache.karaf.shell.support.ansi.SimpleAnsi;
+
+
+/**
+ * Base class for exceptions thrown when executing commands.
+ */
+@SuppressWarnings("serial")
+public class CommandException extends Exception {
+
+    private String help;
+
+    public CommandException() {
+    }
+
+    public CommandException(String message) {
+        super(message);
+    }
+
+    public CommandException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public CommandException(Throwable cause) {
+        super(cause);
+    }
+
+    public CommandException(String help, String message) {
+        super(message);
+        this.help = help;
+    }
+
+    public CommandException(String help, String message, Throwable cause) {
+        super(message, cause);
+        this.help = help;
+    }
+
+    public String getNiceHelp() {
+        return  help != null ? help
+                    : SimpleAnsi.COLOR_RED + "Error executing command: " 
+                    + (getMessage() != null ? getMessage() : getClass().getName())
+                    + SimpleAnsi.COLOR_DEFAULT;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/support/MultiException.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/support/MultiException.java b/shell/core/src/main/java/org/apache/karaf/shell/support/MultiException.java
new file mode 100644
index 0000000..c05f185
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/support/MultiException.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.shell.support;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+@SuppressWarnings("serial")
+public class MultiException extends Exception {
+
+    private List<Exception> exceptions = new ArrayList<Exception>();
+
+    public MultiException(String message) {
+        super(message);
+    }
+
+    public MultiException(String message, List<Exception> exceptions) {
+        super(message);
+        this.exceptions = exceptions;
+    }
+
+    public void addException(Exception e) {
+        exceptions.add(e);
+    }
+
+    public void throwIfExceptions() throws MultiException {
+        if (!exceptions.isEmpty()) {
+            throw this;
+        }
+    }
+    
+    public Throwable[] getCauses() {
+        return exceptions.toArray(new Throwable[exceptions.size()]);
+    }
+
+    @Override
+    public void printStackTrace()
+    {
+        super.printStackTrace();
+        for (Exception e : exceptions) {
+            e.printStackTrace();
+        }
+    }
+
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * @see Throwable#printStackTrace(java.io.PrintStream)
+     */
+    @Override
+    public void printStackTrace(PrintStream out)
+    {
+        super.printStackTrace(out);
+        for (Exception e : exceptions) {
+            e.printStackTrace(out);
+        }
+    }
+
+    @Override
+    public void printStackTrace(PrintWriter out)
+    {
+        super.printStackTrace(out);
+        for (Exception e : exceptions) {
+            e.printStackTrace(out);
+        }
+    }
+
+    public static void throwIf(String message, List<Exception> exceptions) throws MultiException {
+        if (exceptions != null && !exceptions.isEmpty()) {
+            StringBuilder sb = new StringBuilder(message);
+            sb.append(":");
+            for (Exception e : exceptions) {
+                sb.append("\n\t");
+                sb.append(e.getMessage());
+            }
+            throw new MultiException(sb.toString(), exceptions);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/support/NameScoping.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/support/NameScoping.java b/shell/core/src/main/java/org/apache/karaf/shell/support/NameScoping.java
new file mode 100644
index 0000000..fc3c5ed
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/support/NameScoping.java
@@ -0,0 +1,79 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.shell.support;
+
+
+import org.apache.karaf.shell.api.console.Session;
+
+/**
+ * A helper class for name scoping
+ */
+public class NameScoping {
+
+    public static final String MULTI_SCOPE_MODE_KEY = "MULTI_SCOPE_MODE";
+
+    /**
+     * Returns the name of the command which can omit the global scope prefix if the command starts with the
+     * same prefix as the current application
+     */
+    public static String getCommandNameWithoutGlobalPrefix(Session session, String key) {
+        if (!isMultiScopeMode(session)) {
+            String globalScope = (String) (session != null ? session.get("APPLICATION") : null);
+            if (globalScope != null) {
+                String prefix = globalScope + ":";
+                if (key.startsWith(prefix)) {
+                    // TODO we may only want to do this for single-scope mode when outside of OSGi?
+                    // so we may want to also check for a isMultiScope mode == false
+                    return key.substring(prefix.length());
+                }
+            }
+        }
+        return key;
+    }
+
+    /**
+     * Returns true if the given scope is the global scope so that it can be hidden from help messages
+     */
+    public static boolean isGlobalScope(Session session, String scope) {
+        if (session == null)
+            return false;
+
+        if (!isMultiScopeMode(session)) {
+            String globalScope = (String) session.get("APPLICATION");
+            if (globalScope != null) {
+                return scope.equals(globalScope);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if we are in multi-scope mode (the default) or if we are in single scope mode which means we
+     * avoid prefixing commands with their scope
+     */
+    public static boolean isMultiScopeMode(Session session) {
+        if (session == null)
+            return false;
+
+        Object value = session.get(MULTI_SCOPE_MODE_KEY);
+        if (value != null && value.equals("false")) {
+            return false;
+        }
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/support/ShellUtil.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/support/ShellUtil.java b/shell/core/src/main/java/org/apache/karaf/shell/support/ShellUtil.java
new file mode 100644
index 0000000..3887565
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/support/ShellUtil.java
@@ -0,0 +1,214 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.support;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+
+import javax.security.auth.Subject;
+
+import org.apache.karaf.jaas.boot.principal.UserPrincipal;
+import org.apache.karaf.shell.api.console.Session;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.startlevel.BundleStartLevel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.karaf.shell.support.ansi.SimpleAnsi.COLOR_DEFAULT;
+import static org.apache.karaf.shell.support.ansi.SimpleAnsi.COLOR_RED;
+import static org.apache.karaf.shell.support.ansi.SimpleAnsi.INTENSITY_BOLD;
+import static org.apache.karaf.shell.support.ansi.SimpleAnsi.INTENSITY_NORMAL;
+
+public class ShellUtil {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(ShellUtil.class);
+
+    public static String getBundleName(Bundle bundle) {
+        if (bundle != null) {
+            String name = (String) bundle.getHeaders().get(Constants.BUNDLE_NAME);
+            return (name == null)
+                    ? "Bundle " + Long.toString(bundle.getBundleId())
+                    : name + " (" + Long.toString(bundle.getBundleId()) + ")";
+        }
+        return "[STALE BUNDLE]";
+    }
+
+    public static String getUnderlineString(String s) {
+        StringBuilder sb = new StringBuilder(s.length());
+        for (int i = 0; i < s.length(); i++) {
+            sb.append('-');
+        }
+        return sb.toString();
+    }
+
+    public static String getValueString(Object obj) {
+        if (obj == null) {
+            return "null";
+        } else if (obj.getClass().isArray()) {
+            Object[] array = (Object[]) obj;
+            StringBuilder sb = new StringBuilder();
+            sb.append("[");
+            for (int i = 0; i < array.length; i++) {
+                if (i != 0) {
+                    sb.append(", ");
+                }
+                sb.append(getValueString(array[i]));
+            }
+            sb.append("]");
+            return sb.toString();
+        } else if (obj instanceof String) {
+            return (String) obj;
+        } else if (obj instanceof Boolean) {
+            return ((Boolean) obj).toString();
+        } else if (obj instanceof Long) {
+            return ((Long) obj).toString();
+        } else if (obj instanceof Integer) {
+            return ((Integer) obj).toString();
+        } else if (obj instanceof Short) {
+            return ((Short) obj).toString();
+        } else if (obj instanceof Double) {
+            return ((Double) obj).toString();
+        } else if (obj instanceof Float) {
+            return ((Float) obj).toString();
+        } else if (obj instanceof URL) {
+            return ((URL) obj).toExternalForm();
+        } else if (obj instanceof URI) {
+            try {
+                return ((URI) obj).toURL().toExternalForm();
+            } catch (MalformedURLException e) {
+                LOGGER.error("URI could not be transformed to URL", e);
+                return obj.toString();
+            }
+        } else {
+            return obj.toString();
+        }
+    }
+
+    /**
+     * Check if a bundle is a system bundle (start level < 50)
+     *
+     * @param bundleContext
+     * @param bundle
+     * @return true if the bundle has start level minor than 50
+     */
+    public static boolean isASystemBundle(BundleContext bundleContext, Bundle bundle) {
+        int level = bundle.adapt(BundleStartLevel.class).getStartLevel();
+        int sbsl = 49;
+        final String sbslProp = bundleContext.getProperty("karaf.systemBundlesStartLevel");
+        if (sbslProp != null) {
+            try {
+                sbsl = Integer.valueOf(sbslProp);
+            } catch (Exception ignore) {
+                // ignore
+            }
+        }
+        return level <= sbsl;
+    }
+
+    public static boolean getBoolean(Session session, String name) {
+        Object s = session.get(name);
+        if (s == null) {
+            s = System.getProperty(name);
+        }
+        if (s == null) {
+            return false;
+        }
+        if (s instanceof Boolean) {
+            return (Boolean) s;
+        }
+        return Boolean.parseBoolean(s.toString());
+    }
+
+    public static void logException(Session session, Throwable t) {
+        try {
+            // Store last exception in session
+            session.put(Session.LAST_EXCEPTION, t);
+            // Log exception
+            String name = t.getClass().getSimpleName();
+            if ("CommandNotFoundException".equals(name)) {
+                LOGGER.debug("Unknown command entered", t);
+            } else if ("CommandException".equals(name)) {
+                LOGGER.debug("Command exception (Undefined option, ...)", t);
+            } else {
+                LOGGER.error("Exception caught while executing command", t);
+            }
+            // Display exception
+            String pst = getPrintStackTraces(session);
+            if ("always".equals(pst)) {
+                session.getConsole().print(COLOR_RED);
+                t.printStackTrace(session.getConsole());
+                session.getConsole().print(COLOR_DEFAULT);
+            } else if ("CommandNotFoundException".equals(name)) {
+                String str = COLOR_RED + "Command not found: "
+                        + INTENSITY_BOLD + t.getClass().getMethod("getCommand").invoke(t) + INTENSITY_NORMAL
+                        + COLOR_DEFAULT;
+                session.getConsole().println(str);
+            } else if ("CommandException".equals(name)) {
+                String str;
+                try {
+                    str = (String) t.getClass().getMethod("getNiceHelp").invoke(t);
+                } catch (Throwable ignore) {
+                    str = COLOR_RED + t.getMessage() + COLOR_DEFAULT;
+                }
+                session.getConsole().println(str);
+            } else  if ("execution".equals(pst)) {
+                session.getConsole().print(COLOR_RED);
+                t.printStackTrace(session.getConsole());
+                session.getConsole().print(COLOR_DEFAULT);
+            } else {
+                String str = COLOR_RED + "Error executing command: "
+                        + (t.getMessage() != null ? t.getMessage() : t.getClass().getName())
+                        + COLOR_DEFAULT;
+                session.getConsole().println(str);
+            }
+        } catch (Exception ignore) {
+            // ignore
+        }
+    }
+
+    private static String getPrintStackTraces(Session session) {
+        Object pst = session.get(Session.PRINT_STACK_TRACES);
+        if (pst == null) {
+            pst = System.getProperty(Session.PRINT_STACK_TRACES);
+        }
+        if (pst == null) {
+            return "never";
+        } else if (pst instanceof Boolean) {
+            return ((Boolean) pst) ? "always" : "never";
+        } else {
+            return pst.toString().toLowerCase();
+        }
+    }
+
+    public static String getCurrentUserName() {
+        AccessControlContext acc = AccessController.getContext();
+        final Subject subject = Subject.getSubject(acc);
+        if (subject != null && subject.getPrincipals(UserPrincipal.class).iterator().hasNext()) {
+            return subject.getPrincipals(UserPrincipal.class).iterator().next().getName();
+        } else {
+            return null;
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/e7d23bef/shell/core/src/main/java/org/apache/karaf/shell/support/ansi/SimpleAnsi.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/support/ansi/SimpleAnsi.java b/shell/core/src/main/java/org/apache/karaf/shell/support/ansi/SimpleAnsi.java
new file mode 100644
index 0000000..d7d75d7
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/support/ansi/SimpleAnsi.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.support.ansi;
+
+import org.fusesource.jansi.Ansi;
+import org.fusesource.jansi.Ansi.Color;
+
+public class SimpleAnsi {
+    public static String COLOR_RED = Ansi.ansi().fg(Color.RED).toString();
+    public static String COLOR_DEFAULT = Ansi.ansi().fg(Color.DEFAULT).toString();
+    
+    public static String INTENSITY_BOLD = Ansi.ansi().bold().toString();
+    public static String INTENSITY_NORMAL = Ansi.ansi().boldOff().toString();
+}


[06/10] git commit: [KARAF-2805] Add a compatibility layer and switch to the new console

Posted by gn...@apache.org.
[KARAF-2805] Add a compatibility layer and switch to the new console

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

Branch: refs/heads/master
Commit: 2e3c3ef97965351dcd9e6f7e562d434793f82681
Parents: e7d23be
Author: Guillaume Nodet <gn...@gmail.com>
Authored: Wed Mar 5 15:16:38 2014 +0100
Committer: Guillaume Nodet <gn...@gmail.com>
Committed: Wed Mar 5 15:37:33 2014 +0100

----------------------------------------------------------------------
 assemblies/features/framework/pom.xml           |   4 +-
 .../apache/karaf/itests/KarafTestSupport.java   |  10 +-
 .../apache/karaf/itests/SshCommandTestBase.java |   2 +-
 shell/console/pom.xml                           |  15 +-
 .../karaf/shell/compat/ArgumentCompleter.java   | 479 +++++++++++++++++++
 .../karaf/shell/compat/CommandTracker.java      | 166 +++++++
 .../shell/compat/OldArgumentCompleter.java      | 450 +++++++++++++++++
 .../OSGI-INF/blueprint/karaf-console.xml        |  71 +--
 .../src/main/resources/OSGI-INF/bundle.info     |   4 +-
 9 files changed, 1121 insertions(+), 80 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/2e3c3ef9/assemblies/features/framework/pom.xml
----------------------------------------------------------------------
diff --git a/assemblies/features/framework/pom.xml b/assemblies/features/framework/pom.xml
index 6a26aae..ed48318 100644
--- a/assemblies/features/framework/pom.xml
+++ b/assemblies/features/framework/pom.xml
@@ -159,11 +159,11 @@
 
         <dependency>
             <groupId>org.apache.karaf.shell</groupId>
-            <artifactId>org.apache.karaf.shell.console</artifactId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
         </dependency>
         <dependency>
             <groupId>org.apache.karaf.shell</groupId>
-            <artifactId>org.apache.karaf.shell.help</artifactId>
+            <artifactId>org.apache.karaf.shell.console</artifactId>
         </dependency>
         <dependency>
             <groupId>org.apache.karaf.shell</groupId>

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e3c3ef9/itests/src/test/java/org/apache/karaf/itests/KarafTestSupport.java
----------------------------------------------------------------------
diff --git a/itests/src/test/java/org/apache/karaf/itests/KarafTestSupport.java b/itests/src/test/java/org/apache/karaf/itests/KarafTestSupport.java
index ccde975..4507d5f 100644
--- a/itests/src/test/java/org/apache/karaf/itests/KarafTestSupport.java
+++ b/itests/src/test/java/org/apache/karaf/itests/KarafTestSupport.java
@@ -51,11 +51,11 @@ import javax.management.remote.JMXConnectorFactory;
 import javax.management.remote.JMXServiceURL;
 import javax.security.auth.Subject;
 
-import org.apache.felix.service.command.CommandProcessor;
-import org.apache.felix.service.command.CommandSession;
 import org.apache.karaf.features.BootFinished;
 import org.apache.karaf.features.Feature;
 import org.apache.karaf.features.FeaturesService;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.console.SessionFactory;
 import org.junit.Assert;
 import org.junit.Rule;
 import org.ops4j.pax.exam.Configuration;
@@ -161,8 +161,8 @@ public class KarafTestSupport {
         String response;
         final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
         final PrintStream printStream = new PrintStream(byteArrayOutputStream);
-        final CommandProcessor commandProcessor = getOsgiService(CommandProcessor.class);
-        final CommandSession commandSession = commandProcessor.createSession(System.in, printStream, System.err);
+        final SessionFactory sessionFactory = getOsgiService(SessionFactory.class);
+        final Session session = sessionFactory.create(System.in, printStream, System.err);
 
         final Callable<String> commandCallable = new Callable<String>() {
             @Override
@@ -171,7 +171,7 @@ public class KarafTestSupport {
                     if (!silent) {
                         System.err.println(command);
                     }
-                    commandSession.execute(command);
+                    session.execute(command);
                 } catch (Exception e) {
                     throw new RuntimeException(e.getMessage(), e);
                 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e3c3ef9/itests/src/test/java/org/apache/karaf/itests/SshCommandTestBase.java
----------------------------------------------------------------------
diff --git a/itests/src/test/java/org/apache/karaf/itests/SshCommandTestBase.java b/itests/src/test/java/org/apache/karaf/itests/SshCommandTestBase.java
index adc1cc0..e687f4c 100644
--- a/itests/src/test/java/org/apache/karaf/itests/SshCommandTestBase.java
+++ b/itests/src/test/java/org/apache/karaf/itests/SshCommandTestBase.java
@@ -21,7 +21,7 @@ import java.io.PipedOutputStream;
 import java.util.Arrays;
 import java.util.HashSet;
 
-import junit.framework.Assert;
+import org.junit.Assert;
 
 import org.apache.karaf.features.Feature;
 import org.apache.sshd.ClientChannel;

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e3c3ef9/shell/console/pom.xml
----------------------------------------------------------------------
diff --git a/shell/console/pom.xml b/shell/console/pom.xml
index df03dd9..e44682c 100644
--- a/shell/console/pom.xml
+++ b/shell/console/pom.xml
@@ -31,7 +31,7 @@
     <artifactId>org.apache.karaf.shell.console</artifactId>
     <packaging>bundle</packaging>
     <name>Apache Karaf :: Shell :: Console</name>
-    <description>This bundle provides OSGi shell integration and console support.</description>
+    <description>This bundle provides compatibility with the previous console.</description>
 
     <properties>
         <appendedResourcesDirectory>${basedir}/../../etc/appended-resources</appendedResourcesDirectory>
@@ -65,6 +65,10 @@
             <groupId>org.apache.karaf.jaas</groupId>
             <artifactId>org.apache.karaf.jaas.boot</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
+        </dependency>
 
         <dependency>
             <groupId>org.apache.aries.blueprint</groupId>
@@ -132,6 +136,9 @@
                 <artifactId>maven-bundle-plugin</artifactId>
                 <configuration>
                     <instructions>
+                        <Fragment-Host>
+                            org.apache.karaf.shell.core
+                        </Fragment-Host>
                         <Import-Package>
                             !org.apache.felix.gogo.runtime.*,
                             !org.apache.karaf.shell.inject.impl,
@@ -165,15 +172,11 @@
                         </Export-Package>
                         <Private-Package>
                         	org.apache.karaf.shell.commands.ansi,
+                            org.apache.karaf.shell.compat,
                         	org.apache.karaf.shell.console.impl*,
                             org.apache.karaf.shell.security.impl*,
                             org.apache.karaf.shell.inject.impl*,
-                            org.apache.felix.utils.extender,
-                            org.apache.felix.utils.manifest
                         </Private-Package>
-                        <Bundle-Activator>
-                            org.apache.felix.gogo.runtime.activator.Activator
-                        </Bundle-Activator>
                         <Main-Class>
                             org.apache.karaf.shell.console.impl.Main
                         </Main-Class>

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e3c3ef9/shell/console/src/main/java/org/apache/karaf/shell/compat/ArgumentCompleter.java
----------------------------------------------------------------------
diff --git a/shell/console/src/main/java/org/apache/karaf/shell/compat/ArgumentCompleter.java b/shell/console/src/main/java/org/apache/karaf/shell/compat/ArgumentCompleter.java
new file mode 100644
index 0000000..8f52503
--- /dev/null
+++ b/shell/console/src/main/java/org/apache/karaf/shell/compat/ArgumentCompleter.java
@@ -0,0 +1,479 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.compat;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.felix.gogo.commands.Action;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.CommandWithAction;
+import org.apache.karaf.shell.commands.CompleterValues;
+import org.apache.karaf.shell.commands.HelpOption;
+import org.apache.karaf.shell.commands.Option;
+import org.apache.karaf.shell.console.CommandSessionHolder;
+import org.apache.karaf.shell.console.CompletableFunction;
+import org.apache.karaf.shell.console.Completer;
+import org.apache.karaf.shell.console.completer.FileCompleter;
+import org.apache.karaf.shell.console.completer.NullCompleter;
+import org.apache.karaf.shell.console.completer.StringsCompleter;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Session;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ArgumentCompleter {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(ArgumentCompleter.class);
+
+    public static final String ARGUMENTS_LIST = "ARGUMENTS_LIST";
+
+    public static final String COMMANDS = ".commands";
+
+    final String scope;
+    final String name;
+    final Completer commandCompleter;
+    final Completer optionsCompleter;
+    final List<Completer> argsCompleters;
+    final Map<String, Completer> optionalCompleters;
+    final CommandWithAction function;
+    final Map<Option, Field> fields = new HashMap<Option, Field>();
+    final Map<String, Option> options = new HashMap<String, Option>();
+    final Map<Integer, Field> arguments = new HashMap<Integer, Field>();
+    boolean strict = true;
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public ArgumentCompleter(CommandWithAction function, String scope, String name, boolean scoped) {
+        this.function = function;
+        this.scope = scope;
+        this.name = name;
+        // Command name completer
+        String[] names = scoped ? new String[] { name } : new String[] { name, scope + ":" + name };
+        commandCompleter = new StringsCompleter(names);
+        // Build options completer
+        for (Class<?> type = function.getActionClass(); type != null; type = type.getSuperclass()) {
+            for (Field field : type.getDeclaredFields()) {
+                Option option = field.getAnnotation(Option.class);
+                if (option != null) {
+                    fields.put(option, field);
+                    options.put(option.name(), option);
+                    String[] aliases = option.aliases();
+                    if (aliases != null) {
+                        for (String alias : aliases) {
+                            options.put(alias, option);
+                        }
+                    }
+                }
+                Argument argument = field.getAnnotation(Argument.class);
+                if (argument != null) {
+                    Integer key = argument.index();
+                    if (arguments.containsKey(key)) {
+                        LOGGER.warn("Duplicate @Argument annotations on class " + type.getName() + " for index: " + key + " see: " + field);
+                    } else {
+                        arguments.put(key, field);
+                    }
+                }
+            }
+        }
+        options.put(HelpOption.HELP.name(), HelpOption.HELP);
+        optionsCompleter = new StringsCompleter(options.keySet());
+
+        // Build arguments completers
+        List<Completer> argsCompleters = null;
+        Map<String, Completer> optionalCompleters = null;
+
+        if (function instanceof CompletableFunction) {
+            Map<String, Completer> focl = ((CompletableFunction) function).getOptionalCompleters();
+            List<Completer> fcl = ((CompletableFunction) function).getCompleters();
+            if (focl != null || fcl != null) {
+                argsCompleters = new ArrayList<Completer>();
+                if (fcl != null) {
+                    for (Completer c : fcl) {
+                        argsCompleters.add(c == null ? NullCompleter.INSTANCE : c);
+                    }
+                }
+                optionalCompleters = focl;
+            }
+        }
+        if (argsCompleters == null) {
+            final Map<Integer, Object> values = getCompleterValues(function);
+            argsCompleters = new ArrayList<Completer>();
+            boolean multi = false;
+            for (int key = 0; key < arguments.size(); key++) {
+                Completer completer = null;
+                Field field = arguments.get(key);
+                if (field != null) {
+                    Argument argument = field.getAnnotation(Argument.class);
+                    multi = (argument != null && argument.multiValued());
+                    org.apache.karaf.shell.commands.Completer ann = field.getAnnotation(org.apache.karaf.shell.commands.Completer.class);
+                    if (ann != null) {
+                        Class clazz = ann.value();
+                        String[] value = ann.values();
+                        if (clazz != null) {
+                            if (value.length > 0 && clazz == StringsCompleter.class) {
+                                completer = new StringsCompleter(value, ann.caseSensitive());
+                            } else {
+                                BundleContext context = FrameworkUtil.getBundle(function.getClass()).getBundleContext();
+                                completer = new ProxyServiceCompleter(context, clazz);
+                            }
+                        }
+                    } else if (values.containsKey(key)) {
+                        Object value = values.get(key);
+                        if (value instanceof String[]) {
+                            completer = new StringsCompleter((String[]) value);
+                        } else if (value instanceof Collection) {
+                            completer = new StringsCompleter((Collection<String>) value);
+                        } else {
+                            LOGGER.warn("Could not use value " + value + " as set of completions!");
+                        }
+                    } else {
+                        completer = getDefaultCompleter(field);
+                    }
+                }
+                if (completer == null) {
+                    completer = NullCompleter.INSTANCE;
+                }
+                argsCompleters.add(completer);
+            }
+            if (argsCompleters.isEmpty() || !multi) {
+                argsCompleters.add(NullCompleter.INSTANCE);
+            }
+            optionalCompleters = new HashMap<String, Completer>();
+            for (Option option : fields.keySet()) {
+                Completer completer = null;
+                Field field = fields.get(option);
+                if (field != null) {
+                    org.apache.karaf.shell.commands.Completer ann = field.getAnnotation(org.apache.karaf.shell.commands.Completer.class);
+                    if (ann != null) {
+                        Class clazz = ann.value();
+                        String[] value = ann.values();
+                        if (clazz != null) {
+                            if (value.length > 0 && clazz == StringsCompleter.class) {
+                                completer = new StringsCompleter(value, ann.caseSensitive());
+                            } else {
+                                BundleContext context = FrameworkUtil.getBundle(function.getClass()).getBundleContext();
+                                completer = new ProxyServiceCompleter(context, clazz);
+                            }
+                        }
+                    }
+                }
+                if (completer == null) {
+                    completer = NullCompleter.INSTANCE;
+                }
+                optionalCompleters.put(option.name(), completer);
+                if (option.aliases() != null) {
+                    for (String alias : option.aliases()) {
+                        optionalCompleters.put(alias, completer);
+                    }
+                }
+            }
+        }
+        this.argsCompleters = argsCompleters;
+        this.optionalCompleters = optionalCompleters;
+    }
+
+    private Map<Integer, Object> getCompleterValues(CommandWithAction function) {
+        final Map<Integer, Object> values = new HashMap<Integer, Object>();
+        Action action = null;
+        try {
+            for (Class<?> type = function.getActionClass(); type != null; type = type.getSuperclass()) {
+                for (Method method : type.getDeclaredMethods()) {
+                    CompleterValues completerMethod = method.getAnnotation(CompleterValues.class);
+                    if (completerMethod != null) {
+                        int index = completerMethod.index();
+                        Integer key = index;
+                        if (index >= arguments.size() || index < 0) {
+                            LOGGER.warn("Index out of range on @CompleterValues on class " + type.getName() + " for index: " + key + " see: " + method);
+                        } else if (values.containsKey(key)) {
+                            LOGGER.warn("Duplicate @CompleterMethod annotations on class " + type.getName() + " for index: " + key + " see: " + method);
+                        } else {
+                            try {
+                                Object value;
+                                if (Modifier.isStatic(method.getModifiers())) {
+                                    value = method.invoke(null);
+                                } else {
+                                    if (action == null) {
+                                        action = function.createNewAction();
+                                    }
+                                    value = method.invoke(action);
+                                }
+                                values.put(key, value);
+                            } catch (IllegalAccessException e) {
+                                LOGGER.warn("Could not invoke @CompleterMethod on " + function + ". " + e, e);
+                            } catch (InvocationTargetException e) {
+                                Throwable target = e.getTargetException();
+                                if (target == null) {
+                                    target = e;
+                                }
+                                LOGGER.warn("Could not invoke @CompleterMethod on " + function + ". " + target, target);
+                            }
+                        }
+                    }
+                }
+            }
+        } finally {
+            if (action != null) {
+                try {
+                    function.releaseAction(action);
+                } catch (Exception e) {
+                    LOGGER.warn("Failed to release action: " + action + ". " + e, e);
+                }
+            }
+        }
+        return values;
+    }
+
+    private Completer getDefaultCompleter(Field field) {
+        Completer completer = null;
+        Class<?> type = field.getType();
+        if (type.isAssignableFrom(File.class)) {
+            completer = new FileCompleter(null);
+        } else if (type.isAssignableFrom(Boolean.class) || type.isAssignableFrom(boolean.class)) {
+            completer = new StringsCompleter(new String[] {"false", "true"}, false);
+        } else if (type.isAssignableFrom(Enum.class)) {
+            Set<String> values = new HashSet<String>();
+            for (Object o : EnumSet.allOf((Class<Enum>) type)) {
+                values.add(o.toString());
+            }
+            completer = new StringsCompleter(values, false);
+        } else {
+            // TODO any other completers we can add?
+        }
+        return completer;
+    }
+
+    /**
+     *  If true, a completion at argument index N will only succeed
+     *  if all the completions from 0-(N-1) also succeed.
+     */
+    public void setStrict(final boolean strict) {
+        this.strict = strict;
+    }
+
+    /**
+     *  Returns whether a completion at argument index N will succees
+     *  if all the completions from arguments 0-(N-1) also succeed.
+     */
+    public boolean getStrict() {
+        return this.strict;
+    }
+
+    public int complete(final Session session, final CommandLine list, final List<String> candidates) {
+        int argpos = list.getArgumentPosition();
+        int argIndex = list.getCursorArgumentIndex();
+
+        //Store the argument list so that it can be used by completers.
+        CommandSession commandSession = CommandSessionHolder.getSession();
+        if(commandSession != null) {
+            commandSession.put(ARGUMENTS_LIST,list);
+        }
+
+        Completer comp = null;
+        String[] args = list.getArguments();
+        int index = 0;
+        // First argument is command name
+        if (index < argIndex) {
+            // Verify scope
+            if (!Session.SCOPE_GLOBAL.equals(scope) && !session.resolveCommand(args[index]).equals(scope + ":" + name)) {
+                return -1;
+            }
+            // Verify command name
+            if (!verifyCompleter(commandCompleter, args[index])) {
+                return -1;
+            }
+            index++;
+        } else {
+            comp = commandCompleter;
+        }
+        // Now, check options
+        if (comp == null) {
+            while (index < argIndex && args[index].startsWith("-")) {
+                if (!verifyCompleter(optionsCompleter, args[index])) {
+                    return -1;
+                }
+                Option option = options.get(args[index]);
+                if (option == null) {
+                    return -1;
+                }
+                Field field = fields.get(option);
+                if (field != null && field.getType() != boolean.class && field.getType() != Boolean.class) {
+                    if (++index == argIndex) {
+                        comp = NullCompleter.INSTANCE;
+                    }
+                }
+                index++;
+            }
+            if (comp == null && index >= argIndex && index < args.length && args[index].startsWith("-")) {
+                comp = optionsCompleter;
+            }
+        }
+        //Now check for if last Option has a completer
+        int lastAgurmentIndex = argIndex - 1;
+        if (lastAgurmentIndex >= 1) {
+            Option lastOption = options.get(args[lastAgurmentIndex]);
+            if (lastOption != null) {
+
+                Field lastField = fields.get(lastOption);
+                if (lastField != null && lastField.getType() != boolean.class && lastField.getType() != Boolean.class) {
+                    Option option = lastField.getAnnotation(Option.class);
+                    if (option != null) {
+                        Completer optionValueCompleter = null;
+                        String name = option.name();
+                        if (optionalCompleters != null && name != null) {
+                            optionValueCompleter = optionalCompleters.get(name);
+                            if (optionValueCompleter == null) {
+                                String[] aliases = option.aliases();
+                                if (aliases.length > 0) {
+                                    for (int i = 0; i < aliases.length && optionValueCompleter == null; i++) {
+                                        optionValueCompleter = optionalCompleters.get(option.aliases()[i]);
+                                    }
+                                }
+                            }
+                        }
+                        if(optionValueCompleter != null) {
+                            comp = optionValueCompleter;
+                        }
+                    }
+                }
+            }
+        }
+
+        // Check arguments
+        if (comp == null) {
+            int indexArg = 0;
+            while (index < argIndex) {
+                Completer sub = argsCompleters.get(indexArg >= argsCompleters.size() ? argsCompleters.size() - 1 : indexArg);
+                if (!verifyCompleter(sub, args[index])) {
+                    return -1;
+                }
+                index++;
+                indexArg++;
+            }
+            comp = argsCompleters.get(indexArg >= argsCompleters.size() ? argsCompleters.size() - 1 : indexArg);
+        }
+
+        int ret = comp.complete(list.getCursorArgument(), argpos, candidates);
+
+        if (ret == -1) {
+            return -1;
+        }
+
+        int pos = ret + (list.getBufferPosition() - argpos);
+
+        /**
+         *  Special case: when completing in the middle of a line, and the
+         *  area under the cursor is a delimiter, then trim any delimiters
+         *  from the candidates, since we do not need to have an extra
+         *  delimiter.
+         *
+         *  E.g., if we have a completion for "foo", and we
+         *  enter "f bar" into the buffer, and move to after the "f"
+         *  and hit TAB, we want "foo bar" instead of "foo  bar".
+         */
+        String buffer = list.getBuffer();
+        int cursor = list.getBufferPosition();
+        if ((buffer != null) && (cursor != buffer.length()) && isDelimiter(buffer, cursor)) {
+            for (int i = 0; i < candidates.size(); i++) {
+                String val = candidates.get(i);
+
+                while ((val.length() > 0)
+                        && isDelimiter(val, val.length() - 1)) {
+                    val = val.substring(0, val.length() - 1);
+                }
+
+                candidates.set(i, val);
+            }
+        }
+
+        return pos;
+    }
+
+    protected boolean verifyCompleter(Completer completer, String argument) {
+        List<String> candidates = new ArrayList<String>();
+        return completer.complete(argument, argument.length(), candidates) != -1 && !candidates.isEmpty();
+    }
+
+    /**
+     *  Returns true if the specified character is a whitespace
+     *  parameter. Check to ensure that the character is not
+     *  escaped and returns true from
+     *  {@link #isDelimiterChar}.
+     *
+     *  @param  buffer the complete command buffer
+     *  @param  pos    the index of the character in the buffer
+     *  @return        true if the character should be a delimiter
+     */
+    public boolean isDelimiter(final String buffer, final int pos) {
+        return !isEscaped(buffer, pos) && isDelimiterChar(buffer, pos);
+    }
+
+    public boolean isEscaped(final String buffer, final int pos) {
+        return pos > 0 && buffer.charAt(pos) == '\\' && !isEscaped(buffer, pos - 1);
+    }
+
+    /**
+     *  The character is a delimiter if it is whitespace, and the
+     *  preceeding character is not an escape character.
+     */
+    public boolean isDelimiterChar(String buffer, int pos) {
+        return Character.isWhitespace(buffer.charAt(pos));
+    }
+
+    public static class ProxyServiceCompleter implements Completer {
+        private final BundleContext context;
+        private final Class<? extends Completer> clazz;
+
+        public ProxyServiceCompleter(BundleContext context, Class<? extends Completer> clazz) {
+            this.context = context;
+            this.clazz = clazz;
+        }
+
+        @Override
+        public int complete(String buffer, int cursor, List<String> candidates) {
+            ServiceReference<? extends Completer> ref = context.getServiceReference(clazz);
+            if (ref != null) {
+                Completer completer = context.getService(ref);
+                if (completer != null) {
+                    try {
+                        return completer.complete(buffer, cursor, candidates);
+                    } finally {
+                        context.ungetService(ref);
+                    }
+                }
+            }
+            return -1;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e3c3ef9/shell/console/src/main/java/org/apache/karaf/shell/compat/CommandTracker.java
----------------------------------------------------------------------
diff --git a/shell/console/src/main/java/org/apache/karaf/shell/compat/CommandTracker.java b/shell/console/src/main/java/org/apache/karaf/shell/compat/CommandTracker.java
new file mode 100644
index 0000000..191735c
--- /dev/null
+++ b/shell/console/src/main/java/org/apache/karaf/shell/compat/CommandTracker.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.compat;
+
+import java.util.List;
+
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.CommandWithAction;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.console.SessionFactory;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Filter;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+
+public class CommandTracker implements ServiceTrackerCustomizer<Object, Object> {
+
+    SessionFactory sessionFactory;
+    BundleContext context;
+    ServiceTracker<?, ?> tracker;
+
+    public void setSessionFactory(SessionFactory sessionFactory) {
+        this.sessionFactory = sessionFactory;
+    }
+
+    public void setContext(BundleContext context) {
+        this.context = context;
+    }
+
+    public void init() throws Exception {
+        Filter filter = context.createFilter(String.format("(&(%s=*)(%s=*))",
+                CommandProcessor.COMMAND_SCOPE, CommandProcessor.COMMAND_FUNCTION));
+        this.tracker = new ServiceTracker<Object, Object>(context, filter, this);
+        this.tracker.open();
+    }
+
+    public void destroy() {
+        tracker.close();
+    }
+
+    @Override
+    public Object addingService(final ServiceReference reference) {
+        Object service = context.getService(reference);
+        if (service instanceof CommandWithAction) {
+            final CommandWithAction oldCommand = (CommandWithAction) service;
+            final org.apache.karaf.shell.api.console.Command command = new org.apache.karaf.shell.api.console.Command() {
+                @Override
+                public String getScope() {
+                    return reference.getProperty(CommandProcessor.COMMAND_SCOPE).toString();
+                }
+
+                @Override
+                public String getName() {
+                    return reference.getProperty(CommandProcessor.COMMAND_FUNCTION).toString();
+                }
+
+                @Override
+                public String getDescription() {
+                    final Command cmd = oldCommand.getActionClass().getAnnotation(Command.class);
+                    if (cmd != null) {
+                        return cmd.description();
+                    } else {
+                        return getName();
+                    }
+                }
+
+                @Override
+                public Completer getCompleter(final boolean scoped) {
+                    final ArgumentCompleter completer = new ArgumentCompleter(oldCommand, getScope(), getName(), scoped);
+                    return new Completer() {
+                        @Override
+                        public int complete(Session session, CommandLine commandLine, List<String> candidates) {
+                            return completer.complete(session, commandLine, candidates);
+                        }
+                    };
+                }
+
+                @Override
+                public Object execute(Session session, List<Object> arguments) throws Exception {
+                    // TODO: remove not really nice cast
+                    CommandSession commandSession = (CommandSession) session.get(".commandSession");
+                    return oldCommand.execute(commandSession, arguments);
+                }
+            };
+            sessionFactory.getRegistry().register(command);
+            return command;
+        } else if (service instanceof org.apache.felix.gogo.commands.CommandWithAction) {
+            final org.apache.felix.gogo.commands.CommandWithAction oldCommand = (org.apache.felix.gogo.commands.CommandWithAction) service;
+            final org.apache.karaf.shell.api.console.Command command = new org.apache.karaf.shell.api.console.Command() {
+                @Override
+                public String getScope() {
+                    return reference.getProperty(CommandProcessor.COMMAND_SCOPE).toString();
+                }
+
+                @Override
+                public String getName() {
+                    return reference.getProperty(CommandProcessor.COMMAND_FUNCTION).toString();
+                }
+
+                @Override
+                public String getDescription() {
+                    final org.apache.felix.gogo.commands.Command cmd = oldCommand.getActionClass().getAnnotation(org.apache.felix.gogo.commands.Command.class);
+                    if (cmd != null) {
+                        return cmd.description();
+                    } else {
+                        return getName();
+                    }
+                }
+
+                @Override
+                public Completer getCompleter(final boolean scoped) {
+                    final OldArgumentCompleter completer = new OldArgumentCompleter(oldCommand, getScope(), getName(), scoped);
+                    return new Completer() {
+                        @Override
+                        public int complete(Session session, CommandLine commandLine, List<String> candidates) {
+                            return completer.complete(session, commandLine, candidates);
+                        }
+                    };
+                }
+
+                @Override
+                public Object execute(Session session, List<Object> arguments) throws Exception {
+                    // TODO: remove not really nice cast
+                    CommandSession commandSession = (CommandSession) session.get(".commandSession");
+                    return oldCommand.execute(commandSession, arguments);
+                }
+            };
+            sessionFactory.getRegistry().register(command);
+            return command;
+        }
+        return service;
+    }
+
+    @Override
+    public void modifiedService(ServiceReference reference, Object service) {
+    }
+
+    @Override
+    public void removedService(ServiceReference reference, Object service) {
+        if (service instanceof org.apache.karaf.shell.api.console.Command) {
+            sessionFactory.getRegistry().unregister(service);
+        }
+        context.ungetService(reference);
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e3c3ef9/shell/console/src/main/java/org/apache/karaf/shell/compat/OldArgumentCompleter.java
----------------------------------------------------------------------
diff --git a/shell/console/src/main/java/org/apache/karaf/shell/compat/OldArgumentCompleter.java b/shell/console/src/main/java/org/apache/karaf/shell/compat/OldArgumentCompleter.java
new file mode 100644
index 0000000..407244c
--- /dev/null
+++ b/shell/console/src/main/java/org/apache/karaf/shell/compat/OldArgumentCompleter.java
@@ -0,0 +1,450 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.shell.compat;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.felix.gogo.commands.Action;
+import org.apache.felix.gogo.commands.Argument;
+import org.apache.felix.gogo.commands.CommandWithAction;
+import org.apache.felix.gogo.commands.CompleterValues;
+import org.apache.felix.gogo.commands.Option;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.console.CommandSessionHolder;
+import org.apache.karaf.shell.console.CompletableFunction;
+import org.apache.karaf.shell.console.Completer;
+import org.apache.karaf.shell.console.NameScoping;
+import org.apache.karaf.shell.console.completer.FileCompleter;
+import org.apache.karaf.shell.console.completer.NullCompleter;
+import org.apache.karaf.shell.console.completer.StringsCompleter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class OldArgumentCompleter {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(OldArgumentCompleter.class);
+
+    public static final String ARGUMENTS_LIST = "ARGUMENTS_LIST";
+
+    final String scope;
+    final String name;
+    final Completer commandCompleter;
+    final Completer optionsCompleter;
+    final List<Completer> argsCompleters;
+    final Map<String, Completer> optionalCompleters;
+    final CommandWithAction function;
+    final Map<Option, Field> fields = new HashMap<Option, Field>();
+    final Map<String, Option> options = new HashMap<String, Option>();
+    final Map<Integer, Field> arguments = new HashMap<Integer, Field>();
+    boolean strict = true;
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public OldArgumentCompleter(CommandWithAction function, String scope, String name, boolean scoped) {
+        this.function = function;
+        this.scope = scope;
+        this.name = name;
+        // Command name completer
+        String[] names = scoped ? new String[] { name } : new String[] { name, scope + ":" + name };
+        commandCompleter = new StringsCompleter(names);
+        // Build options completer
+        for (Class<?> type = function.getActionClass(); type != null; type = type.getSuperclass()) {
+            for (Field field : type.getDeclaredFields()) {
+                Option option = field.getAnnotation(Option.class);
+                if (option != null) {
+                    fields.put(option, field);
+                    options.put(option.name(), option);
+                    String[] aliases = option.aliases();
+                    if (aliases != null) {
+                        for (String alias : aliases) {
+                            options.put(alias, option);
+                        }
+                    }
+                }
+                Argument argument = field.getAnnotation(Argument.class);
+                if (argument != null) {
+                    Integer key = argument.index();
+                    if (arguments.containsKey(key)) {
+                        LOGGER.warn("Duplicate @Argument annotations on class " + type.getName() + " for index: " + key + " see: " + field);
+                    } else {
+                        arguments.put(key, field);
+                    }
+                }
+            }
+        }
+//        options.put(HelpOption.HELP.name(), HelpOption.HELP);
+        optionsCompleter = new StringsCompleter(options.keySet());
+        // Build arguments completers
+        argsCompleters = new ArrayList<Completer>();
+
+        if (function instanceof CompletableFunction) {
+            Map<String, Completer> opt;
+            try {
+                //
+                opt = ((CompletableFunction) function).getOptionalCompleters();
+            } catch (Throwable t) {
+                opt = new HashMap<String, Completer>();
+            }
+            optionalCompleters = opt;
+            List<Completer> fcl = ((CompletableFunction) function).getCompleters();
+            if (fcl != null) {
+                for (Completer c : fcl) {
+                    argsCompleters.add(c == null ? NullCompleter.INSTANCE : c);
+                }
+            } else {
+                argsCompleters.add(NullCompleter.INSTANCE);
+            }
+        } else {
+            optionalCompleters = new HashMap<String, Completer>();
+            final Map<Integer, Method> methods = new HashMap<Integer, Method>();
+            for (Class<?> type = function.getActionClass(); type != null; type = type.getSuperclass()) {
+                for (Method method : type.getDeclaredMethods()) {
+                    CompleterValues completerMethod = method.getAnnotation(CompleterValues.class);
+                    if (completerMethod != null) {
+                        int index = completerMethod.index();
+                        Integer key = index;
+                        if (index >= arguments.size() || index < 0) {
+                            LOGGER.warn("Index out of range on @CompleterValues on class " + type.getName() + " for index: " + key + " see: " + method);
+                        }
+                        if (methods.containsKey(key)) {
+                            LOGGER.warn("Duplicate @CompleterMethod annotations on class " + type.getName() + " for index: " + key + " see: " + method);
+                        } else {
+                            methods.put(key, method);
+                        }
+                    }
+                }
+            }
+            for (int i = 0, size = arguments.size(); i < size; i++) {
+                Completer argCompleter = NullCompleter.INSTANCE;
+                Method method = methods.get(i);
+                if (method != null) {
+                    // lets invoke the method
+                    Action action = function.createNewAction();
+                    try {
+                        Object value = method.invoke(action);
+                        if (value instanceof String[]) {
+                            argCompleter = new StringsCompleter((String[]) value);
+                        } else if (value instanceof Collection) {
+                            argCompleter = new StringsCompleter((Collection<String>) value);
+                        } else {
+                            LOGGER.warn("Could not use value " + value + " as set of completions!");
+                        }
+                    } catch (IllegalAccessException e) {
+                        LOGGER.warn("Could not invoke @CompleterMethod on " + function + ". " + e, e);
+                    } catch (InvocationTargetException e) {
+                        Throwable target = e.getTargetException();
+                        if (target == null) {
+                            target = e;
+                        }
+                        LOGGER.warn("Could not invoke @CompleterMethod on " + function + ". " + target, target);
+                    } finally {
+                        try {
+                            function.releaseAction(action);
+                        } catch (Exception e) {
+                            LOGGER.warn("Failed to release action: " + action + ". " + e, e);
+                        }
+                    }
+                } else {
+                    Field field = arguments.get(i);
+                    Class<?> type = field.getType();
+                    if (type.isAssignableFrom(File.class)) {
+                        argCompleter = new FileCompleter(null);
+                    } else if (type.isAssignableFrom(Boolean.class) || type.isAssignableFrom(boolean.class)) {
+                        argCompleter = new StringsCompleter(new String[] {"false", "true"}, false);
+                    } else if (type.isAssignableFrom(Enum.class)) {
+                        Set<String> values = new HashSet<String>();
+                        for (Object o : EnumSet.allOf((Class<Enum>) type)) {
+                            values.add(o.toString());
+                        }
+                        argCompleter = new StringsCompleter(values, false);
+                    } else {
+                        // TODO any other completers we can add?
+                    }
+                }
+                argsCompleters.add(argCompleter);
+            }
+        }
+    }
+
+    private String[] getNames(CommandSession session, String scopedCommand) {
+        String command = NameScoping.getCommandNameWithoutGlobalPrefix(session, scopedCommand);
+        String[] s = command.split(":");
+        if (s.length == 1) {
+            return s;
+        } else {
+            return new String[] { command, s[1] };
+        }
+    }
+
+    /**
+     *  If true, a completion at argument index N will only succeed
+     *  if all the completions from 0-(N-1) also succeed.
+     */
+    public void setStrict(final boolean strict) {
+        this.strict = strict;
+    }
+
+    /**
+     *  Returns whether a completion at argument index N will succees
+     *  if all the completions from arguments 0-(N-1) also succeed.
+     */
+    public boolean getStrict() {
+        return this.strict;
+    }
+
+    public int complete(final Session session, final CommandLine list, final List<String> candidates) {
+        int argpos = list.getArgumentPosition();
+        int argIndex = list.getCursorArgumentIndex();
+
+        //Store the argument list so that it can be used by completers.
+        CommandSession commandSession = CommandSessionHolder.getSession();
+        if(commandSession != null) {
+            commandSession.put(ARGUMENTS_LIST,list);
+        }
+
+        Completer comp = null;
+        String[] args = list.getArguments();
+        int index = 0;
+        // First argument is command name
+        if (index < argIndex) {
+            // Verify scope
+            if (!Session.SCOPE_GLOBAL.equals(scope) && !session.resolveCommand(args[index]).equals(scope + ":" + name)) {
+                return -1;
+            }
+            // Verify command name
+            if (!verifyCompleter(commandCompleter, args[index])) {
+                return -1;
+            }
+            index++;
+        } else {
+            comp = commandCompleter;
+        }
+        // Now, check options
+        if (comp == null) {
+            while (index < argIndex && args[index].startsWith("-")) {
+                if (!verifyCompleter(optionsCompleter, args[index])) {
+                    return -1;
+                }
+                Option option = options.get(args[index]);
+                if (option == null) {
+                    return -1;
+                }
+                Field field = fields.get(option);
+                if (field != null && field.getType() != boolean.class && field.getType() != Boolean.class) {
+                    if (++index == argIndex) {
+                        comp = NullCompleter.INSTANCE;
+                    }
+                }
+                index++;
+            }
+            if (comp == null && index >= argIndex && index < args.length && args[index].startsWith("-")) {
+                comp = optionsCompleter;
+            }
+        }
+        //Now check for if last Option has a completer
+        int lastAgurmentIndex = argIndex - 1;
+        if (lastAgurmentIndex >= 1) {
+            Option lastOption = options.get(args[lastAgurmentIndex]);
+            if (lastOption != null) {
+
+                Field lastField = fields.get(lastOption);
+                if (lastField != null && lastField.getType() != boolean.class && lastField.getType() != Boolean.class) {
+                    Option option = lastField.getAnnotation(Option.class);
+                    if (option != null) {
+                        Completer optionValueCompleter = null;
+                        String name = option.name();
+                        if (optionalCompleters != null && name != null) {
+                            optionValueCompleter = optionalCompleters.get(name);
+                            if (optionValueCompleter == null) {
+                                String[] aliases = option.aliases();
+                                if (aliases.length > 0) {
+                                    for (int i = 0; i < aliases.length && optionValueCompleter == null; i++) {
+                                        optionValueCompleter = optionalCompleters.get(option.aliases()[i]);
+                                    }
+                                }
+                            }
+                        }
+                        if(optionValueCompleter != null) {
+                            comp = optionValueCompleter;
+                        }
+                    }
+                }
+            }
+        }
+
+        // Check arguments
+        if (comp == null) {
+            int indexArg = 0;
+            while (index < argIndex) {
+                Completer sub = argsCompleters.get(indexArg >= argsCompleters.size() ? argsCompleters.size() - 1 : indexArg);
+                if (!verifyCompleter(sub, args[index])) {
+                    return -1;
+                }
+                index++;
+                indexArg++;
+            }
+            comp = argsCompleters.get(indexArg >= argsCompleters.size() ? argsCompleters.size() - 1 : indexArg);
+        }
+
+        int ret = comp.complete(list.getCursorArgument(), argpos, candidates);
+
+        if (ret == -1) {
+            return -1;
+        }
+
+        int pos = ret + (list.getBufferPosition() - argpos);
+
+        /**
+         *  Special case: when completing in the middle of a line, and the
+         *  area under the cursor is a delimiter, then trim any delimiters
+         *  from the candidates, since we do not need to have an extra
+         *  delimiter.
+         *
+         *  E.g., if we have a completion for "foo", and we
+         *  enter "f bar" into the buffer, and move to after the "f"
+         *  and hit TAB, we want "foo bar" instead of "foo  bar".
+         */
+        String buffer = list.getBuffer();
+        int cursor = list.getBufferPosition();
+        if ((buffer != null) && (cursor != buffer.length()) && isDelimiter(buffer, cursor)) {
+            for (int i = 0; i < candidates.size(); i++) {
+                String val = candidates.get(i);
+
+                while ((val.length() > 0)
+                    && isDelimiter(val, val.length() - 1)) {
+                    val = val.substring(0, val.length() - 1);
+                }
+
+                candidates.set(i, val);
+            }
+        }
+
+        return pos;
+    }
+
+    protected boolean verifyCompleter(Completer completer, String argument) {
+        List<String> candidates = new ArrayList<String>();
+        return completer.complete(argument, argument.length(), candidates) != -1 && !candidates.isEmpty();
+    }
+
+    /**
+     *  Returns true if the specified character is a whitespace
+     *  parameter. Check to ensure that the character is not
+     *  escaped and returns true from
+     *  {@link #isDelimiterChar}.
+     *
+     *  @param  buffer the complete command buffer
+     *  @param  pos    the index of the character in the buffer
+     *  @return        true if the character should be a delimiter
+     */
+    public boolean isDelimiter(final String buffer, final int pos) {
+        return !isEscaped(buffer, pos) && isDelimiterChar(buffer, pos);
+    }
+
+    public boolean isEscaped(final String buffer, final int pos) {
+        return pos > 0 && buffer.charAt(pos) == '\\' && !isEscaped(buffer, pos - 1);
+    }
+
+    /**
+     *  The character is a delimiter if it is whitespace, and the
+     *  preceeding character is not an escape character.
+     */
+    public boolean isDelimiterChar(String buffer, int pos) {
+        return Character.isWhitespace(buffer.charAt(pos));
+    }
+
+    /**
+     *  The result of a delimited buffer.
+     */
+    public static class ArgumentList {
+        private String[] arguments;
+        private int cursorArgumentIndex;
+        private int argumentPosition;
+        private int bufferPosition;
+
+        /**
+         *  @param  arguments           the array of tokens
+         *  @param  cursorArgumentIndex the token index of the cursor
+         *  @param  argumentPosition    the position of the cursor in the
+         *                              current token
+         *  @param  bufferPosition      the position of the cursor in
+         *                              the whole buffer
+         */
+        public ArgumentList(String[] arguments, int cursorArgumentIndex,
+            int argumentPosition, int bufferPosition) {
+            this.arguments = arguments;
+            this.cursorArgumentIndex = cursorArgumentIndex;
+            this.argumentPosition = argumentPosition;
+            this.bufferPosition = bufferPosition;
+        }
+
+        public void setCursorArgumentIndex(int cursorArgumentIndex) {
+            this.cursorArgumentIndex = cursorArgumentIndex;
+        }
+
+        public int getCursorArgumentIndex() {
+            return this.cursorArgumentIndex;
+        }
+
+        public String getCursorArgument() {
+            if ((cursorArgumentIndex < 0)
+                || (cursorArgumentIndex >= arguments.length)) {
+                return null;
+            }
+
+            return arguments[cursorArgumentIndex];
+        }
+
+        public void setArgumentPosition(int argumentPosition) {
+            this.argumentPosition = argumentPosition;
+        }
+
+        public int getArgumentPosition() {
+            return this.argumentPosition;
+        }
+
+        public void setArguments(String[] arguments) {
+            this.arguments = arguments;
+        }
+
+        public String[] getArguments() {
+            return this.arguments;
+        }
+
+        public void setBufferPosition(int bufferPosition) {
+            this.bufferPosition = bufferPosition;
+        }
+
+        public int getBufferPosition() {
+            return this.bufferPosition;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e3c3ef9/shell/console/src/main/resources/OSGI-INF/blueprint/karaf-console.xml
----------------------------------------------------------------------
diff --git a/shell/console/src/main/resources/OSGI-INF/blueprint/karaf-console.xml b/shell/console/src/main/resources/OSGI-INF/blueprint/karaf-console.xml
index f8b8837..6c41a6e 100644
--- a/shell/console/src/main/resources/OSGI-INF/blueprint/karaf-console.xml
+++ b/shell/console/src/main/resources/OSGI-INF/blueprint/karaf-console.xml
@@ -17,69 +17,14 @@
     limitations under the License.
 
 -->
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
-           xmlns:ext="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0">
-
-    <ext:property-placeholder placeholder-prefix="$[" placeholder-suffix="]">
-        <ext:default-properties>
-            <ext:property name="karaf.startLocalConsole" value="true"/>
-        </ext:default-properties>
-    </ext:property-placeholder>
-
-    <reference id="commandProcessor" interface="org.apache.felix.service.command.CommandProcessor"/>
-    <reference id="threadIO" interface="org.apache.felix.service.threadio.ThreadIO" />
-
-    <bean id="consoleFactoryService" class="org.apache.karaf.shell.console.impl.jline.ConsoleFactoryService">
-        <argument ref="blueprintBundleContext"/>
-        <argument ref="commandProcessor"/>
-        <argument ref="threadIO"/>
-    </bean>
-    <service interface="org.apache.karaf.shell.console.factory.ConsoleFactory" ref="consoleFactoryService"/>
-
-    <bean id="consoleFactory" class="org.apache.karaf.shell.console.impl.jline.LocalConsoleManager"
-          destroy-method="stop">
-        <argument value="$[karaf.startLocalConsole]"/>
-        <argument value="$[org.osgi.framework.startlevel.beginning]"/>
-        <argument ref="blueprintBundleContext"/>
-        <argument ref="terminalFactory"/>
-        <argument ref="consoleFactoryService"/>
-    </bean>
-
-    <bean id="converters" class="org.apache.karaf.shell.console.impl.Converters">
-        <argument ref="blueprintBundleContext"/>
-    </bean>
-    <service ref="converters" interface="org.apache.felix.service.command.Converter"/>
-
-    <bean id="terminalFactory" class="org.apache.karaf.shell.console.impl.jline.TerminalFactory"
-          destroy-method="destroy"/>
-
-    <service>
-        <interfaces>
-            <value>org.apache.felix.service.command.Function</value>
-            <value>org.apache.karaf.shell.console.CompletableFunction</value>
-        </interfaces>
-        <service-properties>
-            <entry key="osgi.command.scope" value="*"/>
-            <entry key="osgi.command.function" value="exit"/>
-        </service-properties>
-        <bean class="org.apache.karaf.shell.console.commands.BlueprintCommand">
-            <property name="blueprintContainer" ref="blueprintContainer"/>
-            <property name="blueprintConverter" ref="blueprintConverter"/>
-            <property name="actionId" value="exit"/>
-        </bean>
-    </service>
-
-    <bean id="exit" class="org.apache.karaf.shell.console.ExitAction" activation="lazy" scope="prototype"/>
-
-    <!-- Get a reference to the Configuration Admin Service -->
-    <reference id="configAdmin" interface="org.osgi.service.cm.ConfigurationAdmin"/>
-
-    <!-- For role-based security on the shell commands -->
-    <bean id="secureCommandConfigTransformer"
-          class="org.apache.karaf.shell.security.impl.SecuredCommandConfigTransformer"
-          init-method="init">
-        <property name="configAdmin" ref="configAdmin"/>
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
+
+    <bean id="commandTracker" class="org.apache.karaf.shell.compat.CommandTracker"
+            init-method="init" destroy-method="destroy">
+        <property name="context" ref="blueprintBundleContext" />
+        <property name="sessionFactory">
+            <reference interface="org.apache.karaf.shell.api.console.SessionFactory" />
+        </property>
     </bean>
-    <service ref="secureCommandConfigTransformer" interface="org.osgi.service.cm.ConfigurationListener"/>
 
 </blueprint>

http://git-wip-us.apache.org/repos/asf/karaf/blob/2e3c3ef9/shell/console/src/main/resources/OSGI-INF/bundle.info
----------------------------------------------------------------------
diff --git a/shell/console/src/main/resources/OSGI-INF/bundle.info b/shell/console/src/main/resources/OSGI-INF/bundle.info
index 4d4296e..23fcee4 100644
--- a/shell/console/src/main/resources/OSGI-INF/bundle.info
+++ b/shell/console/src/main/resources/OSGI-INF/bundle.info
@@ -9,8 +9,6 @@ Maven URL:
 
 h1. Description
 
-This bundle provides the integration of Apache Felix Gogo, shell, and console.
-
-It provides the default Karaf branding including the default welcome message.
+This bundle provides the compatibility layer with Karaf 2.x and 3.x console.
 
 h1. See also