View Javadoc

1   package org.lcsim.job;
2   
3   import hep.aida.ref.BatchAnalysisFactory;
4   
5   import java.io.BufferedReader;
6   import java.io.File;
7   import java.io.FileInputStream;
8   import java.io.FileNotFoundException;
9   import java.io.FileOutputStream;
10  import java.io.FileReader;
11  import java.io.IOException;
12  import java.io.InputStream;
13  import java.lang.reflect.Method;
14  import java.net.MalformedURLException;
15  import java.net.URL;
16  import java.net.URLClassLoader;
17  import java.util.ArrayList;
18  import java.util.Date;
19  import java.util.HashMap;
20  import java.util.Iterator;
21  import java.util.LinkedHashMap;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Map.Entry;
25  import java.util.Properties;
26  import java.util.logging.Level;
27  import java.util.logging.Logger;
28  import java.util.regex.Matcher;
29  import java.util.regex.Pattern;
30  
31  import org.apache.commons.cli.CommandLine;
32  import org.apache.commons.cli.CommandLineParser;
33  import org.apache.commons.cli.HelpFormatter;
34  import org.apache.commons.cli.Option;
35  import org.apache.commons.cli.Options;
36  import org.apache.commons.cli.ParseException;
37  import org.apache.commons.cli.PosixParser;
38  import org.freehep.record.loop.RecordEvent;
39  import org.jdom.Attribute;
40  import org.jdom.Document;
41  import org.jdom.Element;
42  import org.jdom.Text;
43  import org.jdom.input.SAXBuilder;
44  import org.jdom.output.Format;
45  import org.jdom.output.XMLOutputter;
46  import org.lcsim.conditions.ConditionsManager;
47  import org.lcsim.conditions.ConditionsManager.ConditionsNotFoundException;
48  import org.lcsim.event.EventHeader;
49  import org.lcsim.units.Constants;
50  import org.lcsim.util.Driver;
51  import org.lcsim.util.DriverAdapter;
52  import org.lcsim.util.cache.FileCache;
53  import org.lcsim.util.loop.LCIOEventSource;
54  import org.lcsim.util.loop.LCSimConditionsManagerImplementation;
55  import org.lcsim.util.loop.LCSimLoop;
56  import org.lcsim.util.xml.ClasspathEntityResolver;
57  import org.lcsim.util.xml.JDOMExpressionFactory;
58  
59  /**
60   * <p>
61   * This class provides a front end for running and managing LCSim event processing jobs using XML steering files.
62   * <p>
63   * More details about this XML format can be found at the<br/>
64   * <a href="https://confluence.slac.stanford.edu/display/ilc/lcsim+xml">LCSim XML Confluence Page</a>.
65   * <p>
66   * The command line syntax is:<br/>
67   * <code>java org.lcsim.job.JobManager steeringFile.xml [options]</code>
68   * <p>
69   * To see the available command line options with descriptions, run with "-h" as the only option.
70   * <p>
71   * Command-line parameters that can be defined using switches are overridden by the corresponding settings in the job
72   * XML file, if they are present. This means that if these parameters are to be taken from the CL, the matching settings
73   * should be left out of the XML job file. This is not the case, however, for input files specified by the "-i" option,
74   * which are appended to the ones listed in the steering file.
75   *
76   * @version $Id: JobControlManager.java,v 1.65 2013/02/14 22:30:07 jeremy Exp $
77   * @author Jeremy McCormick
78   */
79  @SuppressWarnings({"unchecked", "rawtypes"})
80  public class JobControlManager {
81  
82      /**
83       * Initialize the logger which uses the package name.
84       */
85      protected static final Logger LOGGER = Logger.getLogger(JobControlManager.class.getPackage().getName());
86  
87      /**
88       * The command line options.
89       */
90      private static final Options OPTIONS = createCommandLineOptions();
91  
92      /**
93       * The regular expression for extracting the variables from an XML file.
94       */
95      private static final Pattern VARIABLE_PATTERN = Pattern.compile("[$][{][a-zA-Z_-]*[}]");
96  
97      /**
98       * Create the command line options.
99       *
100      * @return The command line options for the manager.
101      */
102     private static Options createCommandLineOptions() {
103         final Options options = new Options();
104         options.addOption(new Option("h", "help", false, "Print help and exit"));
105         options.addOption(new Option("p", "properties", true, "Load a properties file containing variable definitions"));
106         options.addOption(new Option("D", "define", true, "Define a variable with form [name]=[value]"));
107         options.addOption(new Option("w", "rewrite", true, "Rewrite the XML file with variables resolved"));
108         options.addOption(new Option("s", "skip", true, "Set the number of events to skip"));
109         options.addOption(new Option("n", "nevents", true, "Set the max number of events to process"));
110         options.addOption(new Option("x", "dry-run", false, "Perform a dry run which does not process events"));
111         options.addOption(new Option("i", "input-file", true, "Add an LCIO input file to process"));
112         options.addOption(new Option("r", "resource", false, "Use a steering resource rather than a file"));
113         options.addOption(new Option("b", "batch", false, "Run in batch mode in which plots will not be shown."));
114         options.addOption(new Option("e", "event-print", true, "Event print interval"));
115         options.addOption(new Option("d", "detector", true, "user supplied detector name (careful!)"));
116         options.addOption(new Option("R", "run", true, "user supplied run number (careful!)"));
117         return options;
118     }
119 
120     /**
121      * Get the Java primitive type class from a type name.
122      *
123      * @param name The name of the type.
124      * @return The primitive type class.
125      */
126     private static Class getPrimitiveType(final String name) {
127         if (name.equals("byte")) {
128             return byte.class;
129         }
130         if (name.equals("short")) {
131             return short.class;
132         }
133         if (name.equals("int")) {
134             return int.class;
135         }
136         if (name.equals("long")) {
137             return long.class;
138         }
139         if (name.equals("char")) {
140             return char.class;
141         }
142         if (name.equals("float")) {
143             return float.class;
144         }
145         if (name.equals("double")) {
146             return double.class;
147         }
148         if (name.equals("boolean")) {
149             return boolean.class;
150         }
151         if (name.equals("String")) {
152             return String.class;
153         }
154         return null;
155     }
156 
157     /**
158      * Run from the command line.
159      * <p>
160      * Takes command-line options (use -h option to see them).
161      *
162      * @param args the command line arguments
163      */
164     public static void main(final String args[]) {
165         final JobControlManager mgr = new JobControlManager();
166         mgr.parse(args);
167         mgr.run();
168     }
169 
170     /**
171      * Print help and exit.
172      */
173     private static void printHelp() {
174         LOGGER.info("java " + JobControlManager.class.getCanonicalName() + " [options] steeringFile.xml");
175         final HelpFormatter help = new HelpFormatter();
176         help.printHelp(" ", OPTIONS);
177         System.exit(1);
178     }
179 
180     /**
181      * Root directory for file caching.
182      */
183     private File cacheDirectory;
184 
185     /**
186      * The class loader that will be used for the job.
187      */
188     private ClassLoader classLoader;
189 
190     /**
191      * Command line that is setup from <code>main</code> arguments.
192      */
193     CommandLine commandLine = null;
194 
195     /**
196      * Map of constants definitions.
197      */
198     private final Map<String, Double> constantsMap = new HashMap<String, Double>();
199 
200     /**
201      * User supplied detector name.
202      */
203     private String detectorName = null;
204 
205     /**
206      * A driver adapter created on the fly in case it is needed by an external program.
207      */
208     private DriverAdapter driverAdapter = null;
209 
210     /**
211      * List of drivers to execute in the job.
212      */
213     private final List<Driver> driverExec = new ArrayList<Driver>();
214 
215     /**
216      * Map of driver names to objects.
217      */
218     private final Map<String, Driver> driverMap = new LinkedHashMap<String, Driver>();
219 
220     /**
221      * Enable dry run so no events are processed.
222      */
223     private boolean dryRun;
224 
225     /**
226      * Setup a "dummy" detector in the conditions system.
227      */
228     private boolean dummyDetector;
229 
230     /**
231      * Event printing interval (null means no event printing).
232      */
233     private Long eventPrintInterval = null;
234 
235     /**
236      * JDOM expression factory for variables.
237      */
238     private final JDOMExpressionFactory factory = new JDOMExpressionFactory();
239 
240     /**
241      * File cache.
242      */
243     private FileCache fileCache; // Start with default dir.
244 
245     /**
246      * List of input LCIO files.
247      */
248     private final List<File> inputFiles = new ArrayList<File>();
249 
250     /**
251      * Flag set to <code>true</code> after setup is performed.
252      */
253     private boolean isSetup;
254 
255     /**
256      * The job end timestamp in ms.
257      */
258     private long jobEnd = 0;
259 
260     /**
261      * The job start timestamp in ms.
262      */
263     private long jobStart = 0;
264 
265     /**
266      * The LCIO record loop.
267      */
268     private LCSimLoop loop;
269 
270     /**
271      * Number of events to run before stopping job.
272      */
273     private int numberOfEvents = -1;
274 
275     /**
276      * Helper for converting Driver parameters.
277      */
278     private final ParameterConverters paramConverter = new ParameterConverters(factory);
279 
280     /**
281      * Set to <code>true</code> to print out driver statistics at the end of the job.
282      */
283     private boolean printDriverStatistics;
284 
285     /**
286      * Path for rewriting steering file with variables resolved.
287      */
288     private File rewriteFile;
289 
290     /**
291      * Enable rewriting of the steering file to a new path with variables resolved.
292      */
293     private boolean rewriteSteering;
294 
295     /**
296      * The root node of the XML document providing config to the manager.
297      */
298     private Element root;
299 
300     /**
301      * User supplied run number.
302      */
303     private Integer runNumber = null;
304 
305     /**
306      * Number of events to skip at start of job.
307      */
308     private int skipEvents = -1;
309 
310     /**
311      * Interpret steering file argument as a resource rather than file path.
312      */
313     private boolean useSteeringResource;
314 
315     /**
316      * Map of variable names to their values.
317      */
318     private final Map<String, String> variableMap = new HashMap<String, String>();
319 
320     /**
321      * The default constructor.
322      */
323     public JobControlManager() {
324 
325         try {
326             fileCache = new FileCache();
327         } catch (final IOException x) {
328             throw new RuntimeException(x);
329         }
330 
331         // FIXME: Should this instead be done when the job is started?
332         LCSimConditionsManagerImplementation.register();
333     }
334 
335     /**
336      * Add a Driver to the internal Driver map.
337      *
338      * @param name the unique name of the Driver
339      * @param driver the instance of the Driver
340      */
341     private void addDriver(final String name, final Driver driver) {
342         if (driverMap.containsKey(name)) {
343             throw new RuntimeException("Duplicate driver name: " + name);
344         }
345         driverMap.put(name, driver);
346     }
347 
348     /**
349      * Add an input LCIO file to be proceesed.
350      *
351      * @param inputFile The input LCIO file.
352      */
353     public void addInputFile(final File inputFile) {
354         if (isSetup) {
355             throw new RuntimeException("Input files cannot be added when manager has already been setup.");
356         }
357         inputFiles.add(inputFile);
358     }
359 
360     /**
361      * Add a variable definition to be substituted into the job's XML file. This method is public so that caller's not
362      * using the CL can still define necessary variables for the steering file.
363      *
364      * @param key The variable name.
365      * @param value The variable's value.
366      */
367     public void addVariableDefinition(final String key, final String value) {
368         LOGGER.config(key + " = " + value);
369         if (!this.variableMap.containsKey(key)) {
370             variableMap.put(key, value);
371         } else {
372             throw new RuntimeException("Duplicate variable definition: " + key);
373         }
374     }
375 
376     /**
377      * Configure start of job (usually done automatically).
378      */
379     public void configure() {
380         this.getDriverAdapter().start(null);
381     }
382 
383     /**
384      * Create a driver adapter.
385      */
386     private void createDriverAdapter() {
387         if (this.isSetup == false) {
388             throw new IllegalStateException("The job manager was never setup.");
389         }
390         final Driver topDriver = new Driver();
391         for (final Driver driver : this.getDriverExecList()) {
392             topDriver.add(driver);
393         }
394         driverAdapter = new DriverAdapter(topDriver);
395     }
396 
397     /**
398      * Create the <code>Driver</code> execution list.
399      */
400     private void createDriverExecList() {
401         // Make a list of Drivers to be executed.
402         final List<Element> exec = root.getChild("execute").getChildren("driver");
403         for (final Element execDriver : exec) {
404             final String driverName = execDriver.getAttributeValue("name");
405             final Driver driverFind = driverMap.get(driverName);
406             if (driverFind != null) {
407                 driverExec.add(driverFind);
408             } else {
409                 throw new RuntimeException("A Driver called " + driverName + " was not found.");
410             }
411         }
412 
413         // Add the drivers to the LCSimLoop.
414         for (final Driver driver : driverExec) {
415             loop.add(driver);
416         }
417     }
418 
419     /**
420      * Turn on the batch analysis factory so plots are not shown on the screen even when <code>Plotter.show()</code> is
421      * called.
422      */
423     public void enableHeadlessMode() {
424         System.setProperty("hep.aida.IAnalysisFactory", BatchAnalysisFactory.class.getName());
425     }
426 
427     /**
428      * Activate end of job hooks (usually done automatically).
429      */
430     public void finish() {
431         this.getDriverAdapter().finish(null);
432     }
433 
434     /**
435      * Get a <code>DriverAdapter</code> from the currently configured Driver list.
436      *
437      * @return the driver adapter
438      */
439     public DriverAdapter getDriverAdapter() {
440         if (driverAdapter == null) {
441             // Driver adapter created on demand.
442             this.createDriverAdapter();
443         }
444         return driverAdapter;
445     }
446 
447     /**
448      * Return a list of Drivers to be executed. This can be used from an external framework like JAS3. The list will be
449      * empty unless the <code>setup()</code> method has been called.
450      *
451      * @return A <code>List</code> of <code>Drivers</code>.
452      */
453     public List<Driver> getDriverExecList() {
454         return this.driverExec;
455     }
456 
457     /**
458      * Get the <code>LCSimLoop</code> of this JobManager.
459      *
460      * @return The LCSimLoop.
461      */
462     public LCSimLoop getLCSimLoop() {
463         return loop;
464     }
465 
466     /**
467      * Get a list of a class's setter methods.
468      *
469      * @param klass The class.
470      * @return A list of setter methods.
471      */
472     private List<Method> getSetterMethods(final Class klass) {
473         final List<Method> methods = new ArrayList<Method>();
474         Class currentClass = klass;
475         while (currentClass != null) {
476             for (final Method method : currentClass.getMethods()) {
477                 if (method.getName().startsWith("set") && !methods.contains(method)) {
478                     methods.add(method);
479                 }
480             }
481             currentClass = currentClass.getSuperclass();
482         }
483         return methods;
484     }
485 
486     /**
487      * Initialize the <code>LCSimLoop</code>.
488      */
489     private void initializeLoop() {
490         LOGGER.config("initializing LCSim loop");
491         loop = new LCSimLoop();
492         if (this.eventPrintInterval != null) {
493             loop.addRecordListener(new EventPrintLoopAdapter(this.eventPrintInterval));
494             LOGGER.config("enabled event marker printing with interval " + eventPrintInterval);
495         } else {
496             LOGGER.config("no event printing enabled");
497         }
498     }
499 
500     /**
501      * Parse command-line options and setup job state from them. This method calls {@link #setup(File)} to load the
502      * steering paramters from an XML file, after processing other command line options. This method is private so that
503      * callers must all use the {@link #main(String[])} routine as the primary entry point.
504      *
505      * @param args The command line arguments.
506      */
507     public void parse(final String args[]) {
508         
509         LOGGER.config("parsing command line arguments");
510 
511         // Setup parser.
512         final CommandLineParser parser = new PosixParser();
513 
514         // Parse the command line arguments.
515         try {
516             commandLine = parser.parse(OPTIONS, args);
517         } catch (final ParseException x) {
518             throw new RuntimeException("Problem parsing command line options.", x);
519         }
520         
521         // Print help and exit.
522         if (args.length == 0 || commandLine.hasOption("h")) {
523             printHelp();
524         }
525 
526         // Load a properties file containing variable definitions.
527         if (commandLine.hasOption("p")) {
528             final String[] propValues = commandLine.getOptionValues("p");
529             for (final String propFileName : propValues) {
530                 InputStream in = null;
531                 try {
532                     in = new FileInputStream(propFileName);
533                 } catch (final FileNotFoundException e) {
534                     throw new RuntimeException(e);
535                 }
536                 final Properties props = new Properties();
537                 try {
538                     props.load(in);
539                 } catch (final IOException e) {
540                     throw new RuntimeException(e);
541                 }
542                 for (final Entry<Object, Object> entry : props.entrySet()) {
543                     final String key = (String) entry.getKey();
544                     final String value = (String) entry.getValue();
545                     this.addVariableDefinition(key, value);
546                 }
547                 LOGGER.config("loaded variable definitions from " + propFileName);
548             }            
549         }
550 
551         // Process the user variable definitions.
552         if (commandLine.hasOption("D")) {
553             final String[] defValues = commandLine.getOptionValues("D");
554             for (final String def : defValues) {
555                 final String[] s = def.split("=");
556                 if (s.length != 2) {
557                     throw new RuntimeException("Bad variable format: " + def);
558                 }
559                 final String key = s[0];
560                 final String value = s[1];
561                 this.addVariableDefinition(key, value);
562                 LOGGER.config("defined " + key + " = " + value);
563             }
564         }
565 
566         // Rewrite XML file with variables resolved.
567         if (commandLine.hasOption("w")) {
568             this.rewriteSteering = true;
569             final String rewritePath = commandLine.getOptionValue("w");
570             this.rewriteFile = new File(rewritePath);
571             if (this.rewriteFile.exists()) {
572                 throw new RuntimeException("Rewrite file already exists: " + rewritePath);
573             }
574             LOGGER.config("XML will be rewritten to " + this.rewriteFile.getPath());
575         }
576 
577         // Set max number of events to run.
578         if (commandLine.hasOption("n")) {
579             this.numberOfEvents = Integer.valueOf(commandLine.getOptionValue("n"));
580             LOGGER.config("max number of events set to " + this.numberOfEvents);
581         }
582 
583         // Set number of events to skip.
584         if (commandLine.hasOption("s")) {
585             this.skipEvents = Integer.valueOf(commandLine.getOptionValue("s"));
586             LOGGER.config("skip events set to " + this.skipEvents);
587         }
588 
589         // Perform a dry run, not processing any events but doing job setup.
590         if (commandLine.hasOption("x")) {
591             this.dryRun = true;
592             LOGGER.config("dry run is enabled");
593         }
594 
595         // Interpret steering argument as a resource rather than file path.
596         if (commandLine.hasOption("r")) {
597             this.useSteeringResource = true;
598             LOGGER.config("steering resource enabled");
599         }
600 
601         // Check that there is exactly one extra argument for the XML steering file.
602         if (commandLine.getArgList().size() == 0) {
603             throw new RuntimeException("Missing LCSim XML file argument.");
604         } else if (commandLine.getArgList().size() > 1) {
605             throw new RuntimeException("Too many extra arguments.");
606         }
607 
608         // Local LCIO files to process.
609         if (commandLine.hasOption("i")) {
610             final String[] files = commandLine.getOptionValues("i");
611             for (final String fileName : files) {
612                 final File file = new File(fileName);
613                 if (!file.exists()) {
614                     throw new RuntimeException("File given as command line option does not exist: " + fileName);
615                 }
616                 inputFiles.add(new File(fileName));
617                 LOGGER.config("added input file " + fileName);
618             }
619         }
620 
621         // Run in headless mode in which plots will not show.
622         if (commandLine.hasOption("b")) {
623             this.enableHeadlessMode();
624             LOGGER.config("headless mode enabled");
625         }
626 
627         // Steering argument points to either a file or embedded resource.
628         final String steering = (String) commandLine.getArgList().get(0);
629 
630         if (commandLine.hasOption("e")) {
631             this.eventPrintInterval = Long.parseLong(commandLine.getOptionValue("e"));
632             LOGGER.config("eventPrintInterval: " + this.eventPrintInterval);
633             if (this.eventPrintInterval <= 0) {
634                 throw new IllegalArgumentException("The event print interval must be > 0.");
635             }
636         }
637 
638         if (commandLine.hasOption("d")) {
639             this.detectorName = commandLine.getOptionValue("d");
640             LOGGER.config("detector: " + this.detectorName);
641         }
642 
643         if (commandLine.hasOption("R")) {
644             this.runNumber = Integer.parseInt(commandLine.getOptionValue("R"));
645             LOGGER.config("runNumber: " + this.runNumber);
646         }
647 
648         if (this.detectorName != null && this.runNumber == null || 
649                 this.runNumber != null && this.detectorName == null) {
650             throw new IllegalArgumentException("The detector name and run number must be given together.");
651         }
652         
653         // This will actually initialize everything from the steering file so it must come last.
654         if (this.useSteeringResource) {
655             // Using an embedded resource in the jar for steering.
656             LOGGER.config("initializing from steering resource " + steering);
657             this.setup(steering);
658         } else {
659             // Steering from a local file.
660             final File xmlRunControlFile = new File(steering);
661             if (!xmlRunControlFile.exists()) {
662                 throw new RuntimeException("The steering file " + args[0] + " does not exist!");
663             }
664             LOGGER.config("initializing from steering file " + xmlRunControlFile.getPath());
665             this.setup(xmlRunControlFile);
666         }
667     }
668 
669     /**
670      * Print the list of input files.
671      */
672     private void printInputFileList() {
673         final StringBuffer sb = new StringBuffer();
674         sb.append('\n');
675         sb.append("--- Input Files ---");
676         for (final File file : inputFiles) {
677             sb.append(file.getAbsolutePath());
678             sb.append('\n');
679         }
680         LOGGER.config(sb.toString());
681     }
682 
683     /**
684      * Print out extra URLs added to the classpath from the XML.
685      */
686     private void printUserClasspath() {
687         final StringBuffer sb = new StringBuffer();
688         final URL[] urls = ((URLClassLoader) classLoader).getURLs();
689         if (urls.length > 0) {
690             for (final URL url : ((URLClassLoader) classLoader).getURLs()) {
691                 sb.append(url + " ");
692             }
693             sb.append('\n');
694             LOGGER.config("Extra classpath URLs:" + sb.toString());
695         }
696     }
697 
698     /**
699      * Create the constants from the XML file.
700      */
701     private void processConstants() {
702         final Element define = root.getChild("define");
703         if (define != null) {
704             for (final Object o : define.getChildren()) {
705                 final Element e = (Element) o;
706                 final Text txt = (Text) e.getContent().get(0);
707                 final double dval = factory.computeDouble(txt.getValue());
708                 this.constantsMap.put(e.getName(), dval);
709                 factory.addConstant(e.getName(), dval);
710             }
711         }
712     }
713 
714     /**
715      * A fairly ugly method to process the provided XML parameters on a <code>Driver</code>.
716      *
717      * @param driverClass the Java class of the driver
718      * @param newDriver the instantiated Driver
719      * @param parameters the list of XML parameters
720      */
721     private void processDriverParameters(final Class driverClass, final Driver newDriver, final List<Element> parameters) {
722         // Process the parameter elements.
723         for (final Element parameterElement : parameters) {
724 
725             // The parameter's setter method that we will try to find.
726             Method setter = null;
727 
728             // The parameter's type that will be inferred from the method or provided by an XML attribute.
729             Class propertyType = null;
730 
731             // Get the parameter's name.
732             final String pname = parameterElement.getName();
733 
734             // Find setter methods that look like good matches for this parameter.
735             final List<Method> methods = this.getSetterMethods(driverClass);
736             final List<Method> methodCandidates = new ArrayList<Method>();
737             for (final Method method : methods) {
738                 String propHack = method.getName().replaceFirst("set", "");
739                 propHack = propHack.substring(0, 1).toLowerCase() + propHack.substring(1);
740                 if (propHack.equals(pname)) {
741                     methodCandidates.add(method);
742                 }
743             }
744             if (methodCandidates.size() == 1) {
745                 // Found the single setter method so try to use it.
746                 setter = methodCandidates.get(0);
747                 if (setter.getParameterTypes().length > 1) {
748                     throw new RuntimeException("The set method has too many arguments for parameter: " + pname);
749                 }
750                 propertyType = setter.getParameterTypes()[0];
751             } else if (methodCandidates.size() > 1) {
752                 // Found several, overloaded methods. Try to disambiguate them if possible.
753                 if (parameterElement.getAttribute("type") == null) {
754                     throw new RuntimeException("Parameter " + pname + " in Driver " + driverClass.getCanonicalName()
755                             + " is overloaded, but a type field is missing from the parameter's XML element.");
756                 }
757                 try {
758                     // Try a primitive type first.
759                     propertyType = getPrimitiveType(parameterElement.getAttribute("type").getValue());
760 
761                     // If type is null, then parameter is an Object and not a primitive, or it
762                     // is not a valid type.
763                     if (propertyType == null) {
764                         propertyType = Class.forName(parameterElement.getAttribute("type").getValue());
765                     }
766                 } catch (final ClassNotFoundException x) {
767                     throw new RuntimeException("Bad type " + parameterElement.getAttribute("type").getValue()
768                             + " given for parameter " + pname + ".");
769                 }
770                 // Find a method that matches the user type.
771                 for (final Method candidateMethod : methodCandidates) {
772                     if (candidateMethod.getParameterTypes().length == 1
773                             && candidateMethod.getParameterTypes()[0].equals(propertyType)) {
774                         setter = candidateMethod;
775                         break;
776                     }
777                 }
778             } else if (methodCandidates.size() == 0) {
779                 // No method found. The parameter name is probably invalid.
780                 throw new RuntimeException("Set method for Driver parameter " + pname + " was not found.");
781             }
782 
783             // No setter method found.
784             if (setter == null) {
785                 throw new RuntimeException("Unable to find set method for parameter " + pname + ".");
786             }
787 
788             // Convert the parameter to the appropriate type.
789             final IParameterConverter converter = paramConverter.getConverterForType(propertyType);
790             if (converter == null) {
791                 throw new RuntimeException("No converter found for parameter " + parameterElement.getName()
792                         + " with type " + propertyType.getName() + ".");
793             }
794             final Object nextParameter = converter.convert(factory, parameterElement);
795 
796             // Call the setter with the parameter as argument.
797             final Object pargs[] = new Object[1];
798             pargs[0] = nextParameter;
799             try {
800                 // This invokes the setter method of the driver.
801                 setter.invoke(newDriver, pargs);
802 
803                 // Print parameters and values as they are set.
804                 LOGGER.fine("    " + pname + " = " + parameterElement.getText().trim());
805                 
806             } catch (final Exception x) {
807                 throw new RuntimeException("Problem processing parameter " + parameterElement.getName() + ".", x);
808             }
809         } // parameter loop
810     }
811 
812     /**
813      * Process a single event.
814      *
815      * @param event
816      */
817     public void processEvent(final EventHeader event) {
818         this.getDriverAdapter().recordSupplied(new RecordEvent(loop, event));
819     }
820 
821     /**
822      * Create a <code>File</code> object from the text in an XML element.
823      *
824      * @param fileElement The element containing a file path or URL.
825      * @param fileList List to append new <code>File</code>.
826      * @return The <code>File</code> object.
827      */
828     private File processFileElement(final Element fileElement, final List<File> fileList) {
829 
830         final String fileLoc = this.processPath(fileElement.getText().trim());
831         File file = null;
832 
833         // Try to process the file text as a URL.
834         try {
835             final URL fileURL = new URL(fileLoc);
836 
837             // Local file URL.
838             if (fileLoc.startsWith("file:")) {
839                 file = new File(fileURL.getPath());
840             } else {
841                 // Remote file URL.
842                 try {
843                     file = this.fileCache.getCachedFile(fileURL);
844                 } catch (final IOException x) {
845                     throw new RuntimeException("Unable to fetch file " + fileLoc + " to the cache directory.", x);
846                 }
847             }
848         } catch (final MalformedURLException x) {
849             // Interpret as local file.
850             file = new File(fileLoc);
851         }
852 
853         // Add to the list.
854         if (fileList != null) {
855             fileList.add(file);
856         }
857 
858         return file;
859     }
860 
861     /**
862      * Cleanup file text and add to the file list.
863      *
864      * @param fileText the text containing the file's path from the XML
865      * @param fileList the list of files to which the new file will be appended
866      * @return the file that was created from the text
867      */
868     private File processFileText(final String fileText, final List<File> fileList) {
869         final Element fileElement = new Element("file");
870         fileElement.setText(fileText.trim());
871         return this.processFileElement(fileElement, fileList);
872     }
873 
874     /**
875      * Process the file path string to substitute in the user's home directory for the "~" character. This is needed if
876      * running on Windows.
877      *
878      * @param path The original path.
879      * @return The path with home dir substitution.
880      */
881     private String processPath(final String path) {
882         if (path.startsWith("~")) {
883             return path.replaceFirst("~", System.getProperty("user.home"));
884         } else {
885             return path;
886         }
887     }
888 
889     /**
890      * Rewrite the XML steering file after resolving variables (done externally). The output path is set by command line
891      * argument to "-w".
892      *
893      * @param doc The XML steering doc with variables substituted.
894      */
895     private void rewriteXMLSteering(final Document doc) {
896         LOGGER.info("Rewriting XML to " + this.rewriteFile);
897         final XMLOutputter outputter = new XMLOutputter();
898         outputter.setFormat(Format.getPrettyFormat());
899         try {
900             final FileOutputStream out = new FileOutputStream(this.rewriteFile);
901             outputter.output(doc, out);
902             out.close();
903         } catch (final Exception e) {
904             throw new RuntimeException(e);
905         }
906     }
907 
908     /**
909      * Execute a job using the current parameters.
910      */
911     public boolean run() {
912 
913         // If setup was not called first, then abort the job.
914         if (!isSetup) { 
915             LOGGER.info("Aborting job!  Setup was never called.");
916             return false;
917         }
918 
919         // Dry run selected. No events will be processed.
920         if (dryRun) {
921             LOGGER.info("Executed dry run.  No events processed!");
922             return false;
923         }
924 
925         boolean okay = true;
926 
927         try {
928             // Add the LCIO files to the loop.
929             loop.setLCIORecordSource(new LCIOEventSource(this.getClass().getSimpleName(), inputFiles));
930 
931             // Set up user supplied conditions information (already checked that these were both given if one was used).
932             initializeConditions();
933             
934             // Setup dummy detector if selected.
935             if (dummyDetector) {
936                 LOGGER.info("Using dummy detector for conditions system!");
937                 loop.setDummyDetector("dummy");
938             }
939 
940             this.jobStart = System.currentTimeMillis();
941 
942             LOGGER.info("Job started: " + new Date(jobStart));
943 
944             if (this.skipEvents > 0) {
945                 LOGGER.info("Skipping " + skipEvents + " events.");
946                 loop.skip(skipEvents);
947             }
948 
949             // Execute the loop.
950             final long processedEvents = loop.loop(numberOfEvents, this.printDriverStatistics ? System.out : null);
951             if (numberOfEvents != -1 && processedEvents != numberOfEvents) {
952                 LOGGER.info("End of file was reached.");
953             }
954             LOGGER.info("Job processed " + processedEvents + " events.");
955             this.jobEnd = System.currentTimeMillis();
956 
957             LOGGER.info("Job ended: " + new Date(this.jobEnd));
958             final long elapsed = this.jobStart - this.jobEnd;
959             LOGGER.info("Job took " + elapsed + " which is " + elapsed / processedEvents + " ms/event.");
960 
961         } catch (final Exception e) {
962             LOGGER.log(Level.SEVERE, "A fatal error occurred during the job.", e);
963             okay = false;
964         } finally {
965             try {
966                 // Dispose of the loop.
967                 loop.dispose();
968             } catch (final Exception e) {
969                 LOGGER.log(Level.WARNING, "An error occurred during job cleanup.", e);
970                 okay = false;
971             }
972         }
973 
974         // Return true or false depending on whether job was successfully executed.
975         return okay;
976     }
977 
978     /**
979      * Set whether a dry run should be performed which will only perform setup and not process any events.
980      *
981      * @param dryRun <code>true</code> to enable a dry run
982      */
983     public void setDryRun(final boolean dryRun) {
984         this.dryRun = dryRun;
985     }
986 
987     /**
988      * Set the number of events to run on the loop before ending the job. This should be called after the
989      * {@link #setup(File)} method is called or it will be overridden.
990      */
991     public void setNumberOfEvents(final int numberOfEvents) {
992         this.numberOfEvents = numberOfEvents;
993     }
994 
995     /**
996      * Setup the job parameters from an XML Document.
997      * <p>
998      * This method contains the primary logic for setting up job parameters from an XML file. The other setup methods
999      * such as {@link #setup(InputStream)}, {@link #setup(String)} and {@link #setup(File)} all call this method.
1000      *
1001      * @param xmlDocument The lcsim recon XML document describing the job.
1002      */
1003     private void setup(final Document xmlDocument) {
1004 
1005         // This method should not be called more than once.
1006         if (isSetup) {
1007             throw new IllegalStateException("The job manager was already setup.");
1008         }
1009 
1010         // Set the root element from the XML document.
1011         root = xmlDocument.getRootElement();
1012 
1013         // Do variable substitutions into the document first.
1014         this.substituteVariables(xmlDocument);
1015 
1016         // Rewrite XML after variable substitution.
1017         if (this.rewriteSteering) {
1018             this.rewriteXMLSteering(xmlDocument);
1019         }
1020 
1021         // Setup the job control parameters.
1022         this.setupJobControlParameters();
1023 
1024         // Setup the class loader.
1025         this.setupClassLoader();
1026 
1027         // Setup system of units.
1028         this.setupUnits();
1029 
1030         // Process the constant definitions.
1031         this.processConstants();
1032 
1033         // Initialize the LCSimLoop.
1034         this.initializeLoop();
1035 
1036         // Setup drivers with parameters and execution order.
1037         this.setupDrivers();
1038 
1039         // Setup the file cache.
1040         this.setupFileCache();
1041 
1042         // Setup the input files.
1043         this.setupInputFiles();
1044 
1045         // Throw an error if there were no files provided and dry run is not enabled.
1046         if (inputFiles.size() == 0 && !this.dryRun) {
1047             LOGGER.severe("No input files provided and dry run is not enabled.");
1048             throw new IllegalStateException("No input files to process.");
1049         }
1050 
1051         // Flag JobManager as setup.
1052         isSetup = true;
1053     }
1054 
1055     /**
1056      * Setup job parameters from a <code>File</code>.
1057      *
1058      * @param file the path to the XML file
1059      */
1060     public void setup(final File file) {
1061         try {
1062             this.setup(new FileInputStream(file));
1063         } catch (final FileNotFoundException x) {
1064             throw new RuntimeException(x);
1065         }
1066     }
1067 
1068     /**
1069      * Setup job parameters from an <code>InputStream</code> that should be valid XML text.
1070      *
1071      * @param in the XML input stream
1072      */
1073     public void setup(final InputStream in) {
1074 
1075         // Make the Document builder.
1076         final SAXBuilder builder = new SAXBuilder();
1077 
1078         // Setup XML schema validation.
1079         builder.setEntityResolver(new ClasspathEntityResolver());
1080         builder.setValidation(true);
1081         builder.setFeature("http://apache.org/xml/features/validation/schema", true);
1082 
1083         // Setup expression resolution.
1084         builder.setFactory(factory);
1085 
1086         // Build the document.
1087         Document doc = null;
1088         try {
1089             doc = builder.build(in);
1090         } catch (final Exception x) {
1091             throw new RuntimeException(x);
1092         }
1093 
1094         // Setup the JobControlManager from the XML file.
1095         this.setup(doc);
1096     }
1097 
1098     /**
1099      * Setup job parameters from an embedded resource. This method calls {@link #setup(InputStream)}.
1100      */
1101     public void setup(final String resourceURL) {
1102         this.setup(this.getClass().getResourceAsStream(resourceURL));
1103     }
1104 
1105     /**
1106      * Setup the manager's class loader.
1107      */
1108     private void setupClassLoader() {
1109 
1110         if (classLoader != null) {
1111             LOGGER.info("The ClassLoader was already set externally, so custom classpaths will be ignored!");
1112             return;
1113         }
1114 
1115         final Element classpath = root.getChild("classpath");
1116         final List<URL> urlList = new ArrayList<URL>();
1117         if (classpath != null) {
1118             for (final Object jarObject : classpath.getChildren("jar")) {
1119                 final Element jarElement = (Element) jarObject;
1120                 try {
1121                     urlList.add(new File(this.processPath(jarElement.getText())).toURI().toURL());
1122                 } catch (final Exception x) {
1123                     throw new RuntimeException("Bad jar location: " + jarElement.getText(), x);
1124                 }
1125             }
1126             for (final Object jarUrlObject : classpath.getChildren("jarUrl")) {
1127                 final Element jarUrlElement = (Element) jarUrlObject;
1128                 try {
1129                     urlList.add(new URL(jarUrlElement.getText()));
1130                 } catch (final Exception x) {
1131                     throw new RuntimeException("Bad jar URL: " + jarUrlElement.getText(), x);
1132                 }
1133             }
1134             for (final Object cpDirObject : classpath.getChildren("directory")) {
1135                 final Element cpDirElement = (Element) cpDirObject;
1136                 try {
1137                     final File cpFile = new File(this.processPath(cpDirElement.getText()));
1138                     if (!cpFile.isDirectory()) {
1139                         throw new RuntimeException("The classpath component " + cpFile.getPath()
1140                                 + " is not a valid directory!");
1141                     }
1142                     urlList.add(cpFile.toURI().toURL());
1143                 } catch (final Exception x) {
1144                     throw new RuntimeException("Bad classpath directory: " + cpDirElement.getText(), x);
1145                 }
1146             }
1147         }
1148         final URL[] urls = urlList.toArray(new URL[] {});
1149 
1150         classLoader = new LCSimClassLoader(urls);
1151 
1152         // Print extra user classpath entries.
1153         this.printUserClasspath();
1154     }
1155 
1156     /**
1157      * Create the drivers from the XML.
1158      */
1159     private void setupDrivers() {
1160 
1161         // Loop over the list of driver elements.
1162         final List<Element> drivers = root.getChild("drivers").getChildren("driver");
1163         for (final Element driver : drivers) {
1164 
1165             // Get the name of the Driver.
1166             final String name = driver.getAttributeValue("name");
1167 
1168             // Get the fully qualified type of the Driver. ([packageName].[className])
1169             final String type = driver.getAttributeValue("type");
1170 
1171             // Get the Java class of the Driver.
1172             Class driverClass;
1173             try {
1174                 driverClass = classLoader.loadClass(type);
1175             } catch (final ClassNotFoundException x) {
1176                 throw new RuntimeException("The Driver class " + type + " was not found.", x);
1177             }
1178 
1179             LOGGER.fine("adding driver " + driverClass.getCanonicalName());
1180 
1181             // Create an instance of the driver.
1182             Driver newDriver;
1183             try {
1184                 newDriver = (Driver) driverClass.newInstance();
1185             } catch (final InstantiationException x) {
1186                 throw new RuntimeException("Failed to create a Driver of class " + type + ".", x);
1187             } catch (final IllegalAccessException x) {
1188                 throw new RuntimeException("Cannot access Driver type " + type + ".", x);
1189             }
1190 
1191             // Get the list of Driver parameters from the XML.
1192             final List<Element> parameters = driver.getChildren();
1193 
1194             // Process the parameters provided for the driver.
1195             this.processDriverParameters(driverClass, newDriver, parameters);
1196 
1197             // Add the driver to the manager.
1198             this.addDriver(name, newDriver);
1199 
1200         } // driver loop
1201 
1202         // Make the list of drivers to execute.
1203         this.createDriverExecList();
1204     }
1205 
1206     /**
1207      * Setup the file cache.
1208      */
1209     private void setupFileCache() {
1210         if (cacheDirectory != null) {
1211             try {
1212                 fileCache = new FileCache();
1213                 fileCache.setCacheDirectory(cacheDirectory);
1214                 fileCache.setPrintStream(null);
1215                 LOGGER.config("File cache created at " + cacheDirectory);
1216             } catch (final IOException x) {
1217                 throw new RuntimeException(x);
1218             }
1219         }
1220     }
1221 
1222     /**
1223      * Setup the list of input files to be processed from the XML job file.
1224      */
1225     private void setupInputFiles() {
1226 
1227         if (root.getChild("inputFiles") == null) {
1228             // This is not a warning because input files can be provided via the command line.
1229             LOGGER.config("No input files in XML file.");
1230             return;
1231         }
1232 
1233         // Process the <file> elements.
1234         final List<Element> files = root.getChild("inputFiles").getChildren("file");
1235         for (final Element fileElem : files) {
1236             this.processFileElement(fileElem, this.inputFiles);
1237         }
1238 
1239         // Read lists of file locations given by <fileList> elements.
1240         final List<Element> fileLists = root.getChild("inputFiles").getChildren("fileList");
1241         for (final Element fileList : fileLists) {
1242             final String filePath = fileList.getText();
1243             BufferedReader input;
1244             try {
1245                 input = new BufferedReader(new FileReader(new File(filePath)));
1246             } catch (final FileNotFoundException x) {
1247                 throw new RuntimeException("File not found: " + filePath, x);
1248             }
1249             String line = null;
1250             try {
1251                 // Read the next file, turn the text into an XML element, and process it using
1252                 // common method.
1253                 while ((line = input.readLine()) != null) {
1254                     this.processFileText(line.trim(), inputFiles);
1255                 }
1256             } catch (final IOException x) {
1257                 throw new RuntimeException(x);
1258             } finally {
1259                 if (input != null) {
1260                     try {
1261                         input.close();
1262                     } catch (final IOException e) {
1263                         e.printStackTrace();
1264                     }
1265                 }
1266             }
1267         }
1268 
1269         // Process <fileSet> elements.
1270         final List<Element> fileSets = root.getChild("inputFiles").getChildren("fileSet");
1271         for (final Element fileSet : fileSets) {
1272             final Attribute basedirAttrib = fileSet.getAttribute("baseDir");
1273             String basedir = "";
1274             if (basedirAttrib != null) {
1275                 basedir = basedirAttrib.getValue();
1276             }
1277             final List<Element> fsFiles = fileSet.getChildren("file");
1278             for (final Element file : fsFiles) {
1279                 final String filePath = basedir + File.separator + file.getText().trim();
1280                 this.processFileText(filePath, inputFiles);
1281             }
1282         }
1283 
1284         // Read <fileRegExp> elements, which may only reference local files, not URLs.
1285         final List<Element> fileRegExps = root.getChild("inputFiles").getChildren("fileRegExp");
1286         for (final Element fileRegExp : fileRegExps) {
1287             final Pattern pattern = Pattern.compile(fileRegExp.getText());
1288             final String basedir = fileRegExp.getAttributeValue("baseDir");
1289             final File dir = new File(basedir);
1290             if (!dir.isDirectory()) {
1291                 throw new RuntimeException(basedir + " is not a valid directory!");
1292             }
1293             final String dirlist[] = dir.list();
1294             for (final String file : dirlist) {
1295                 if (file.endsWith(".slcio")) {
1296                     final Matcher matcher = pattern.matcher(file);
1297                     if (matcher.matches()) {
1298                         this.processFileText(basedir + File.separator + file, inputFiles);
1299                         LOGGER.fine("Matched file <" + file.toString() + "> to pattern <" + pattern.toString() + ">");
1300                     } else {
1301                         LOGGER.fine("Did NOT match file <" + file.toString() + "> to pattern <" + pattern.toString()
1302                                 + ">");
1303                     }
1304                 }
1305             }
1306         }
1307 
1308         // Check that all the files exist if job is not a dry run.
1309         if (!dryRun) {
1310             for (final File file : inputFiles) {
1311                 if (!file.exists()) {
1312                     LOGGER.info("The input file " + file.getAbsolutePath() + " does not exist.");
1313                     throw new RuntimeException("The input file " + file.getAbsolutePath() + " does not exist!");
1314                 }
1315             }
1316         }
1317 
1318         // Print out the input file list.
1319         this.printInputFileList();
1320 
1321         if (inputFiles.size() == 0) {
1322             LOGGER.info("No input files were provided by the steering file or command line options.  Dry run will be enabled.");
1323             this.dryRun = true;
1324         }
1325     }
1326 
1327     /**
1328      * Setup the job control parameters using the XML steering document.
1329      */
1330     private void setupJobControlParameters() {
1331 
1332         final Element control = root.getChild("control");
1333         
1334         if (control == null) {
1335             // The control element is optional.
1336             return;
1337         }
1338 
1339         // Print hello world message to appear at top of log.
1340         LOGGER.config(this.getClass().getCanonicalName() + " is initialized.");
1341 
1342         // Number of events to run.
1343         final Element controlElement = control.getChild("numberOfEvents");
1344         if (controlElement != null) {
1345             numberOfEvents = Integer.valueOf(controlElement.getText());
1346             LOGGER.config("numberOfEvents: " + numberOfEvents);
1347         }
1348 
1349         final Element skipElement = control.getChild("skipEvents");
1350         if (skipElement != null) {
1351             skipEvents = Integer.valueOf(skipElement.getText());
1352             LOGGER.config("skipEvents: " + skipEvents);
1353         }
1354 
1355         final Element dryRunElement = control.getChild("dryRun");
1356         if (dryRunElement != null) {
1357             dryRun = Boolean.valueOf(dryRunElement.getText());
1358             LOGGER.config("dryRun: " + dryRun);
1359         }
1360 
1361         // The cache directory. Defaults to the current directory.
1362         final Element cacheDirElement = control.getChild("cacheDirectory");
1363         if (cacheDirElement != null) {
1364             cacheDirectory = new File(cacheDirElement.getText());
1365             if (!cacheDirectory.exists()) {
1366                 throw new RuntimeException("cacheDirectory does not exist at location: " + cacheDirElement.getText());
1367             }
1368         } else {
1369             // Cache directory defaults to user home dir.
1370             cacheDirectory = new File(System.getProperties().get("user.dir").toString());
1371         }
1372 
1373         LOGGER.config("cacheDirectory: " + cacheDirectory);
1374 
1375         final Element printStatisticsElement = control.getChild("printDriverStatistics");
1376         if (printStatisticsElement != null) {
1377             printDriverStatistics = Boolean.valueOf(printStatisticsElement.getText());
1378             LOGGER.config("printDriverStatistics: " + printDriverStatistics);
1379         }
1380 
1381         final Element dummyDetectorElement = control.getChild("dummyDetector");
1382         if (dummyDetectorElement != null) {
1383             dummyDetector = Boolean.valueOf(dummyDetectorElement.getText());
1384             LOGGER.config("dummyDetector: " + dummyDetector);
1385         }
1386     }
1387 
1388     /**
1389      * Setup the system of units.
1390      */
1391     private void setupUnits() {
1392         final Constants constants = Constants.getInstance();
1393         for (final Entry<String, Double> unit : constants.entrySet()) {
1394             factory.addConstant(unit.getKey(), unit.getValue());
1395             LOGGER.finest(unit.getKey() + " = " + unit.getValue());
1396         }
1397     }
1398 
1399     /**
1400      * Perform variable substitution within all text data in a entire document.
1401      *
1402      * @param doc The XML document.
1403      */
1404     private void substituteVariables(final Document doc) {
1405         this.substituteVariables(doc.getRootElement());
1406     }
1407 
1408     /**
1409      * Substitute values from the <code>variableMap</code> into an XML element and all its children, recursively.
1410      *
1411      * @param element The XML element.
1412      * @throw RuntimeException If a variable does not exist in the <code>variableMap</code>.
1413      */
1414     private void substituteVariables(final Element element) {
1415 
1416         final String text = element.getTextNormalize();
1417 
1418         if (text.length() != 0) {
1419 
1420             // Create a new matcher.
1421             final Matcher match = VARIABLE_PATTERN.matcher(text);
1422 
1423             // No variables were used.
1424             if (!match.find()) {
1425                 return;
1426             }
1427 
1428             // Text data on which to perform substitutions.
1429             String newText = new String(text);
1430 
1431             // Reset the matcher.
1432             match.reset();
1433 
1434             // Loop over the matches.
1435             while (match.find()) {
1436 
1437                 // Variable string with ${...} enclosure included.
1438                 final String var = match.group();
1439 
1440                 // The name of the variable for lookup.
1441                 final String varName = var.substring(2, var.length() - 1);
1442 
1443                 // The value of the variable.
1444                 final String varValue = variableMap.get(varName);
1445 
1446                 // If a variable was not defined, then the application will immediately exit here.
1447                 if (varValue == null) {
1448                     throw new RuntimeException("Required variable was not defined: " + varName);
1449                 }
1450 
1451                 // Substitute this variable's value into the text.
1452                 newText = newText.replace(var, varValue);
1453             }
1454 
1455             // Set this element's new text value.
1456             element.setText(newText);
1457         }
1458 
1459         // Recursively process all child elements of this one.
1460         for (final Iterator it = element.getChildren().iterator(); it.hasNext();) {
1461             this.substituteVariables((Element) it.next());
1462         }
1463     }
1464     
1465     /**
1466      * Initialize the conditions system before the job starts.
1467      * <p>
1468      * Sub-classes should override this if custom conditions system initialization is required.
1469      * 
1470      * @throws ConditionsNotFoundException if some conditions were not found
1471      */
1472     protected void initializeConditions() throws ConditionsNotFoundException {
1473         // Set up user supplied conditions information (we already checked that these were both given if one was provided).
1474         if (this.detectorName != null) {
1475             LOGGER.config("initializing conditions system with detector " + this.detectorName + " and run " + this.runNumber);
1476             ConditionsManager.defaultInstance().setDetector(this.detectorName, this.runNumber);
1477         }
1478     }
1479     
1480     /**
1481      * Get the run number set by the command line options.
1482      * @return the run number or <code>null</code> if not set
1483      */
1484     protected Integer getRunNumber() {
1485         return this.runNumber;
1486     }
1487     
1488     /**
1489      * Get the detector name set by the command line options.
1490      * @return the detector name or <code>null</code> if not set
1491      */
1492     protected String getDetectorName() {
1493         return this.detectorName;
1494     }
1495 }