FELIX-2055: Use a nicer shutdown mechanism when stopping a managed instance

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@907993 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/internal/AdminServiceImpl.java b/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/internal/AdminServiceImpl.java
index f6b8eae..69c7f4c 100644
--- a/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/internal/AdminServiceImpl.java
+++ b/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/internal/AdminServiceImpl.java
@@ -48,6 +48,8 @@
 
     private File storageLocation;
 
+    private long stopTimeout = 30000;
+
     public File getStorageLocation() {
         return storageLocation;
     }
@@ -56,6 +58,14 @@
         this.storageLocation = storage;
     }
 
+    public long getStopTimeout() {
+        return stopTimeout;
+    }
+
+    public void setStopTimeout(long stopTimeout) {
+        this.stopTimeout = stopTimeout;
+    }
+
     private Properties loadStorage(File location) throws IOException {
         InputStream is = null;
         try {
diff --git a/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/internal/InstanceImpl.java b/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/internal/InstanceImpl.java
index c3131c7..91fca88 100644
--- a/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/internal/InstanceImpl.java
+++ b/karaf/admin/core/src/main/java/org/apache/felix/karaf/admin/internal/InstanceImpl.java
@@ -16,14 +16,12 @@
  */
 package org.apache.felix.karaf.admin.internal;
 
-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.OutputStream;
+import java.io.*;
 import java.net.Socket;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Properties;
 
 import org.apache.felix.karaf.admin.Instance;
@@ -37,6 +35,20 @@
 
     private static final Logger LOG = LoggerFactory.getLogger(InstanceImpl.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";
+
     private AdminServiceImpl service;
     private String name;
     private String location;
@@ -170,7 +182,11 @@
         if (this.process == null) {
             throw new IllegalStateException("Instance not started");
         }
-        this.process.destroy();
+        // Try a clean shutdown
+        cleanShutdown();
+        if (this.process != null) {
+            this.process.destroy();
+        }
     }
 
     public synchronized void destroy() throws Exception {
@@ -215,6 +231,44 @@
         }
     }
 
+    protected void cleanShutdown() {
+        try {
+            File file = new File(new File(location, "etc"), CONFIG_PROPERTIES_FILE_NAME);
+            URL configPropURL = file.toURI().toURL();
+            Properties props = loadPropertiesFile(configPropURL);
+            props.put("karaf.base", new File(location).getCanonicalPath());
+            props.put("karaf.home", System.getProperty("karaf.home"));
+            for (Enumeration e = props.propertyNames(); e.hasMoreElements();) {
+                String name = (String) e.nextElement();
+                props.setProperty(name,
+                        substVars(props.getProperty(name), name, 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() + service.getStopTimeout();
+                do {
+                    Thread.sleep(100);
+                    checkProcess();
+                } while (System.currentTimeMillis() < t && process != null);
+            }
+        } catch (Exception e) {
+            LOG.debug("Unable to cleanly shutdown instance", e);
+        }
+    }
+
     protected static boolean deleteFile(File fileToDelete) {
         if (fileToDelete == null || !fileToDelete.exists()) {
             return true;
@@ -241,4 +295,122 @@
         result &= fileToDelete.delete();
         return result;
     }
+
+    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 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;
+    }
+
 }
diff --git a/karaf/main/src/main/java/org/apache/felix/karaf/main/Main.java b/karaf/main/src/main/java/org/apache/felix/karaf/main/Main.java
index 453c8d0..663a399 100644
--- a/karaf/main/src/main/java/org/apache/felix/karaf/main/Main.java
+++ b/karaf/main/src/main/java/org/apache/felix/karaf/main/Main.java
@@ -1203,6 +1203,7 @@
                                 // Match against our command string
                                 boolean match = command.toString().equals(shutdown);
                                 if (match) {
+                                    LOG.log(Level.INFO, "Karaf shutdown socket: received shutdown command. Stopping framework...");
                                     framework.stop();
                                     break;
                                 } else {