org.jboss.varia.process.ChildProcessService Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.varia.process;
import java.util.Properties;
import java.util.Iterator;
import java.io.File;
import java.io.InputStream;
import java.io.Reader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.jboss.system.ServiceMBeanSupport;
import org.jboss.util.NullArgumentException;
/**
* A service to manage a child process.
*
* @jmx:mbean extends="org.jboss.system.ServiceMBean"
*
* @version $Revision: 89151 $
* @author Jason Dillon
*/
public class ChildProcessService
extends ServiceMBeanSupport
implements ChildProcessServiceMBean
{
/** The command line of the process to execute. */
protected String commandLine;
/** The environment for the process. */
protected Properties env;
/** The working directory of the process. */
protected File workingDir;
/** The child, we are so proud. */
protected Process childProcess;
/**
* The name of the logger adapter for the child process' streams.
*/
protected String loggerAdapterName = this.getClass().getName();
/**
* The input adapter, which takes the process' STDOUT and
* turns them into logger calls.
*/
protected ReaderLoggerAdapter inputAdapter;
/**
* The input adapter, which takes the process' STDERR and
* turns them into logger calls.
*/
protected ReaderLoggerAdapter errorAdapter;
/**
* @jmx:managed-attribute
*/
public void setCommandLine(final String commandLine)
{
if (commandLine == null)
throw new NullArgumentException("commandLine");
this.commandLine = commandLine;
}
/**
* @jmx:managed-attribute
*/
public String getCommandLine()
{
return commandLine;
}
/**
* @jmx:managed-attribute
*/
public void setEnvironment(final Properties env)
{
this.env = env;
}
/**
* @jmx:managed-attribute
*/
public Properties getEnvironment()
{
return env;
}
/**
* @jmx:managed-attribute
*/
public void setWorkingDirectory(final File dir)
{
// only check if not null
if (dir != null) {
if (dir.exists()) {
if (!dir.isDirectory()) {
throw new IllegalArgumentException
("Directory argument does not point to a directory: " + dir);
}
}
}
this.workingDir = dir;
}
/**
* @jmx:managed-attribute
*/
public File getWorkingDirectory()
{
return workingDir;
}
/**
* @jmx:managed-attribute
*/
public Integer getExitValue()
{
if (childProcess != null) {
return new Integer(childProcess.exitValue());
}
return null;
}
/**
* @jmx:managed-attribute
*/
public void setLoggerAdapterName(final String name)
{
this.loggerAdapterName = name;
}
/**
* @jmx:managed-attribute
*/
public String getLoggerAdapterName()
{
return loggerAdapterName;
}
protected String[] makeEnvArray(final Properties props)
{
if (props == null)
return new String[0];
String[] envArray = new String[props.keySet().size()];
Iterator iter = props.keySet().iterator();
int i = 0;
while (iter.hasNext()) {
String name = (String)iter.next();
envArray[i++] = name + "=" + props.getProperty(name);
}
return envArray;
}
///////////////////////////////////////////////////////////////////////////
// Reader/InputStream Logger Adapter //
///////////////////////////////////////////////////////////////////////////
protected static class ReaderLoggerAdapter
implements Runnable
{
protected BufferedReader reader;
protected boolean shutdown;
protected Logger log;
protected Level level;
public ReaderLoggerAdapter(final Reader reader, final Logger log, final Level level)
{
if (reader instanceof BufferedReader) {
this.reader = (BufferedReader)reader;
}
else {
this.reader = new BufferedReader(reader);
}
this.log = log;
this.level = level;
}
public ReaderLoggerAdapter(final InputStream input, final Logger log, final Level level)
{
this(new InputStreamReader(input), log, level);
}
public void shutdown()
{
shutdown = true;
}
public void run()
{
boolean intr = false;
try
{
while (!shutdown) {
try {
String data = reader.readLine();
if (data == null) {
try {
Thread.sleep(1000);
}
catch (InterruptedException ignore)
{
intr = true;
}
}
else {
log.log(level, data);
}
}
catch (IOException e) {
log.error("Failed to read data from reader", e);
}
}
}
finally
{
if (intr) Thread.currentThread().interrupt();
}
}
}
///////////////////////////////////////////////////////////////////////////
// ServiceMBeanSupport Overrides //
///////////////////////////////////////////////////////////////////////////
protected void startService() throws Exception
{
Runtime rt = Runtime.getRuntime();
childProcess = rt.exec(commandLine, makeEnvArray(env), workingDir);
log.info("Spawned child process: " + commandLine);
// hook up the processes output streams to logging
Logger logger = Logger.getLogger(loggerAdapterName);
InputStream input = childProcess.getInputStream();
inputAdapter = new ReaderLoggerAdapter(input, logger, Level.INFO);
new Thread(inputAdapter).start();
InputStream error = childProcess.getErrorStream();
errorAdapter = new ReaderLoggerAdapter(error, logger, Level.ERROR);
new Thread(errorAdapter).start();
}
protected void stopService() throws Exception
{
childProcess.destroy();
log.debug("Child process destroyed; waiting for process to exit");
childProcess.waitFor();
log.info("Child exited with code: " + getExitValue());
inputAdapter.shutdown();
errorAdapter.shutdown();
childProcess = null;
inputAdapter = null;
errorAdapter = null;
}
}