/*
* Copyright (C) 2014 Jan Pokorsky
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cz.cas.lib.proarc.common.process;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConversionException;
/**
* The helper to run external processes. It builds command line and environment
* from {@link Configuration} properties.
*
* @author Jan Pokorsky
*/
public class ExternalProcess implements Runnable {
private static final Logger LOG = Logger.getLogger(ExternalProcess.class.getName());
/**
* The process environment.
*/
public static final String PROP_ENVIRONMENT = "environment";
/**
* The full path to executable file.
*/
public static final String PROP_EXEC = "exec";
/**
* The command line argument.
*/
public static final String PROP_ARG = "arg";
/**
* The number of attempts to tun a process again in case of failure.
*/
public static final String PROP_RETRY = "retry";
/**
* The time to wait for a running process.
*/
public static final String PROP_TIMEOUT = "timeout";
/**
* The processor type.
*/
public static final String PROP_TYPE = "type";
public static final long DEFAULT_TIMEOUT = 2 * 60 * 1000;
public static final int DEFAULT_RETRY_ATTEMPTS = 0;
private final Configuration conf;
private AsyncProcess asyncProcess;
public String style;
protected ExternalProcess(Configuration conf) {
this.conf = conf;
}
@Override
public void run() {
Map<String, String> env = buildEnv(conf);
List<String> cmdLine = buildCmdLine(conf);
try {
int retry = getRetryCount() + 1;
for (int i = 0; i < retry; i++) {
runCmdLine(cmdLine, env);
if (isOk()) {
return ;
}
LOG.log(Level.WARNING, "{0}. failure, \n{1}, \nCmd: {2}",
new Object[]{i + 1, getFullOutput(), cmdLine});
}
} catch (IOException ex) {
throw new IllegalStateException(ex);
} catch (InterruptedException ex) {
throw new IllegalStateException(ex);
}
}
protected Map<String, String> buildEnv(Configuration conf) {
Configuration envConfig = conf.subset(PROP_ENVIRONMENT);
Map<String, String> env = new HashMap<String, String>();
for (Iterator<String> it = envConfig.getKeys(); it.hasNext();) {
String envKey = it.next();
env.put(envKey, envConfig.getString(envKey));
}
return env;
}
protected List<String> buildCmdLine(Configuration conf) {
String exec = conf.getString(PROP_EXEC);
if (exec == null) {
throw new IllegalStateException("Missing 'exec'!");
}
String[] args = conf.getStringArray(PROP_ARG);
List<String> cmdLine = new ArrayList<String>();
cmdLine.add(exec);
cmdLine.addAll(Arrays.asList(args));
return cmdLine;
}
private int runCmdLine(List<String> cmdLine, Map<String, String> env) throws IOException, InterruptedException {
StringBuilder debug = new StringBuilder();
for (String arg : cmdLine) {
debug.append(arg).append(" ");
}
LOG.fine("run: " + debug);
asyncProcess = new AsyncProcess(cmdLine, env);
asyncProcess.start();
long timeout = getTimeout();
asyncProcess.join(timeout);
asyncProcess.kill();
LOG.fine(getFullOutput());
return asyncProcess.getExitCode();
}
public String getOut() {
return asyncProcess == null ? null: asyncProcess.getOut();
}
public String getErr() {
return null;
}
public int getExitCode() {
return asyncProcess == null ? -1: asyncProcess.getExitCode();
}
public boolean isOk() {
return getExitCode() == 0;
}
public String getFullOutput() {
return String.format("exit: %s,\nout: %s", getExitCode(), getOut());
}
int getRetryCount() {
try {
int retry = conf.getInt("retry", DEFAULT_RETRY_ATTEMPTS);
retry = Math.max(0, retry);
retry = Math.min(100, retry);
return retry;
} catch (ConversionException ex) {
LOG.log(Level.WARNING, null, ex);
return DEFAULT_RETRY_ATTEMPTS;
}
}
long getTimeout() {
try {
long timeout = conf.getLong(PROP_TIMEOUT, DEFAULT_TIMEOUT);
timeout = Math.max(0, timeout);
return timeout;
} catch (ConversionException ex) {
LOG.log(Level.WARNING, null, ex);
return DEFAULT_TIMEOUT;
}
}
}