package com.revolsys.parallel.tools;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.MissingOptionException;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.apache.commons.jexl.Expression;
import org.apache.commons.jexl.context.HashMapContext;
import org.apache.log4j.Appender;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Layout;
import org.apache.log4j.PatternLayout;
import org.springframework.beans.InvalidPropertyException;
import org.springframework.beans.MethodInvocationException;
import org.springframework.beans.PropertyAccessException;
import org.springframework.beans.PropertyBatchUpdateException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.support.GenericApplicationContext;
import com.revolsys.beans.propertyeditor.ResourceEditorRegistrar;
import com.revolsys.collection.map.ThreadSharedProperties;
import com.revolsys.logging.Logs;
import com.revolsys.logging.log4j.ThreadLocalFileAppender;
import com.revolsys.parallel.process.ProcessNetwork;
import com.revolsys.util.JexlUtil;
import com.revolsys.util.ManifestUtil;
public class ScriptTool {
private static final String LOG_FILE = "logFile";
private static final String LOG_FILE_OPTION = "l";
private static final String PROPERTIES = "properties";
private static final String PROPERTIES_OPTION = "p";
private static final String SCRIPT = "script";
private static final String SCRIPT_OPTION = "s";
private static final String VERSION = "version";
private static final String VERSION_OPTION = "v";
private static Throwable getBeanExceptionCause(final BeanCreationException e) {
Throwable cause = e.getCause();
if (cause == null) {
return e;
}
while (cause instanceof BeanCreationException || cause instanceof MethodInvocationException
|| cause instanceof PropertyAccessException || cause instanceof PropertyBatchUpdateException
|| cause instanceof InvalidPropertyException) {
Throwable newCause;
if (cause instanceof PropertyBatchUpdateException) {
final PropertyBatchUpdateException batchEx = (PropertyBatchUpdateException)cause;
newCause = batchEx.getPropertyAccessExceptions()[0];
} else {
newCause = cause.getCause();
}
if (newCause != null) {
cause = newCause;
} else {
return cause;
}
}
return cause;
}
/**
* @param args
*/
public static void main(final String[] args) {
final ScriptTool app = new ScriptTool();
app.start(args);
}
private CommandLine commandLine;
private boolean displayVersion;
private File logFile;
private final Options options = new Options();
private final Map<String, String> parameters = new LinkedHashMap<>();
private String propertiesName;
private File scriptFile;
private String scriptFileName;
public ScriptTool() {
newOptions();
}
private void displayVersion() {
this.displayVersion = true;
final String implementationTitle = System.getProperty("script.implementationTitle");
if (implementationTitle != null) {
final String build = ManifestUtil.getMainAttributeByImplementationTitle(implementationTitle,
"SCM-Revision");
if (build != null) {
System.out.println(implementationTitle + " (build " + build + ")");
} else {
System.out.println(implementationTitle);
}
System.out.println();
}
}
private boolean loadProperties(final String name) {
final Class<? extends ScriptTool> clazz = getClass();
final ClassLoader classLoader = clazz.getClassLoader();
final InputStream scriptIn = classLoader.getResourceAsStream(name);
return loadProperties(name, scriptIn);
}
private boolean loadProperties(final String name, final InputStream in) {
if (in != null) {
try {
final Properties props = new Properties();
props.load(in);
for (final Entry<Object, Object> parameter : props.entrySet()) {
final String key = (String)parameter.getKey();
final String value = (String)parameter.getValue();
ThreadSharedProperties.setProperty(key, value);
System.setProperty(key, value);
}
return true;
} catch (final Exception e) {
System.err.println("Unable to load properties from " + name);
e.printStackTrace();
return false;
}
} else {
return false;
}
}
private void newOptions() {
final Option script = new Option(SCRIPT_OPTION, SCRIPT, true,
"the script file that defines the processor pipeline");
script.setRequired(false);
this.options.addOption(script);
final Option logFile = new Option(LOG_FILE_OPTION, LOG_FILE, true,
"The file to write log messages to");
logFile.setRequired(false);
this.options.addOption(logFile);
final Option properties = new Option(PROPERTIES_OPTION, PROPERTIES, true,
"The file to load properties from");
properties.setRequired(false);
this.options.addOption(properties);
final Option version = new Option(VERSION_OPTION, VERSION, false, "Display the version number");
properties.setRequired(false);
this.options.addOption(version);
OptionBuilder.withDescription("use value for given property");
OptionBuilder.withArgName("property=value");
OptionBuilder.withValueSeparator();
OptionBuilder.hasArgs(2);
final Option property = OptionBuilder.create("D");
this.options.addOption(property);
}
public boolean processArguments(final String[] args) {
try {
loadProperties("script.properties");
final CommandLineParser parser = new PosixParser();
this.commandLine = parser.parse(this.options, args);
final Option[] options = this.commandLine.getOptions();
for (final Option option : options) {
final String shortOpt = option.getOpt();
if (shortOpt != null && shortOpt.equals("D")) {
final Properties properties = this.commandLine.getOptionProperties("D");
for (final Entry<Object, Object> property : properties.entrySet()) {
final String key = (String)property.getKey();
final String value = (String)property.getValue();
this.parameters.put(key, value);
System.setProperty(key, value);
ThreadSharedProperties.setProperty(key, value);
}
}
}
if (this.commandLine.hasOption(SCRIPT_OPTION)) {
if (!setScriptFileName(this.commandLine.getOptionValue(SCRIPT_OPTION))) {
return false;
}
}
if (this.commandLine.hasOption(PROPERTIES_OPTION)) {
this.propertiesName = this.commandLine.getOptionValue(PROPERTIES_OPTION);
try {
final File propertiesFile = new File(this.propertiesName);
if (propertiesFile.exists()) {
final InputStream in = new FileInputStream(propertiesFile);
loadProperties(this.propertiesName, in);
} else {
if (!loadProperties(this.propertiesName)) {
System.err.println("Properties file '" + this.propertiesName + "' does not exist");
return false;
}
}
} catch (final IOException e) {
System.err.println(
"Properties file '" + this.propertiesName + "' could not be read:" + e.getMessage());
return false;
}
}
if (this.commandLine.hasOption(LOG_FILE_OPTION)) {
this.logFile = new File(this.commandLine.getOptionValue(LOG_FILE_OPTION));
final File logDirectory = this.logFile.getParentFile();
if (!logDirectory.exists()) {
if (!logDirectory.mkdirs()) {
System.err
.println("Unable to create Log directory '" + logDirectory.getAbsolutePath() + "'");
return false;
}
}
} else {
String logFileName = System.getProperty("logFile");
if (logFileName != null) {
try {
while (logFileName.contains("${")) {
final Expression expression = JexlUtil.newExpression(logFileName);
final HashMapContext context = new HashMapContext();
context.setVars(ThreadSharedProperties.getProperties());
logFileName = (String)JexlUtil.evaluateExpression(context, expression);
}
} catch (final Exception e) {
e.printStackTrace();
logFileName = null;
}
}
}
if (this.logFile != null) {
if (this.logFile.exists() && !this.logFile.isFile()) {
System.err.println("Log file '" + this.logFile.getAbsolutePath() + "' is not a file");
return false;
}
System.setProperty("logFile", this.logFile.getAbsolutePath());
}
if (this.commandLine.hasOption(VERSION_OPTION)) {
displayVersion();
return false;
}
if (this.scriptFileName == null) {
final String[] extraArgs = this.commandLine.getArgs();
if (extraArgs.length > 0) {
if (!setScriptFileName(extraArgs[0])) {
return false;
}
}
}
return true;
} catch (final MissingOptionException e) {
if (this.commandLine.hasOption(VERSION_OPTION)) {
displayVersion();
} else {
System.err.println("Missing " + e.getMessage() + " argument");
}
return false;
} catch (final ParseException e) {
System.err.println("Unable to process command line arguments: " + e.getMessage());
return false;
}
}
private void run() {
final long startTime = System.currentTimeMillis();
final ThreadLocalFileAppender localAppender = ThreadLocalFileAppender.getAppender();
if (localAppender != null && this.logFile != null) {
final File parentFile = this.logFile.getParentFile();
if (parentFile != null) {
parentFile.mkdirs();
}
localAppender.setLocalFile(this.logFile.getAbsolutePath());
} else if (this.logFile != null) {
final org.apache.log4j.Logger rootLogger = org.apache.log4j.Logger.getRootLogger();
try {
final Layout layout = new PatternLayout("%d\t%p\t%m%n");
final Appender appender = new FileAppender(layout, this.logFile.getAbsolutePath(), false);
rootLogger.addAppender(appender);
} catch (final IOException e) {
final Layout layout = new PatternLayout("%p\t%m%n");
final Appender appender = new ConsoleAppender(layout);
rootLogger.addAppender(appender);
Logs.error(this, "Cannot find log file " + this.logFile, e);
}
}
final StringBuilder message = new StringBuilder("Processing ");
message.append(" -s ");
message.append(this.scriptFileName);
if (this.propertiesName != null) {
message.append(" -p ");
message.append(this.propertiesName);
}
for (final Entry<String, String> parameter : this.parameters.entrySet()) {
message.append(" ");
message.append(parameter.getKey());
message.append("=");
message.append(parameter.getValue());
}
System.out.println(message);
if (System.getProperty("applicationHome") == null) {
ThreadSharedProperties.setProperty("applicationHome", ".");
System.setProperty("applicationHome", ".");
}
try {
final GenericApplicationContext beans = new GenericApplicationContext();
AnnotationConfigUtils.registerAnnotationConfigProcessors(beans, null);
beans.getBeanFactory().addPropertyEditorRegistrar(new ResourceEditorRegistrar());
if (this.scriptFile != null) {
new XmlBeanDefinitionReader(beans)
.loadBeanDefinitions("file:" + this.scriptFile.getAbsolutePath());
} else {
new XmlBeanDefinitionReader(beans).loadBeanDefinitions("classpath:" + this.scriptFileName);
}
beans.refresh();
try {
Logs.info(this, message.toString());
final Object bean = beans.getBean("processNetwork");
final ProcessNetwork pipeline = (ProcessNetwork)bean;
pipeline.startAndWait();
} finally {
beans.close();
}
} catch (final BeanCreationException e) {
final Throwable cause = getBeanExceptionCause(e);
Logs.error(this, cause.getMessage(), cause);
cause.printStackTrace();
System.err.flush();
}
final long endTime = System.currentTimeMillis();
final long time = endTime - startTime;
long seconds = time / 1000;
final long minutes = seconds / 60;
seconds = seconds % 60;
Logs.info(this, minutes + " minutes " + seconds + " seconds");
System.out.println(minutes + " minutes " + seconds + " seconds");
}
private boolean setScriptFileName(final String scriptFileName) {
if (new File(scriptFileName).exists()) {
this.scriptFileName = scriptFileName;
this.scriptFile = new File(scriptFileName);
return true;
} else if (getClass().getClassLoader().getResource(scriptFileName) == null) {
System.err.println("The script '" + scriptFileName + "' does not exist");
return false;
} else {
this.scriptFileName = scriptFileName;
return true;
}
}
public void start(final String[] args) {
if (processArguments(args)) {
run();
} else {
if (!this.displayVersion) {
final HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("scriptTool", this.options);
}
}
}
}