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 2013/03/05 17:56:54 UTC

svn commit: r1452894 [1/2] - in /karaf/branches/karaf-2.3.x: ./ admin/command/src/main/java/org/apache/karaf/admin/main/ admin/core/ admin/core/src/main/java/org/apache/karaf/admin/ admin/core/src/main/java/org/apache/karaf/admin/internal/ admin/core/s...

Author: gnodet
Date: Tue Mar  5 16:56:54 2013
New Revision: 1452894

URL: http://svn.apache.org/r1452894
Log:
[KARAF-2221] The admin service is not safe when used to create / start agents quickly

Added:
    karaf/branches/karaf-2.3.x/util/src/main/java/org/apache/karaf/util/properties/
    karaf/branches/karaf-2.3.x/util/src/main/java/org/apache/karaf/util/properties/FileLockUtils.java
Modified:
    karaf/branches/karaf-2.3.x/admin/command/src/main/java/org/apache/karaf/admin/main/Execute.java
    karaf/branches/karaf-2.3.x/admin/core/pom.xml
    karaf/branches/karaf-2.3.x/admin/core/src/main/java/org/apache/karaf/admin/AdminService.java
    karaf/branches/karaf-2.3.x/admin/core/src/main/java/org/apache/karaf/admin/Instance.java
    karaf/branches/karaf-2.3.x/admin/core/src/main/java/org/apache/karaf/admin/internal/AdminServiceImpl.java
    karaf/branches/karaf-2.3.x/admin/core/src/main/java/org/apache/karaf/admin/internal/InstanceImpl.java
    karaf/branches/karaf-2.3.x/admin/core/src/main/resources/OSGI-INF/blueprint/admin-core.xml
    karaf/branches/karaf-2.3.x/main/pom.xml
    karaf/branches/karaf-2.3.x/main/src/main/java/org/apache/karaf/main/Main.java
    karaf/branches/karaf-2.3.x/pom.xml
    karaf/branches/karaf-2.3.x/shell/console/pom.xml
    karaf/branches/karaf-2.3.x/util/pom.xml

Modified: karaf/branches/karaf-2.3.x/admin/command/src/main/java/org/apache/karaf/admin/main/Execute.java
URL: http://svn.apache.org/viewvc/karaf/branches/karaf-2.3.x/admin/command/src/main/java/org/apache/karaf/admin/main/Execute.java?rev=1452894&r1=1452893&r2=1452894&view=diff
==============================================================================
--- karaf/branches/karaf-2.3.x/admin/command/src/main/java/org/apache/karaf/admin/main/Execute.java (original)
+++ karaf/branches/karaf-2.3.x/admin/command/src/main/java/org/apache/karaf/admin/main/Execute.java Tue Mar  5 16:56:54 2013
@@ -130,7 +130,6 @@ public class Execute {
                 
         AdminServiceImpl admin = new AdminServiceImpl();
         admin.setStorageLocation(storageFile);
-        admin.init();
         command.setAdminService(admin);
         command.execute(null);
     }

Modified: karaf/branches/karaf-2.3.x/admin/core/pom.xml
URL: http://svn.apache.org/viewvc/karaf/branches/karaf-2.3.x/admin/core/pom.xml?rev=1452894&r1=1452893&r2=1452894&view=diff
==============================================================================
--- karaf/branches/karaf-2.3.x/admin/core/pom.xml (original)
+++ karaf/branches/karaf-2.3.x/admin/core/pom.xml Tue Mar  5 16:56:54 2013
@@ -52,6 +52,11 @@
 
         <dependency>
             <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.utils</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix</groupId>
             <artifactId>org.apache.felix.bundlerepository</artifactId>
         </dependency>
 
@@ -66,6 +71,11 @@
         </dependency>
 
         <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.util</artifactId>
+        </dependency>
+
+        <dependency>
             <groupId>org.springframework.osgi</groupId>
             <artifactId>spring-osgi-core</artifactId>
             <scope>test</scope>
@@ -180,6 +190,8 @@
                             org.apache.karaf.admin.etc,
                             org.apache.karaf.admin.internal,
                             org.apache.karaf.jpm.impl,
+                            org.apache.karaf.util.properties,
+                            org.apache.felix.utils.properties
                         </Private-Package>
                     </instructions>
                 </configuration>

Modified: karaf/branches/karaf-2.3.x/admin/core/src/main/java/org/apache/karaf/admin/AdminService.java
URL: http://svn.apache.org/viewvc/karaf/branches/karaf-2.3.x/admin/core/src/main/java/org/apache/karaf/admin/AdminService.java?rev=1452894&r1=1452893&r2=1452894&view=diff
==============================================================================
--- karaf/branches/karaf-2.3.x/admin/core/src/main/java/org/apache/karaf/admin/AdminService.java (original)
+++ karaf/branches/karaf-2.3.x/admin/core/src/main/java/org/apache/karaf/admin/AdminService.java Tue Mar  5 16:56:54 2013
@@ -21,7 +21,8 @@ public interface AdminService {
     Instance createInstance(String name, InstanceSettings settings) throws Exception;
 
     void renameInstance(String name, String newName) throws Exception;
-    
+
+    @Deprecated
     void refreshInstance() throws Exception;
 
     Instance cloneInstance(String name, String cloneName, InstanceSettings settings) throws Exception;

Modified: karaf/branches/karaf-2.3.x/admin/core/src/main/java/org/apache/karaf/admin/Instance.java
URL: http://svn.apache.org/viewvc/karaf/branches/karaf-2.3.x/admin/core/src/main/java/org/apache/karaf/admin/Instance.java?rev=1452894&r1=1452893&r2=1452894&view=diff
==============================================================================
--- karaf/branches/karaf-2.3.x/admin/core/src/main/java/org/apache/karaf/admin/Instance.java (original)
+++ karaf/branches/karaf-2.3.x/admin/core/src/main/java/org/apache/karaf/admin/Instance.java Tue Mar  5 16:56:54 2013
@@ -25,12 +25,14 @@ public interface Instance {
 
     String getName();
 
+    @Deprecated
     void setName(String name);
     
     boolean isRoot();
 
     String getLocation();
 
+    @Deprecated
     void setLocation(String location);
 
     int getPid();
@@ -58,7 +60,8 @@ public interface Instance {
     void destroy() throws Exception;
 
     String getState() throws Exception;
-    
+
+    @Deprecated
     boolean isAttached();
 
 }

Modified: karaf/branches/karaf-2.3.x/admin/core/src/main/java/org/apache/karaf/admin/internal/AdminServiceImpl.java
URL: http://svn.apache.org/viewvc/karaf/branches/karaf-2.3.x/admin/core/src/main/java/org/apache/karaf/admin/internal/AdminServiceImpl.java?rev=1452894&r1=1452893&r2=1452894&view=diff
==============================================================================
--- karaf/branches/karaf-2.3.x/admin/core/src/main/java/org/apache/karaf/admin/internal/AdminServiceImpl.java (original)
+++ karaf/branches/karaf-2.3.x/admin/core/src/main/java/org/apache/karaf/admin/internal/AdminServiceImpl.java Tue Mar  5 16:56:54 2013
@@ -16,22 +16,39 @@
  */
 package org.apache.karaf.admin.internal;
 
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 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.io.RandomAccessFile;
+import java.net.Socket;
+import java.net.URL;
+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;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 
 import org.apache.karaf.admin.AdminService;
 import org.apache.karaf.admin.Instance;
 import org.apache.karaf.admin.InstanceSettings;
+import org.apache.karaf.jpm.*;
+import org.apache.karaf.jpm.Process;
+import org.apache.karaf.jpm.impl.ScriptUtils;
+import org.apache.karaf.util.properties.FileLockUtils;
 import org.fusesource.jansi.Ansi;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -44,18 +61,41 @@ public class AdminServiceImpl implements
 
     private static final Logger LOGGER = LoggerFactory.getLogger(AdminServiceImpl.class);
 
-    private Map<String, Instance> instances = new HashMap<String, Instance>();
+    private static final String CONFIG_PROPERTIES_FILE_NAME = "config.properties";
 
-    private int defaultSshPortStart = 8101;
+    private static final String KARAF_SHUTDOWN_PORT = "karaf.shutdown.port";
 
-    private int defaultRmiRegistryPortStart = 1099;
+    private static final String KARAF_SHUTDOWN_HOST = "karaf.shutdown.host";
 
-    private int defaultRmiServerPortStart = 44444;
+    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";
+
+    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 File getStorageLocation() {
         return storageLocation;
     }
@@ -72,360 +112,708 @@ public class AdminServiceImpl implements
         this.stopTimeout = stopTimeout;
     }
 
-    private Properties loadStorage(File location) throws IOException {
-        InputStream is = null;
-        try {
-            is = new FileInputStream(location);
-            Properties props = new Properties();
-            props.load(is);
-            return props;
-        } finally {
-            if (is != null) {
-                is.close();
+    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));
+            }
+        }
+        for (String name : this.proxies.keySet()) {
+            if (!state.instances.containsKey(name)) {
+                this.proxies.remove(name);
             }
         }
+        return state;
     }
 
-    private void saveStorage(Properties props, File location, String comment) throws IOException {
-        OutputStream os = null;
-        try {
-            os = new FileOutputStream(location);
-            props.store(os, comment);
-        } finally {
-            if (os != null) {
-                os.close();
-            }
+   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;
         }
     }
 
-    public synchronized void init() throws Exception {
-        try {
-            File storageFile = new File(storageLocation, STORAGE_FILE);
+    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;
+    }
+
+    <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()) {
-                if (storageFile.exists()) {
-                    LOGGER.error("Instances storage location should be a file: " + storageFile);
-                }
-                return;
+                throw new IllegalStateException("Instance storage location should be a file: " + storageFile);
             }
-            Properties storage = loadStorage(storageFile);
-            int count = Integer.parseInt(storage.getProperty("count", "0"));
-            defaultSshPortStart = Integer.parseInt(storage.getProperty("ssh.port", Integer.toString(defaultSshPortStart)));
-            defaultRmiRegistryPortStart = Integer.parseInt(storage.getProperty("rmi.registry.port", Integer.toString(defaultRmiRegistryPortStart)));
-            defaultRmiServerPortStart = Integer.parseInt(storage.getProperty("rmi.server.port", Integer.toString(defaultRmiServerPortStart)));
-            Map<String, Instance> newInstances = new HashMap<String, Instance>();
-            for (int i = 0; i < count; i++) {
-                String name = storage.getProperty("item." + i + ".name", null);
-                String loc = storage.getProperty("item." + i + ".loc", null);
-                String opts = storage.getProperty("item." + i + ".opts", null);
-                int pid = Integer.parseInt(storage.getProperty("item." + i + ".pid", "0"));
-                boolean root = Boolean.parseBoolean(storage.getProperty("item." + i + ".root", "false"));
-                if (name != null) {
-                    InstanceImpl instance = new InstanceImpl(this, name, loc, opts, root);
-                    if (pid > 0) {
-                        try {
-                            instance.attach(pid);
-                        } catch (IOException e) {
-                            // Ignore
-                        }
+            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;
                     }
-                    newInstances.put(name, instance);
-                }
+                });
+            } catch (IOException e) {
+                throw new RuntimeException(e);
             }
-            instances = newInstances;
-        } catch (Exception e) {
-            LOGGER.warn("Unable to reload Karaf instance list", e);
+        } else {
+            throw new IllegalStateException("Instance storage location does not exist: " + storageFile);
         }
     }
-    
-    
+
     public synchronized void refreshInstance() throws Exception {
-        try {
-            init();
-            File storageFile = new File(storageLocation, STORAGE_FILE);
-            if (!storageFile.isFile()) {
-                if (storageFile.exists()) {
-                    LOGGER.error("Instances storage location should be a file: " + storageFile);
+    }
+
+    public synchronized Instance createInstance(final String name, final InstanceSettings settings) 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");
                 }
-                return;
-            }
-            Properties storage = loadStorage(storageFile);
-            int count = Integer.parseInt(storage.getProperty("count", "0"));
-            for (int i = 0; i < count; i++) {
-                String name = storage.getProperty("item." + i + ".name", null);
-                int pid = Integer.parseInt(storage.getProperty("item." + i + ".pid", "0"));
-                if (name != null) {
-                    InstanceImpl instance = (InstanceImpl)instances.get(name);
-                    if (pid > 0 && instance != null && !instance.isAttached()) {
-                        try {
-                            instance.attach(pid);
-                        } catch (IOException e) {
-                            // Ignore
-                        }
+                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;
+                }
+                println(Ansi.ansi().a("Creating new instance on SSH port ").a(sshPort).a(" and RMI ports ").a(rmiRegistryPort).a("/").a(rmiServerPort).a(" at: ").a(Ansi.Attribute.INTENSITY_BOLD).a(karafBase).a(Ansi.Attribute.RESET).toString());
+
+                mkdir(karafBase, "bin");
+                mkdir(karafBase, "etc");
+                mkdir(karafBase, "system");
+                mkdir(karafBase, "deploy");
+                mkdir(karafBase, "data");
+
+                copyResourceToDir(karafBase, "etc/config.properties", true);
+                copyResourceToDir(karafBase, "etc/jre.properties", true);
+                copyResourceToDir(karafBase, "etc/custom.properties", true);
+                copyResourceToDir(karafBase, "etc/java.util.logging.properties", true);
+                copyResourceToDir(karafBase, "etc/org.apache.felix.fileinstall-deploy.cfg", true);
+                copyResourceToDir(karafBase, "etc/org.apache.karaf.log.cfg", true);
+                copyResourceToDir(karafBase, FEATURES_CFG, true);
+                copyResourceToDir(karafBase, "etc/org.ops4j.pax.logging.cfg", true);
+                copyResourceToDir(karafBase, "etc/org.ops4j.pax.url.mvn.cfg", true);
+                copyResourceToDir(karafBase, "etc/startup.properties", true);
+                copyResourceToDir(karafBase, "etc/users.properties", true);
+                copyResourceToDir(karafBase, "etc/keys.properties", true);
+
+                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);
+                copyFilteredResourceToDir(karafBase, "etc/org.apache.karaf.shell.cfg", props);
+                copyFilteredResourceToDir(karafBase, "etc/org.apache.karaf.management.cfg", props);
+                // If we use batch files, use batch files, else use bash scripts (even on cygwin)
+                boolean windows = System.getProperty("os.name").startsWith("Win");
+                boolean cygwin = windows && new File(System.getProperty("karaf.home"), "bin/admin").exists();
+                if (windows && !cygwin) {
+                    copyFilteredResourceToDir(karafBase, "bin/karaf.bat", props);
+                    copyFilteredResourceToDir(karafBase, "bin/start.bat", props);
+                    copyFilteredResourceToDir(karafBase, "bin/stop.bat", props);
+                } else {
+                    copyFilteredResourceToDir(karafBase, "bin/karaf", props);
+                    copyFilteredResourceToDir(karafBase, "bin/start", props);
+                    copyFilteredResourceToDir(karafBase, "bin/stop", props);
+                    if (!cygwin) {
+                        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 (Exception e) {
-            LOGGER.warn("Unable to reload Karaf instance list", e);
-        }
-    }
 
-    public synchronized Instance createInstance(String name, InstanceSettings settings) throws Exception {
-        try {
-            init();
-        } catch (Exception e) {
-            LOGGER.warn("Unable to reload Karaf instance list", e);
-        }
-        if (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 = ++defaultSshPortStart;
-        }
-        int rmiRegistryPort = settings.getRmiRegistryPort();
-        if (rmiRegistryPort <= 0) {
-            rmiRegistryPort = ++defaultRmiRegistryPortStart;
-        }
-        int rmiServerPort = settings.getRmiServerPort();
-        if (rmiServerPort <= 0) {
-            rmiServerPort = ++defaultRmiServerPortStart;
-        }
-        println(Ansi.ansi().a("Creating new instance on SSH port ").a(sshPort).a(" and RMI ports ").a(rmiRegistryPort).a("/").a(rmiServerPort).a(" at: ").a(Ansi.Attribute.INTENSITY_BOLD).a(karafBase).a(Ansi.Attribute.RESET).toString());
-
-        mkdir(karafBase, "bin");
-        mkdir(karafBase, "etc");
-        mkdir(karafBase, "system");
-        mkdir(karafBase, "deploy");
-        mkdir(karafBase, "data");
-
-        copyResourceToDir(karafBase, "etc/config.properties", true);
-        copyResourceToDir(karafBase, "etc/jre.properties", true);
-        copyResourceToDir(karafBase, "etc/custom.properties", true);
-        copyResourceToDir(karafBase, "etc/java.util.logging.properties", true);
-        copyResourceToDir(karafBase, "etc/org.apache.felix.fileinstall-deploy.cfg", true);
-        copyResourceToDir(karafBase, "etc/org.apache.karaf.log.cfg", true);
-        copyResourceToDir(karafBase, FEATURES_CFG, true);
-        copyResourceToDir(karafBase, "etc/org.ops4j.pax.logging.cfg", true);
-        copyResourceToDir(karafBase, "etc/org.ops4j.pax.url.mvn.cfg", true);
-        copyResourceToDir(karafBase, "etc/startup.properties", true);
-        copyResourceToDir(karafBase, "etc/users.properties", true);
-        copyResourceToDir(karafBase, "etc/keys.properties", true);
-
-        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);
-        copyFilteredResourceToDir(karafBase, "etc/org.apache.karaf.shell.cfg", props);
-        copyFilteredResourceToDir(karafBase, "etc/org.apache.karaf.management.cfg", props);
-        // If we use batch files, use batch files, else use bash scripts (even on cygwin)
-        boolean windows = System.getProperty("os.name").startsWith("Win");
-        boolean cygwin = windows && new File( System.getProperty("karaf.home"), "bin/admin" ).exists();
-        if( windows && !cygwin ) {
-            copyFilteredResourceToDir(karafBase, "bin/karaf.bat", props);
-            copyFilteredResourceToDir(karafBase, "bin/start.bat", props);
-            copyFilteredResourceToDir(karafBase, "bin/stop.bat", props);
-        } else {
-            copyFilteredResourceToDir(karafBase, "bin/karaf", props);
-            copyFilteredResourceToDir(karafBase, "bin/start", props);
-            copyFilteredResourceToDir(karafBase, "bin/stop", props);
-            if ( !cygwin ) {
-                chmod(new File(karafBase, "bin/karaf"), "a+x");
-                chmod(new File(karafBase, "bin/start"), "a+x");
-                chmod(new File(karafBase, "bin/stop"), "a+x");
-            }
-        }
-        
-        handleFeatures(new File(karafBase, FEATURES_CFG), settings);
+                handleFeatures(new File(karafBase, FEATURES_CFG), settings);
 
-        String javaOpts = settings.getJavaOpts();
-        if (javaOpts == null || javaOpts.length() == 0) {
-            javaOpts = "-server -Xmx512M -Dcom.sun.management.jmxremote";
-        }
-        Instance instance = new InstanceImpl(this, name, karafBase.toString(), settings.getJavaOpts());
-        instances.put(name, instance);
-        saveState();
-        return instance;
+                String javaOpts = settings.getJavaOpts();
+                if (javaOpts == null || javaOpts.length() == 0) {
+                    javaOpts = "-server -Xmx512M -Dcom.sun.management.jmxremote";
+                }
+                InstanceState is = new InstanceState();
+                is.name = name;
+                is.loc = karafBase.toString();
+                is.opts = javaOpts;
+                state.instances.put(name, is);
+                InstanceImpl instance = new InstanceImpl(AdminServiceImpl.this, name);
+                AdminServiceImpl.this.proxies.put(name, instance);
+                return instance;
+            }
+        });
     }
 
-    void handleFeatures(File featuresCfg, InstanceSettings settings) throws IOException {
-        Properties p = loadStorage(featuresCfg);
-
-        appendToPropList(p, "featuresBoot", settings.getFeatures());
-        appendToPropList(p, "featuresRepositories", settings.getFeatureURLs());
-        saveStorage(p, featuresCfg, "Features Configuration");
+    void handleFeatures(File file, final InstanceSettings settings) throws IOException {
+        FileLockUtils.execute(file, 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(Properties p, String key, List<String> elements) {
+    private void appendToPropList(org.apache.felix.utils.properties.Properties p, String key, List<String> elements) {
         if (elements == null) {
             return;
         }
-        StringBuilder sb = new StringBuilder(p.getProperty(key).trim());
+        StringBuilder sb = new StringBuilder(p.get(key).toString().trim());
         for (String f : elements) {
             if (sb.length() > 0) {
                 sb.append(',');
             }
             sb.append(f);
         }
-        p.setProperty(key, sb.toString());
+        p.put(key, sb.toString());
     }
     
-    public synchronized Instance[] getInstances() {
-        return instances.values().toArray(new Instance[0]);
+    public Instance[] getInstances() {
+        return execute(new Task<Instance[]>() {
+            public Instance[] call(State state) throws IOException {
+                return proxies.values().toArray(new Instance[proxies.size()]);
+            }
+        });
     }
 
-    public synchronized Instance getInstance(String name) {
-        try {
-            init();
-        } catch (Exception e) {
-            LOGGER.warn("Unable to reload Karaf instance list", e);
-        }
-        return instances.get(name);
+    public Instance getInstance(final String name) {
+        return execute(new Task<Instance>() {
+            public Instance call(State state) throws IOException {
+                return proxies.get(name);
+            }
+        });
     }
 
-    synchronized void forget(String name) {
-        instances.remove(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 = "-server -Xmx512M -Dcom.sun.management.jmxremote";
+                }
+                String karafOpts = System.getProperty("karaf.opts", "");
+
+                File libDir = new File(System.getProperty("karaf.home"), "lib");
+                File[] jars = libDir.listFiles(new FilenameFilter() {
+                    public boolean accept(File dir, String name) {
+                        return name.endsWith(".jar");
+                    }
+                });
+                StringBuilder classpath = new StringBuilder();
+                for (File jar : jars) {
+                    if (classpath.length() > 0) {
+                        classpath.append(System.getProperty("path.separator"));
+                    }
+                    classpath.append(jar.getCanonicalPath());
+                }
+                String location = instance.loc;
+                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.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 = ProcessBuilderFactory.newInstance().newBuilder()
+                        .directory(new File(location))
+                        .command(command)
+                        .start();
+                instance.pid = process.getPid();
+                return null;
+            }
+        });
     }
 
-    public synchronized void renameInstance(String oldName, String newName) throws Exception {
-        try {
-            init();
-        } catch (Exception e) {
-            LOGGER.warn("Unable to reload Karaf instance list", e);
-        }
-        if (instances.get(newName) != null) {
-            throw new IllegalArgumentException("Instance " + newName + " already exists");
-        }
-        Instance instance = instances.get(oldName);
-        if (instance == null) {
-            throw new IllegalArgumentException("Instance " + oldName + " not found");
-        }
-        if (instance.isRoot()) {
-            throw new IllegalArgumentException("Root instance cannot be renamed");
-        }
-        if (instance.getPid() != 0) {
-            throw new IllegalStateException("Instance not stopped");
-        }
+    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 = ProcessBuilderFactory.newInstance().newBuilder().attach(instance.pid);
+                    process.destroy();
+                }
+                return null;
+            }
+        });
+    }
 
-        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());
-        // remove the old instance
-        instances.remove(oldName);
-        // update instance
-        instance.setName(newName);
-        // rename directory
-        String oldLocationPath = instance.getLocation();
-        File oldLocation = new File(oldLocationPath);
-        String basedir = oldLocation.getParent();
-        File newLocation = new File(basedir, newName);
-        oldLocation.renameTo(newLocation);
-        // update the instance location
-        instance.setLocation(newLocation.getPath());
-        // create the properties map including the instance name and instance location
-        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);
-        // add the renamed instances
-        instances.put(newName, instance);
-        // save instance definition in the instances.properties
-        saveState();
+    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);
+                AdminServiceImpl.this.proxies.remove(name);
+                return null;
+            }
+        });
     }
 
-    public synchronized Instance cloneInstance(String name, String cloneName, InstanceSettings settings) throws Exception {
+    public void renameInstance(final String oldName, final String newName) 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 = AdminServiceImpl.this.proxies.remove(oldName);
+                if (proxy == null) {
+                    proxy = new InstanceImpl(AdminServiceImpl.this, newName);
+                } else {
+                    proxy.doSetName(newName);
+                }
+                AdminServiceImpl.this.proxies.put(newName, proxy);
+                return null;
+            }
+        });
+    }
+
+    public synchronized Instance cloneInstance(final String name, final String cloneName, final InstanceSettings settings) throws Exception {
+        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(getInstanceSshPort(instance.name)), Integer.toString(settings.getSshPort()));
+                if (settings.getRmiRegistryPort() > 0)
+                    props.put(Integer.toString(getInstanceRmiRegistryPort(instance.name)), Integer.toString(settings.getRmiRegistryPort()));
+                if (settings.getRmiServerPort() > 0)
+                    props.put(Integer.toString(getInstanceRmiServerPort(instance.name)), 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/org.ops4j.pax.logging.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 = "-server -Xmx512M -Dcom.sun.management.jmxremote";
+                }
+                InstanceState is = new InstanceState();
+                is.name = cloneName;
+                is.loc = cloneLocation.toString();
+                is.opts = javaOpts;
+                state.instances.put(cloneName, is);
+                InstanceImpl cloneInstance = new InstanceImpl(AdminServiceImpl.this, cloneName);
+                AdminServiceImpl.this.proxies.put(cloneName, cloneInstance);
+                return cloneInstance;
+            }
+        });
+    }
+
+    private void checkPid(InstanceState instance) throws IOException {
+        if (instance.pid != 0) {
+            Process process = ProcessBuilderFactory.newInstance().newBuilder().attach(instance.pid);
+            if (!process.isRunning()) {
+                instance.pid = 0;
+            }
+        }
+    }
+
+    protected void cleanShutdown(InstanceState instance) {
         try {
-            init();
+            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());
+            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.warn("Unable to reload Karaf instance list", e);
-        }
-        if (instances.get(cloneName) != null) {
-            throw new IllegalArgumentException("Instance " + cloneName + " already exists");
+            LOGGER.debug("Unable to cleanly shutdown instance " + instance.name, e);
         }
-        Instance instance = instances.get(name);
+    }
+
+    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 AdminServiceImpl.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;
+            }
+        });
+    }
 
-        // 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.getLocation();
-        File location = new File(locationPath);
-        copy(location, cloneLocation);
-        // create the properties map including the instance name, location, ssh and rmi port numbers
-        HashMap<String, String> props = new HashMap<String, String>();
-        props.put(name, cloneName);
-        props.put(locationPath, cloneLocationPath);
-        if (settings.getSshPort() > 0)
-            props.put(new Integer(instance.getSshPort()).toString(), new Integer(settings.getSshPort()).toString());
-        if (settings.getRmiRegistryPort() > 0)
-            props.put(new Integer(instance.getRmiRegistryPort()).toString(), new Integer(settings.getRmiRegistryPort()).toString());
-        if (settings.getRmiServerPort() > 0)
-            props.put(new Integer(instance.getRmiServerPort()).toString(), new Integer(settings.getRmiServerPort()).toString());
-        // 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/org.ops4j.pax.logging.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 = "-server -Xmx512M -Dcom.sun.management.jmxremote";
-        }
-        Instance cloneInstance = new InstanceImpl(this, cloneName, cloneLocation.toString(), settings.getJavaOpts());
-        instances.put(cloneName, cloneInstance);
-        saveState();
-        return cloneInstance;
-    }
-
-    synchronized void saveState() throws IOException {
-        Properties storage = new Properties();
-        Instance[] data = getInstances();
-        storage.setProperty("ssh.port", Integer.toString(defaultSshPortStart));
-        storage.setProperty("rmi.registry.port", Integer.toString(defaultRmiRegistryPortStart));
-        storage.setProperty("rmi.server.port", Integer.toString(defaultRmiServerPortStart));
-        storage.setProperty("count", Integer.toString(data.length));
-        for (int i = 0; i < data.length; i++) {
-            storage.setProperty("item." + i + ".name", data[i].getName());
-            storage.setProperty("item." + i + ".root", data[i].isRoot() + "");
-            storage.setProperty("item." + i + ".loc", data[i].getLocation());
-            storage.setProperty("item." + i + ".pid", Integer.toString(data[i].getPid()));
-            storage.setProperty("item." + i + ".opts", data[i].getJavaOpts() != null ? data[i].getJavaOpts() : "");
+    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();
+                    }
+                }
+            }
         }
-        saveStorage(storage, new File(storageLocation, STORAGE_FILE), "Admin Service storage");
+        result &= fileToDelete.delete();
+        return result;
     }
-    
-    private void copyResourceToDir(File target, String resource, boolean text) throws Exception {
+
+    private void copyResourceToDir(File target, String resource, boolean text) throws IOException {
         File outFile = new File(target, resource);
         if( !outFile.exists() ) {
             println(Ansi.ansi().a("Creating file: ").a(Ansi.Attribute.INTENSITY_BOLD).a(outFile.getPath()).a(Ansi.Attribute.RESET).toString());
@@ -465,7 +853,31 @@ public class AdminServiceImpl implements
         System.out.println(st);
     }
 
-    private void filterResource(File basedir, String path, HashMap<String, String> props) throws Exception {
+    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()) {
@@ -479,7 +891,7 @@ public class AdminServiceImpl implements
         bak.delete();
     }
 
-    private void copyFilteredResourceToDir(File target, String resource, HashMap<String, String> props) throws Exception {
+    private void copyFilteredResourceToDir(File target, String resource, HashMap<String, String> props) throws IOException {
         File outFile = new File(target, resource);
         if( !outFile.exists() ) {
             println(Ansi.ansi().a("Creating file: ").a(Ansi.Attribute.INTENSITY_BOLD).a(outFile.getPath()).a(Ansi.Attribute.RESET).toString());
@@ -488,7 +900,7 @@ public class AdminServiceImpl implements
         }
     }
 
-    private void copyAndFilterResource(InputStream source, OutputStream target, HashMap<String, String> props) throws Exception {
+    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);
@@ -547,10 +959,10 @@ public class AdminServiceImpl implements
         }
     }
 
-    private int chmod(File serviceFile, String mode) throws Exception {
-        ProcessBuilder builder = new ProcessBuilder();
+    private int chmod(File serviceFile, String mode) throws IOException {
+        java.lang.ProcessBuilder builder = new java.lang.ProcessBuilder();
         builder.command("chmod", mode, serviceFile.getCanonicalPath());
-        Process p = builder.start();
+        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
@@ -559,12 +971,15 @@ public class AdminServiceImpl implements
         //PumpStreamHandler handler = new PumpStreamHandler(io.inputStream, io.outputStream, io.errorStream);
         //handler.attach(p);
         //handler.start();
-        int status = p.waitFor();
+        try {
+            return p.waitFor();
+        } catch (InterruptedException e) {
+            throw (IOException) new InterruptedIOException().initCause(e);
+        }
         //handler.stop();
-        return status;
     }
 
-    private void copy(File source, File destination) throws Exception {
+    private void copy(File source, File destination) throws IOException {
         if (source.getName().equals("cache.lock")) {
             // ignore cache.lock file
             return;
@@ -595,4 +1010,97 @@ public class AdminServiceImpl implements
         }
     }
 
+    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;
+    }
+
 }