lcsim/src/org/lcsim/job
diff -u -r1.48 -r1.49
--- JobControlManager.java 9 Nov 2010 22:26:00 -0000 1.48
+++ JobControlManager.java 31 Mar 2011 01:36:38 -0000 1.49
@@ -16,13 +16,21 @@
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Properties;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.commons.cli.PosixParser;
import org.freehep.application.studio.Studio;
import org.freehep.jas.services.DynamicClassLoader;
import org.freehep.record.loop.LoopSourceExhaustedException;
@@ -42,41 +50,68 @@
import org.lcsim.util.xml.JDOMExpressionFactory;
/**
- * The <code>JobControlManager</code> provides an XML frontend for running LCSim jobs.
- * The command line syntax is <code>java -jar ./lib/lcsim.jar steeringFile.xml</code>.
+ * Provides a frontend for running LCSim jobs using an XML steering file.
*
- * @author jeremym
- * @version $Id: JobControlManager.java,v 1.48 2010/11/09 22:26:00 jeremy Exp $
+ * The command line syntax is
+ * <code>java -jar ./lib/lcsim-bin.jar -pPropFile.prop -Dvar=value steeringFile.xml</code>.
+ *
+ * The <code>p</code> option is for loading external variable definitions from a
+ * properties file. An unlimited number of properties files can be specified.
+ *
+ * The <code>D</code> option can be used to set variable definitions that will be
+ * substituted into the XML file. An unlimited number of command-line variable
+ * definitions are allowed.
+ *
+ * In the XML file, variables have the format <code>${var}</code>. No nested variable
+ * definitions are allowed (e.g. no variables within variables).
+ *
+ * @version $Id: JobControlManager.java,v 1.49 2011/03/31 01:36:38 jeremy Exp $
+ * @author Jeremy McCormick
*/
public class JobControlManager
{
- Map<String, Driver> driverMap;
- List<Driver> driverExec;
- List<File> inputFiles;
- Map<String, String> availableDrivers = new HashMap<String, String>();
- int maxEvents = -1;
- int skipEvents = -1;
- File cacheDirectory;
- FileCache fileCache; // Start with default dir.
- ClassLoader loader;
- boolean printInputFiles;
- boolean printDriverStatistics;
- boolean printSystemProperties;
- boolean printUserClassPath;
- boolean printDriversDetailed;
- boolean printVersion;
- boolean verbose;
- boolean wasSetup;
- boolean dryRun;
- PrintStream logStream = System.out;
- LCSimLoop loop;
- Element root;
- Map<String,Double> constants = new HashMap<String,Double>();
- JDOMExpressionFactory factory;
+ private LCSimLoop loop;
+
+ private Map<String, Driver> driverMap;
+ private List<Driver> driverExec;
+ private Map<String, String> availableDrivers = new HashMap<String, String>();
+
+ private List<File> inputFiles;
+ private Map<String,String> variableMap;
+ private Map<String,Double> constants = new HashMap<String,Double>();
+
+ private int maxEvents = -1;
+ private int skipEvents = -1;
+
+ private File cacheDirectory;
+ private FileCache fileCache; // Start with default dir.
+
+ private ClassLoader loader;
+
+ // Boolean job options.
+ private boolean printInputFiles;
+ private boolean printDriverStatistics;
+ private boolean printSystemProperties;
+ private boolean printUserClassPath;
+ private boolean printDriversDetailed;
+ private boolean printVersion;
+ private boolean verbose;
+ private boolean wasSetup;
+ private boolean dryRun;
+
+ // The log stream.
+ private PrintStream logStream = System.out;
+
+ private Element root;
+
+ private JDOMExpressionFactory factory;
+
private static ParameterConverters paramConverter;
+ private Options options;
+
/**
- * Default no-argument constructor.
+ * This is the default no-argument constructor.
*/
public JobControlManager()
{
@@ -88,33 +123,164 @@
{
throw new RuntimeException(x);
}
+
+ // Create Options object to represent command-line args.
+ this.options = createCommandLineOptions();
}
/**
- * Simple frontend method taking the name of the XML steering file.
+ * Run the manager using a main. Takes command-line options (see class description).
* @param args The command line arguments.
*/
public static void main(String args[])
- {
- if (args.length == 0)
- throw new RuntimeException("Missing XML input file argument!");
- File xmlRunControlFile = new File(args[0]);
- if (!xmlRunControlFile.exists())
- throw new RuntimeException("The file " + args[0] + " does not exist!");
+ {
+ // FIXME Manual usage printout bypassing CLI.
+ if (args.length == 0)
+ {
+ System.out.println("Usage: -pPropFile.prop -Dvar=value steeringFile.xml");
+ return;
+ }
JobControlManager mgr = new JobControlManager();
- mgr.setup(xmlRunControlFile);
+ mgr.parseCommandLineOptions(args);
mgr.run();
}
+
+ /**
+ * Create the command line options.
+ * @return The command line options.
+ */
+ private Options createCommandLineOptions()
+ {
+ Options options = new Options();
+
+ Option propOpt = new Option("p", true, "Properties file with variable definitions.");
+ Option defOpt = new Option("D", true, "Variable definition.");
+
+ options.addOption(propOpt);
+ options.addOption(defOpt);
+
+ return options;
+ }
+
+ /**
+ * Parse command-line options and setup internal state from them. This method
+ * calls {@link #setup(File)} to load the steering paramters from the XML file
+ * after processing other command line options.
+ *
+ * @param args The command line arguments.
+ */
+ private void parseCommandLineOptions(String args[])
+ {
+ CommandLineParser parser = new PosixParser();
+ CommandLine cl = null;
+
+ // Parse arguments.
+ try
+ {
+ cl = parser.parse(options, args);
+ }
+ catch (ParseException x)
+ {
+ throw new RuntimeException(x);
+ }
+
+ // Init variable map.
+ variableMap = new HashMap<String, String>();
+
+ // Load properties file.
+ if (cl.hasOption("p"))
+ {
+ String[] propValues = cl.getOptionValues("p");
+ for (String propFileName : propValues)
+ {
+ InputStream in = null;
+ try
+ {
+ in = new FileInputStream(propFileName);
+ }
+ catch (FileNotFoundException e)
+ {
+ throw new RuntimeException(e);
+ }
+ Properties props = new Properties();
+ try
+ {
+ props.load(in);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ for (Entry<Object,Object> entry : props.entrySet())
+ {
+ String key = (String) entry.getKey();
+ String value = (String) entry.getValue();
+ addVariableDefinition(key, value);
+ }
+ }
+ }
+
+ // Variable definitions.
+ if (cl.hasOption("D"))
+ {
+ String[] defValues = cl.getOptionValues("D");
+ for (String def : defValues)
+ {
+ String[] s = def.split("=");
+ if (s.length != 2)
+ {
+ throw new RuntimeException("Bad variable format: " + def);
+ }
+ String key = s[0];
+ String value = s[1];
+ addVariableDefinition(key, value);
+ }
+ }
+
+ // Extra argument is name of XML steering file.
+ if (cl.getArgList().size() == 0)
+ {
+ throw new RuntimeException("Missing LCSim XML file argument.");
+ }
+ else if (cl.getArgList().size() > 1)
+ {
+ throw new RuntimeException("Too many extra arguments.");
+ }
+
+ // Setup steering file and check for existence.
+ File xmlRunControlFile = new File((String) cl.getArgList().get(0));
+ if (!xmlRunControlFile.exists())
+ throw new RuntimeException("The steering file " + args[0] + " does not exist!");
+
+ // Setup from XML.
+ setup(xmlRunControlFile);
+ }
+
+ /**
+ * Add a variable definition to be substituted into the XML steering file.
+ * @param key The variable name.
+ * @param value The variable's value.
+ */
+ private void addVariableDefinition(String key, String value)
+ {
+ //logStream.println(key + " = " + value);
+ if (!this.variableMap.containsKey(key))
+ {
+ variableMap.put(key, value);
+ }
+ else
+ {
+ throw new RuntimeException("Duplicate variable definition: " + key);
+ }
+ }
/**
- * User method for executing the job if JobControlManager is being
- * explicitly instantiated rather than created by calling the {#{@link JobControlManager#main(String[])}.
- * method.
+ * Execute a job using the current steering state.
*/
public void run()
{
if (!wasSetup)
- throw new RuntimeException("Aborting job! The setup method was never called.");
+ throw new RuntimeException("Aborting job! The setup() method was never called.");
if (printVersion)
{
@@ -255,10 +421,81 @@
{
throw new RuntimeException(x);
}
-
+
// Setup the JobControlManager from the XML file.
setup(doc);
}
+
+ /**
+ * Perform variable substitution for an entire document.
+ * @param doc The XML document.
+ */
+ private void substituteVariables(Document doc)
+ {
+ substituteVariables(doc.getRootElement());
+ }
+
+ // Pattern to extract variables from text of the form "${varName}". Underscores
+ // and dashes are accepted in variable names, as well as the usual letters and numbers.
+ static private final Pattern varPattern = Pattern.compile("[$][{][a-zA-Z_-]*[}]");
+
+ /**
+ * Substitute values from the previously filled internal <code>variableMap</code>
+ * into an XML element and all its children, recursively.
+ *
+ * @param element The XML element.
+ * @throw RuntimeException If variable does not exist in the <code>variableMap</code>.
+ */
+ private void substituteVariables(Element element)
+ {
+ String text = element.getTextNormalize();
+ if (text.length() != 0)
+ {
+ // Create a new matcher.
+ Matcher match = varPattern.matcher(text);
+
+ // No variables were used so return.
+ if (!match.find())
+ return;
+
+ // New text from current to contain resolved variable values.
+ String newText = new String(text);
+
+ // Reset the matcher.
+ match.reset();
+
+ // Loop over the matches.
+ while (match.find())
+ {
+ // Variable string with ${...} enclosure included.
+ String var = match.group();
+
+ // The name of the variable for lookup.
+ String varName = var.substring(2, var.length() - 1);
+
+ // Get the value of the variable.
+ String varValue = variableMap.get(varName);
+
+ // If a variable is not defined, then the application will immediately exit here.
+ if (varValue == null)
+ {
+ throw new RuntimeException("Variable not defined: " + varName);
+ }
+
+ // Substitute this variable's value into the text.
+ newText = newText.replace(var, varValue);
+ }
+
+ // Set the element's new text with the substituted values.
+ element.setText(newText);
+ }
+
+ // Recursively process all child elements.
+ for (Iterator it = element.getChildren().iterator(); it.hasNext(); )
+ {
+ substituteVariables((Element)it.next());
+ }
+ }
/**
* The primary setup method. Public setup methods all end up here.
@@ -266,11 +503,14 @@
*/
private void setup(Document doc)
{
- // Clear data structures in case a previous job was run.
+ // Clear and/or setup data structures for the next run.
clear();
// Set the root element.
root = doc.getRootElement();
+
+ // Do variable substitutions.
+ substituteVariables(doc);
// Setup the job control parameters.
setupJobControlParameters();
@@ -301,6 +541,10 @@
wasSetup = true;
}
+ /**
+ * Get the map of numerical constants to values.
+ * @return The map of numerical constants.
+ */
public Map<String,Double> getConstants()
{
return this.constants;
@@ -364,6 +608,12 @@
return processFileElement(fileElement, fileList);
}
+ /**
+ * Create a <code>File</code> object from the text in an XML element.
+ * @param fileElement The element containing a file path or URL.
+ * @param fileList List to append new <code>File</code>.
+ * @return The <code>File</code> object.
+ */
private File processFileElement(Element fileElement, List<File> fileList)
{
String fileLoc = processPath(fileElement.getText().trim());
@@ -407,6 +657,9 @@
return file;
}
+ /**
+ * Setup the input files to be processed using the XML steering document.
+ */
@SuppressWarnings("unchecked")
private void setupInputFiles()
{
@@ -524,6 +777,9 @@
}
}
+ /**
+ * Setup the job control parameters using the XML steering document.
+ */
private void setupJobControlParameters()
{
Element control = root.getChild("control");
@@ -679,6 +935,9 @@
}
}
+ /**
+ * Setup the manager's class loader.
+ */
private void setupClassLoader()
{
Element classpath = root.getChild("classpath");
@@ -752,6 +1011,9 @@
}
}
+ /**
+ * Setup the file cache.
+ */
private void setupFileCache()
{
if (cacheDirectory == null)
@@ -768,6 +1030,9 @@
}
}
+ /**
+ * Setup the drivers required by this job.
+ */
private void setupDrivers()
{
if (printDriversDetailed)
@@ -953,6 +1218,9 @@
}
}
+ /**
+ * Create the constants from the XML file.
+ */
private void processConstants()
{
Element define = root.getChild("define");
@@ -969,6 +1237,9 @@
}
}
+ /**
+ * Setup the system of units.
+ */
private void setupUnits()
{
Constants constants = Constants.getInstance();
@@ -978,6 +1249,11 @@
}
}
+ /**
+ * Get the Java primitive type class from a type name.
+ * @param name The name of the type.
+ * @return The primitive type class.
+ */
private static Class getPrimitiveType(String name)
{
if (name.equals("byte")) return byte.class;
@@ -992,6 +1268,11 @@
return null;
}
+ /**
+ * Get the setter methods of a class.
+ * @param klass The class.
+ * @return A list of setter methods.
+ */
public List<Method> getSetterMethods(Class klass)
{
List<Method> methods = new ArrayList<Method>();
@@ -1010,6 +1291,12 @@
return methods;
}
+ /**
+ * Process the path string to substitute in the user's home directory for
+ * the "~" character.
+ * @param path The original path.
+ * @return The path with home dir substitution.
+ */
public String processPath(String path)
{
if (path.startsWith("~"))