You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ace.apache.org by ma...@apache.org on 2012/04/19 16:24:38 UTC

svn commit: r1327960 [2/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...

Added: ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/StringSplitter.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/StringSplitter.java?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/StringSplitter.java (added)
+++ ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/impl/StringSplitter.java Thu Apr 19 14:24:36 2012
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ace.processlauncher.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Splits a string on spaces and puts them into an array, taking care of single and double quotes.
+ * Escaping quotes is allowed by prefixing them with a single backslash.
+ */
+public final class StringSplitter {
+
+    private static final char ESCAPE = '\\';
+    private static final char SINGLE_QUOTE = '\'';
+    private static final char DOUBLE_QUOTE = '"';
+    private static final char SPACE = ' ';
+    private static final char TAB = '\t';
+
+    /**
+     * Creates a new StringSplitter instance, never used.
+     */
+    private StringSplitter() {
+        // No-op
+    }
+
+    /**
+     * Splits a given input on whitespace (= tabs and/or spaces). The backslash character can be
+     * used to escape stuff, for example to avoid splitting on certain spaces.
+     * 
+     * @param input the input to split, may be <code>null</code>.
+     * @return an empty array if the given input was <code>null</code> or empty, otherwise the split
+     *         results, never <code>null</code>.
+     */
+    public static String[] split(String input) {
+        return split(input, true /* includeQuotes */);
+    }
+
+    /**
+     * Splits a given input on whitespace (= tabs and/or spaces). The backslash character can be
+     * used to escape stuff, for example to avoid splitting on certain spaces.
+     * 
+     * @param input the input to split, may be <code>null</code>;
+     * @param includeQuotes <code>true</code> if quotes should be included in the output,
+     *        <code>false</code> to omit them in the output unless they are escaped.
+     * @return an empty array if the given input was <code>null</code> or empty, otherwise the split
+     *         results, never <code>null</code>.
+     */
+    public static String[] split(String input, boolean includeQuotes) {
+        if (input == null || input.trim().isEmpty()) {
+            return new String[0];
+        }
+
+        List<String> result = new ArrayList<String>();
+
+        State state = State.NORMAL;
+        boolean escapeSeen = false;
+        StringBuilder token = new StringBuilder();
+
+        for (int i = 0; i < input.length(); i++) {
+            char ch = input.charAt(i);
+
+            switch (ch) {
+                case ESCAPE:
+                    if (!escapeSeen) {
+                        escapeSeen = true;
+                    }
+                    else {
+                        // Escaped escape character...
+                        token.append(ch);
+                        escapeSeen = false;
+                    }
+                    break;
+
+                case SINGLE_QUOTE:
+                case DOUBLE_QUOTE:
+                    if (!escapeSeen) {
+                        if ((ch == DOUBLE_QUOTE && state.isInDoubleQuote())
+                            || (ch == SINGLE_QUOTE && state.isInSingleQuote())) {
+                            state = State.NORMAL;
+                        }
+                        else if (state.isNormal()) {
+                            state = (ch == DOUBLE_QUOTE) ? State.IN_DOUBLE_QUOTE : State.IN_SINGLE_QUOTE;
+                        }
+                    }
+                    if (includeQuotes) {
+                        token.append(ch);
+                    }
+                    escapeSeen = false;
+                    break;
+
+                case SPACE:
+                case TAB:
+                    if (state.isNormal() && !escapeSeen) {
+                        // Whitespace seen: emit a new token and start over...
+                        result.add(token.toString());
+                        token.setLength(0);
+                        break;
+                    }
+                    // Fallthrough!
+                default:
+                    token.append(ch);
+                    escapeSeen = false;
+                    break;
+            }
+        }
+
+        if (token.length() > 0) {
+            result.add(token.toString());
+        }
+
+        return result.toArray(new String[result.size()]);
+    }
+
+    private static enum State {
+        NORMAL, IN_SINGLE_QUOTE, IN_DOUBLE_QUOTE;
+
+        public boolean isInDoubleQuote() {
+            return this == IN_DOUBLE_QUOTE;
+        }
+
+        public boolean isInSingleQuote() {
+            return this == IN_SINGLE_QUOTE;
+        }
+
+        public boolean isNormal() {
+            return this == NORMAL;
+        }
+    }
+}

Added: ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/osgi/Activator.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/osgi/Activator.java?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/osgi/Activator.java (added)
+++ ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/osgi/Activator.java Thu Apr 19 14:24:36 2012
@@ -0,0 +1,74 @@
+/*
+ * 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.osgi;
+
+import java.util.Properties;
+
+import org.apache.ace.processlauncher.ProcessLauncherService;
+import org.apache.ace.processlauncher.impl.ProcessLauncherServiceImpl;
+import org.apache.ace.processlauncher.impl.ProcessManager;
+import org.apache.ace.processlauncher.impl.ProcessManagerImpl;
+import org.apache.felix.dm.DependencyActivatorBase;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.osgi.service.log.LogService;
+
+/**
+ * Provides the actual bundle activator (based on Felix Dependency Manager).
+ */
+public class Activator extends DependencyActivatorBase {
+
+    private ProcessLauncherServiceImpl m_processLauncherService;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void destroy(BundleContext context, DependencyManager manager) throws Exception {
+        m_processLauncherService.shutdown();
+        m_processLauncherService = null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void init(BundleContext context, DependencyManager manager) throws Exception {
+        // In the future we might want to publish this as an external service...
+        ProcessManager processManager = new ProcessManagerImpl();
+
+        manager.add(createComponent().setImplementation(processManager).add(
+            createServiceDependency().setService(LogService.class).setRequired(false)));
+
+        // We publish the service under multiple interfaces...
+        String[] interfaces = { ManagedServiceFactory.class.getName(), ProcessLauncherService.class.getName() };
+
+        // Service properties
+        Properties props = new Properties();
+        props.put(Constants.SERVICE_PID, ProcessLauncherService.PID);
+
+        m_processLauncherService = new ProcessLauncherServiceImpl();
+        m_processLauncherService.setProcessManager(processManager);
+
+        manager.add(createComponent().setInterface(interfaces, props).setImplementation(m_processLauncherService)
+            .add(createServiceDependency().setService(LogService.class).setRequired(false)));
+    }
+}

Added: ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/util/InputStreamRedirector.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/util/InputStreamRedirector.java?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/util/InputStreamRedirector.java (added)
+++ ace/trunk/ace-processlauncher/src/main/java/org/apache/ace/processlauncher/util/InputStreamRedirector.java Thu Apr 19 14:24:36 2012
@@ -0,0 +1,129 @@
+/*
+ * 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.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+/**
+ * Redirects an {@link InputStream} by reading all of its contents and writing this to a given
+ * {@link OutputStream}.
+ */
+public class InputStreamRedirector extends Thread {
+
+    private static final boolean DEBUG = Boolean.parseBoolean(System.getProperty(
+        "org.apache.ace.processlauncher.debug", "false"));
+
+    private static final int BUF_SIZE = 32;
+
+    private final InputStream m_inputStream;
+    private final OutputStream m_outputStream;
+
+    /**
+     * Creates a new {@link InputStreamRedirector} instance that redirects to /dev/null. Essentially
+     * this means that all read data is immediately thrown away.
+     * 
+     * @param inputStream the {@link InputStream} that is to be read, cannot be <code>null</code>.
+     */
+    public InputStreamRedirector(final InputStream inputStream) {
+        this(inputStream, null);
+    }
+
+    /**
+     * Creates a new {@link InputStreamRedirector} instance that redirects to a given output stream.
+     * 
+     * @param inputStream the {@link InputStream} that is to be redirected, cannot be
+     *        <code>null</code>;
+     * @param outputStream the {@link OutputStream} that is to be redirected to, may be
+     *        <code>null</code> in which case the input stream is redirected to nothing (/dev/null).
+     */
+    public InputStreamRedirector(final InputStream inputStream, final OutputStream outputStream) {
+        this.m_inputStream = inputStream;
+        this.m_outputStream = outputStream;
+
+        setName("InputStreamRedirector-" + getId());
+    }
+
+    /**
+     * Reads all bytes from the contained input stream and (optionally) writes it to the contained
+     * output stream.
+     */
+    @Override
+    public void run() {
+        try {
+            final byte[] buf = new byte[BUF_SIZE];
+
+            while (!Thread.currentThread().isInterrupted()) {
+                int read = m_inputStream.read(buf, 0, buf.length);
+                if (read < 0) {
+                    // EOF; break out of our main loop...
+                    break;
+                }
+
+                if (DEBUG) {
+                    System.out.write(buf, 0, read);
+                }
+
+                if (m_outputStream != null) {
+                    m_outputStream.write(buf, 0, read);
+                }
+            }
+        }
+        catch (IOException ioe) {
+            handleException(ioe);
+        }
+        finally {
+            cleanUp();
+        }
+    }
+
+    /**
+     * Clean up of the contained output stream by closing it properly.
+     */
+    private void cleanUp() {
+        try {
+            if (this.m_outputStream != null) {
+                // Ensure the last few bits are written...
+                this.m_outputStream.flush();
+                // Close the output stream and we're done...
+                this.m_outputStream.close();
+            }
+        }
+        catch (IOException e) {
+            // Ignore; we'll assume it is being handled elsewhere...
+        }
+    }
+
+    /**
+     * Handles the given I/O exception by either printing it to the contained output stream,
+     * otherwise to the stderr stream.
+     * 
+     * @param exception the exception to handle, cannot be <code>null</code>.
+     */
+    private void handleException(final IOException exception) {
+        if (this.m_outputStream != null) {
+            exception.printStackTrace(new PrintStream(this.m_outputStream));
+        }
+        else {
+            exception.printStackTrace(System.out);
+        }
+    }
+}

Added: ace/trunk/ace-processlauncher/src/main/resources/LICENSE
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/src/main/resources/LICENSE?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/src/main/resources/LICENSE (added)
+++ ace/trunk/ace-processlauncher/src/main/resources/LICENSE Thu Apr 19 14:24:36 2012
@@ -0,0 +1,395 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+XStream License
+---------------
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of
+conditions and the following disclaimer. Redistributions in binary form must reproduce
+the above copyright notice, this list of conditions and the following disclaimer in
+the documentation and/or other materials provided with the distribution.
+
+Neither the name of XStream nor the names of its contributors may be used to endorse
+or promote products derived from this software without specific prior written
+permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
+XPP3 License
+------------
+* The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 2000 The Apache Software Foundation.  All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Apache" and "Apache Software Foundation" must
+ *    not be used to endorse or promote products derived from this
+ *    software without prior written permission. For written
+ *    permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache",
+ *    nor may "Apache" appear in their name, without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+
+kxml License
+---------------
+Copyright (c) 2002,2003, Stefan Haustein, Oberhausen, Rhld., Germany
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The  above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
+
+knopflerfish License
+--------------------
+Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following
+  conditions are met:
+ 
+  - Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+ 
+  - Redistributions in binary form must reproduce the above
+    copyright notice, this list of conditions and the following
+    disclaimer in the documentation and/or other materials
+    provided with the distribution.
+ 
+  - Neither the name of the KNOPFLERFISH project nor the names of its
+    contributors may be used to endorse or promote products derived
+    from this software without specific prior written permission.
+ 
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+  OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Jersey and JSR-250 License
+
+    Copyright (c) 2010 Oracle and/or its affiliates. All rights reserved.
+
+    The contents of this file are subject to the terms of either the GNU
+    General Public License Version 2 only ("GPL") or the Common Development
+    and Distribution License("CDDL") (collectively, the "License").  You
+    may not use this file except in compliance with the License.  You can
+    obtain a copy of the License at
+    https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
+    or packager/legal/LICENSE.txt.  See the License for the specific
+    language governing permissions and limitations under the License.
+
+    When distributing the software, include this License Header Notice in each
+    file and include the License file at packager/legal/LICENSE.txt.
+
+    GPL Classpath Exception:
+    Oracle designates this particular file as subject to the "Classpath"
+    exception as provided by Oracle in the GPL Version 2 section of the License
+    file that accompanied this code.
+
+    Modifications:
+    If applicable, add the following below the License Header, with the fields
+    enclosed by brackets [] replaced by your own identifying information:
+    "Portions Copyright [year] [name of copyright owner]"
+
+    Contributor(s):
+    If you wish your version of this file to be governed by only the CDDL or
+    only the GPL Version 2, indicate your decision by adding "[Contributor]
+    elects to include this software in this distribution under the [CDDL or GPL
+    Version 2] license."  If you don't indicate a single choice of license, a
+    recipient has the option to distribute your version of this file under
+    either the CDDL, the GPL Version 2 or to extend the choice of license to
+    its licensees as provided above.  However, if you add GPL Version 2 code
+    and therefore, elected the GPL Version 2 license, then the option applies
+    only if the new code is made subject to such option by the copyright
+    holder.
+
+Jcraft Jsch License
+
+Copyright (c) 2002-2010 Atsuhiko Yamanaka, JCraft,Inc. 
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+  1. Redistributions of source code must retain the above copyright notice,
+     this list of conditions and the following disclaimer.
+
+  2. Redistributions in binary form must reproduce the above copyright 
+     notice, this list of conditions and the following disclaimer in 
+     the documentation and/or other materials provided with the distribution.
+
+  3. The names of the authors may not be used to endorse or promote products
+     derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
+INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Added: ace/trunk/ace-processlauncher/src/main/resources/README
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/src/main/resources/README?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/src/main/resources/README (added)
+++ ace/trunk/ace-processlauncher/src/main/resources/README Thu Apr 19 14:24:36 2012
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Introduction
+============
+This project allows external processes to be started from ACE by means of 
+configurations. A typical use case would be to provision this bundle to an 
+empty OSGi-container, push one or more configurations to it, which, in their 
+turn, launch one or more processes.
+
+
+Usage
+=====
+In its minimal form, the configuration that should be supplied to the service
+factory with PID "org.apache.ace.processlauncher" is:
+
+  executable.name = /path/to/executable
+  executable.args = -arg1 -arg2=foo -arg3=/qux/quu
+
+This configuration will cause a single instance of "executable" to be started,
+with the arguments "-arg1 -arg2=foo -arg3=/qux/quu". If the process finished,
+no new instance will be started.
+
+A complete configuration looks like:
+
+  # Denotes how many instances of the process should be started, >= 0.
+  # Optional, defaults to 1.
+  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/executable
+  # The arguments for the executable. Mandatory, no default.
+  executable.args = -arg1 -arg2
+  # The OSGi-filter clause that should resolve to a ProcessStreamListener 
+  # service-instance. When given, it will be used to provide access to the 
+  # launched process' stdin/stdout streams. Optional, defaults to an empty/no 
+  # filter.
+  executable.processStreamListener = (&(objectClass=eu.luminis.technologies.MyStreamListener)(foo=bar))
+  # 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. Option, defaults to an empty/no 
+  # filter.
+  executable.processLifecycleListener = (objectClass=eu.luminis.technologies.MyLifecycleListener)
+  # 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
+
+While most of the configuration options are rather obvious, some of them are
+explained in more detailed below.
+
+executable.processStreamListener
+--------------------------------
+This option allows you to provide an OSGi-filter that should resolve to an 
+instance of org.apache.ace.processlauncher.ProcessStreamListener. When given,
+this process stream listener will be used to allow interaction with the 
+launched process. With this, for example, you can start a Felix instance with
+shell bundles and interact through shell commands. 
+
+Note 1: that if you interact with processes this way, you need to ensure that 
+the standard output of the process is consumed to avoid native buffers from 
+filling up. If you do not care about the process' output, you can either let 
+ProcessStreamListener#wantsStdout return 'false', or use an instance of 
+org.apache.ace.processlauncher.util.InputStreamRedirector to consume it for 
+you. See [1] for a typical example and background information.
+
+Note 2: some processes only terminate when their standard input is closed or 
+receives an EOF. This means that you need to close the standard input stream
+of the process *explicitly* yourself, should you choose to interact with a
+process.
+
+executable.processLifecycleListener
+-----------------------------------
+This option allows you to provide an OSGi-filter that should resolve to an 
+instance of org.apache.ace.processlauncher.ProcessLifecycleListener. When 
+given, this lifecycle listener will be called right *before* a process instance
+is started, and right *after* a process is terminated.
+
+You can use this functionality to set up a proper environment for your to-be-
+launched process, for example, by creating working directories, patching some
+configuration files, and so on. It also allows you to adjust the environment
+settings of the process, for example to let it point to a newly created 
+temporary directory.
+
+executable.normalExitValue & executable.respawnAutomatically
+------------------------------------------------------------
+These options can be used to monitor a process and automatically relaunch it 
+when it suddenly terminates. Most of the times, a process uses an exit value 
+of 0 to denote it terminated normally, i.e., without any errors. All non-zero
+values thus can be interpreted as a signal that a process should be 
+relaunched. 
+
+When executable.respawnAutomatically is set to 'true', the process will be 
+monitored whether it is still alive. If not, it will be restarted. Note that 
+there can be a small delay between the termination of the process and the
+relaunching of a new process instance.
+
+instance.count
+--------------
+Allows you to start multiple instances of a single process. In combination with
+the executable.processLifecycleListener, it allows you to create multiple
+independent instances of a single process.
+
+executable.args
+---------------
+This option allows you to supply optional arguments to the launched process. 
+Internally, the string supplied to this option will be split on spaces to
+obtain the individual arguments. If you have an argument (value) that contains
+spaces, and you want to preserve this, you can escape it with a single 
+backslash. For example:
+
+  executable.args = -arg1 -arg2=foo bar
+
+will be interpreted as *three* arguments: "-arg1", "-arg2=foo" and "bar". 
+While:
+
+  executable.args = -arg1 -arg2=foo\ bar
+
+will be interpreted as *two* arguments: "-arg1" and "-arg2=foo bar".
+
+executable.name
+---------------
+The path to the executable to start. The executable name must always be 
+supplied with the full path, as there are no guarantees that the executable
+will be found otherwise.
+
+
+Resources
+=========
+1. http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html

Added: ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/LaunchConfigurationFactoryTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/LaunchConfigurationFactoryTest.java?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/LaunchConfigurationFactoryTest.java (added)
+++ ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/LaunchConfigurationFactoryTest.java Thu Apr 19 14:24:36 2012
@@ -0,0 +1,254 @@
+/*
+ * 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.test.impl;
+
+import java.util.Properties;
+
+import junit.framework.TestCase;
+
+import org.apache.ace.processlauncher.LaunchConfiguration;
+import org.apache.ace.processlauncher.impl.LaunchConfigurationFactory;
+import org.osgi.service.cm.ConfigurationException;
+
+/**
+ * Test cases for {@link LaunchConfigurationFactory}.
+ */
+public final class LaunchConfigurationFactoryTest extends TestCase {
+
+    private static void assertArrayEquals(Object[] expected, Object[] actual) {
+        assertEquals("Array length mismatch!", expected.length, actual.length);
+        for (int i = 0; i < expected.length; i++) {
+            assertEquals("Array element (" + i + ") mismatch!", expected[i], actual[i]);
+        }
+    }
+
+    /**
+     * Tests whether creating a launch configuration based on a properties file works correctly.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    public void testCreateLaunchConfigurationBasedOnPropertiesFileOk() throws Exception {
+        Properties props = TestUtil.getProperties("launch.properties");
+        assertNotNull(props);
+
+        LaunchConfiguration config = LaunchConfigurationFactory.create(props);
+        assertNotNull(config);
+
+        assertEquals(2, config.getInstanceCount());
+        assertEquals("/bin/sh", config.getExecutableName());
+        assertArrayEquals(new String[] { "-c", "'sleep 1 && exit'", "-c", "'echo \"foo bar!\n\"'" },
+            config.getExecutableArgs());
+        assertNull(config.getProcessStreamListener());
+    }
+
+    /**
+     * Tests that an incomplete configuration causes an exception.
+     * 
+     * @throws ConfigurationException not part of this test case.
+     */
+    public void testCreateWithCompleteConfigOk() throws ConfigurationException {
+        Properties props = new Properties();
+        props.put(LaunchConfigurationFactory.INSTANCE_COUNT, "1");
+        props.put(LaunchConfigurationFactory.EXECUTABLE_NAME, "/path/to/foo");
+        props.put(LaunchConfigurationFactory.EXECUTABLE_ARGS, "");
+        props.put(LaunchConfigurationFactory.PROCESS_STREAM_LISTENER_FILTER, "(foo=bar)");
+        props.put(LaunchConfigurationFactory.RESPAWN_AUTOMATICALLY, "false");
+        props.put(LaunchConfigurationFactory.NORMAL_EXIT_VALUE, "2");
+
+        LaunchConfiguration config = LaunchConfigurationFactory.create(props);
+        assertNotNull(config);
+
+        assertEquals(1, config.getInstanceCount());
+        assertEquals("/path/to/foo", config.getExecutableName());
+        assertArrayEquals(new String[0], config.getExecutableArgs());
+        assertEquals(2, config.getNormalExitValue());
+        assertNotNull(config.getProcessStreamListener());
+        assertFalse(config.isRespawnAutomatically());
+    }
+
+    /**
+     * Tests that an incomplete configuration causes an exception.
+     */
+    public void testCreateWithEmptyExecutableNameFail() {
+        Properties props = new Properties();
+        props.put(LaunchConfigurationFactory.INSTANCE_COUNT, "1");
+        props.put(LaunchConfigurationFactory.EXECUTABLE_NAME, "");
+        props.put(LaunchConfigurationFactory.EXECUTABLE_ARGS, "");
+        props.put(LaunchConfigurationFactory.PROCESS_STREAM_LISTENER_FILTER, "true");
+
+        try {
+            LaunchConfigurationFactory.create(props);
+            fail("Exception expected!");
+        }
+        catch (ConfigurationException expected) {
+            // Ok...
+        }
+    }
+
+    /**
+     * Tests that an incomplete configuration causes an exception.
+     */
+    public void testCreateWithInvalidInstanceCountFail() {
+        Properties props = new Properties();
+        props.put(LaunchConfigurationFactory.INSTANCE_COUNT, "0");
+        props.put(LaunchConfigurationFactory.EXECUTABLE_NAME, "/path/to/foo");
+        props.put(LaunchConfigurationFactory.EXECUTABLE_ARGS, "");
+        props.put(LaunchConfigurationFactory.PROCESS_STREAM_LISTENER_FILTER, "true");
+
+        try {
+            LaunchConfigurationFactory.create(props);
+            fail("Exception expected!");
+        }
+        catch (ConfigurationException expected) {
+            // Ok...
+        }
+    }
+
+    /**
+     * Tests that an incomplete configuration causes an exception.
+     * 
+     * @throws ConfigurationException not part of this test case.
+     */
+    public void testCreateWithInvalidProcessStreamListenerFilterFail() throws ConfigurationException {
+        Properties props = new Properties();
+        props.put(LaunchConfigurationFactory.INSTANCE_COUNT, "1");
+        props.put(LaunchConfigurationFactory.EXECUTABLE_NAME, "/path/to/foo");
+        props.put(LaunchConfigurationFactory.EXECUTABLE_ARGS, "");
+        props.put(LaunchConfigurationFactory.PROCESS_STREAM_LISTENER_FILTER, "aap"); // <=
+// incorrect!
+        props.put(LaunchConfigurationFactory.RESPAWN_AUTOMATICALLY, "false");
+        props.put(LaunchConfigurationFactory.NORMAL_EXIT_VALUE, "2");
+
+        try {
+            LaunchConfigurationFactory.create(props);
+            fail("Exception expected!");
+        }
+        catch (ConfigurationException exception) {
+            // Ok...
+        }
+    }
+
+    /**
+     * Tests that an incomplete configuration causes an exception.
+     */
+    public void testCreateWithNonNumericExitValueFail() {
+        Properties props = new Properties();
+        props.put(LaunchConfigurationFactory.INSTANCE_COUNT, "1");
+        props.put(LaunchConfigurationFactory.EXECUTABLE_NAME, "/path/to/foo");
+        props.put(LaunchConfigurationFactory.EXECUTABLE_ARGS, "");
+        props.put(LaunchConfigurationFactory.PROCESS_STREAM_LISTENER_FILTER, "true");
+        props.put(LaunchConfigurationFactory.NORMAL_EXIT_VALUE, "foo");
+
+        try {
+            LaunchConfigurationFactory.create(props);
+            fail("Exception expected!");
+        }
+        catch (ConfigurationException expected) {
+            // Ok...
+        }
+    }
+
+    /**
+     * Tests that an incomplete configuration causes an exception.
+     */
+    public void testCreateWithNonNumericInstanceCountFail() {
+        Properties props = new Properties();
+        props.put(LaunchConfigurationFactory.INSTANCE_COUNT, "foo");
+        props.put(LaunchConfigurationFactory.EXECUTABLE_NAME, "/path/to/foo");
+        props.put(LaunchConfigurationFactory.EXECUTABLE_ARGS, "");
+        props.put(LaunchConfigurationFactory.PROCESS_STREAM_LISTENER_FILTER, "true");
+
+        try {
+            LaunchConfigurationFactory.create(props);
+            fail("Exception expected!");
+        }
+        catch (ConfigurationException expected) {
+            // Ok...
+        }
+    }
+
+    /**
+     * Tests that a null configuration cannot be given to this factory.
+     * 
+     * @throws ConfigurationException not part of this test case.
+     */
+    public void testCreateWithNullConfigFail() throws ConfigurationException {
+        try {
+            LaunchConfigurationFactory.create(null);
+            fail("Exception expected!");
+        }
+        catch (IllegalArgumentException expected) {
+            // Ok...
+        }
+    }
+
+    /**
+     * Tests that an incomplete configuration causes an exception.
+     */
+    public void testCreateWithNullExecutableArgumentsFail() {
+        Properties props = new Properties();
+        props.put(LaunchConfigurationFactory.INSTANCE_COUNT, "1");
+        props.put(LaunchConfigurationFactory.EXECUTABLE_NAME, "/path/to/foo");
+        props.put(LaunchConfigurationFactory.PROCESS_STREAM_LISTENER_FILTER, "true");
+
+        try {
+            LaunchConfigurationFactory.create(props);
+            fail("Exception expected!");
+        }
+        catch (ConfigurationException expected) {
+            // Ok...
+        }
+    }
+
+    /**
+     * Tests that an incomplete configuration causes an exception.
+     */
+    public void testCreateWithNullExecutableNameFail() {
+        Properties props = new Properties();
+        props.put(LaunchConfigurationFactory.INSTANCE_COUNT, "1");
+        props.put(LaunchConfigurationFactory.EXECUTABLE_ARGS, "");
+        props.put(LaunchConfigurationFactory.PROCESS_STREAM_LISTENER_FILTER, "true");
+
+        try {
+            LaunchConfigurationFactory.create(props);
+            fail("Exception expected!");
+        }
+        catch (ConfigurationException e) {
+            // Ok...
+        }
+    }
+
+    /**
+     * Tests that an incomplete configuration causes an exception.
+     */
+    public void testCreateWithNullInstanceCountFail() {
+        Properties props = new Properties();
+        props.put(LaunchConfigurationFactory.EXECUTABLE_NAME, "/path/to/foo");
+        props.put(LaunchConfigurationFactory.EXECUTABLE_ARGS, "");
+        props.put(LaunchConfigurationFactory.PROCESS_STREAM_LISTENER_FILTER, "true");
+
+        try {
+            LaunchConfigurationFactory.create(props);
+            fail("Exception expected!");
+        }
+        catch (ConfigurationException expected) {
+            // Ok...
+        }
+    }
+}

Added: ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/LaunchConfigurationTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/LaunchConfigurationTest.java?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/LaunchConfigurationTest.java (added)
+++ ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/LaunchConfigurationTest.java Thu Apr 19 14:24:36 2012
@@ -0,0 +1,161 @@
+/*
+ * 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.test.impl;
+
+import junit.framework.TestCase;
+
+import org.apache.ace.processlauncher.LaunchConfiguration;
+import org.apache.ace.processlauncher.impl.LaunchConfigurationImpl;
+
+/**
+ * Test cases for {@link LaunchConfigurationImpl}.
+ */
+public final class LaunchConfigurationTest extends TestCase {
+
+    /**
+     * Test that creating a valid {@link LaunchConfigurationImpl} works.
+     */
+    public void testCreateLaunchConfigurationOk() {
+        LaunchConfiguration launchConfig = new LaunchConfigurationImpl(1, "/path/to/foo", new String[0], null, false);
+        assertNotNull(launchConfig);
+    }
+
+    /**
+     * Test that the {@link LaunchConfigurationImpl} constructor validates the executable name
+     * properly.
+     */
+    public void testCreateLaunchConfigurationWithEmptyExecutableNameFail() {
+        try {
+            new LaunchConfigurationImpl(1, "", new String[0], null, false);
+            fail("Exception expected!");
+        }
+        catch (IllegalArgumentException expected) {
+            // Ok...
+        }
+    }
+
+    /**
+     * Test that the {@link LaunchConfigurationImpl} constructor validates the instance count
+     * properly.
+     */
+    public void testCreateLaunchConfigurationWithNegativeInstanceCountFail() {
+        try {
+            new LaunchConfigurationImpl(-1, "/path/to/foo", new String[0], null, false);
+            fail("Exception expected!");
+        }
+        catch (IllegalArgumentException expected) {
+            // Ok...
+        }
+    }
+
+    /**
+     * Test that the {@link LaunchConfigurationImpl} constructor validates the executable arguments
+     * properly.
+     */
+    public void testCreateLaunchConfigurationWithNullExecutableArgsFail() {
+        try {
+            new LaunchConfigurationImpl(1, "/path/to/foo", null, null, false);
+            fail("Exception expected!");
+        }
+        catch (IllegalArgumentException expected) {
+            // Ok...
+        }
+    }
+
+    /**
+     * Test that the {@link LaunchConfigurationImpl} constructor validates the executable name
+     * properly.
+     */
+    public void testCreateLaunchConfigurationWithNullExecutableNameFail() {
+        try {
+            new LaunchConfigurationImpl(1, null, new String[0], null, false);
+            fail("Exception expected!");
+        }
+        catch (IllegalArgumentException expected) {
+            // Ok...
+        }
+    }
+
+    /**
+     * Test that the {@link LaunchConfigurationImpl} constructor validates the instance count
+     * properly.
+     */
+    public void testCreateLaunchConfigurationWithZeroInstanceCountFail() {
+        try {
+            new LaunchConfigurationImpl(0, "/path/to/foo", new String[0], null, false);
+            fail("Exception expected!");
+        }
+        catch (IllegalArgumentException expected) {
+            // Ok...
+        }
+    }
+
+    /**
+     * Test that the {@link LaunchConfigurationImpl#getCommandLine()} method works properly when one
+     * executable arguments are given.
+     */
+    public void testGetCommandLineWithOneArgumentsOk() {
+        LaunchConfiguration launchConfig =
+            new LaunchConfigurationImpl(1, "/path/to/foo", new String[] { "-bar" }, null, false);
+
+        String[] commandLine = launchConfig.getCommandLine();
+        assertNotNull(commandLine);
+        assertEquals(2, commandLine.length);
+
+        assertEquals("/path/to/foo", commandLine[0]);
+        assertEquals("-bar", commandLine[1]);
+    }
+
+    /**
+     * Test that the {@link LaunchConfigurationImpl#getCommandLine()} method works properly when no
+     * executable arguments are given.
+     */
+    public void testGetCommandLineWithoutArgumentsOk() {
+        LaunchConfiguration launchConfig =
+            new LaunchConfigurationImpl(1, "cwd", "/path/to/foo", new String[0], 0, "(foo=bar)", "(qux=quu)", false);
+
+        String[] commandLine = launchConfig.getCommandLine();
+        assertNotNull(commandLine);
+        assertEquals(1, commandLine.length);
+
+        assertEquals("/path/to/foo", commandLine[0]);
+        assertEquals("cwd", launchConfig.getWorkingDirectory().getName());
+        assertNotNull(launchConfig.getProcessStreamListener());
+        assertFalse(launchConfig.isRespawnAutomatically());
+    }
+
+    /**
+     * Test that the {@link LaunchConfigurationImpl#getCommandLine()} method works properly when two
+     * executable arguments are given.
+     */
+    public void testGetCommandLineWithTwoArgumentsOk() {
+        LaunchConfiguration launchConfig =
+            new LaunchConfigurationImpl(1, "/path/to/foo", new String[] { "-qux", "-bar" }, null, true);
+
+        String[] commandLine = launchConfig.getCommandLine();
+        assertNotNull(commandLine);
+        assertEquals(3, commandLine.length);
+
+        assertEquals("/path/to/foo", commandLine[0]);
+        assertEquals("-qux", commandLine[1]);
+        assertEquals("-bar", commandLine[2]);
+        assertNull(launchConfig.getProcessStreamListener());
+        assertTrue(launchConfig.isRespawnAutomatically());
+    }
+}

Added: ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/ProcessLauncherServiceImplTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/ProcessLauncherServiceImplTest.java?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/ProcessLauncherServiceImplTest.java (added)
+++ ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/ProcessLauncherServiceImplTest.java Thu Apr 19 14:24:36 2012
@@ -0,0 +1,178 @@
+/*
+ * 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.test.impl;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Dictionary;
+import java.util.Properties;
+
+import junit.framework.TestCase;
+
+import org.apache.ace.processlauncher.impl.LaunchConfigurationFactory;
+import org.apache.ace.processlauncher.impl.ProcessLauncherServiceImpl;
+import org.apache.ace.processlauncher.impl.ProcessManager;
+import org.apache.ace.processlauncher.impl.ProcessManagerImpl;
+import org.apache.ace.test.utils.TestUtils;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.log.LogService;
+
+/**
+ * Test cases for {@link ProcessLauncherServiceImpl}.
+ */
+public class ProcessLauncherServiceImplTest extends TestCase {
+
+    private ProcessLauncherServiceImpl m_service;
+    private Dictionary<Object, Object> m_launchConfig;
+
+    /**
+     * Tests that adding/inserting a new launch configuration causes a configuration entry to be
+     * created.
+     * 
+     * @throws ConfigurationException not part of this test case.
+     */
+    public void testAddConfigurationWorksOk() throws ConfigurationException {
+        final String pid = "existing-pid";
+
+        assertEquals(0, m_service.getLaunchConfigurationCount());
+
+        m_service.updated(pid, m_launchConfig);
+        assertEquals(1, m_service.getLaunchConfigurationCount());
+    }
+
+    /**
+     * Tests that deleting an existing launch configuration works & doesn't cause an exception.
+     * 
+     * @throws ConfigurationException not part of this test case.
+     */
+    public void testDeleteExistingConfigurationOk() throws ConfigurationException {
+        final String pid = "existing-pid";
+
+        m_service.updated(pid, m_launchConfig);
+        assertTrue(m_service.containsPid(pid));
+
+        m_service.deleted(pid);
+        assertFalse(m_service.containsPid(pid));
+    }
+
+    /**
+     * Tests that deleting a non-existing launch configuration doesn't cause an exception.
+     */
+    public void testDeleteNonExistingConfigurationOk() {
+        m_service.deleted("non-existing-pid");
+    }
+
+    /**
+     * Tests that the getName method doesn't return <code>null</code>.
+     */
+    public void testGetNameOk() {
+        assertNotNull(m_service.getName());
+    }
+
+    /**
+     * Tests the running process count is obtained from the (mocked) process manager.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    public void testGetRunningProcessCountOk() throws Exception {
+        final String pid = "existing-pid";
+
+        m_service.updated(pid, m_launchConfig);
+        assertEquals(2, m_service.getRunningProcessCount());
+    }
+
+    /**
+     * Tests that updating an existing launch configuration cause the original configuration entry
+     * to be updated.
+     * 
+     * @throws ConfigurationException not part of this test case.
+     */
+    public void testReplacingConfigurationWorksOk() throws ConfigurationException {
+        final String pid = "existing-pid";
+
+        assertEquals(0, m_service.getLaunchConfigurationCount());
+
+        m_service.updated(pid, m_launchConfig);
+        assertEquals(1, m_service.getLaunchConfigurationCount());
+
+        m_service.updated(pid, m_launchConfig);
+        assertEquals(1, m_service.getLaunchConfigurationCount());
+    }
+
+    /**
+     * Tests that all existing launch configurations are removed when the service shutdown method is
+     * called.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    public void testShutdownRemovesAllConfigurationsOk() throws Exception {
+        assertEquals(0, m_service.getLaunchConfigurationCount());
+
+        m_service.updated("pid1", m_launchConfig);
+        m_service.updated("pid2", m_launchConfig);
+
+        assertEquals(2, m_service.getLaunchConfigurationCount());
+
+        m_service.shutdown();
+
+        assertEquals(0, m_service.getLaunchConfigurationCount());
+    }
+
+    /**
+     * Tests that updating an existing launch configuration cause the original configuration entry
+     * to be updated.
+     * 
+     * @throws ConfigurationException not part of this test case.
+     */
+    public void testUpdateConfigurationWorksOk() throws ConfigurationException {
+        final String pid = "existing-pid";
+
+        assertEquals(0, m_service.getLaunchConfigurationCount());
+
+        m_service.updated(pid, null);
+        assertEquals(1, m_service.getLaunchConfigurationCount());
+
+        m_service.updated(pid, m_launchConfig);
+        assertEquals(1, m_service.getLaunchConfigurationCount());
+    }
+
+    /**
+     * Set up for each test case.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    @Override
+    protected void setUp() throws Exception {
+        m_service = new ProcessLauncherServiceImpl();
+        m_service.setLogger(TestUtils.createNullObject(LogService.class));
+
+        ProcessManager processManager = mock(ProcessManagerImpl.class);
+        when(processManager.getRunningProcessesCount()).thenReturn(2);
+        m_service.setProcessManager(processManager);
+
+        m_launchConfig = new Properties();
+        m_launchConfig.put(LaunchConfigurationFactory.INSTANCE_COUNT, "1");
+        m_launchConfig.put(LaunchConfigurationFactory.EXECUTABLE_NAME, "/path/to/foo");
+        m_launchConfig.put(LaunchConfigurationFactory.EXECUTABLE_ARGS, "");
+        m_launchConfig.put(LaunchConfigurationFactory.PROCESS_STREAM_LISTENER_FILTER, "(foo=bar)");
+        m_launchConfig.put(LaunchConfigurationFactory.RESPAWN_AUTOMATICALLY, "false");
+        m_launchConfig.put(LaunchConfigurationFactory.NORMAL_EXIT_VALUE, "0");
+    }
+}

Added: ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/ProcessLauncherTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/ProcessLauncherTest.java?rev=1327960&view=auto
==============================================================================
--- ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/ProcessLauncherTest.java (added)
+++ ace/trunk/ace-processlauncher/src/test/java/org/apache/ace/processlauncher/test/impl/ProcessLauncherTest.java Thu Apr 19 14:24:36 2012
@@ -0,0 +1,509 @@
+/*
+ * 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.test.impl;
+
+import static org.apache.ace.processlauncher.test.impl.TestUtil.getOSName;
+import static org.apache.ace.processlauncher.test.impl.TestUtil.sleep;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Properties;
+
+import junit.framework.TestCase;
+
+import org.apache.ace.processlauncher.LaunchConfiguration;
+import org.apache.ace.processlauncher.ProcessLifecycleListener;
+import org.apache.ace.processlauncher.ProcessStreamListener;
+import org.apache.ace.processlauncher.impl.LaunchConfigurationImpl;
+import org.apache.ace.processlauncher.impl.ProcessLauncher;
+import org.apache.ace.processlauncher.util.InputStreamRedirector;
+
+/**
+ * Test cases for {@link ProcessLauncher}.
+ */
+public class ProcessLauncherTest extends TestCase {
+
+    /**
+     * Tests that an existing executable (Java) can be called with valid arguments and its output
+     * can be read.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    public void testCallAlreadyRunningProcessCausesExceptionFail() throws Exception {
+        String execName = determineJavaExecutable();
+
+        TestProcessStreamListener psl = new TestProcessStreamListener(false /* wantsStdout */);
+
+        ProcessLauncher launcher = createProcessLauncher(psl, null, execName);
+
+        launcher.run();
+
+        try {
+            launcher.run(); // should fail!
+            fail("Exception expected!");
+        }
+        catch (IllegalStateException expected) {
+            // Ok...
+        }
+    }
+
+    /**
+     * Tests that for a given {@link ProcessLifecycleListener} the lifecycle methods are called.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    public void testLifecycleMethodsAreCalledOk() throws Exception {
+        String execName = determineJavaExecutable();
+
+        TestProcessStreamListener psl = new TestProcessStreamListener(false /* wantsStdout */);
+        TestProcessLifecycleListener pll = new TestProcessLifecycleListener();
+
+        ProcessLauncher launcher = createProcessLauncher(psl, pll, execName);
+
+        launcher.run();
+
+        assertTrue(pll.m_beforeCalled);
+
+        // Will wait until process is finished and calls our lifecycle method...
+        launcher.waitForTermination();
+
+        assertTrue(pll.m_afterCalled);
+    }
+
+    /**
+     * Tests that calling {@link ProcessLauncher#cleanup()} will cause an exception if the process
+     * is not yet terminated.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    public void testCallCleanupOnRunningProcessFails() throws Exception {
+        String execName = determineJavaExecutable();
+
+        TestProcessStreamListener psl = new TestProcessStreamListener(false /* wantsStdout */);
+
+        ProcessLauncher launcher = createProcessLauncher(psl, null, execName);
+
+        launcher.run();
+
+        try {
+            launcher.cleanup(); // should fail!
+            fail("Exception expected!");
+        }
+        catch (IllegalStateException expected) {
+            // Ok...
+        }
+    }
+
+    /**
+     * Tests that calling {@link ProcessLauncher#cleanup()} will cause no exception if the process
+     * is terminated.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    public void testCallCleanupOnTerminatedProcessOk() throws Exception {
+        String execName = determineJavaExecutable();
+
+        TestProcessStreamListener psl = new TestProcessStreamListener(false /* wantsStdout */);
+
+        ProcessLauncher launcher = createProcessLauncher(psl, null, execName);
+
+        launcher.run();
+
+        sleep(500);
+
+        launcher.cleanup();
+    }
+
+    /**
+     * Tests that an existing executable (Java) can be called with valid arguments and its output
+     * can be read.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    public void testCallExistingExecutableWithoutArgumentOk() throws Exception {
+        String execName = determineJavaExecutable();
+
+        TestProcessStreamListener psl = new TestProcessStreamListener(true /* wantsStdout */);
+
+        ProcessLauncher launcher = createProcessLauncher(psl, null, execName);
+
+        launcher.run();
+        Integer exitValue = launcher.waitForTermination();
+
+        assertNotNull(exitValue);
+        assertEquals(0, exitValue.intValue());
+        // Both methods should return the same exit value!
+        assertEquals(exitValue, launcher.getExitValue());
+
+        String stdout = psl.slurpStdout();
+        assertNotNull(stdout);
+        assertTrue(stdout.length() > 0);
+
+        // Make sure the test doesn't fail when the usage text is translated or
+        // something...
+        assertTrue(stdout, stdout.contains("Usage: java"));
+    }
+
+    /**
+     * Tests that an existing executable (Java) can be called with invalid arguments and its output
+     * can be read.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    public void testCallExistingExecutableWithUnknownArgumentsOk() throws Exception {
+        String execName = determineJavaExecutable();
+        String execArgs = "-nonExistingArg";
+
+        ProcessLauncher launcher = createProcessLauncher(execName, execArgs);
+
+        launcher.run();
+        Integer exitValue = launcher.waitForTermination();
+
+        assertNotNull(exitValue);
+        assertFalse(0 == exitValue.intValue());
+        // Both methods should return the same exit value!
+        assertEquals(exitValue, launcher.getExitValue());
+    }
+
+    /**
+     * Tests that an existing executable (Java) can be called with valid arguments and its output
+     * can be read.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    public void testCallExistingExecutableWithValidArgumentOk() throws Exception {
+        String execName = determineJavaExecutable();
+        String execArgs = "-version";
+
+        TestProcessStreamListener psl = new TestProcessStreamListener(true /* wantsStdout */);
+
+        ProcessLauncher launcher = createProcessLauncher(psl, null, execName, execArgs);
+
+        launcher.run();
+        Integer exitValue = launcher.waitForTermination();
+
+        assertNotNull(exitValue);
+        assertEquals(0, exitValue.intValue());
+
+        String stdout = psl.slurpStdout();
+        assertNotNull(stdout);
+        assertTrue(stdout.length() > 0);
+
+        // Make sure the test doesn't fail when the usage text is translated or
+        // something...
+        assertTrue(stdout, stdout.contains("java version"));
+    }
+
+    /**
+     * Tests that an existing executable (Java) can be called and its output can be read.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    public void testCallNonExistingExecutableOk() throws Exception {
+        String execName = "/path/to/java";
+        String execArgs = "-version";
+
+        ProcessLauncher launcher = createProcessLauncher(execName, execArgs);
+
+        try {
+            launcher.run(); // should fail!
+            fail("Exception expected!");
+        }
+        catch (IOException expected) {
+            // Ok...
+        }
+    }
+
+    /**
+     * Tests that attempting to create a new {@link ProcessLauncher} without a valid launch
+     * configuration yields an exception.
+     */
+    public void testCreateProcessLauncherWithoutLaunchConfigurationFail() {
+        try {
+            new ProcessLauncher(null);
+            fail("Exception expected!");
+        }
+        catch (IllegalArgumentException expected) {
+            // Ok...
+        }
+    }
+
+    /**
+     * Tests that attempting to obtain the exit value without a launched process yields a null
+     * value.
+     */
+    public void testGetExitValueWithoutLaunchedProcessReturnsNull() {
+        String execName = determineJavaExecutable();
+        String execArgs = "-version";
+
+        ProcessLauncher launcher = createProcessLauncher(execName, execArgs);
+
+        assertNull(launcher.getExitValue());
+    }
+
+    /**
+     * Tests that we can send commands to a running process and capture the results of these
+     * commands.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    public void testInteractWithProcessOk() throws Exception {
+        // Test will not work on Windows!
+        if (getOSName().contains("windows")) {
+            return;
+        }
+
+        String execName = "/bin/sh";
+        String input = "echo '1'\nls $0\nsleep 1\n";
+
+        TestProcessStreamListener psl = new TestProcessStreamListener(true /* wantsStdin */, true /* wantsStdout */);
+
+        ProcessLauncher launcher = createProcessLauncher(psl, null, execName);
+
+        launcher.run();
+
+        psl.writeToStdin(input);
+        psl.closeStdin();
+
+        Integer exitValue = launcher.waitForTermination();
+
+        assertNotNull(exitValue);
+        assertEquals(0, exitValue.intValue());
+
+        String stdout = psl.slurpStdout();
+        assertNotNull(stdout);
+        assertTrue(stdout.length() > 0);
+
+        // Make sure the test doesn't fail when the usage text is translated or
+        // something...
+        assertEquals("1\n/bin/sh\n", stdout);
+    }
+
+    /**
+     * Tests that we can send commands to a running process and capture the results of these
+     * commands.
+     * 
+     * @throws Exception not part of this test case.
+     */
+    public void testInteractWithProcessThroughArgumentsOk() throws Exception {
+        // Test will not work on Windows!
+        if (getOSName().contains("windows")) {
+            return;
+        }
+
+        String execName = "/bin/sh";
+        String[] execArgs = { "-c", "echo '1'\nls $0\nsleep 1\n" };
+
+        TestProcessStreamListener psl = new TestProcessStreamListener(false /* wantsStdin */, true /* wantsStdout */);
+
+        ProcessLauncher launcher = createProcessLauncher(psl, null, execName, execArgs);
+
+        launcher.run();
+
+        Integer exitValue = launcher.waitForTermination();
+
+        String stdout = psl.slurpStdout();
+        assertNotNull(stdout);
+        assertTrue(stdout.length() > 0);
+
+        assertNotNull(exitValue);
+        assertEquals(0, exitValue.intValue());
+
+        assertEquals("1\n/bin/sh\n", stdout);
+    }
+
+    /**
+     * Tests that we can send commands to a running cat-process and capture the results of these
+     * commands.
+     * <p>
+     * The cat-command is a somewhat "nasty" command as it won't exit until its input stream is
+     * properly closed.
+     * </p>
+     * 
+     * @throws Exception not part of this test case.
+     */
+    public void testProcessStdinIsProperlyClosedOk() throws Exception {
+        // Test will not work on Windows!
+        if (getOSName().contains("windows")) {
+            return;
+        }
+
+        String execName = "/bin/cat";
+        String input = "echo '1'\nls $0\nqux qoo\n";
+
+        TestProcessStreamListener psl = new TestProcessStreamListener(true /* wantsStdin */, true /* wantsStdout */);
+
+        ProcessLauncher launcher = createProcessLauncher(psl, null, execName);
+
+        launcher.run();
+
+        // Issue the command...
+        psl.writeToStdin(input);
+
+        sleep(1000);
+
+        psl.closeStdin();
+
+        Integer exitValue = launcher.waitForTermination();
+
+        assertNotNull(exitValue);
+        assertEquals(0, exitValue.intValue());
+
+        String stdout = psl.slurpStdout();
+        assertNotNull(stdout);
+        assertTrue(stdout.length() > 0);
+
+        // We should get the exact same output as we've put into the command...
+        assertEquals(input, stdout);
+    }
+
+    /**
+     * Creates a new launch configuration for the given executable and arguments.
+     * 
+     * @param captureProcessOutput <code>true</code> if the process output is to be captured,
+     *        <code>false</code> otherwise;
+     * @param execName the name of the executable;
+     * @param execArgs the (optional) arguments.
+     * @return a {@link LaunchConfigurationImpl} instance, never <code>null</code>.
+     */
+    private LaunchConfiguration createLaunchConfiguration(boolean respawnAutomatically, String execName,
+        String... execArgs) {
+        return new LaunchConfigurationImpl(1, execName, execArgs, null, respawnAutomatically);
+    }
+
+    /**
+     * Creates a new process launcher instance for the given executable and arguments.
+     * 
+     * @param execName the name of the executable;
+     * @param execArgs the (optional) arguments.
+     * @return a {@link ProcessLauncher} instance, never <code>null</code>.
+     */
+    private ProcessLauncher createProcessLauncher(ProcessStreamListener processStreamListener,
+        ProcessLifecycleListener processLifecycleListener, String execName, String... execArgs) {
+        return new ProcessLauncher(createLaunchConfiguration(false, execName, execArgs), processStreamListener,
+            processLifecycleListener);
+    }
+
+    /**
+     * Creates a new process launcher instance for the given executable and arguments.
+     * 
+     * @param execName the name of the executable;
+     * @param execArgs the (optional) arguments.
+     * @return a {@link ProcessLauncher} instance, never <code>null</code>.
+     */
+    private ProcessLauncher createProcessLauncher(String execName, String... execArgs) {
+        return new ProcessLauncher(createLaunchConfiguration(false, execName, execArgs));
+    }
+
+    /**
+     * Returns the full path to the java executable (the one that is used to invoke this test).
+     * 
+     * @return a full path to the executable, never <code>null</code>.
+     */
+    private String determineJavaExecutable() {
+        StringBuilder sb = new StringBuilder(System.getProperty("java.home"));
+        sb.append(File.separatorChar).append("bin").append(File.separatorChar).append("java");
+        return sb.toString();
+    }
+
+    /**
+     * Test implementation of {@link ProcessLifecycleListener}.
+     * 
+     * @author jwjanssen
+     */
+    static final class TestProcessLifecycleListener implements ProcessLifecycleListener {
+        private volatile boolean m_beforeCalled;
+        private volatile boolean m_afterCalled;
+
+        /**
+         * {@inheritDoc}
+         */
+        public void afterProcessEnd(LaunchConfiguration configuration) {
+            m_afterCalled = true;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public Properties beforeProcessStart(LaunchConfiguration configuration) {
+            m_beforeCalled = true;
+            return null;
+        }
+    }
+
+    /**
+     * Provides a mock implementation of {@link ProcessStreamListener} that is used in the various
+     * test cases.
+     * 
+     * @author jwjanssen
+     */
+    static final class TestProcessStreamListener implements ProcessStreamListener {
+        private final boolean m_wantsStdin;
+        private final boolean m_wantsStdout;
+
+        private OutputStream m_stdin;
+        private OutputStream m_stdout;
+
+        public TestProcessStreamListener(boolean wantsStdout) {
+            this(false /* wantsStdin */, wantsStdout);
+        }
+
+        public TestProcessStreamListener(boolean wantsStdin, boolean wantsStdout) {
+            m_wantsStdin = wantsStdin;
+            m_wantsStdout = wantsStdout;
+        }
+
+        public synchronized void closeStdin() throws IOException {
+            m_stdin.flush();
+            m_stdin.close();
+        }
+
+        public void setStdin(LaunchConfiguration launchConfiguration, OutputStream outputStream) {
+            m_stdin = outputStream;
+        }
+
+        public void setStdout(LaunchConfiguration launchConfiguration, InputStream inputStream) {
+            m_stdout = new ByteArrayOutputStream(1024);
+            InputStreamRedirector isr = new InputStreamRedirector(inputStream, m_stdout);
+            isr.start();
+        }
+
+        public String slurpStdout() throws IOException {
+            assertNotNull(m_stdout);
+            m_stdout.flush();
+            return m_stdout.toString();
+        }
+
+        public boolean wantsStdin() {
+            return m_wantsStdin;
+        }
+
+        public boolean wantsStdout() {
+            return m_wantsStdout;
+        }
+
+        public void writeToStdin(String commands) throws IOException {
+            assertNotNull(m_stdin);
+            m_stdin.write(commands.getBytes());
+        }
+    }
+}