ace-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ma...@apache.org
Subject svn commit: r1327960 [1/3] - in /ace/trunk: ./ ace-processlauncher/ ace-processlauncher/src/ ace-processlauncher/src/main/ ace-processlauncher/src/main/java/ ace-processlauncher/src/main/java/org/ ace-processlauncher/src/main/java/org/apache/ ace-proce...
Date Thu, 19 Apr 2012 14:24:38 GMT
Author: marrs
Date: Thu Apr 19 14:24:36 2012
New Revision: 1327960

URL: http://svn.apache.org/viewvc?rev=1327960&view=rev
Log:
ACE-215 added the supplied code with some minor tweaks to the pom and some headers

Added:
    ace/trunk/ace-processlauncher/
    ace/trunk/ace-processlauncher/pom.xml
    ace/trunk/ace-processlauncher/src/
    ace/trunk/ace-processlauncher/src/main/
    ace/trunk/ace-processlauncher/src/main/java/
    ace/trunk/ace-processlauncher/src/main/java/org/
    ace/trunk/ace-processlauncher/src/main/java/org/apache/
    ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/
    ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/
    ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/LaunchConfiguration.java
    ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/ProcessLauncherService.java
    ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/ProcessLifecycleListener.java
    ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/ProcessStreamListener.java
    ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/
    ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/LaunchConfigurationFactory.java
    ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/LaunchConfigurationImpl.java
    ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/ProcessLauncher.java
    ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/ProcessLauncherServiceImpl.java
    ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/ProcessManager.java
    ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/ProcessManagerImpl.java
    ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/StringSplitter.java
    ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/osgi/
    ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/osgi/Activator.java
    ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/util/
    ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/util/InputStreamRedirector.java
    ace/trunk/ace-processlauncher/src/main/resources/
    ace/trunk/ace-processlauncher/src/main/resources/LICENSE
    ace/trunk/ace-processlauncher/src/main/resources/README
    ace/trunk/ace-processlauncher/src/test/
    ace/trunk/ace-processlauncher/src/test/java/
    ace/trunk/ace-processlauncher/src/test/java/org/
    ace/trunk/ace-processlauncher/src/test/java/org/apache/
    ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/
    ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/
    ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/
    ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/
    ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/LaunchConfigurationFactoryTest.java
    ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/LaunchConfigurationTest.java
    ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/ProcessLauncherServiceImplTest.java
    ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/ProcessLauncherTest.java
    ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/ProcessManagerImplTest.java
    ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/StringSplitterTest.java
    ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/TestUtil.java
    ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/osgi/
    ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/osgi/ProcessLauncherRespawnIntegrationTest.java
    ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/osgi/ProcessLauncherServiceIntegrationTest.java
    ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/util/
    ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/util/InputStreamRedirectorTest.java
    ace/trunk/ace-processlauncher/src/test/resources/
    ace/trunk/ace-processlauncher/src/test/resources/org/
    ace/trunk/ace-processlauncher/src/test/resources/org/apache/
    ace/trunk/ace-processlauncher/src/test/resources/org/apache/ace/
    ace/trunk/ace-processlauncher/src/test/resources/org/apache/ace/processlauncher/
    ace/trunk/ace-processlauncher/src/test/resources/org/apache/ace/processlauncher/test/
    ace/trunk/ace-processlauncher/src/test/resources/org/apache/ace/processlauncher/test/impl/
    ace/trunk/ace-processlauncher/src/test/resources/org/apache/ace/processlauncher/test/impl/launch.properties
Modified:
    ace/trunk/pom.xml

Added: ace/trunk/ace-processlauncher/pom.xml
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/pom.xml?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/pom.xml (added)
+++ ace/trunk/ace-processlauncher/pom.xml Thu Apr 19 14:24:36 2012
@@ -0,0 +1,162 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <!--
+
+        Licensed to the Apache Software Foundation (ASF) under one or more
+        contributor license agreements.  See the NOTICE file distributed with
+        this work for additional information regarding copyright ownership.
+        The ASF licenses this file to You under the Apache License, Version 2.0
+        (the "License"); you may not use this file except in compliance with
+        the License.  You may obtain a copy of the License at
+
+           http://www.apache.org/licenses/LICENSE-2.0
+
+        Unless required by applicable law or agreed to in writing, software
+        distributed under the License is distributed on an "AS IS" BASIS,
+        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+        See the License for the specific language governing permissions and
+        limitations under the License.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.ace</groupId>
+        <artifactId>ace-pom</artifactId>
+        <version>0.8.1-SNAPSHOT</version>
+        <relativePath>../pom/pom.xml</relativePath>
+    </parent>
+
+    <version>0.8.1-SNAPSHOT</version>
+    <artifactId>org.apache.ace.processlauncher</artifactId>
+    <packaging>bundle</packaging>
+    
+    <name>Apache ACE :: Process Launcher</name>
+    <description>Launcher that can be configured to bootstrap an arbitrary number of processes.</description>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/ace/trunk/ace-processlauncher</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/ace/trunk/ace-processlauncher</developerConnection>
+        <url>http://svn.apache.org/repos/asf/ace/trunk/ace-processlauncher</url>
+    </scm>
+    
+    <properties>
+        <pax.exam.version>2.3.0</pax.exam.version>
+        <pax.exam.inject.version>2.3.0</pax.exam.inject.version>
+        <pax.exam.junit4.version>2.3.0</pax.exam.junit4.version>
+
+        <pax.url.aether.version>1.3.5</pax.url.aether.version>
+        <pax.url.assembly.version>1.3.5</pax.url.assembly.version>
+        <pax.url.link.version>1.3.5</pax.url.link.version>
+        <pax.url.wrap.version>1.3.5</pax.url.wrap.version>
+
+        <org.slf4j.simple.version>1.6.1</org.slf4j.simple.version>
+
+        <org.apache.felix.framework.version>4.0.2</org.apache.felix.framework.version>
+        <org.apache.felix.prefs.version>1.0.4</org.apache.felix.prefs.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.framework</artifactId>
+            <version>${org.apache.felix.framework.version}</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.dependencymanager</artifactId>
+            <version>3.0.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.ace</groupId>
+            <artifactId>org.apache.ace.util</artifactId>
+            <version>0.8.1-SNAPSHOT</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.8.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.8.5</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>javax.inject</groupId>
+            <artifactId>javax.inject</artifactId>
+            <version>1</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-container-native</artifactId>
+            <version>${pax.exam.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-inject</artifactId>
+            <version>${pax.exam.inject.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-junit4</artifactId>
+            <version>${pax.exam.junit4.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-link-mvn</artifactId>
+            <version>${pax.exam.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.url</groupId>
+            <artifactId>pax-url-aether</artifactId>
+            <version>${pax.url.aether.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.url</groupId>
+            <artifactId>pax-url-assembly</artifactId>
+            <version>${pax.url.assembly.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.url</groupId>
+            <artifactId>pax-url-link</artifactId>
+            <version>${pax.url.link.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.url</groupId>
+            <artifactId>pax-url-wrap</artifactId>
+            <version>${pax.url.wrap.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>${org.slf4j.simple.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+    </dependencies>
+</project>

Added: ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/LaunchConfiguration.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/LaunchConfiguration.java?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/LaunchConfiguration.java (added)
+++ ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/LaunchConfiguration.java Thu Apr 19 14:24:36 2012
@@ -0,0 +1,156 @@
+/*
+ * 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.ace.processlauncher;
+
+import java.io.File;
+
+/**
+ * Denotes a particular launch configuration for a process, describing what and how to launch.
+ * <p>
+ * You can create a new launch configuration by "pushing" a new configuration to the ConfigAdmin
+ * service. If you use something like Felix FileInstall, you can do this by creating a new
+ * properties file with the following contents:
+ * </p>
+ * 
+ * <pre>
+ * # Denotes how many instances of the process should be started, >= 0.
+ * # Optional, defaults to 1 instance.
+ * #instance.count = 1
+ * # What working directory should the executable start in?
+ * # Optional, defaults to the current working directory.
+ * #executable.workingDir = /path/to/cwd
+ * # The executable to start, should be the fully qualified path to the
+ * # executable.
+ * # Mandatory, no default.
+ * executable.name = /path/to/java
+ * # The arguments for the executable.
+ * # Mandatory, no default.
+ * executable.args = -jar /path/to/jar
+ * # The OSGi-filter clause which should resolve to a (single!)
+ * # ProcessStreamListener instance. When given, it will be used to provide
+ * # access to the launched process' stdin/stdout streams. NOTE: if you interact
+ * # with the process this way, it could be that the process only terminates
+ * # when you *explicitly* close the stdin stream.
+ * # Optional, defaults to an empty/no filter.
+ * #executable.processStreamListener =
+ * # The OSGi-filter clause that should resolve to a ProcessLifecycleListener
+ * # service-instance. When given, it will be used to provide hooks to when the
+ * # executable is (re)started and stopped. 
+ * # Optional, defaults to an empty/no filter.
+ * #executable.processLifecycleListener = 
+ * # When 'true' the process will automatically be restarted when it terminates
+ * # with a 'abnormal' exit value (see 'executable.normalExitValue'). Any
+ * # defined process stream listener will be re-invoked with the newly started
+ * # process.
+ * # Optional, defaults to 'false'.
+ * #executable.respawnAutomatically = false
+ * # Denotes what the 'normal' exit value of the process is, so the launcher can
+ * # determine when a process is terminated abnormally.
+ * # Optional, defaults to 0.
+ * #executable.normalExitValue = 0
+ * </pre>
+ */
+public interface LaunchConfiguration {
+
+    /**
+     * Creates a fully qualified command line as array, in which the first element is the command to
+     * execute, and the remainder of the array consists of the arguments that are passed to the
+     * command.
+     * 
+     * @return the command line, as array, never <code>null</code>.
+     */
+    String[] getCommandLine();
+
+    /**
+     * Returns the optional arguments that should be passed to the executable.
+     * 
+     * @return the executable arguments, never <code>null</code>, but can be an empty array.
+     * @see #getExecutableName()
+     */
+    String[] getExecutableArgs();
+
+    /**
+     * Returns the full path-name to the executable to launch.
+     * 
+     * @return the executable name, never <code>null</code>.
+     */
+    String getExecutableName();
+
+    /**
+     * Returns the number of instances that should be launched.
+     * 
+     * @return an instance count, >= 1.
+     */
+    int getInstanceCount();
+
+    /**
+     * Returns the exit value that indicates whether or not the process is terminated
+     * successfully/normally. By convention, this is 0, but not all executables adhere to this
+     * convention.
+     * 
+     * @return the normal exit value, defaults to 0.
+     */
+    int getNormalExitValue();
+
+    /**
+     * Returns a filter-string to obtain the process' stream listener that wants to interact with
+     * the process.
+     * <p>
+     * Only a single process stream listener should be resolved by the returned filter condition.
+     * When multiple listeners are returned, the one with the highest service-ID will be used as
+     * defined in the OSGi specification.
+     * </p>
+     * 
+     * @return a OSGi-filter condition, or <code>null</code> (the default) if no interaction with
+     *         the process is desired.
+     */
+    String getProcessStreamListener();
+
+    /**
+     * Returns a filter-string to obtain the process' lifecycle listener that wants to get notified
+     * about the lifecycle of the process.
+     * <p>
+     * Only a single process lifecycle listener should be resolved by the returned filter condition.
+     * When multiple listeners are returned, the one with the highest service-ID will be used as
+     * defined in the OSGi specification.
+     * </p>
+     * 
+     * @return a OSGi-filter condition, or <code>null</code> (the default) if no notifications on
+     *         the process lifecycle are desired.
+     */
+    String getProcessLifecycleListener();
+
+    /**
+     * Returns the working directory to use when launching the executable.
+     * 
+     * @return a working directory, as {@link File} object, or <code>null</code> if no working
+     *         directory is to be set and the default should be used.
+     */
+    File getWorkingDirectory();
+
+    /**
+     * Returns whether the process should be respawned after it terminated with a non-zero exit
+     * code.
+     * 
+     * @return <code>true</code> if the process should be respawned when it terminates with a
+     *         non-zero exit code, <code>false</code> to leave it as-is (terminated).
+     */
+    boolean isRespawnAutomatically();
+
+}

Added: ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/ProcessLauncherService.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/ProcessLauncherService.java?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/ProcessLauncherService.java (added)
+++ ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/ProcessLauncherService.java Thu Apr 19 14:24:36 2012
@@ -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.ace.processlauncher;
+
+import java.io.IOException;
+
+/**
+ * Provides a managed service factory for launching processes based on a certain launch
+ * configuration.
+ */
+public interface ProcessLauncherService {
+
+    /** The service PID that is used for registration of this service factory. */
+    String PID = "org.apache.ace.processlauncher";
+
+    /**
+     * Returns the number of launch configurations currently available.
+     * 
+     * @return the number of launch configurations, >= 0.
+     */
+    int getLaunchConfigurationCount();
+
+    /**
+     * Returns the number of running processes.
+     * 
+     * @return a running process count, >= 0.
+     * @throws IOException in case of I/O problems determining the number of running processes.
+     */
+    int getRunningProcessCount() throws IOException;
+
+}

Added: ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/ProcessLifecycleListener.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/ProcessLifecycleListener.java?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/ProcessLifecycleListener.java (added)
+++ ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/ProcessLifecycleListener.java Thu Apr 19 14:24:36 2012
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ace.processlauncher;
+
+import java.util.Properties;
+
+/**
+ * Allows code to be run <em>before</em> a process is actually launched, and <em>after</em> a
+ * process is terminated.
+ * <p>
+ * A typical use case for this would be that you might want to set up some process-specific
+ * directories and/or configuration files for each individually launched process.
+ * </p>
+ */
+public interface ProcessLifecycleListener {
+
+    /**
+     * Called right before the process denoted by the given launch configuration is started.
+     * <p>
+     * Use this method to set up directories, or pre-process (provisioned) data/configuration files
+     * or other actions that need to be done in order to get the process properly up and running.
+     * </p>
+     * <p>
+     * This method can also adjust the environment of the to-be-created process by returning a
+     * populated {@link Properties} object.
+     * </p>
+     * 
+     * @param configuration the launch configuration of the process that is about to start, never
+     *        <code>null</code>.
+     * @return the environment properties of the to-be-created process. Can be <code>null</code> if
+     *         no additional environment settings are wanted/desired.
+     */
+    Properties beforeProcessStart(LaunchConfiguration configuration);
+
+    /**
+     * Called right after the process denoted by the given launch configuration is terminated.
+     * <p>
+     * Use this method to clean up directories.
+     * </p>
+     * 
+     * @param configuration the launch configuration of the process that is just terminated, never
+     *        <code>null</code>.
+     */
+    void afterProcessEnd(LaunchConfiguration configuration);
+}

Added: ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/ProcessStreamListener.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/ProcessStreamListener.java?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/ProcessStreamListener.java (added)
+++ ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/ProcessStreamListener.java Thu Apr 19 14:24:36 2012
@@ -0,0 +1,80 @@
+/*
+ * 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.ace.processlauncher;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Provides a listener interface for interacting with a process' input/output stream.
+ */
+public interface ProcessStreamListener {
+
+    /**
+     * Returns the process' input, as input stream to allow interaction with the process itself.
+     * <p>
+     * NOTE: if the given output stream is <strong>closed</strong> by the implementing code, the
+     * process will probably terminate. This also means that for some processes, you need to
+     * explicitly close the given stream in order to let the process terminate properly.
+     * </p>
+     * 
+     * @param launchConfiguration the launch configuration for which a stdin output stream is set,
+     *        cannot be <code>null</code>;
+     * @param outputStream the stdin {@link OutputStream} that can be used to write to the process'
+     *        stdin.
+     * @see #wantsStdin()
+     */
+    void setStdin(LaunchConfiguration launchConfiguration, OutputStream outputStream);
+
+    /**
+     * Called with the process' stdout {@link InputStream}.
+     * <p>
+     * NOTE: when not interacting with the given stream, strange and unpredictable behavior might
+     * occur, as the native streams that are used underneath the I/O streams in Java might
+     * overflow/block!<br/>
+     * Hence, <strong>always</strong> consume all bytes from this stream when implementing this
+     * method!
+     * </p>
+     * 
+     * @param launchConfiguration the launch configuration for which a stdout input stream is set,
+     *        cannot be <code>null</code>;
+     * @param inputStream the stdout {@link InputStream} that can be used to read from the process'
+     *        stdout and stderr.
+     * @see #wantsStdout()
+     */
+    void setStdout(LaunchConfiguration launchConfiguration, InputStream inputStream);
+
+    /**
+     * Returns whether or not the standard input of the process is desired by this listener.
+     * 
+     * @return <code>true</code> if the stdin {@link OutputStream} is to be set on this listener
+     *         (see {@link #setStdin(LaunchConfiguration, OutputStream)}), <code>false</code> if the
+     *         stdin {@link OutputStream} should be ignored.
+     */
+    boolean wantsStdin();
+
+    /**
+     * Returns whether or not the standard output of the process is desired by this listener.
+     * 
+     * @return <code>true</code> if the stdout {@link InputStream} is to be set on this listener
+     *         (see {@link #setStdout(LaunchConfiguration, InputStream)}), <code>false</code> if the
+     *         stdout {@link InputStream} should be redirected to '/dev/null'.
+     */
+    boolean wantsStdout();
+}

Added: ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/LaunchConfigurationFactory.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/LaunchConfigurationFactory.java?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/LaunchConfigurationFactory.java (added)
+++ ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/LaunchConfigurationFactory.java Thu Apr 19 14:24:36 2012
@@ -0,0 +1,228 @@
+/*
+ * 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.ace.processlauncher.impl;
+
+import java.util.Dictionary;
+import java.util.Enumeration;
+
+import org.apache.ace.processlauncher.LaunchConfiguration;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.cm.ConfigurationException;
+
+/**
+ * Provides a factory for creating new {@link LaunchConfiguration}s.
+ */
+public abstract class LaunchConfigurationFactory {
+    /** Denotes the number of instances to start (defaults to 1). */
+    public static final String INSTANCE_COUNT = "instance.count";
+    /** Denotes the working directory to start from (defaults to the current working directory). */
+    public static final String WORKING_DIRECTORY = "executable.workingDir";
+    /** Denotes the executable to start (fully qualified pathname). */
+    public static final String EXECUTABLE_NAME = "executable.name";
+    /** Denotes the executable arguments (optional). */
+    public static final String EXECUTABLE_ARGS = "executable.args";
+    /** Denotes the filter-clause to obtain the process stream listener (optional). */
+    public static final String PROCESS_STREAM_LISTENER_FILTER = "executable.processStreamListener";
+    /** Denotes the filter-clause to obtain the process lifecycle listener (optional). */
+    public static final String PROCESS_LIFECYCLE_LISTENER_FILTER = "executable.processLifecycleListener";
+    /**
+     * Denotes whether or not the executable should be restarted upon unexpected termination
+     * (defaults to false).
+     */
+    public static final String RESPAWN_AUTOMATICALLY = "executable.respawnAutomatically";
+    /** Denotes the exit value that is to be considered normal (defaults to 0). */
+    public static final String NORMAL_EXIT_VALUE = "executable.normalExitValue";
+
+    /** The minimal instance count; less than one has no sense. */
+    private static final int MINIMAL_INSTANCE_COUNT = 1;
+
+    /**
+     * Creates a new {@link LaunchConfigurationFactory} instance, never used.
+     */
+    private LaunchConfigurationFactory() {
+        // No-op
+    }
+
+    /**
+     * Creates a new {@link LaunchConfiguration} instance.
+     * 
+     * @param config the configuration to create a {@link LaunchConfiguration} for, cannot be
+     *        <code>null</code>.
+     * @return a new {@link LaunchConfiguration} instance based on the information in the given
+     *         configuration, never <code>null</code>.
+     * @throws ConfigurationException in case the given configuration contains invalid keys and/or
+     *         values.
+     */
+    public static LaunchConfiguration create(final Dictionary<Object, Object> config) throws ConfigurationException {
+        if (config == null) {
+            throw new IllegalArgumentException("Config cannot be null!");
+        }
+
+        // Sanity check; make sure all mandatory properties are available...
+        checkMandatoryProperties(config, EXECUTABLE_ARGS, EXECUTABLE_NAME);
+
+        int instanceCount = 1;
+        String workingDirectory = null;
+        String executableName = null;
+        String[] executableArgs = null;
+        int normalExitValue = 0;
+        String processStreamListenerFilter = null;
+        String processLifecycleListenerFilter = null;
+        boolean respawnAutomatically = false;
+
+        Enumeration<Object> keys = config.keys();
+        while (keys.hasMoreElements()) {
+            Object key = keys.nextElement();
+            String value = String.valueOf(config.get(key)).trim();
+
+            if (INSTANCE_COUNT.equals(key)) {
+                instanceCount = parseInstanceCount(value);
+            }
+            else if (WORKING_DIRECTORY.equals(key)) {
+                workingDirectory = value.isEmpty() ? null : value;
+            }
+            else if (EXECUTABLE_NAME.equals(key)) {
+                executableName = parseExecutableName(value);
+            }
+            else if (EXECUTABLE_ARGS.equals(key)) {
+                executableArgs = parseExecutableArguments(value);
+            }
+            else if (PROCESS_STREAM_LISTENER_FILTER.equals(key)) {
+                processStreamListenerFilter = parseFilter(value);
+            }
+            else if (PROCESS_LIFECYCLE_LISTENER_FILTER.equals(key)) {
+                processLifecycleListenerFilter = parseFilter(value);
+            }
+            else if (RESPAWN_AUTOMATICALLY.equals(key)) {
+                respawnAutomatically = Boolean.parseBoolean(value);
+            }
+            else if (NORMAL_EXIT_VALUE.equals(key)) {
+                normalExitValue = parseExitValue(value);
+            }
+        }
+
+        return new LaunchConfigurationImpl(instanceCount, workingDirectory, executableName, executableArgs,
+            normalExitValue, processStreamListenerFilter, processLifecycleListenerFilter, respawnAutomatically);
+    }
+
+    /**
+     * Tests whether all mandatory properties are available in a given configuration.
+     * 
+     * @param config the configuration to test for mandatory keys;
+     * @param mandatoryKeys the keys to check.
+     * @throws ConfigurationException in case one of the given keys is missing from the given
+     *         configuration.
+     */
+    private static void checkMandatoryProperties(final Dictionary<Object, Object> config, final String... mandatoryKeys)
+        throws ConfigurationException {
+        for (String key : mandatoryKeys) {
+            if (config.get(key) == null) {
+                throw new ConfigurationException(key, "Missing configuration property: " + key);
+            }
+        }
+    }
+
+    /**
+     * Parses the given value and splits it into a string array, taking care of quoted strings.
+     * 
+     * @param value the value to split to a string array.
+     * @return a string array, never <code>null</code>, but can be empty if the given value was
+     *         <code>null</code> or empty.
+     */
+    private static String[] parseExecutableArguments(final String value) {
+        if (value == null || value.trim().isEmpty()) {
+            return new String[0];
+        }
+        return StringSplitter.split(value);
+    }
+
+    /**
+     * Parses the given executable name to something sensible.
+     * 
+     * @param value the value to parse as executable name.
+     * @return the executable name, never <code>null</code>.
+     * @throws ConfigurationException in case the given value was <code>null</code> or empty.
+     */
+    private static String parseExecutableName(final String value) throws ConfigurationException {
+        if (value == null || value.trim().isEmpty()) {
+            throw new ConfigurationException(EXECUTABLE_NAME, "Invalid executable name!");
+        }
+        return value;
+    }
+
+    /**
+     * Parses the given string value as integer exit value.
+     * 
+     * @param value the string to parse as exit value, can be <code>null</code>.
+     * @return the integer representation of the given string.
+     * @throws ConfigurationException in case the given value is not an integer value, or is an
+     *         invalid value for instance counts.
+     */
+    private static int parseExitValue(String value) throws ConfigurationException {
+        try {
+            return Integer.parseInt(value);
+        }
+        catch (NumberFormatException exception) {
+            throw new ConfigurationException(NORMAL_EXIT_VALUE, "Invalid exit value!");
+        }
+    }
+
+    /**
+     * Parses the given value as OSGi {@link Filter}.
+     * 
+     * @param value the value to parse as filter, cannot be <code>null</code>.
+     * @return the given input value, if it is a valid filter condition.
+     * @throws ConfigurationException in case the given value consists of an invalid filter.
+     */
+    private static String parseFilter(String value) throws ConfigurationException {
+        try {
+            FrameworkUtil.createFilter(value);
+            return value;
+        }
+        catch (InvalidSyntaxException exception) {
+            throw new ConfigurationException(PROCESS_STREAM_LISTENER_FILTER, "Invalid filter syntax! Reason: "
+                + exception.getMessage());
+        }
+    }
+
+    /**
+     * Parses a given string value into a numeric instance count.
+     * 
+     * @param value the string to parse as instance count, can be <code>null</code>.
+     * @return the instance count.
+     * @throws ConfigurationException in case the given value is not an integer value, or is an
+     *         invalid value for instance counts.
+     */
+    private static int parseInstanceCount(final String value) throws ConfigurationException {
+        int instanceCount = -1;
+        try {
+            instanceCount = Integer.parseInt(value);
+        }
+        catch (NumberFormatException exception) {
+            // Ignore, will be picked up below...
+        }
+
+        if (instanceCount < MINIMAL_INSTANCE_COUNT) {
+            throw new ConfigurationException(INSTANCE_COUNT, "Invalid instance count!");
+        }
+        return instanceCount;
+    }
+}

Added: ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/LaunchConfigurationImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/LaunchConfigurationImpl.java?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/LaunchConfigurationImpl.java (added)
+++ ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/LaunchConfigurationImpl.java Thu Apr 19 14:24:36 2012
@@ -0,0 +1,193 @@
+/*
+ * 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.ace.processlauncher.impl;
+
+import java.io.File;
+
+import org.apache.ace.processlauncher.LaunchConfiguration;
+import org.apache.ace.processlauncher.ProcessLifecycleListener;
+import org.apache.ace.processlauncher.ProcessStreamListener;
+
+/**
+ * Denotes a launch configuration, describing what and how a process should be launched.
+ */
+public final class LaunchConfigurationImpl implements LaunchConfiguration {
+    /** The convention is to use zero as normal exit value. */
+    private static final int NORMAL_EXIT_VALUE = 0;
+
+    /**
+     * Denotes the number of instances that will be launched for this configuration.
+     */
+    private final int m_instanceCount;
+    /** Denotes the full path-name to the executable to launch. */
+    private final String m_executableName;
+    /** Denotes the arguments to pass to the executable. */
+    private final String[] m_executableArgs;
+    /**
+     * The process stream listener that should be called for interaction with the process.
+     */
+    private final String m_processStreamListenerFilter;
+    /**
+     * The process lifecycle listener that should be called for the lifecycle changes of the
+     * process.
+     */
+    private final String m_processLifecycleListenerFilter;
+    /** Whether or not we should respawn the process once it died. */
+    private final boolean m_respawnAutomatically;
+    /** Which directory should be set before launching the executable. */
+    private final File m_workingDirectory;
+    /**
+     * What exit-value is to be considered "normal" for this process? By convention, this is 0.
+     */
+    private final int m_normalExitValue;
+
+    /**
+     * Creates a new {@link LaunchConfigurationImpl} instance.
+     * 
+     * @param instanceCount the number of instances to launch, >= 1;
+     * @param workingDirectory the optional working directory to use, can be <code>null</code> if
+     *        the default working directory should be used;
+     * @param executableName the full path-name to the executable to launch, cannot be
+     *        <code>null</code> or empty;
+     * @param executableArgs the optional arguments to pass to the executable, can be
+     *        <code>null</code>;
+     * @param normalExitValue the "normal" exit value to determine whether or not the process has
+     *        terminated normally;
+     * @param processStreamListenerFilter denotes the filter to use to obtain the
+     *        {@link ProcessStreamListener} to redirect the process input/output to, can be
+     *        <code>null</code> if no interaction is desired;
+     * @param processLifecycleListenerFilter denotes the filter to use to obtain the
+     *        {@link ProcessLifecycleListener};
+     * @param respawnAutomatically <code>true</code> if the process should be respawned
+     *        automatically upon non-zero exit codes.
+     */
+    public LaunchConfigurationImpl(int instanceCount, String workingDirectory, String executableName,
+        String[] executableArgs, int normalExitValue, String processStreamListenerFilter,
+        String processLifecycleListenerFilter, boolean respawnAutomatically) {
+        if (instanceCount <= 0) {
+            throw new IllegalArgumentException("Invalid instance count!");
+        }
+        if (executableName == null || executableName.trim().isEmpty()) {
+            throw new IllegalArgumentException("Invalid executable name!");
+        }
+        if (executableArgs == null) {
+            throw new IllegalArgumentException("Invalid executable args!");
+        }
+        m_instanceCount = instanceCount;
+        m_workingDirectory =
+            (workingDirectory == null || workingDirectory.trim().isEmpty()) ? null : new File(workingDirectory);
+        m_executableName = executableName;
+        m_executableArgs = executableArgs;
+        m_normalExitValue = normalExitValue;
+        m_processStreamListenerFilter = processStreamListenerFilter;
+        m_processLifecycleListenerFilter = processLifecycleListenerFilter;
+        m_respawnAutomatically = respawnAutomatically;
+    }
+
+    /**
+     * Creates a new {@link LaunchConfigurationImpl} instance.
+     * 
+     * @param instanceCount the number of instances to launch, >= 1;
+     * @param executableName the full path-name to the executable to launch, cannot be
+     *        <code>null</code> or empty;
+     * @param executableArgs the optional arguments to pass to the executable, can be
+     *        <code>null</code>;
+     * @param processStreamListenerFilter denotes the filter to use to obtain the
+     *        {@link ProcessStreamListener} to redirect the process input/output to, can be
+     *        <code>null</code> if no interaction is desired;
+     * @param respawnAutomatically <code>true</code> if the process should be respawned
+     *        automatically upon non-zero exit codes.
+     */
+    public LaunchConfigurationImpl(int instanceCount, String executableName, String[] executableArgs,
+        String processStreamListenerFilter, boolean respawnAutomatically) {
+        this(instanceCount, null, executableName, executableArgs, NORMAL_EXIT_VALUE, processStreamListenerFilter, null,
+            respawnAutomatically);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String[] getCommandLine() {
+        int size = this.m_executableArgs.length;
+        String[] result = new String[1 + size];
+
+        result[0] = this.m_executableName;
+        if (size > 0) {
+            System.arraycopy(this.m_executableArgs, 0, result, 1, size);
+        }
+
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String[] getExecutableArgs() {
+        return m_executableArgs;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getExecutableName() {
+        return m_executableName;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getInstanceCount() {
+        return m_instanceCount;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getNormalExitValue() {
+        return m_normalExitValue;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public File getWorkingDirectory() {
+        return m_workingDirectory;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getProcessStreamListener() {
+        return m_processStreamListenerFilter;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getProcessLifecycleListener() {
+        return m_processLifecycleListenerFilter;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isRespawnAutomatically() {
+        return m_respawnAutomatically;
+    }
+}

Added: ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/ProcessLauncher.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/ProcessLauncher.java?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/ProcessLauncher.java (added)
+++ ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/ProcessLauncher.java Thu Apr 19 14:24:36 2012
@@ -0,0 +1,318 @@
+/*
+ * 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.ace.processlauncher.impl;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.ace.processlauncher.LaunchConfiguration;
+import org.apache.ace.processlauncher.ProcessLifecycleListener;
+import org.apache.ace.processlauncher.ProcessStreamListener;
+import org.apache.ace.processlauncher.util.InputStreamRedirector;
+
+/**
+ * Denotes a service that can launch a <em>single</em> process and possibly allows interaction with
+ * it.
+ * <p>
+ * If for a process multiple instances should be launched, multiple {@link ProcessLauncher}
+ * instances should be created!
+ * </p>
+ * 
+ * @author Jan Willem Janssen <janwillem.janssen@luminis.eu>
+ */
+public class ProcessLauncher {
+
+    private final LaunchConfiguration m_launchConfiguration;
+
+    private volatile ProcessStreamListener m_processStreamListener;
+    private volatile ProcessLifecycleListener m_processLifecycleListener;
+    private volatile Process m_runningProcess;
+
+    private InputStreamRedirector m_processStdoutRedirector;
+    private InputStreamRedirector m_processStdinRedirector;
+
+    /**
+     * Creates a new {@link ProcessLauncher} instance which redirects all input/output of the
+     * running process to the given streams.
+     * 
+     * @param launchConfiguration the launch configuration to use, cannot be <code>null</code>;
+     * @throws IllegalArgumentException in case the given launch configuration was <code>null</code>
+     *         .
+     */
+    public ProcessLauncher(LaunchConfiguration launchConfiguration) {
+        if (launchConfiguration == null) {
+            throw new IllegalArgumentException("Launch configuration cannot be null!");
+        }
+        m_launchConfiguration = launchConfiguration;
+    }
+
+    /**
+     * Creates a new {@link ProcessLauncher} instance which redirects all input/output of the
+     * running process to the given streams.
+     * 
+     * @param launchConfiguration the launch configuration to use, cannot be <code>null</code>;
+     * @param processStreamListener the input stream to redirect the process input to, can be
+     *        <code>null</code>;
+     * @param processLifecycleListener the output stream to redirect the process output to, can be
+     *        <code>null</code> if the process output is not to be redirected.
+     * @throws IllegalArgumentException in case the given launch configuration was <code>null</code>
+     *         .
+     */
+    public ProcessLauncher(LaunchConfiguration launchConfiguration, ProcessStreamListener processStreamListener,
+        ProcessLifecycleListener processLifecycleListener) {
+        if (launchConfiguration == null) {
+            throw new IllegalArgumentException("Launch configuration cannot be null!");
+        }
+        m_launchConfiguration = launchConfiguration;
+        m_processStreamListener = processStreamListener;
+        m_processLifecycleListener = processLifecycleListener;
+    }
+
+    /**
+     * If the process is finished, returns its exit value.
+     * 
+     * @return the process' exit value, or <code>null</code> if the process is still running.
+     * @see #isAlive()
+     */
+    public Integer getExitValue() {
+        // runningProcess can only be null if #run() is not yet called!
+        if ((m_runningProcess == null) || isAlive()) {
+            return null;
+        }
+
+        return m_runningProcess.exitValue();
+    }
+
+    /**
+     * Returns the launch configuration for this process launcher.
+     * 
+     * @return the launch configuration, never <code>null</code>.
+     */
+    public LaunchConfiguration getLaunchConfiguration() {
+        return m_launchConfiguration;
+    }
+
+    /**
+     * Call to clean up the administration of this process launcher, and to invoke the proper
+     * lifecycle methods on any interested listener.
+     * 
+     * @throws IllegalStateException in case the process is still alive.
+     * @see #isAlive()
+     */
+    public void cleanup() throws IllegalStateException {
+        if (isAlive()) {
+            throw new IllegalStateException("Process is still alive; cannot clean up!");
+        }
+
+        if (m_processLifecycleListener != null) {
+            m_processLifecycleListener.afterProcessEnd(m_launchConfiguration);
+        }
+
+        closeProcessStreamRedirects();
+    }
+
+    /**
+     * Kills any running processes and updates the internal administration (even if the process is
+     * already killed).
+     */
+    public void kill() {
+        if (isAlive()) {
+            // This does a simple kill, which might be ignored by the running
+            // process. In such situations, you should want to do something like
+            // 'kill -9', but this is not easily done in Java (without doing
+            // nasty hacks; see for example:
+            // <http://stackoverflow.com/questions/4912282/java-tool-method-to-force-kill-a-child-process>).
+            m_runningProcess.destroy();
+
+            try {
+                // We don't care for the result...
+                waitForTermination();
+            }
+            catch (InterruptedException exception) {
+                exception.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * Creates a new process from the contained launch configuration and starts it as a new
+     * {@link Process}. After this, it waits until the process is completed.
+     * 
+     * @throws IllegalStateException in case there is already a running process that is not yet
+     *         finished;
+     * @throws IOException if an I/O error occurs during the invocation of the process.
+     */
+    public void run() throws IllegalStateException, IOException {
+        if (isAlive()) {
+            throw new IllegalStateException("Process is still running & alive!");
+        }
+
+        Properties customEnv = null;
+        if (m_processLifecycleListener != null) {
+            customEnv = m_processLifecycleListener.beforeProcessStart(m_launchConfiguration);
+        }
+
+        final ProcessBuilder pb = createProcessBuilder(customEnv);
+
+        // Invoke the actual executable asynchronously...
+        m_runningProcess = pb.start();
+
+        // Make sure we don't overflow any native buffers...
+        redirectProcessStreams(m_runningProcess);
+    }
+
+    /**
+     * Waits until the process is terminated.
+     * 
+     * @return the exit value of the terminated process, or <code>null</code> if the process was
+     *         never started.
+     * @throws InterruptedException in case we're interrupted while waiting for the process to
+     *         terminate.
+     */
+    public final Integer waitForTermination() throws InterruptedException {
+        if (m_runningProcess != null) {
+            final int result = m_runningProcess.waitFor();
+
+            cleanup();
+
+            return result;
+        }
+        return null;
+    }
+
+    /**
+     * Interrupts and closes all process stream redirects.
+     */
+    private void closeProcessStreamRedirects() {
+        // Make sure the redirectors are interrupted as well...
+        if (m_processStdinRedirector != null) {
+            try {
+                m_processStdinRedirector.join(1000);
+            }
+            catch (InterruptedException exception) {
+                exception.printStackTrace();
+            }
+            m_processStdinRedirector = null;
+        }
+        if (m_processStdoutRedirector != null) {
+            try {
+                m_processStdoutRedirector.join(1000);
+            }
+            catch (InterruptedException exception) {
+                exception.printStackTrace();
+            }
+            m_processStdoutRedirector = null;
+        }
+    }
+
+    /**
+     * Creates the process builder and ensures all of its settings are correct to be launched.
+     * 
+     * @param customEnv the custom environment settings of the to-be-created process, can be
+     *        <code>null</code> if no additional environment settings are desired.
+     * @return a {@link ProcessBuilder} instance, never <code>null</code>.
+     */
+    private ProcessBuilder createProcessBuilder(Properties customEnv) {
+        ProcessBuilder pb = new ProcessBuilder(m_launchConfiguration.getCommandLine());
+        // Make sure we grab both stdout *and* stderr!
+        pb.redirectErrorStream(true /* redirectErrorStream */);
+        pb.directory(m_launchConfiguration.getWorkingDirectory());
+        // We do *not* override/set a new environment for this process. This can
+        // be easily done with shell scripting as well, if desired...
+        if (customEnv != null) {
+            Map<String, String> env = pb.environment();
+            for (Object obj : customEnv.keySet()) {
+                String key = (String) obj;
+                env.put(key, customEnv.getProperty(key));
+            }
+        }
+        return pb;
+    }
+
+    /**
+     * Factory method for creating a {@link InputStreamRedirector} instance that redirects the stdin
+     * of the given process to the contained input stream (if available).
+     * 
+     * @param process the {@link Process} to create the input stream redirector for, can be
+     *        <code>null</code>.
+     * @return an input stream redirector instance, never <code>null</code>.
+     */
+    private InputStreamRedirector createStdinRedirector(Process process) {
+        if (m_processStreamListener != null && m_processStreamListener.wantsStdin()) {
+            m_processStreamListener.setStdin(m_launchConfiguration, process.getOutputStream());
+        }
+        return null;
+    }
+
+    /**
+     * Factory method for creating a {@link InputStreamRedirector} instance that redirects the
+     * stdout of the given process to either the contained output stream, or to '/dev/null'.
+     * 
+     * @param process the {@link Process} to create the input stream redirector for, cannot be
+     *        <code>null</code>.
+     * @return an input stream redirector instance, never <code>null</code>.
+     */
+    private InputStreamRedirector createStdoutRedirector(Process process) {
+        if (m_processStreamListener != null && m_processStreamListener.wantsStdout()) {
+            m_processStreamListener.setStdout(m_launchConfiguration, process.getInputStream());
+
+            return null;
+        }
+        // Redirect to /dev/null!
+        return new InputStreamRedirector(process.getInputStream());
+    }
+
+    /**
+     * Returns an indication whether or not the process is still alive and running, or already
+     * terminated.
+     * 
+     * @return <code>true</code> if the process is still running, <code>false</code> otherwise.
+     */
+    private boolean isAlive() {
+        try {
+            if (m_runningProcess != null) {
+                // If the process is still alive it'll throw an exception...
+                m_runningProcess.exitValue();
+            }
+
+            // No longer alive...
+            return false;
+        }
+        catch (IllegalThreadStateException e) {
+            return true;
+        }
+    }
+
+    /**
+     * Redirects the stdin/stdout streams of the given process.
+     * 
+     * @param process the process to redirect the streams for, cannot be <code>null</code>.
+     */
+    private void redirectProcessStreams(Process process) {
+        m_processStdoutRedirector = createStdoutRedirector(process);
+        if (m_processStdoutRedirector != null) {
+            m_processStdoutRedirector.start();
+        }
+        m_processStdinRedirector = createStdinRedirector(process);
+        if (m_processStdinRedirector != null) {
+            m_processStdinRedirector.start();
+        }
+    }
+}

Added: ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/ProcessLauncherServiceImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/ProcessLauncherServiceImpl.java?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/ProcessLauncherServiceImpl.java (added)
+++ ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/ProcessLauncherServiceImpl.java Thu Apr 19 14:24:36 2012
@@ -0,0 +1,239 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ace.processlauncher.impl;
+
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.ace.processlauncher.LaunchConfiguration;
+import org.apache.ace.processlauncher.ProcessLauncherService;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.osgi.service.log.LogService;
+
+/**
+ * Provides a managed service factory for launching processes based on a certain launch
+ * configuration.
+ */
+public class ProcessLauncherServiceImpl implements ManagedServiceFactory, ProcessLauncherService {
+
+    private static final String NAME = "Launcher Service Factory";
+
+    /** Contains all current launch configurations. */
+    private final Map<String, LaunchConfiguration> m_launchConfigurations = new HashMap<String, LaunchConfiguration>();
+    /** Manages all running processes for us. */
+    private volatile ProcessManager m_processManager;
+    private volatile LogService m_logger;
+
+    /**
+     * Returns whether or not a given PID is contained as launch configuration.
+     * 
+     * @param pid the PID to test for, cannot be <code>null</code>.
+     * @return <code>true</code> if the given PID exists as launch configuration, <code>false</code>
+     *         otherwise.
+     */
+    public boolean containsPid(String pid) {
+        synchronized (m_launchConfigurations) {
+            return m_launchConfigurations.containsKey(pid);
+        }
+    }
+
+    /**
+     * Called when a launch configuration with the given PID is removed from the config-admin.
+     * 
+     * @param pid the service PID that is to be deleted, never <code>null</code>.
+     * @see org.osgi.service.cm.ManagedServiceFactory#deleted(java.lang.String)
+     */
+    public final void deleted(final String pid) {
+        LaunchConfiguration oldLaunchConfig = null;
+
+        synchronized (m_launchConfigurations) {
+            oldLaunchConfig = m_launchConfigurations.remove(pid);
+        }
+
+        if (oldLaunchConfig != null) {
+            terminateProcesses(pid, oldLaunchConfig);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getLaunchConfigurationCount() {
+        synchronized (m_launchConfigurations) {
+            return m_launchConfigurations.size();
+        }
+    }
+
+    /**
+     * Returns the symbolic name for this service factory.
+     * 
+     * @return a symbolic name, never <code>null</code>.
+     * @see org.osgi.service.cm.ManagedServiceFactory#getName()
+     */
+    public final String getName() {
+        return NAME;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getRunningProcessCount() throws IOException {
+        if (m_processManager == null) {
+            return 0;
+        }
+        return m_processManager.getRunningProcessesCount();
+    }
+
+    /**
+     * Sets the logging service.
+     * 
+     * @param logger the log service to set, can be <code>null</code>.
+     */
+    public void setLogger(LogService logger) {
+        m_logger = logger;
+    }
+
+    /**
+     * Sets the process manager.
+     * 
+     * @param processManager the process manager to set, cannot be <code>null</code>.
+     */
+    public void setProcessManager(ProcessManager processManager) {
+        m_processManager = processManager;
+    }
+
+    /**
+     * Shuts down this service and terminates all running processes.
+     * 
+     * @throws IOException in case of problems shutting down processes.
+     */
+    public void shutdown() throws IOException {
+        synchronized (m_launchConfigurations) {
+            for (Map.Entry<String, LaunchConfiguration> entry : m_launchConfigurations.entrySet()) {
+                terminateProcesses(entry.getKey(), entry.getValue());
+            }
+            m_launchConfigurations.clear();
+        }
+        // Shut down the process manager as well...
+        m_processManager.shutdown();
+    }
+
+    /**
+     * Called when a new configuration is added, or when an existing configuration is updated.
+     * 
+     * @param pid the service PID that is added/updated, never <code>null</code>;
+     * @param config the service configuration that is added/updated, can be <code>null</code>.
+     * @throws ConfigurationException in case the given service configuration is incorrect.
+     * @see org.osgi.service.cm.ManagedServiceFactory#updated(java.lang.String,
+     *      java.util.Dictionary)
+     */
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public final void updated(final String pid, final Dictionary config) throws ConfigurationException {
+        LaunchConfiguration oldLaunchConfig = null;
+        LaunchConfiguration newLaunchConfig = null;
+
+        if (config != null) {
+            newLaunchConfig = createLaunchConfiguration(config);
+        }
+
+        synchronized (m_launchConfigurations) {
+            oldLaunchConfig = m_launchConfigurations.put(pid, newLaunchConfig);
+        }
+
+        if (oldLaunchConfig != null) {
+            terminateProcesses(pid, oldLaunchConfig);
+        }
+        if (newLaunchConfig != null) {
+            launchProcesses(pid, newLaunchConfig);
+        }
+    }
+
+    /**
+     * Converts the given properties to a complete launch configuration.
+     * 
+     * @param config the properties to convert to a launch configuration, cannot be
+     *        <code>null</code>.
+     * @return a {@link LaunchConfiguration} instance, never <code>null</code>.
+     * @throws ConfigurationException in case an invalid configuration property was found.
+     */
+    private LaunchConfiguration createLaunchConfiguration(final Dictionary<Object, Object> config)
+        throws ConfigurationException {
+        return LaunchConfigurationFactory.create(config);
+    }
+
+    /**
+     * Creates a process identifier based on the given identifier and value.
+     * 
+     * @param id the identifier part;
+     * @param value the value part.
+     * @return a process identifier, as String, never <code>null</code>.
+     */
+    private String createPID(final String id, int value) {
+        return String.format("%s-%d", id, value);
+    }
+
+    /**
+     * Executes a given launch configuration.
+     * 
+     * @param id the identifier of the launch configuration to execute, cannot be <code>null</code>
+     *        or empty;
+     * @param launchConfiguration the launch configuration to execute, cannot be <code>null</code>.
+     */
+    private void launchProcesses(final String id, final LaunchConfiguration launchConfiguration) {
+
+        int count = launchConfiguration.getInstanceCount();
+        while (count-- > 0) {
+            String pid = createPID(id, count);
+            try {
+                m_processManager.launch(pid, launchConfiguration);
+
+                m_logger.log(LogService.LOG_DEBUG, "Launched instance #" + count + " of process " + pid);
+            }
+            catch (IOException e) {
+                m_logger.log(LogService.LOG_WARNING, "Process failed to launch!", e);
+            }
+        }
+    }
+
+    /**
+     * Cleans up & terminates all processes of a given launch configuration.
+     * 
+     * @param id the identifier of the launch configuration to execute, cannot be <code>null</code>
+     *        or empty;
+     * @param launchConfiguration the launch configuration to clean up, cannot be <code>null</code>.
+     */
+    private void terminateProcesses(final String id, final LaunchConfiguration launchConfiguration) {
+
+        int count = launchConfiguration.getInstanceCount();
+        while (count-- > 0) {
+            String pid = createPID(id, count);
+            try {
+                m_processManager.terminate(pid);
+
+                m_logger.log(LogService.LOG_DEBUG, "Terminated instance #" + count + " of process " + pid);
+            }
+            catch (IOException e) {
+                m_logger.log(LogService.LOG_WARNING, "Process failed to terminate!", e);
+            }
+        }
+    }
+}

Added: ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/ProcessManager.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/ProcessManager.java?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/ProcessManager.java (added)
+++ ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/ProcessManager.java Thu Apr 19 14:24:36 2012
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ace.processlauncher.impl;
+
+import java.io.IOException;
+
+import org.apache.ace.processlauncher.LaunchConfiguration;
+
+/**
+ * Provides a process manager service, which is able to launch a process (or multiple processes).
+ */
+public interface ProcessManager {
+
+    /**
+     * Returns the number of currently running processes.
+     * <p>
+     * As a side effect, all completed processes are removed from the internal administration.
+     * </p>
+     * 
+     * @return a running process count.
+     * @throws IOException in case of I/O problems.
+     */
+    int getRunningProcessesCount() throws IOException;
+
+    /**
+     * Launches a new process.
+     * 
+     * @param pid the PID of the process to launch, used for bookkeeping;
+     * @param launchConfiguration the launch configuration to use for the process to be launched.
+     * @throws IOException in case of I/O problems during the launch of the process.
+     */
+    void launch(final String pid, final LaunchConfiguration launchConfiguration) throws IOException;
+
+    /**
+     * Terminates all managed processes and shuts down this process manager.
+     * 
+     * @throws IOException in case of I/O problems during the launch of the process.
+     */
+    void shutdown() throws IOException;
+
+    /**
+     * Terminates a running process.
+     * 
+     * @param pid the PID of the process to terminate, used for bookkeeping.
+     * @throws IOException in case of I/O problems during the launch of the process.
+     */
+    void terminate(final String pid) throws IOException;
+
+}

Added: ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/ProcessManagerImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/ProcessManagerImpl.java?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/ProcessManagerImpl.java (added)
+++ ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/ProcessManagerImpl.java Thu Apr 19 14:24:36 2012
@@ -0,0 +1,301 @@
+/*
+ * 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.ace.processlauncher.impl;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ace.processlauncher.LaunchConfiguration;
+import org.apache.ace.processlauncher.ProcessLifecycleListener;
+import org.apache.ace.processlauncher.ProcessStreamListener;
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.service.log.LogService;
+
+/**
+ * Manager for launched processes.
+ */
+public class ProcessManagerImpl implements ProcessManager {
+
+    private final Map<String, ProcessLauncher> m_runningProcesses;
+
+    private volatile DependencyManager m_dependencyManager;
+    private volatile ProcessStateUpdater m_reaper;
+    private volatile LogService m_logger;
+
+    /**
+     * Creates a new {@link ProcessManagerImpl} instance.
+     */
+    public ProcessManagerImpl() {
+        m_runningProcesses = new HashMap<String, ProcessLauncher>();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getRunningProcessesCount() throws IOException {
+        int result = 0;
+        synchronized (m_runningProcesses) {
+            updateAdministration();
+            result = m_runningProcesses.size();
+        }
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void launch(final String pid, final LaunchConfiguration launchConfiguration) throws IOException {
+        // If this is the first time we're being called, make sure there's a
+        // reaper task up and running...
+        if (m_reaper == null || !m_reaper.isAlive()) {
+            m_reaper = new ProcessStateUpdater();
+            m_reaper.start();
+        }
+
+        // Create the process launcher service...
+        ProcessLauncher launcher = createProcessLauncher(launchConfiguration);
+
+        // Update our administration...
+        ProcessLauncher oldProcess = null;
+        synchronized (m_runningProcesses) {
+            oldProcess = m_runningProcesses.put(pid, launcher);
+        }
+
+        // Clean up any old processes...
+        killProcess(oldProcess);
+        // Submit it for execution...
+        launcher.run();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void shutdown() throws IOException {
+        synchronized (m_runningProcesses) {
+            // Cancel/kill all ongoing processes...
+            for (ProcessLauncher launcher : m_runningProcesses.values()) {
+                killProcess(launcher);
+            }
+            m_runningProcesses.clear();
+        }
+
+        if (m_reaper != null) {
+            m_reaper.interrupt();
+            try {
+                m_reaper.join();
+            }
+            catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+            }
+            finally {
+                m_reaper = null;
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void terminate(final String pid) {
+        ProcessLauncher launcher = null;
+        synchronized (m_runningProcesses) {
+            launcher = m_runningProcesses.remove(pid);
+        }
+        killProcess(launcher);
+    }
+
+    /**
+     * Updates the administration by cleaning up all future's that are finished.
+     * 
+     * @throws IOException in case of I/O problems.
+     */
+    final void updateAdministration() throws IOException {
+        synchronized (m_runningProcesses) {
+            List<String> pids = new ArrayList<String>(m_runningProcesses.keySet());
+            for (String pid : pids) {
+                ProcessLauncher launcher = m_runningProcesses.get(pid);
+                if (launcher.getExitValue() != null) {
+
+                    String logLine =
+                        String.format("Process %s (%s) terminated with code %d." + " Removing it from administration.",
+                            pid, launcher.getLaunchConfiguration().getExecutableName(), launcher.getExitValue());
+                    m_logger.log(LogService.LOG_DEBUG, logLine);
+
+                    launcher.cleanup();
+
+                    m_runningProcesses.remove(pid);
+                    // Take care of the termination; should it be relaunched?!
+                    if (processNeedsToBeRespawned(pid, launcher)) {
+                        launch(pid, launcher.getLaunchConfiguration());
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Factory method for creating a {@link ProcessLauncher} instance.
+     * 
+     * @param launchConfiguration the launch configuration to create a launch configuration for,
+     *        cannot be <code>null</code>.
+     * @return a new {@link ProcessLauncher} instance, never <code>null</code>.
+     * @throws IOException in case the {@link ProcessLauncher} failed to instantiate.
+     */
+    private ProcessLauncher createProcessLauncher(LaunchConfiguration launchConfiguration) throws IOException {
+        ProcessLauncher processLauncher = new ProcessLauncher(launchConfiguration);
+
+        String lcFilter = launchConfiguration.getProcessLifecycleListener();
+        String psFilter = launchConfiguration.getProcessStreamListener();
+
+        // Create the proper service dependencies for the process launcher...
+        if (lcFilter != null || psFilter != null) {
+            Component comp = m_dependencyManager.createComponent().setImplementation(processLauncher);
+
+            if (psFilter != null) {
+                comp.add(m_dependencyManager.createServiceDependency()
+                    .setService(ProcessStreamListener.class, psFilter).setRequired(false));
+            }
+
+            if (lcFilter != null) {
+                comp.add(m_dependencyManager.createServiceDependency()
+                    .setService(ProcessLifecycleListener.class, lcFilter).setRequired(false));
+            }
+
+            m_dependencyManager.add(comp);
+        }
+
+        return processLauncher;
+    }
+
+    /**
+     * Determines whether or not the given exit value is "normal" indicating a successful or
+     * non-successful termination.
+     * 
+     * @param exitValue the process exit value, as integer value, can be <code>null</code>.
+     * @return <code>true</code> if the process is non-successfully terminated, <code>false</code>
+     *         if the processes terminated successfully.
+     */
+    private boolean isNonSuccessfullyTerminated(LaunchConfiguration config, Integer exitValue) {
+        return (exitValue != null) && (config.getNormalExitValue() != exitValue);
+    }
+
+    /**
+     * Cancels a given future, if it is not already completed its task.
+     * 
+     * @param launcher the process launcher to cancel, can be <code>null</code> in which case this
+     *        method does nothing.
+     */
+    private void killProcess(final ProcessLauncher launcher) {
+        if (launcher != null) {
+            String logLine =
+                String.format("Killing process (%s)...", launcher.getLaunchConfiguration().getExecutableName());
+            m_logger.log(LogService.LOG_INFO, logLine);
+
+            launcher.kill();
+        }
+    }
+
+    /**
+     * Handles a given terminated process, which might need to be relaunched if it is not cleanly
+     * terminated for example.
+     * 
+     * @param pid the PID of the process that was launched, cannot be <code>null</code>;
+     * @param launcher the terminated process to handle, cannot be <code>null</code>.
+     * @throws IOException in case of I/O problems during the respawn of a terminated process.
+     */
+    private boolean processNeedsToBeRespawned(String pid, ProcessLauncher launcher) throws IOException {
+        LaunchConfiguration config = launcher.getLaunchConfiguration();
+        Integer exitValue = launcher.getExitValue();
+
+        // Is the process non-successfully terminated?
+        if (isNonSuccessfullyTerminated(config, exitValue)) {
+            // If so, does it need to be respawned?
+            if (config.isRespawnAutomatically()) {
+                // We need to respawn the process automatically!
+                String logLine =
+                    String.format("Process %s (%s) terminated with value %d;" + " respawning it as requested...", pid,
+                        config.getExecutableName(), exitValue);
+                m_logger.log(LogService.LOG_INFO, logLine);
+
+                // Simply relaunch the process again...
+                return true;
+            }
+            else {
+                // Don't bother restarting the process...
+                String logLine =
+                    String.format("Process %s (%s) terminated with value %d.", pid, config.getExecutableName(),
+                        exitValue);
+                m_logger.log(LogService.LOG_INFO, logLine);
+            }
+        }
+        else {
+            // Process ended normally...
+            String logLine = String.format("Process %s (%s) terminated normally.", pid, config.getExecutableName());
+            m_logger.log(LogService.LOG_INFO, logLine);
+        }
+
+        return false;
+    }
+
+    /**
+     * Ensures that periodically the completed processes are removed from the administration.
+     */
+    final class ProcessStateUpdater extends Thread {
+        /**
+         * The number of milliseconds to wait before updating the status of all running processes.
+         */
+        private static final int DELAY = 100;
+
+        /**
+         * Creates a new {@link ProcessStateUpdater} instance.
+         */
+        public ProcessStateUpdater() {
+            super("Process state update thread");
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void run() {
+            while (!Thread.interrupted()) {
+                try {
+                    Thread.sleep(DELAY);
+
+                    m_logger.log(LogService.LOG_DEBUG, "Updating process administration...");
+
+                    // Update the administration...
+                    updateAdministration();
+                }
+                catch (InterruptedException e) {
+                    // Update the current thread's administration!
+                    Thread.currentThread().interrupt();
+                }
+                catch (IOException e) {
+                    m_logger.log(LogService.LOG_WARNING, "Respawn failed!", e);
+                }
+            }
+        }
+    }
+
+}



Mime
View raw message