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/04/10 16:15:48 UTC

[26/59] [abbrv] [KARAF-2852] Merge instance/core and instance/command

http://git-wip-us.apache.org/repos/asf/karaf/blob/7c2db062/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java
----------------------------------------------------------------------
diff --git a/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java b/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java
new file mode 100644
index 0000000..535c2a0
--- /dev/null
+++ b/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java
@@ -0,0 +1,1166 @@
+/*
+ * 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.instance.core.internal;
+
+import org.apache.karaf.instance.core.Instance;
+import org.apache.karaf.instance.core.InstanceService;
+import org.apache.karaf.instance.core.InstanceSettings;
+import org.apache.karaf.jpm.Process;
+import org.apache.karaf.jpm.impl.ProcessBuilderFactoryImpl;
+import org.apache.karaf.jpm.impl.ScriptUtils;
+import org.apache.karaf.util.locks.FileLockUtils;
+import org.fusesource.jansi.Ansi;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.net.Socket;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Scanner;
+
+public class InstanceServiceImpl implements InstanceService {
+
+    public static final String STORAGE_FILE = "instance.properties";
+    public static final String BACKUP_EXTENSION = ".bak";
+    private static final String FEATURES_CFG = "etc/org.apache.karaf.features.cfg";
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(InstanceServiceImpl.class);
+
+    private static final String CONFIG_PROPERTIES_FILE_NAME = "config.properties";
+
+    private static final String KARAF_SHUTDOWN_PORT = "karaf.shutdown.port";
+
+    private static final String KARAF_SHUTDOWN_HOST = "karaf.shutdown.host";
+
+    private static final String KARAF_SHUTDOWN_PORT_FILE = "karaf.shutdown.port.file";
+
+    private static final String KARAF_SHUTDOWN_COMMAND = "karaf.shutdown.command";
+
+    private static final String KARAF_SHUTDOWN_PID_FILE = "karaf.shutdown.pid.file";
+
+    private static final String DEFAULT_SHUTDOWN_COMMAND = "SHUTDOWN";
+
+    public static final String DEFAULT_JAVA_OPTS = "-server -Xmx512M -Dcom.sun.management.jmxremote -XX:+UnlockDiagnosticVMOptions -XX:+UnsyncloadClass";
+
+    private LinkedHashMap<String, InstanceImpl> proxies = new LinkedHashMap<String, InstanceImpl>();
+
+    private File storageLocation;
+
+    private long stopTimeout = 30000;
+
+    static class InstanceState {
+        String name;
+        String loc;
+        String opts;
+        int pid;
+        boolean root;
+    }
+
+    static class State {
+        int defaultSshPortStart = 8101;
+        int defaultRmiRegistryPortStart = 1099;
+        int defaultRmiServerPortStart = 44444;
+        Map<String, InstanceState> instances;
+    }
+
+    public InstanceServiceImpl() {
+        String prop = System.getProperty("karaf.instances");
+        if (prop != null) {
+            storageLocation = new File(prop);
+        }
+    }
+
+    public File getStorageLocation() {
+        return storageLocation;
+    }
+
+    public void setStorageLocation(File storage) {
+        this.storageLocation = storage;
+    }
+
+    public long getStopTimeout() {
+        return stopTimeout;
+    }
+
+    public void setStopTimeout(long stopTimeout) {
+        this.stopTimeout = stopTimeout;
+    }
+
+    private State loadData(org.apache.felix.utils.properties.Properties storage) {
+        State state = new State();
+        int count = getInt(storage, "count", 0);
+        state.defaultSshPortStart = getInt(storage, "ssh.port", state.defaultSshPortStart);
+        state.defaultRmiRegistryPortStart = getInt(storage, "rmi.registry.port", state.defaultRmiRegistryPortStart);
+        state.defaultRmiServerPortStart = getInt(storage, "rmi.server.port", state.defaultRmiServerPortStart);
+        state.instances = new LinkedHashMap<String, InstanceState>();
+
+        for (int i = 0; i < count; i++) {
+            InstanceState instance = new InstanceState();
+            instance.name = getString(storage, "item." + i + ".name", null);
+            instance.loc = getString(storage, "item." + i + ".loc", null);
+            instance.opts = getString(storage, "item." + i + ".opts", null);
+            instance.pid = getInt(storage, "item." + i + ".pid", 0);
+            instance.root = getBool(storage, "item." + i + ".root", false);
+            state.instances.put(instance.name, instance);
+        }
+        // Update proxies list
+        for (InstanceState instance : state.instances.values()) {
+            if (!this.proxies.containsKey(instance.name)) {
+                proxies.put(instance.name, new InstanceImpl(this, instance.name));
+            }
+        }
+        List<String> names = new ArrayList<String>(this.proxies.keySet());
+        for (String name : names) {
+            if (!state.instances.containsKey(name)) {
+                this.proxies.remove(name);
+            }
+        }
+        return state;
+    }
+
+    private void saveData(State state, org.apache.felix.utils.properties.Properties storage) {
+        storage.put("ssh.port", Integer.toString(state.defaultSshPortStart));
+        storage.put("rmi.registry.port", Integer.toString(state.defaultRmiRegistryPortStart));
+        storage.put("rmi.server.port", Integer.toString(state.defaultRmiServerPortStart));
+        storage.put("count", Integer.toString(state.instances.size()));
+        int i = 0;
+        for (InstanceState instance : state.instances.values()) {
+            storage.put("item." + i + ".name", instance.name);
+            storage.put("item." + i + ".root", Boolean.toString(instance.root));
+            storage.put("item." + i + ".loc", instance.loc);
+            storage.put("item." + i + ".pid", Integer.toString(instance.pid));
+            storage.put("item." + i + ".opts", instance.opts != null ? instance.opts : "");
+            i++;
+        }
+        while (storage.containsKey("item." + i + ".name")) {
+            storage.remove("item." + i + ".name");
+            storage.remove("item." + i + ".root");
+            storage.remove("item." + i + ".loc");
+            storage.remove("item." + i + ".pid");
+            storage.remove("item." + i + ".opts");
+            i++;
+        }
+    }
+
+    private boolean getBool(org.apache.felix.utils.properties.Properties storage, String name, boolean def) {
+        Object value = storage.get(name);
+        if (value != null) {
+            return Boolean.parseBoolean(value.toString());
+        } else {
+            return def;
+        }
+    }
+
+    private int getInt(org.apache.felix.utils.properties.Properties storage, String name, int def) {
+        Object value = storage.get(name);
+        if (value != null) {
+            return Integer.parseInt(value.toString());
+        } else {
+            return def;
+        }
+    }
+
+    private String getString(org.apache.felix.utils.properties.Properties storage, String name, String def) {
+        Object value = storage.get(name);
+        return value != null ? value.toString() : def;
+    }
+
+    interface Task<T> {
+        T call(State state) throws IOException;
+    }
+
+    synchronized <T> T execute(final Task<T> callback) {
+        final File storageFile = new File(storageLocation, STORAGE_FILE);
+        if (!storageFile.exists()) {
+            storageFile.getParentFile().mkdirs();
+            try {
+                storageFile.createNewFile();
+            } catch (IOException e) {
+                // Ignore
+            }
+        }
+        if (storageFile.exists()) {
+            if (!storageFile.isFile()) {
+                throw new IllegalStateException("Instance storage location should be a file: " + storageFile);
+            }
+            try {
+                return FileLockUtils.execute(storageFile, new FileLockUtils.CallableWithProperties<T>() {
+                    public T call(org.apache.felix.utils.properties.Properties properties) throws IOException {
+                        State state = loadData(properties);
+                        T t = callback.call(state);
+                        saveData(state, properties);
+                        return t;
+                    }
+                });
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        } else {
+            throw new IllegalStateException("Instance storage location does not exist: " + storageFile);
+        }
+    }
+
+    public synchronized void refreshInstance() throws Exception {
+    }
+
+    private void logInfo(String message, boolean printOutput, Object... args) {
+        if (LOGGER.isInfoEnabled() || printOutput) {
+            String formatted = String.format(message, args);
+            LOGGER.info(formatted);
+            if (printOutput) {
+                println(formatted);
+            }
+        }
+    }
+
+    public synchronized Instance createInstance(final String name, final InstanceSettings settings, final boolean printOutput) throws Exception {
+        return execute(new Task<Instance>() {
+            public Instance call(State state) throws IOException {
+                if (state.instances.get(name) != null) {
+                    throw new IllegalArgumentException("Instance '" + name + "' already exists");
+                }
+                String loc = settings.getLocation() != null ? settings.getLocation() : name;
+                File karafBase = new File(loc);
+                if (!karafBase.isAbsolute()) {
+                    karafBase = new File(storageLocation, loc);
+                }
+                int sshPort = settings.getSshPort();
+                if (sshPort <= 0) {
+                    sshPort = ++state.defaultSshPortStart;
+                }
+                int rmiRegistryPort = settings.getRmiRegistryPort();
+                if (rmiRegistryPort <= 0) {
+                    rmiRegistryPort = ++state.defaultRmiRegistryPortStart;
+                }
+                int rmiServerPort = settings.getRmiServerPort();
+                if (rmiServerPort <= 0) {
+                    rmiServerPort = ++state.defaultRmiServerPortStart;
+                }
+                logInfo("Creating new instance on SSH port %d and registry port %d / RMI server port %d at: %s",
+                        printOutput, sshPort, rmiRegistryPort, rmiServerPort, karafBase);
+
+                mkdir(karafBase, "bin", printOutput);
+                mkdir(karafBase, "etc", printOutput);
+                mkdir(karafBase, "system", printOutput);
+                mkdir(karafBase, "deploy", printOutput);
+                mkdir(karafBase, "data", printOutput);
+
+                copyResourceToDir(karafBase, "etc/all.policy", printOutput);
+                copyResourceToDir(karafBase, "etc/config.properties", printOutput);
+                copyResourceToDir(karafBase, "etc/custom.properties", printOutput);
+                copyResourceToDir(karafBase, "etc/distribution.info", printOutput);
+                copyResourceToDir(karafBase, "etc/equinox-debug.properties", printOutput);
+                copyResourceToDir(karafBase, "etc/java.util.logging.properties", printOutput);
+                copyResourceToDir(karafBase, "etc/jmx.acl.cfg", printOutput);
+                copyResourceToDir(karafBase, "etc/jmx.acl.java.lang.Memory.cfg", printOutput);
+                copyResourceToDir(karafBase, "etc/jmx.acl.org.apache.karaf.bundle.cfg", printOutput);
+                copyResourceToDir(karafBase, "etc/jmx.acl.org.apache.karaf.config.cfg", printOutput);
+                copyResourceToDir(karafBase, "etc/jmx.acl.org.apache.karaf.security.jmx.cfg", printOutput);
+                copyResourceToDir(karafBase, "etc/jmx.acl.osgi.compendium.cm.cfg", printOutput);
+                copyResourceToDir(karafBase, "etc/jre.properties", printOutput);
+                copyResourceToDir(karafBase, "etc/keys.properties", printOutput);
+                copyResourceToDir(karafBase, "etc/org.apache.felix.fileinstall-deploy.cfg", printOutput);
+                copyResourceToDir(karafBase, "etc/org.apache.karaf.command.acl.bundle.cfg", printOutput);
+                copyResourceToDir(karafBase, "etc/org.apache.karaf.command.acl.config.cfg", printOutput);
+                copyResourceToDir(karafBase, "etc/org.apache.karaf.command.acl.feature.cfg", printOutput);
+                copyResourceToDir(karafBase, "etc/org.apache.karaf.command.acl.jaas.cfg", printOutput);
+                copyResourceToDir(karafBase, "etc/org.apache.karaf.command.acl.kar.cfg", printOutput);
+                copyResourceToDir(karafBase, "etc/org.apache.karaf.command.acl.shell.cfg", printOutput);
+                copyResourceToDir(karafBase, "etc/org.apache.karaf.command.acl.system.cfg", printOutput);
+                copyResourceToDir(karafBase, "etc/org.apache.karaf.features.obr.cfg", printOutput);
+                copyResourceToDir(karafBase, "etc/org.apache.karaf.features.repos.cfg", printOutput);
+                copyResourceToDir(karafBase, "etc/org.apache.karaf.jaas.cfg", printOutput);
+                copyResourceToDir(karafBase, "etc/org.apache.karaf.kar.cfg", printOutput);
+                copyResourceToDir(karafBase, "etc/org.apache.karaf.log.cfg", printOutput);
+                copyResourceToDir(karafBase, "etc/org.ops4j.pax.logging.cfg", printOutput);
+                copyResourceToDir(karafBase, "etc/org.ops4j.pax.url.mvn.cfg", printOutput);
+                copyResourceToDir(karafBase, "etc/regions-config.xml", printOutput);
+                copyResourceToDir(karafBase, "etc/shell.init.script", printOutput);
+                copyResourceToDir(karafBase, "etc/users.properties", printOutput);
+
+                copyResourceToDir(karafBase, FEATURES_CFG, printOutput);
+                addFeaturesFromSettings(new File(karafBase, FEATURES_CFG), settings);
+
+                // The startup.properties is now generated by the karaf maven plugin, so
+                // we use the one from the root instance instead of embedding it
+                File rootEtc = new File(System.getProperty("karaf.etc"));
+                copy(new File(rootEtc, "startup.properties"), new File(karafBase, "etc/startup.properties"));
+
+                HashMap<String, String> props = new HashMap<String, String>();
+                props.put("${SUBST-KARAF-NAME}", name);
+                props.put("${SUBST-KARAF-HOME}", System.getProperty("karaf.home"));
+                props.put("${SUBST-KARAF-BASE}", karafBase.getPath());
+                props.put("${SUBST-SSH-PORT}", Integer.toString(sshPort));
+                props.put("${SUBST-RMI-REGISTRY-PORT}", Integer.toString(rmiRegistryPort));
+                props.put("${SUBST-RMI-SERVER-PORT}", Integer.toString(rmiServerPort));
+                copyFilteredResourceToDir(karafBase, "etc/system.properties", props, printOutput);
+                copyFilteredResourceToDir(karafBase, "etc/org.apache.karaf.shell.cfg", props, printOutput);
+                copyFilteredResourceToDir(karafBase, "etc/org.apache.karaf.management.cfg", props, printOutput);
+               
+                copyFilteredResourceToDir(karafBase, "bin/karaf", props, printOutput);
+                copyFilteredResourceToDir(karafBase, "bin/start", props, printOutput);
+                copyFilteredResourceToDir(karafBase, "bin/stop", props, printOutput);
+
+                copyFilteredResourceToDir(karafBase, "bin/karaf.bat", props, printOutput);
+                copyFilteredResourceToDir(karafBase, "bin/start.bat", props, printOutput);
+                copyFilteredResourceToDir(karafBase, "bin/stop.bat", props, printOutput);
+
+                try {
+                    chmod(new File(karafBase, "bin/karaf"), "a+x");
+                    chmod(new File(karafBase, "bin/start"), "a+x");
+                    chmod(new File(karafBase, "bin/stop"), "a+x");
+                } catch (IOException e) {
+                    LOGGER.debug("Could not set file mode on scripts.", e);
+                }
+
+                String javaOpts = settings.getJavaOpts();
+                if (javaOpts == null || javaOpts.length() == 0) {
+                    javaOpts = DEFAULT_JAVA_OPTS;
+                }
+                InstanceState is = new InstanceState();
+                is.name = name;
+                is.loc = karafBase.toString();
+                is.opts = javaOpts;
+                state.instances.put(name, is);
+                InstanceImpl instance = new InstanceImpl(InstanceServiceImpl.this, name);
+                InstanceServiceImpl.this.proxies.put(name, instance);
+                return instance;
+            }
+        });
+    }
+
+    void addFeaturesFromSettings(File featuresCfg, final InstanceSettings settings) throws IOException {
+        FileLockUtils.execute(featuresCfg, new FileLockUtils.RunnableWithProperties() {
+            public void run(org.apache.felix.utils.properties.Properties properties) throws IOException {
+                appendToPropList(properties, "featuresBoot", settings.getFeatures());
+                appendToPropList(properties, "featuresRepositories", settings.getFeatureURLs());
+            }
+        });
+    }
+
+    private void appendToPropList(org.apache.felix.utils.properties.Properties p, String key, List<String> elements) {
+        if (elements == null) {
+            return;
+        }
+        StringBuilder sb = new StringBuilder(p.get(key).toString().trim());
+        for (String f : elements) {
+            if (sb.length() > 0) {
+                sb.append(',');
+            }
+            sb.append(f);
+        }
+        p.put(key, sb.toString());
+    }
+
+    public Instance[] getInstances() {
+        return execute(new Task<Instance[]>() {
+            public Instance[] call(State state) throws IOException {
+                return proxies.values().toArray(new Instance[proxies.size()]);
+            }
+        });
+    }
+
+    public Instance getInstance(final String name) {
+        return execute(new Task<Instance>() {
+            public Instance call(State state) throws IOException {
+                return proxies.get(name);
+            }
+        });
+    }
+
+    public void startInstance(final String name, final String javaOpts) {
+        execute(new Task<Object>() {
+            public Object call(State state) throws IOException {
+                InstanceState instance = state.instances.get(name);
+                if (instance == null) {
+                    throw new IllegalArgumentException("Instance " + name + " not found");
+                }
+                checkPid(instance);
+                if (instance.pid != 0) {
+                    throw new IllegalStateException("Instance already started");
+                }
+                String opts = javaOpts;
+                if (opts == null || opts.length() == 0) {
+                    opts = instance.opts;
+                }
+                if (opts == null || opts.length() == 0) {
+                    opts = DEFAULT_JAVA_OPTS;
+                }
+
+                // fallback and read karafOpts from KARAF_OPTS environment if no System property present
+                String karafOptsEnv = System.getenv("KARAF_OPTS");
+                String karafOpts = System.getProperty("karaf.opts", karafOptsEnv != null ? karafOptsEnv : "");
+                
+                String location = instance.loc;
+
+                File libDir = new File(System.getProperty("karaf.home"), "lib");
+                File childLibDir = new File(location, "lib");
+                
+                StringBuilder classpath = classpathFromLibDir(libDir);
+                StringBuilder childClasspath = classpathFromLibDir(childLibDir);
+                if (childClasspath.length() > 0 && !libDir.equals(childLibDir)) {
+                    classpath.append(System.getProperty("path.separator"));
+                    classpath.append(childClasspath);
+                }    
+
+                String command = "\""
+                        + new File(System.getProperty("java.home"), ScriptUtils.isWindows() ? "bin\\java.exe" : "bin/java").getCanonicalPath()
+                        + "\" " + opts
+                        + " " + karafOpts
+                        + " -Djava.util.logging.config.file=\"" + new File(location, "etc/java.util.logging.properties").getCanonicalPath() + "\""
+                        + " -Djava.endorsed.dirs=\"" + new File(new File(new File(System.getProperty("java.home"), "jre"), "lib"), "endorsed") + System.getProperty("path.separator") + new File(new File(System.getProperty("java.home"), "lib"), "endorsed") + System.getProperty("path.separator") + new File(libDir, "endorsed").getCanonicalPath() + "\""
+                        + " -Djava.ext.dirs=\"" + new File(new File(new File(System.getProperty("java.home"), "jre"), "lib"), "ext") + System.getProperty("path.separator") + new File(new File(System.getProperty("java.home"), "lib"), "ext") + System.getProperty("path.separator") + new File(libDir, "ext").getCanonicalPath() + "\""
+                        + " -Dkaraf.home=\"" + System.getProperty("karaf.home") + "\""
+                        + " -Dkaraf.base=\"" + new File(location).getCanonicalPath() + "\""
+                        + " -Dkaraf.data=\"" + new File(new File(location).getCanonicalPath(), "data") + "\""
+                        + " -Dkaraf.etc=\"" + new File(new File(location).getCanonicalPath(), "etc") + "\""
+                        + " -Djavax.management.builder.initial=org.apache.karaf.management.boot.KarafMBeanServerBuilder"
+                        + " -Dkaraf.startLocalConsole=false"
+                        + " -Dkaraf.startRemoteShell=true"
+                        + " -classpath \"" + classpath.toString() + "\""
+                        + " org.apache.karaf.main.Main";
+                LOGGER.debug("Starting instance " + name + " with command: " + command);
+                org.apache.karaf.jpm.Process process = new ProcessBuilderFactoryImpl().newBuilder()
+                        .directory(new File(location))
+                        .command(command)
+                        .start();
+                instance.pid = process.getPid();
+                return null;
+            }
+
+            private StringBuilder classpathFromLibDir(File libDir) throws IOException {
+                File[] jars = libDir.listFiles(new FilenameFilter() {
+                    public boolean accept(File dir, String name) {
+                        return name.endsWith(".jar");
+                    }
+                });
+                StringBuilder classpath = new StringBuilder();
+                if (jars != null) {
+                    for (File jar : jars) {
+                        if (classpath.length() > 0) {
+                            classpath.append(System.getProperty("path.separator"));
+                        }
+                        classpath.append(jar.getCanonicalPath());
+                    }
+                }
+                return classpath;
+            }
+        });
+    }
+
+    public void stopInstance(final String name) {
+        execute(new Task<Object>() {
+            public Object call(State state) throws IOException {
+                InstanceState instance = state.instances.get(name);
+                if (instance == null) {
+                    throw new IllegalArgumentException("Instance " + name + " not found");
+                }
+                checkPid(instance);
+                if (instance.pid == 0) {
+                    throw new IllegalStateException("Instance already stopped");
+                }
+                cleanShutdown(instance);
+                if (instance.pid > 0) {
+                    Process process = new ProcessBuilderFactoryImpl().newBuilder().attach(instance.pid);
+                    process.destroy();
+                }
+                return null;
+            }
+        });
+    }
+
+    public void destroyInstance(final String name) {
+        execute(new Task<Object>() {
+            public Object call(State state) throws IOException {
+                InstanceState instance = state.instances.get(name);
+                if (instance == null) {
+                    throw new IllegalArgumentException("Instance " + name + " not found");
+                }
+                checkPid(instance);
+                if (instance.pid != 0) {
+                    throw new IllegalStateException("Instance not stopped");
+                }
+                deleteFile(new File(instance.loc));
+                state.instances.remove(name);
+                InstanceServiceImpl.this.proxies.remove(name);
+                return null;
+            }
+        });
+    }
+
+    public void renameInstance(final String oldName, final String newName, final boolean printOutput) throws Exception {
+        execute(new Task<Object>() {
+            public Object call(State state) throws IOException {
+                if (state.instances.get(newName) != null) {
+                    throw new IllegalArgumentException("Instance " + newName + " already exists");
+                }
+                InstanceState instance = state.instances.get(oldName);
+                if (instance == null) {
+                    throw new IllegalArgumentException("Instance " + oldName + " not found");
+                }
+                if (instance.root) {
+                    throw new IllegalArgumentException("Root instance cannot be renamed");
+                }
+                checkPid(instance);
+                if (instance.pid != 0) {
+                    throw new IllegalStateException("Instance not stopped");
+                }
+
+                println(Ansi.ansi().a("Renaming instance ")
+                        .a(Ansi.Attribute.INTENSITY_BOLD).a(oldName).a(Ansi.Attribute.RESET)
+                        .a(" to ")
+                        .a(Ansi.Attribute.INTENSITY_BOLD).a(newName).a(Ansi.Attribute.RESET).toString());
+                // rename directory
+                String oldLocationPath = instance.loc;
+                File oldLocation = new File(oldLocationPath);
+                String basedir = oldLocation.getParent();
+                File newLocation = new File(basedir, newName);
+                oldLocation.renameTo(newLocation);
+                // create the properties map including the instance name and instance location
+                // TODO: replacing is bad, we should re-extract the needed files
+                HashMap<String, String> props = new HashMap<String, String>();
+                props.put(oldName, newName);
+                props.put(oldLocationPath, newLocation.getPath());
+                // replace all references to the "old" name by the new one in etc/system.properties
+                // NB: it's replacement to avoid to override the user's changes
+                filterResource(newLocation, "etc/system.properties", props);
+                // replace all references to the "old" name by the new one in bin/karaf
+                filterResource(newLocation, "bin/karaf", props);
+                filterResource(newLocation, "bin/start", props);
+                filterResource(newLocation, "bin/stop", props);
+                filterResource(newLocation, "bin/karaf.bat", props);
+                filterResource(newLocation, "bin/start.bat", props);
+                filterResource(newLocation, "bin/stop.bat", props);
+                // update instance
+                instance.name = newName;
+                instance.loc = newLocation.getPath();
+                state.instances.put(newName, instance);
+                state.instances.remove(oldName);
+                InstanceImpl proxy = InstanceServiceImpl.this.proxies.remove(oldName);
+                if (proxy == null) {
+                    proxy = new InstanceImpl(InstanceServiceImpl.this, newName);
+                } else {
+                    proxy.doSetName(newName);
+                }
+                InstanceServiceImpl.this.proxies.put(newName, proxy);
+                return null;
+            }
+        });
+    }
+
+    public synchronized Instance cloneInstance(final String name, final String cloneName, final InstanceSettings settings, final boolean printOutput) throws Exception {
+        final int instanceSshPort = getInstanceSshPort(name);
+        final int instanceRmiRegistryPort = getInstanceRmiRegistryPort(name);
+        final int instanceRmiServerPort = getInstanceRmiServerPort(name);
+
+        return execute(new Task<Instance>() {
+            public Instance call(State state) throws IOException {
+                if (state.instances.get(cloneName) != null) {
+                    throw new IllegalArgumentException("Instance " + cloneName + " already exists");
+                }
+                InstanceState instance = state.instances.get(name);
+                if (instance == null) {
+                    throw new IllegalArgumentException("Instance " + name + " not found");
+                }
+
+                // define the clone instance location
+                String cloneLocationPath = settings.getLocation() != null ? settings.getLocation() : cloneName;
+                File cloneLocation = new File(cloneLocationPath);
+                if (!cloneLocation.isAbsolute()) {
+                    cloneLocation = new File(storageLocation, cloneLocationPath);
+                }
+                // copy instance directory
+                String locationPath = instance.loc;
+                File location = new File(locationPath);
+                copy(location, cloneLocation);
+                // create the properties map including the instance name, location, ssh and rmi port numbers
+                // TODO: replacing stuff anywhere is not really good, we might end up replacing unwanted stuff
+                // TODO: if no ports are overriden, shouldn't we choose new ports ?
+                HashMap<String, String> props = new HashMap<String, String>();
+                props.put(name, cloneName);
+                props.put(locationPath, cloneLocationPath);
+                if (settings.getSshPort() > 0)
+                    props.put(Integer.toString(instanceSshPort), Integer.toString(settings.getSshPort()));
+                if (settings.getRmiRegistryPort() > 0)
+                    props.put(Integer.toString(instanceRmiRegistryPort), Integer.toString(settings.getRmiRegistryPort()));
+                if (settings.getRmiServerPort() > 0)
+                    props.put(Integer.toString(instanceRmiServerPort), Integer.toString(settings.getRmiServerPort()));
+
+                // filtering clone files
+                filterResource(cloneLocation, "etc/custom.properties", props);
+                filterResource(cloneLocation, "etc/org.apache.karaf.management.cfg", props);
+                filterResource(cloneLocation, "etc/org.apache.karaf.shell.cfg", props);
+                filterResource(cloneLocation, "etc/system.properties", props);
+                filterResource(cloneLocation, "bin/karaf", props);
+                filterResource(cloneLocation, "bin/start", props);
+                filterResource(cloneLocation, "bin/stop", props);
+                filterResource(cloneLocation, "bin/karaf.bat", props);
+                filterResource(cloneLocation, "bin/start.bat", props);
+                filterResource(cloneLocation, "bin/stop.bat", props);
+                // create and add the clone instance in the registry
+                String javaOpts = settings.getJavaOpts();
+                if (javaOpts == null || javaOpts.length() == 0) {
+                    javaOpts = DEFAULT_JAVA_OPTS;
+                }
+                InstanceState is = new InstanceState();
+                is.name = cloneName;
+                is.loc = cloneLocation.toString();
+                is.opts = javaOpts;
+                state.instances.put(cloneName, is);
+                InstanceImpl cloneInstance = new InstanceImpl(InstanceServiceImpl.this, cloneName);
+                InstanceServiceImpl.this.proxies.put(cloneName, cloneInstance);
+                return cloneInstance;
+            }
+        });
+    }
+
+    private void checkPid(InstanceState instance) throws IOException {
+        if (instance.pid != 0) {
+            Process process = new ProcessBuilderFactoryImpl().newBuilder().attach(instance.pid);
+            if (!process.isRunning()) {
+                instance.pid = 0;
+            }
+        }
+    }
+
+    protected void cleanShutdown(InstanceState instance) {
+        try {
+            File file = new File(new File(instance.loc, "etc"), CONFIG_PROPERTIES_FILE_NAME);
+            URL configPropURL = file.toURI().toURL();
+            Properties props = loadPropertiesFile(configPropURL);
+            props.put("karaf.base", new File(instance.loc).getCanonicalPath());
+            props.put("karaf.home", System.getProperty("karaf.home"));
+            props.put("karaf.data", new File(new File(instance.loc), "data").getCanonicalPath());
+            props.put("karaf.etc", new File(new File(instance.loc), "etc").getCanonicalPath());
+            for (Enumeration e = props.propertyNames(); e.hasMoreElements();) {
+                String key = (String) e.nextElement();
+                props.setProperty(key,
+                        substVars(props.getProperty(key), key, null, props));
+            }
+            int port = Integer.parseInt(props.getProperty(KARAF_SHUTDOWN_PORT, "0"));
+            String host = props.getProperty(KARAF_SHUTDOWN_HOST, "localhost");
+            String portFile = props.getProperty(KARAF_SHUTDOWN_PORT_FILE);
+            String shutdown = props.getProperty(KARAF_SHUTDOWN_COMMAND, DEFAULT_SHUTDOWN_COMMAND);
+            if (port == 0 && portFile != null) {
+                BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(portFile)));
+                String portStr = r.readLine();
+                port = Integer.parseInt(portStr);
+                r.close();
+            }
+            // We found the port, try to send the command
+            if (port > 0) {
+                Socket s = new Socket(host, port);
+                s.getOutputStream().write(shutdown.getBytes());
+                s.close();
+                long t = System.currentTimeMillis() + getStopTimeout();
+                do {
+                    Thread.sleep(100);
+                    checkPid(instance);
+                } while (System.currentTimeMillis() < t && instance.pid > 0);
+            }
+        } catch (Exception e) {
+            LOGGER.debug("Unable to cleanly shutdown instance " + instance.name, e);
+        }
+    }
+
+    int getInstanceSshPort(String name) {
+        return getKarafPort(name, "etc/org.apache.karaf.shell.cfg", "sshPort");
+    }
+
+    void changeInstanceSshPort(String name, final int port) throws Exception {
+        setKarafPort(name, "etc/org.apache.karaf.shell.cfg", "sshPort", port);
+    }
+
+    int getInstanceRmiRegistryPort(String name) {
+        return getKarafPort(name, "etc/org.apache.karaf.management.cfg", "rmiRegistryPort");
+    }
+
+    void changeInstanceRmiRegistryPort(String name, final int port) throws Exception {
+        setKarafPort(name, "etc/org.apache.karaf.management.cfg", "rmiRegistryPort", port);
+    }
+
+    int getInstanceRmiServerPort(String name) {
+        return getKarafPort(name, "etc/org.apache.karaf.management.cfg", "rmiServerPort");
+    }
+
+    void changeInstanceRmiServerPort(String name, int port) throws Exception {
+        setKarafPort(name, "etc/org.apache.karaf.management.cfg", "rmiServerPort", port);
+    }
+
+    private int getKarafPort(final String name, final String path, final String key) {
+        return execute(new Task<Integer>() {
+            public Integer call(State state) throws IOException {
+                return InstanceServiceImpl.this.getKarafPort(state, name, path, key);
+            }
+        });
+    }
+
+    private Integer getKarafPort(State state, String name, String path, final String key) {
+        InstanceState instance = state.instances.get(name);
+        if (instance == null) {
+            throw new IllegalArgumentException("Instance " + name + " not found");
+        }
+        File f = new File(instance.loc, path);
+        try {
+            return FileLockUtils.execute(f, new FileLockUtils.CallableWithProperties<Integer>() {
+                public Integer call(org.apache.felix.utils.properties.Properties properties) throws IOException {
+                    return Integer.parseInt(properties.get(key).toString());
+                }
+            });
+        } catch (IOException e) {
+            return 0;
+        }
+    }
+
+    private void setKarafPort(final String name, final String path, final String key, final int port) throws IOException {
+        execute(new Task<Object>() {
+            public Object call(State state) throws IOException {
+                InstanceState instance = state.instances.get(name);
+                if (instance == null) {
+                    throw new IllegalArgumentException("Instance " + name + " not found");
+                }
+                checkPid(instance);
+                if (instance.pid != 0) {
+                    throw new IllegalStateException("Instance is not stopped");
+                }
+                File f = new File(instance.loc, path);
+                FileLockUtils.execute(f, new FileLockUtils.RunnableWithProperties() {
+                    public void run(org.apache.felix.utils.properties.Properties properties) throws IOException {
+                        properties.put(key, Integer.toString(port));
+                    }
+                });
+                return null;
+            }
+        });
+    }
+
+    boolean isInstanceRoot(final String name) {
+        return execute(new Task<Boolean>() {
+            public Boolean call(State state) throws IOException {
+                InstanceState instance = state.instances.get(name);
+                if (instance == null) {
+                    throw new IllegalArgumentException("Instance " + name + " not found");
+                }
+                return instance.root;
+            }
+        });
+    }
+
+    String getInstanceLocation(final String name) {
+        return execute(new Task<String>() {
+            public String call(State state) throws IOException {
+                InstanceState instance = state.instances.get(name);
+                if (instance == null) {
+                    throw new IllegalArgumentException("Instance " + name + " not found");
+                }
+                return instance.loc;
+            }
+        });
+    }
+
+    int getInstancePid(final String name) {
+        return execute(new Task<Integer>() {
+            public Integer call(State state) throws IOException {
+                InstanceState instance = state.instances.get(name);
+                if (instance == null) {
+                    throw new IllegalArgumentException("Instance " + name + " not found");
+                }
+                checkPid(instance);
+                return instance.pid;
+            }
+        });
+    }
+
+    String getInstanceJavaOpts(final String name) {
+        return execute(new Task<String>() {
+            public String call(State state) throws IOException {
+                InstanceState instance = state.instances.get(name);
+                if (instance == null) {
+                    throw new IllegalArgumentException("Instance " + name + " not found");
+                }
+                return instance.opts;
+            }
+        });
+    }
+
+    void changeInstanceJavaOpts(final String name, final String opts) {
+        execute(new Task<String>() {
+            public String call(State state) throws IOException {
+                InstanceState instance = state.instances.get(name);
+                if (instance == null) {
+                    throw new IllegalArgumentException("Instance " + name + " not found");
+                }
+                instance.opts = opts;
+                return null;
+            }
+        });
+    }
+
+    String getInstanceState(final String name) {
+        return execute(new Task<String>() {
+            public String call(State state) throws IOException {
+                InstanceState instance = state.instances.get(name);
+                if (instance == null) {
+                    throw new IllegalArgumentException("Instance " + name + " not found");
+                }
+                int port = getKarafPort(state, name, "etc/org.apache.karaf.shell.cfg", "sshPort");
+                if (!new File(instance.loc).isDirectory() || port <= 0) {
+                    return Instance.ERROR;
+                }
+                checkPid(instance);
+                if (instance.pid == 0) {
+                    return Instance.STOPPED;
+                } else {
+                    try {
+                        Socket s = new Socket("localhost", port);
+                        s.close();
+                        return Instance.STARTED;
+                    } catch (Exception e) {
+                        // ignore
+                    }
+                    return Instance.STARTING;
+                }
+            }
+        });
+    }
+
+    private boolean deleteFile(File fileToDelete) {
+        if (fileToDelete == null || !fileToDelete.exists()) {
+            return true;
+        }
+        boolean result = true;
+        if (fileToDelete.isDirectory()) {
+            File[] files = fileToDelete.listFiles();
+            if (files == null) {
+                result = false;
+            } else {
+                for (int i = 0; i < files.length; i++) {
+                    File file = files[i];
+                    if (file.getName().equals(".") || file.getName().equals("..")) {
+                        continue;
+                    }
+                    if (file.isDirectory()) {
+                        result &= deleteFile(file);
+                    } else {
+                        result &= file.delete();
+                    }
+                }
+            }
+        }
+        result &= fileToDelete.delete();
+        return result;
+    }
+
+    private void copyResourceToDir(File target, String resource, boolean printOutput) throws IOException {
+        File outFile = new File(target, resource);
+        if( !outFile.exists() ) {
+            logInfo("Creating file: %s", printOutput, outFile.getPath());
+            InputStream is = getClass().getClassLoader().getResourceAsStream("org/apache/karaf/instance/resources/" + resource);
+            try {
+                // Read it line at a time so that we can use the platform line ending when we write it out.
+                PrintStream out = new PrintStream(new FileOutputStream(outFile));
+                try {
+                    Scanner scanner = new Scanner(is);
+                    while (scanner.hasNextLine() ) {
+                        String line = scanner.nextLine();
+                        out.println(line);
+                    }
+                } finally {
+                    safeClose(out);
+                }
+            } finally {
+                safeClose(is);
+            }
+        }
+    }
+
+    private void println(String st) {
+        System.out.println(st);
+    }
+
+    protected static Properties loadPropertiesFile(URL configPropURL) throws Exception {
+        // Read the properties file.
+        Properties configProps = new Properties();
+        InputStream is = null;
+        try {
+            is = configPropURL.openConnection().getInputStream();
+            configProps.load(is);
+            is.close();
+        }
+        catch (Exception ex) {
+            System.err.println(
+                    "Error loading config properties from " + configPropURL);
+            System.err.println("Main: " + ex);
+            try {
+                if (is != null) is.close();
+            }
+            catch (IOException ex2) {
+                // Nothing we can do.
+            }
+            return null;
+        }
+        return configProps;
+    }
+
+    private void filterResource(File basedir, String path, HashMap<String, String> props) throws IOException {
+        File file = new File(basedir, path);
+        File bak = new File(basedir, path + BACKUP_EXTENSION);
+        if (!file.exists()) {
+            return;
+        }
+        // rename the file to the backup one
+        file.renameTo(bak);
+        // copy and filter the bak file back to the original name
+        copyAndFilterResource(new FileInputStream(bak), new FileOutputStream(file), props);
+        // remove the bak file
+        bak.delete();
+    }
+
+    private void copyFilteredResourceToDir(File target, String resource, HashMap<String, String> props, boolean printOutput) throws IOException {
+        File outFile = new File(target, resource);
+        if( !outFile.exists() ) {
+            logInfo("Creating file: %s", printOutput, outFile.getPath());
+            InputStream is = getClass().getClassLoader().getResourceAsStream("org/apache/karaf/instance/resources/" + resource);
+            copyAndFilterResource(is, new FileOutputStream(outFile), props);
+        }
+    }
+
+    private void copyAndFilterResource(InputStream source, OutputStream target, HashMap<String, String> props) throws IOException {
+        try {
+            // read it line at a time so that we can use the platform line ending when we write it out.
+            PrintStream out = new PrintStream(target);
+            try {
+                Scanner scanner = new Scanner(source);
+                while (scanner.hasNextLine()) {
+                    String line = scanner.nextLine();
+                    line = filter(line, props);
+                    out.println(line);
+                }
+            } finally {
+                safeClose(out);
+            }
+        } finally {
+            safeClose(source);
+        }
+    }
+
+    private void safeClose(InputStream is) throws IOException {
+        if (is == null) {
+            return;
+        }
+        try {
+            is.close();
+        } catch (Throwable ignore) {
+        }
+    }
+
+    private void safeClose(OutputStream is) throws IOException {
+        if (is == null) {
+            return;
+        }
+        try {
+            is.close();
+        } catch (Throwable ignore) {
+        }
+    }
+
+    private String filter(String line, HashMap<String, String> props) {
+        for (Map.Entry<String, String> i : props.entrySet()) {
+            int p1 = line.indexOf(i.getKey());
+            if( p1 >= 0 ) {
+                String l1 = line.substring(0, p1);
+                String l2 = line.substring(p1+i.getKey().length());
+                line = l1+i.getValue()+l2;
+            }
+        }
+        return line;
+    }
+
+    private void mkdir(File karafBase, String path, boolean printOutput) {
+        File file = new File(karafBase, path);
+        if( !file.exists() ) {
+            logInfo("Creating dir: %s", printOutput, file.getPath());
+            file.mkdirs();
+        }
+    }
+
+    private int chmod(File serviceFile, String mode) throws IOException {
+        java.lang.ProcessBuilder builder = new java.lang.ProcessBuilder();
+        builder.command("chmod", mode, serviceFile.getCanonicalPath());
+        java.lang.Process p = builder.start();
+
+        // gnodet: Fix SMX4KNL-46: cpu goes to 100% after running the 'admin create' command
+        // Not sure exactly what happens, but commenting the process io redirection seems
+        // to work around the problem.
+        //
+        //PumpStreamHandler handler = new PumpStreamHandler(io.inputStream, io.outputStream, io.errorStream);
+        //handler.attach(p);
+        //handler.start();
+        try {
+            return p.waitFor();
+        } catch (InterruptedException e) {
+            throw (IOException) new InterruptedIOException().initCause(e);
+        }
+        //handler.stop();
+    }
+
+    private void copy(File source, File destination) throws IOException {
+        if (source.getName().equals("cache.lock")) {
+            // ignore cache.lock file
+            return;
+        }
+        if (source.getName().equals("lock")) {
+            // ignore lock file
+            return;
+        }
+        if (source.getName().matches("transaction_\\d+\\.log")) {
+            // ignore active txlog files
+            return;
+        }
+        if (source.isDirectory()) {
+            if (!destination.exists()) {
+                destination.mkdirs();
+            }
+            String[] children = source.list();
+            for (String child : children) {
+                if (!child.contains("instances") && !child.contains("lib"))
+                    copy(new File(source, child), new File(destination, child));
+            }
+        } else {
+            InputStream in = new FileInputStream(source);
+            OutputStream out = new FileOutputStream(destination);
+            byte[] buffer = new byte[1024];
+            int length;
+            while ((length = in.read(buffer)) > 0) {
+                out.write(buffer, 0, length);
+            }
+            in.close();
+            out.close();
+        }
+    }
+
+    private static final String DELIM_START = "${";
+    private static final String DELIM_STOP = "}";
+
+    protected static String substVars(String val, String currentKey,
+                                      Map<String, String> cycleMap, Properties configProps)
+            throws IllegalArgumentException {
+        // If there is currently no cycle map, then create
+        // one for detecting cycles for this invocation.
+        if (cycleMap == null) {
+            cycleMap = new HashMap<String, String>();
+        }
+
+        // Put the current key in the cycle map.
+        cycleMap.put(currentKey, currentKey);
+
+        // Assume we have a value that is something like:
+        // "leading ${foo.${bar}} middle ${baz} trailing"
+
+        // Find the first ending '}' variable delimiter, which
+        // will correspond to the first deepest nested variable
+        // placeholder.
+        int stopDelim = val.indexOf(DELIM_STOP);
+
+        // Find the matching starting "${" variable delimiter
+        // by looping until we find a start delimiter that is
+        // greater than the stop delimiter we have found.
+        int startDelim = val.indexOf(DELIM_START);
+        while (stopDelim >= 0) {
+            int idx = val.indexOf(DELIM_START, startDelim + DELIM_START.length());
+            if ((idx < 0) || (idx > stopDelim)) {
+                break;
+            } else if (idx < stopDelim) {
+                startDelim = idx;
+            }
+        }
+
+        // If we do not have a start or stop delimiter, then just
+        // return the existing value.
+        if ((startDelim < 0) && (stopDelim < 0)) {
+            return val;
+        }
+        // At this point, we found a stop delimiter without a start,
+        // so throw an exception.
+        else if (((startDelim < 0) || (startDelim > stopDelim))
+                && (stopDelim >= 0)) {
+            throw new IllegalArgumentException(
+                    "stop delimiter with no start delimiter: "
+                            + val);
+        }
+
+        // At this point, we have found a variable placeholder so
+        // we must perform a variable substitution on it.
+        // Using the start and stop delimiter indices, extract
+        // the first, deepest nested variable placeholder.
+        String variable =
+                val.substring(startDelim + DELIM_START.length(), stopDelim);
+
+        // Verify that this is not a recursive variable reference.
+        if (cycleMap.get(variable) != null) {
+            throw new IllegalArgumentException(
+                    "recursive variable reference: " + variable);
+        }
+
+        // Get the value of the deepest nested variable placeholder.
+        // Try to configuration properties first.
+        String substValue = (configProps != null)
+                ? configProps.getProperty(variable, null)
+                : null;
+        if (substValue == null) {
+            // Ignore unknown property values.
+            substValue = System.getProperty(variable, "");
+        }
+
+        // Remove the found variable from the cycle map, since
+        // it may appear more than once in the value and we don't
+        // want such situations to appear as a recursive reference.
+        cycleMap.remove(variable);
+
+        // Append the leading characters, the substituted value of
+        // the variable, and the trailing characters to get the new
+        // value.
+        val = val.substring(0, startDelim)
+                + substValue
+                + val.substring(stopDelim + DELIM_STOP.length(), val.length());
+
+        // Now perform substitution again, since there could still
+        // be substitutions to make.
+        val = substVars(val, currentKey, cycleMap, configProps);
+
+        // Return the value.
+        return val;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/7c2db062/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceToTableMapper.java
----------------------------------------------------------------------
diff --git a/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceToTableMapper.java b/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceToTableMapper.java
new file mode 100644
index 0000000..baaedaf
--- /dev/null
+++ b/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceToTableMapper.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.instance.core.internal;
+
+import java.util.List;
+
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.TabularDataSupport;
+import javax.management.openmbean.TabularType;
+
+import org.apache.karaf.instance.core.Instance;
+import org.apache.karaf.instance.core.InstancesMBean;
+
+public class InstanceToTableMapper {
+    
+    private InstanceToTableMapper() {
+    }
+
+    private static CompositeDataSupport mapInstance(Instance instance, CompositeType comp) throws OpenDataException {
+        String state;
+        try {
+            state = instance.getState();
+        } catch (Exception e) {
+            state = "Error";
+        }
+        Object[] itemValues = new Object[] {instance.getPid(), instance.getName(), instance.isRoot(),
+                                            instance.getSshPort(), instance.getRmiRegistryPort(),
+                                            instance.getRmiServerPort(), state, instance.getLocation(),
+                                            instance.getJavaOpts()};
+        return new CompositeDataSupport(comp, InstancesMBean.INSTANCE, itemValues);
+    }
+
+    private static CompositeType createRowType() throws OpenDataException {
+        String desc = "This type describes Karaf instance";
+        OpenType<?>[] itemTypes = new OpenType[] {SimpleType.INTEGER, SimpleType.STRING, SimpleType.BOOLEAN,
+                                                  SimpleType.INTEGER, SimpleType.INTEGER, SimpleType.INTEGER,
+                                                  SimpleType.STRING, SimpleType.STRING, SimpleType.STRING};
+        String[] descriptions = new String[] {"The Process ID of the instance or 0 if not running",
+                                              "The name of the instance", "Whether the instance is root",
+                                              "The SSH port that can be used to connect to the instance",
+                                              "The RMI registry port that can be used to manage the instance",
+                                              "The RMI server port that can be used to manage the instance",
+                                              "The state of the instance", "The location of the instance",
+                                              "The Java options of the instance"};
+        CompositeType comp = new CompositeType("Instances", desc, InstancesMBean.INSTANCE, descriptions, itemTypes);
+        return comp;
+    }
+
+    public static TabularData tableFrom(List<Instance> instances) {
+        try {
+            CompositeType rowType = createRowType();
+            TabularType tableType = new TabularType("Instances", "Table of all Karaf instances", rowType,
+                                                    new String[] {InstancesMBean.INSTANCE_NAME});
+            TabularDataSupport table = new TabularDataSupport(tableType);
+            for (Instance instance : instances) {
+                CompositeDataSupport row = mapInstance(instance, rowType);
+                table.put(row);
+            }
+            return table;
+        } catch (OpenDataException e) {
+            throw new IllegalStateException("Error building instance table", e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/7c2db062/instance/src/main/java/org/apache/karaf/instance/core/internal/InstancesMBeanImpl.java
----------------------------------------------------------------------
diff --git a/instance/src/main/java/org/apache/karaf/instance/core/internal/InstancesMBeanImpl.java b/instance/src/main/java/org/apache/karaf/instance/core/internal/InstancesMBeanImpl.java
new file mode 100644
index 0000000..b95e4dc
--- /dev/null
+++ b/instance/src/main/java/org/apache/karaf/instance/core/internal/InstancesMBeanImpl.java
@@ -0,0 +1,224 @@
+/*
+ * 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.instance.core.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.management.MBeanException;
+import javax.management.NotCompliantMBeanException;
+import javax.management.StandardMBean;
+import javax.management.openmbean.TabularData;
+
+import org.apache.karaf.instance.core.Instance;
+import org.apache.karaf.instance.core.InstanceSettings;
+import org.apache.karaf.instance.core.InstancesMBean;
+
+public class InstancesMBeanImpl extends StandardMBean implements InstancesMBean {
+
+    static final String DEBUG_OPTS = " -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005";
+    static final String DEFAULT_OPTS = "-server -Xmx512M -Dcom.sun.management.jmxremote";
+
+    private org.apache.karaf.instance.core.InstanceService instanceService;
+
+    public InstancesMBeanImpl(org.apache.karaf.instance.core.InstanceService instanceService) throws NotCompliantMBeanException {
+        super(InstancesMBean.class);
+        this.instanceService = instanceService;
+    }
+
+    public int createInstance(String name, int sshPort, int rmiRegistryPort, int rmiServerPort, String location, String javaOpts, String features, String featureURLs)
+            throws MBeanException {
+        try {
+            if ("".equals(location)) {
+                location = null;
+            }
+            if ("".equals(javaOpts)) {
+                javaOpts = null;
+            }
+
+            InstanceSettings settings = new InstanceSettings(sshPort, rmiRegistryPort, rmiServerPort, location, javaOpts,
+                    parseStringList(featureURLs), parseStringList(features));
+
+            Instance inst = instanceService.createInstance(name, settings, false);
+            if (inst != null) {
+                return inst.getPid();
+            } else {
+                return -1;
+            }
+        } catch (Exception e) {
+            throw new MBeanException(null, e.getMessage());
+        }
+    }
+
+    public void changeSshPort(String name, int port) throws MBeanException {
+        try {
+            getExistingInstance(name).changeSshPort(port);
+        } catch (Exception e) {
+            throw new MBeanException(null, e.getMessage());
+        }
+    }
+
+    public void changeRmiRegistryPort(String name, int port) throws MBeanException {
+        try {
+            getExistingInstance(name).changeRmiRegistryPort(port);
+        } catch (Exception e) {
+            throw new MBeanException(null, e.getMessage());
+        }
+    }
+
+    public void changeRmiServerPort(String name, int port) throws MBeanException {
+        try {
+            getExistingInstance(name).changeRmiServerPort(port);
+        } catch (Exception e) {
+            throw new MBeanException(null, e.getMessage());
+        }
+    }
+
+    public void changeJavaOpts(String name, String javaOpts) throws MBeanException {
+        try {
+            getExistingInstance(name).changeJavaOpts(javaOpts);
+        } catch (Exception e) {
+            throw new MBeanException(null, e.getMessage());
+        }
+    }
+
+    public void destroyInstance(String name) throws MBeanException {
+        try {
+            getExistingInstance(name).destroy();
+        } catch (Exception e) {
+            throw new MBeanException(null, e.getMessage());
+        }
+    }
+
+    public void startInstance(String name) throws MBeanException {
+        try {
+            getExistingInstance(name).start(null);
+        } catch (Exception e) {
+            throw new MBeanException(null, e.getMessage());
+        }
+    }
+
+    public void startInstance(String name, String opts) throws MBeanException {
+        try {
+            getExistingInstance(name).start(opts);
+        } catch (Exception e) {
+            throw new MBeanException(null, e.getMessage());
+        }
+    }
+
+    public void startInstance(String name, String opts, boolean wait, boolean debug) throws MBeanException {
+        try {
+            Instance child = getExistingInstance(name);
+            String options = opts;
+            if (options == null) {
+                options = child.getJavaOpts();
+            }
+            if (options == null) {
+                options = DEFAULT_OPTS;
+            }
+            if (debug) {
+                options += DEBUG_OPTS;
+            }
+            if (wait) {
+                String state = child.getState();
+                if (Instance.STOPPED.equals(state)) {
+                    child.start(opts);
+                }
+                if (!Instance.STARTED.equals(state)) {
+                    do {
+                        Thread.sleep(500);
+                        state = child.getState();
+                    } while (Instance.STARTING.equals(state));
+                }
+            } else {
+                child.start(opts);
+            }
+        } catch (Exception e) {
+            throw new MBeanException(null, e.getMessage());
+        }
+    }
+
+    public void stopInstance(String name) throws MBeanException {
+        try {
+            getExistingInstance(name).stop();
+        } catch (Exception e) {
+            throw new MBeanException(null, e.getMessage());
+        }
+    }
+
+    public void renameInstance(String originalName, String newName) throws MBeanException {
+        try {
+            instanceService.renameInstance(originalName, newName, false);
+        } catch (Exception e) {
+            throw new MBeanException(null, e.getMessage());
+        }
+    }
+
+    public void renameInstance(String originalName, String newName, boolean verbose) throws MBeanException {
+        try {
+            instanceService.renameInstance(originalName, newName, verbose);
+        } catch (Exception e) {
+            throw new MBeanException(null, e.getMessage());
+        }
+    }
+
+    public void cloneInstance(String name, String cloneName, int sshPort, int rmiRegistryPort, int rmiServerPort, String location, String javaOpts) throws MBeanException {
+        try {
+            if ("".equals(location)) {
+                location = null;
+            }
+            if ("".equals(javaOpts)) {
+                javaOpts = null;
+            }
+
+            InstanceSettings settings = new InstanceSettings(sshPort, rmiRegistryPort, rmiServerPort, location, javaOpts, null, null);
+
+            instanceService.cloneInstance(name, cloneName, settings, false);
+        } catch (Exception e) {
+            throw new MBeanException(null, e.getMessage());
+        }
+    }
+
+    public TabularData getInstances() throws MBeanException {
+        List<Instance> instances = Arrays.asList(instanceService.getInstances());
+        TabularData table = InstanceToTableMapper.tableFrom(instances);
+        return table;
+    }
+
+    private Instance getExistingInstance(String name) {
+        Instance i = instanceService.getInstance(name);
+        if (i == null) {
+            throw new IllegalArgumentException("Instance '" + name + "' does not exist");
+        }
+        return i;
+    }
+
+    private List<String> parseStringList(String value) {
+        List<String> list = new ArrayList<String>();
+        if (value != null) {
+            for (String el : value.split(",")) {
+                String trimmed = el.trim();
+                if (trimmed.length() == 0) {
+                    continue;
+                }
+                list.add(trimmed);
+            }
+        }
+        return list;
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/7c2db062/instance/src/main/java/org/apache/karaf/instance/core/internal/osgi/Activator.java
----------------------------------------------------------------------
diff --git a/instance/src/main/java/org/apache/karaf/instance/core/internal/osgi/Activator.java b/instance/src/main/java/org/apache/karaf/instance/core/internal/osgi/Activator.java
new file mode 100644
index 0000000..a50db46
--- /dev/null
+++ b/instance/src/main/java/org/apache/karaf/instance/core/internal/osgi/Activator.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.instance.core.internal.osgi;
+
+import org.apache.karaf.instance.core.InstanceService;
+import org.apache.karaf.instance.core.internal.InstanceServiceImpl;
+import org.apache.karaf.instance.core.internal.InstancesMBeanImpl;
+import org.apache.karaf.util.tracker.BaseActivator;
+
+public class Activator extends BaseActivator {
+
+    @Override
+    protected void doStart() throws Exception {
+        InstanceService instanceService = new InstanceServiceImpl();
+        register(InstanceService.class, instanceService);
+
+        InstancesMBeanImpl mbean = new InstancesMBeanImpl(instanceService);
+        registerMBean(mbean, "type=instance");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/7c2db062/instance/src/main/java/org/apache/karaf/instance/main/Execute.java
----------------------------------------------------------------------
diff --git a/instance/src/main/java/org/apache/karaf/instance/main/Execute.java b/instance/src/main/java/org/apache/karaf/instance/main/Execute.java
new file mode 100644
index 0000000..591873e
--- /dev/null
+++ b/instance/src/main/java/org/apache/karaf/instance/main/Execute.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.instance.main;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+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 {
+    static Class<? extends Action> x = CreateCommand.class;
+    private static final Class<?>[] COMMAND_CLASSES = new Class[]{
+            ChangeOptsCommand.class,
+            ChangeRmiRegistryPortCommand.class,
+            ChangeRmiServerPortCommand.class,
+            ChangeSshPortCommand.class,
+            CloneCommand.class,
+            CreateCommand.class,
+            DestroyCommand.class,
+            ListCommand.class,
+            RenameCommand.class,
+            StartCommand.class,
+            StatusCommand.class,
+            StopCommand.class};
+    private static final Map<String, Class<?>> COMMANDS = new TreeMap<String, Class<?>>();
+
+    static {
+        for (Class<?> c : COMMAND_CLASSES) {
+            Command ann = c.getAnnotation(Command.class);
+            if (ann == null) {
+                continue;
+            }
+            COMMANDS.put(ann.name(), c);
+        }
+    }
+
+    // For testing
+    static boolean exitAllowed = true;
+
+    /**
+     * Environment variable for specifying extra options to the Karaf instance
+     * process kicked off from this Java process.
+     */
+    private static final String ENV_KARAF_OPTS = "KARAF_OPTS";
+
+    /**
+     * System property for specifying extra options to the Karaf instance
+     * process kicked off from this Java process.
+     */
+    private static final String PROP_KARAF_OPTS = "karaf.opts";
+
+    public static void main(String[] args) throws Exception {
+        AnsiConsole.systemInstall();
+
+        if (args.length == 0) {
+            listCommands();
+            exit(0);
+        }
+        String commandName = args[0];
+        Class<?> cls = COMMANDS.get(commandName);
+        if (cls == null) {
+            System.err.println("Command not found: " + commandName);
+            exit(-1);
+        }
+
+        String storage = System.getProperty("karaf.instances");
+        if (storage == null) {
+            System.err.println("System property 'karaf.instances' is not set. \n" +
+                    "This property needs to be set to the full path of the instance.properties file.");
+            exit(-2);
+        }
+        File storageFile = new File(storage);
+        System.setProperty("user.dir", storageFile.getParentFile().getParentFile().getCanonicalPath());
+
+        try {
+            String karafOpts = System.getenv(ENV_KARAF_OPTS);
+            if (karafOpts != null) {
+                System.setProperty(PROP_KARAF_OPTS, karafOpts);
+            }
+        } catch (Exception e) {
+            System.err.println("Could not read KARAF_OPTS environment variable: " + e.getMessage());
+            if (System.getProperty("karaf.showStackTrace") != null) {
+                throw e;
+            }
+        }
+
+        Object command = cls.newInstance();
+        if (command instanceof InstanceCommandSupport) {
+            try {
+                execute((InstanceCommandSupport) command, storageFile, args);
+            } catch (Exception e) {
+                System.err.println("Error execution command '" + commandName + "': " + e.getMessage());
+                if (System.getProperty("karaf.showStackTrace") != null) {
+                    throw e;
+                }
+            }
+        } else {
+            System.err.println("Not an instance command: " + commandName);
+            exit(-3);
+        }
+    }
+
+    static void execute(InstanceCommandSupport command, File storageFile, String[] args) throws Exception {
+        DefaultActionPreparator dap = new DefaultActionPreparator();
+        List<Object> params = new ArrayList<Object>(Arrays.asList(args));
+        params.remove(0); // this is the actual command name
+
+        if (!dap.prepare(command, null, params)) {
+            return;
+        }
+
+        InstanceServiceImpl instanceService = new InstanceServiceImpl();
+        instanceService.setStorageLocation(storageFile);
+        command.setInstanceService(instanceService);
+        command.execute();
+    }
+
+    private static void listCommands() {
+        System.out.println("Available commands:");
+        for (Map.Entry<String, Class<?>> entry : COMMANDS.entrySet()) {
+            Command ann = entry.getValue().getAnnotation(Command.class);
+            System.out.printf("  %s - %s\n", entry.getKey(), ann.description());
+        }
+
+        System.out.println("Type 'command --help' for more help on the specified command.");
+    }
+
+    private static void exit(int rc) {
+        if (exitAllowed) {
+            System.exit(rc);
+        } else {
+            throw new RuntimeException("" + rc);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/7c2db062/instance/src/main/java/org/apache/karaf/jpm/Process.java
----------------------------------------------------------------------
diff --git a/instance/src/main/java/org/apache/karaf/jpm/Process.java b/instance/src/main/java/org/apache/karaf/jpm/Process.java
new file mode 100644
index 0000000..2bd3b36
--- /dev/null
+++ b/instance/src/main/java/org/apache/karaf/jpm/Process.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.jpm;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+/**
+ * Interface representing a process
+ */
+public interface Process extends Serializable {
+
+    /**
+     * Retrieves the PID of the process
+     * @return the pid
+     */
+    int getPid();
+
+    /**
+     * Check if this process is still running
+     * @return <code>true</code> if the process is running
+     * @throws IOException if an error occurs
+     */
+    boolean isRunning() throws IOException;
+
+    /**
+     * Destroy the process.
+     *
+     * @throws IOException
+     */
+    void destroy() throws IOException;
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/7c2db062/instance/src/main/java/org/apache/karaf/jpm/ProcessBuilder.java
----------------------------------------------------------------------
diff --git a/instance/src/main/java/org/apache/karaf/jpm/ProcessBuilder.java b/instance/src/main/java/org/apache/karaf/jpm/ProcessBuilder.java
new file mode 100644
index 0000000..2b6c612
--- /dev/null
+++ b/instance/src/main/java/org/apache/karaf/jpm/ProcessBuilder.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.jpm;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Interface used to create new processes.
+ */
+public interface ProcessBuilder {
+
+    /**
+     * Specified the current directory to run the command from
+     *
+     * @param dir the directory to run the command from
+     * @return the ProcessBuilder instance
+     */
+    ProcessBuilder directory(File dir);
+
+    /**
+     * Set the command to execute
+     *
+     * @param command the command to execute
+     * @return the ProcessBuilder instance
+     */
+    ProcessBuilder command(String command);
+
+    /**
+     * Create and start the process
+     *
+     * @return the process that has been started
+     * @throws IOException if the process can not be created
+     */
+    org.apache.karaf.jpm.Process start() throws IOException;
+
+    /**
+     * Attach to an existing process
+     *
+     * @return the process that has been attached
+     * @throws IOException if the process can not be attached to
+     */
+    org.apache.karaf.jpm.Process attach(int pid) throws IOException;
+
+}

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

http://git-wip-us.apache.org/repos/asf/karaf/blob/7c2db062/instance/src/main/java/org/apache/karaf/jpm/impl/ProcessBuilderFactoryImpl.java
----------------------------------------------------------------------
diff --git a/instance/src/main/java/org/apache/karaf/jpm/impl/ProcessBuilderFactoryImpl.java b/instance/src/main/java/org/apache/karaf/jpm/impl/ProcessBuilderFactoryImpl.java
new file mode 100644
index 0000000..12a6905
--- /dev/null
+++ b/instance/src/main/java/org/apache/karaf/jpm/impl/ProcessBuilderFactoryImpl.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.jpm.impl;
+
+import org.apache.karaf.jpm.ProcessBuilder;
+import org.apache.karaf.jpm.ProcessBuilderFactory;
+
+public class ProcessBuilderFactoryImpl implements ProcessBuilderFactory {
+
+    public ProcessBuilder newBuilder() {
+        return new ProcessBuilderImpl();
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/7c2db062/instance/src/main/java/org/apache/karaf/jpm/impl/ProcessBuilderImpl.java
----------------------------------------------------------------------
diff --git a/instance/src/main/java/org/apache/karaf/jpm/impl/ProcessBuilderImpl.java b/instance/src/main/java/org/apache/karaf/jpm/impl/ProcessBuilderImpl.java
new file mode 100644
index 0000000..08f4407
--- /dev/null
+++ b/instance/src/main/java/org/apache/karaf/jpm/impl/ProcessBuilderImpl.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.jpm.impl;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.karaf.jpm.Process;
+import org.apache.karaf.jpm.ProcessBuilder;
+
+
+public class ProcessBuilderImpl implements ProcessBuilder {
+
+    private File dir;
+    private String command;
+
+    public ProcessBuilder directory(File dir) {
+        this.dir = dir;
+        return this;
+    }
+
+    public ProcessBuilder command(String command) {
+        this.command = command;
+        return this;
+    }
+
+    public Process start() throws IOException {
+        return ProcessImpl.create(dir, command);
+    }
+
+    public Process attach(int pid) throws IOException {
+        return ProcessImpl.attach(pid);
+    }
+}