View Javadoc

1   package org.lsst.ccs.bootstrap;
2   
3   import java.io.File;
4   import java.io.IOException;
5   import java.lang.reflect.InvocationTargetException;
6   import java.lang.reflect.Method;
7   import java.net.MalformedURLException;
8   import java.net.URL;
9   import java.net.URLClassLoader;
10  import java.util.ArrayList;
11  import java.util.Collection;
12  import java.util.HashMap;
13  import java.util.List;
14  import java.util.Map;
15  import java.util.Properties;
16  import java.util.Set;
17  import java.util.StringTokenizer;
18  import java.util.jar.JarFile;
19  import java.util.jar.Manifest;
20  import javax.xml.parsers.DocumentBuilder;
21  import javax.xml.parsers.DocumentBuilderFactory;
22  import javax.xml.parsers.ParserConfigurationException;
23  import org.apache.commons.cli.BasicParser;
24  import org.apache.commons.cli.CommandLine;
25  import org.apache.commons.cli.CommandLineParser;
26  import org.apache.commons.cli.HelpFormatter;
27  import org.apache.commons.cli.Option;
28  import org.apache.commons.cli.OptionBuilder;
29  import org.apache.commons.cli.Options;
30  import org.apache.commons.cli.ParseException;
31  import org.freehep.util.VersionComparator;
32  import org.freehep.util.VersionComparator.Version;
33  import org.lsst.ccs.bootstrap.resources.BootstrapResourceUtils;
34  import org.lsst.ccs.bootstrap.resources.ResourcesUtils;
35  import org.lsst.ccs.bootstrap.util.SystemPropertyMatcher;
36  import org.w3c.dom.Document;
37  import org.w3c.dom.Element;
38  import org.xml.sax.SAXException;
39  
40  /**
41   *
42   * Bootstrap main class. Used to launch any CCS application.
43   *
44   * @author turri
45   */
46  public class Bootstrap {
47  
48      // The Options containing the bootstrap command line options.
49      private Options bootstrapCommandLineOptions;
50      private List<String> additionalCommandLineArguments = new ArrayList<>(), passedAlongOptions = new ArrayList<>();
51      // The name of the application to be launched.    
52      private static String bootstrapApplication = null;
53      static URLClassLoader applicationClassLoader = null;
54      private boolean printHelp = false, listApplications = false, showDistributionInfo = false;
55      private static boolean verbose = false;
56      static final String APPLICATION_OPTION = "application";
57      static final String HELP_OPTION = "help";
58      static final String VERBOSE_OPTION = "verbose";
59      static final String LIST_APPLICATIONS_OPTION = "listApplications";
60      private String showProperties = null;
61      private boolean showClasspath = false;
62      private static final String APPLICATION_ARGS_PROPERTY = "org.lsst.ccs.application.args";
63      private static final String APPLICATION_DESCRIPTION_PROPERTY = "org.lsst.ccs.application.description";
64      private static Properties bootstrapCmdLineProperties = new Properties();
65      private static Map<String, String> additionalClassPathEntriesMap = new HashMap<>();
66      private static List<String> additionalClassPathEntriesList = new ArrayList<>();
67      private static Properties bootstrapApplicationProperties = null;
68      private static final String BOOTSTRAP_ENVIRONMENT_PROPERTY = "org.lsst.ccs.bootstrap.environment";
69      private static String distributionMainJar = null;
70      static boolean quiet = false;
71      private static Class loaderClass = BootstrapUtils.class;
72  
73      Bootstrap() {
74          this(false);
75      }
76  
77      Bootstrap(boolean quiet) {
78          this.quiet = quiet;
79          // define the command line options
80          bootstrapCommandLineOptions = new Options();
81          // The help option
82          bootstrapCommandLineOptions.addOption("h", HELP_OPTION, false, "Print the help message");
83          // The verbose option
84          bootstrapCommandLineOptions.addOption("v", VERBOSE_OPTION, false, "Turns on verbose statements");
85          // The listApplications option. It will list the available applications
86          bootstrapCommandLineOptions.addOption("la", LIST_APPLICATIONS_OPTION, false,
87                  "List the available CCS applications in this distribution");
88          // define the --application option
89          bootstrapCommandLineOptions.addOption("app", APPLICATION_OPTION, true, "The APPLICATION to be launched");
90          getOption(APPLICATION_OPTION).setArgName("APPLICATION");
91  
92          // The System property option
93          Option sysProperty = OptionBuilder.withArgName("SystemProperty=Value").hasArgs(2)
94                  .withValueSeparator().withDescription("Set the Value of a SystemProperty.")
95                  .create("D");
96          bootstrapCommandLineOptions.addOption(sysProperty);
97  
98          // Show the info on the current distribution
99          bootstrapCommandLineOptions.addOption("di", "distInfo", false, "Show information on current distribution.");
100 
101         // Show property tree
102         bootstrapCommandLineOptions.addOption("sp", "showProperties", true, "Show the properties for <FILE_NAME>.");
103         getOption("showProperties").setArgName("FILE_NAME");
104 
105         // Show the classpath
106         bootstrapCommandLineOptions.addOption("scp", "showClasspath", false, "Show the classpath for the given application.");
107     }
108 
109     private static void resetBootstrap() {
110         bootstrapCmdLineProperties = new Properties();
111         additionalClassPathEntriesMap = new HashMap<>();
112         additionalClassPathEntriesList = new ArrayList<>();
113         bootstrapApplication = null;
114         BootstrapUtils.reset();
115     }
116 
117     public static void initializeBootstrap() {
118         initializeBootstrap(BootstrapUtils.class);
119     }
120 
121     /**
122      * Initializes the Bootstrap Environment
123      *
124      * @param theClazz
125      */
126     public static void initializeBootstrap(Class theClazz) {
127         resetBootstrap();
128         Bootstrap.loaderClass = theClazz;
129     }
130 
131     public static Class getLoaderClass() {
132         return loaderClass;
133     }
134 
135     /**
136      * Get the properties for the current CCS application.
137      *
138      * @return The Properties for the current application.
139      */
140     public synchronized static Properties getBootstrapApplicationProperties() {
141         if (bootstrapApplicationProperties == null) {
142             bootstrapApplicationProperties = BootstrapUtils.getApplicationDefinitionProperties(getBootstrapApplication());
143         }
144         return bootstrapApplicationProperties;
145     }
146 
147     protected List<String> getAdditionalCommandLineArguments() {
148         return additionalCommandLineArguments;
149     }
150     
151     /**
152      * Parse the command line arguments and extract the needed information. Any
153      * additional arguments will be passed as command line arguments to the CCS
154      * application to be launched.
155      *
156      * @param args The command line arguments.
157      * @param fromMain Indicates if the method is invoked from the main method,
158      * i.e. if we are parsing the main cmd line. This method is invoked a second
159      * time to extract additional system properties from the arguments passed to
160      * the main class.
161      * @throws ParseException if there are problems parsing the command line.
162      *
163      */
164     protected CommandLine parseCommandLineArguments(String[] args) throws ParseException {
165 
166         //Check for arguments that start with -D and don't have value associated with it.
167         //i.e. arguments of the form: -D some.prop
168         //Also detect if there are any command line options that don't belong to 
169         //the bootstrap. If any is found they are stored to be passed to the main application.
170         List<String> argumentsToParse = new ArrayList<>();
171         boolean skipNext = false;
172         for (int i = 0; i < args.length; i++) {
173             if ( skipNext ) {
174                 skipNext = false;
175                 continue;
176             }
177             String arg = args[i];
178             if (arg.startsWith("-D")) {
179                 // Check if it's a "-D", i.e. there is a space "-D prop=value"
180                 int propertyStrIndex = arg.equals("-D") ? i + 1 : i;
181                 String propertyStr = args[propertyStrIndex].replace("-D", "");
182                 // Check if there is a property separator, either = or :
183                 if (!propertyStr.contains("=") /*&& ! propertyStr.contains(":")*/) {
184                     args[propertyStrIndex] = args[propertyStrIndex].replace(propertyStr, propertyStr + "=");
185                 }
186                 if (arg.equals("-D")) {
187                     argumentsToParse.add(arg);
188                 } else {
189                     additionalCommandLineArguments.add(args[i]);
190                 }
191             } else {
192                 if ( arg.startsWith("-") && ! bootstrapCommandLineOptions.hasOption(arg) ) {
193                     //This is an option that does not belong to the bootstrap.
194                     additionalCommandLineArguments.add(arg);
195                     //Check if the option is followed by another argument
196                     if ( i < args.length -1 ) {
197                         String tmpArg = args[i+1];
198                         if ( ! tmpArg.startsWith("-") ) {
199                             additionalCommandLineArguments.add(tmpArg);
200                             skipNext = true;
201                         }
202                     }
203                 } else {
204                     argumentsToParse.add(arg);
205                 }
206                 
207             }
208         }
209         
210 
211         String[] newArgs = argumentsToParse.toArray(new String[argumentsToParse.size()]);
212         
213         CommandLineParser parser = new BasicParser();
214         CommandLine line = parser.parse(bootstrapCommandLineOptions, newArgs, false);
215         
216         if (line.hasOption(APPLICATION_OPTION)) {
217             String tmpApp = line.getOptionValue(APPLICATION_OPTION);
218             if ("true".equals(System.getProperty("org.lsst.ccs.bootstrap.test")) && !BootstrapUtils.getBootstrapListOfApplications().contains(tmpApp)) {
219                 throw new IllegalArgumentException("Application name:  " + tmpApp + " is not a valid value.");
220             } else {
221                 bootstrapApplication = tmpApp;
222             }
223         }
224 
225         if (line.hasOption(HELP_OPTION)) {
226             printHelp = true;
227         }
228 
229         additionalCommandLineArguments.addAll(line.getArgList());
230 
231         showClasspath = line.hasOption("showClasspath");
232 
233         if (line.hasOption("showProperties")) {
234             if (showProperties == null) {
235                 showProperties = line.getOptionValue("showProperties");
236             }
237         }
238 
239         if (line.hasOption(LIST_APPLICATIONS_OPTION)) {
240             listApplications = true;
241         }
242 
243         verbose = line.hasOption(VERBOSE_OPTION);
244 
245         if (printHelp && !passedAlongOptions.contains("-" + HELP_OPTION)) {
246             passedAlongOptions.add("-" + HELP_OPTION);
247         }
248         if (verbose && !passedAlongOptions.contains("-" + VERBOSE_OPTION)) {
249             passedAlongOptions.add("-" + VERBOSE_OPTION);
250         }
251 
252         //Set the command line properties in the System Properties
253         Properties cmdLineProperties = line.getOptionProperties("D");
254 
255         // Commons CLI sets properties values to "true" by default if such properties don't have a value.
256         // This is overwritten in the following lines, by checking the user provided value
257         // substituting it in the Properties object if needed.
258         Option[] opts = line.getOptions();
259         for (Option opt : opts) {
260             if (opt.getOpt().equals("D")) {
261                 boolean wasSet = (opt.getValuesList().size() == 2);
262                 if (!wasSet) {
263                     cmdLineProperties.setProperty(opt.getValue(), "");
264                 }
265             }
266         }
267 
268         if (verbose() && !quiet) {
269             if (!cmdLineProperties.isEmpty()) {
270                 System.out.println("\n*** Adding the following command line properties to the Properties:");
271                 Set keys = cmdLineProperties.keySet();
272                 for (Object key : keys) {
273                     System.out.println("\t" + key + " = " + cmdLineProperties.getProperty((String) key));
274                 }
275             }
276         }
277         if (!cmdLineProperties.isEmpty()) {
278             bootstrapCmdLineProperties.putAll(cmdLineProperties);
279             System.getProperties().putAll(cmdLineProperties);
280         }
281 
282         if (line.hasOption("distInfo")) {
283             showDistributionInfo = true;
284         }
285 
286         // Additional Command line arguments must be inspected to catch properties
287         // passed with -D withoug having a space in it. See BootstrapParser for code
288         // that lets such arguments pass the parsing phase.
289         List<String> toBeRemoved = new ArrayList<>();
290         for (String additional : additionalCommandLineArguments) {
291             SystemPropertyMatcher m = SystemPropertyMatcher.matcher(additional);
292             if (m.matches()) {
293                 toBeRemoved.add(additional);
294                 bootstrapCmdLineProperties.put(m.getProperty(), m.getValue());
295             }
296         }
297         for (String remove : toBeRemoved) {
298             additionalCommandLineArguments.remove(remove);
299         }
300 
301 
302         return line;
303     }
304 
305     public static Properties getCmdLineProperties() {
306         return bootstrapCmdLineProperties;
307     }
308 
309     /**
310      * The the name of the CCS application to be launched.
311      *
312      * @return The application name.
313      */
314     public static String getBootstrapApplication() {
315 //        if ( bootstrapApplication == null) {
316 //            throw new RuntimeException("The Bootstrap Application has not been defined yet.");
317 //        }
318         return bootstrapApplication;
319     }
320 
321     /**
322      * Return true/false if help was requested.
323      *
324      * @return true/false if help was requested.
325      */
326     public boolean doPrintHelp() {
327         return printHelp;
328     }
329 
330     /**
331      * Return true/false is verbose is turned on.
332      *
333      * @return true/false if verbose is turned on.
334      */
335     public static boolean verbose() {
336         return verbose;
337     }
338 
339     public static boolean isQuiet() {
340         return quiet;
341     }
342 
343     /**
344      * Get the bootstrap command line options.
345      *
346      * @return The Options containing the command line options.
347      *
348      */
349     protected Options getBootstrapCommandLineOptions() {
350         return bootstrapCommandLineOptions;
351     }
352 
353     /**
354      * Prints the content of a URLClassLoader.
355      *
356      * @param classLoader
357      */
358     private static void printBootstrapClassLoader() {
359         URL[] urls = getBootstrapApplicationClassLoader().getURLs();
360         System.out.println("*** CLASSPATH");
361         for (URL url : urls) {
362             System.out.println("\t\t" + url);
363         }
364     }
365 
366     /**
367      * Get a given command line Option
368      *
369      * @param opt The option name.
370      * @return The corresponding Option.
371      */
372     private Option getOption(String opt) {
373         return getBootstrapCommandLineOptions().getOption(opt);
374     }
375 
376     /**
377      * Get the list of arguments to be passed to the application. This list
378      * contains options that are transfered to the application (like -help and
379      * -verbose), the list of arguments defined in the application property file
380      * definition and all the arguments that are left after parsing.
381      *
382      * @param appProperties
383      * @return
384      */
385     private String[] getApplicationArguments(Properties appProperties) throws ParseException {
386 
387         String applicationArgs = appProperties.getProperty(APPLICATION_ARGS_PROPERTY, "").trim();
388         StringTokenizer applicationArgsTokenizer = new StringTokenizer(applicationArgs, " ");
389 
390         int nArgs = passedAlongOptions.size() + additionalCommandLineArguments.size() + applicationArgsTokenizer.countTokens();
391 
392         String[] mainArgs = new String[nArgs];
393 
394         int argCount = 0;
395         while (applicationArgsTokenizer.hasMoreTokens()) {
396             mainArgs[argCount++] = applicationArgsTokenizer.nextToken();
397         }
398         for (String opt : passedAlongOptions) {
399             mainArgs[argCount++] = opt;
400         }
401         for (String cmdArg : additionalCommandLineArguments) {
402             mainArgs[argCount++] = cmdArg;
403         }
404         if (verbose() && !isQuiet()) {
405             System.out.print("*** Command line arguments passed to the mainClass: ");
406             for (String arg : mainArgs) {
407                 System.out.print(arg + " ");
408             }
409             System.out.println();
410         }
411 
412         return mainArgs;
413     }
414 
415     /**
416      * Get the classLoader for this application.
417      *
418      * @return The URLClassLoader.
419      */
420     public static URLClassLoader getBootstrapApplicationClassLoader() {
421         if (applicationClassLoader == null) {
422             buildBootstrapClassLoader();
423         }
424         return applicationClassLoader;
425     }
426 
427     /**
428      * Scan a given Properties file to search for properties that end with
429      * "additional.classpath.entry". Do we enforce uniqueness? Do we load them
430      * all?
431      *
432      * @param props
433      */
434     static void scanPropertiesForClassPathEntries(Properties props) {
435 
436         Set<Object> keySet = BootstrapResourceUtils.getAllKeysInProperties(props);
437         for (Object obj : keySet) {
438             String key = (String) obj;
439             if (key.endsWith("additional.classpath.entry")) {
440                 String entry = props.getProperty(key);
441                 if (additionalClassPathEntriesMap.containsKey(key)) {
442                     if (!quiet) {
443                         System.out.println("*** [WARNING] ignoring additional classpath entry: " + key + "=" + entry + " it was already added to the CLASSPATH as " + additionalClassPathEntriesMap.get(key));
444                     }
445                 } else {
446                     additionalClassPathEntriesMap.put(key, entry);
447                     additionalClassPathEntriesList.add(entry);
448                 }
449             }
450         }
451     }
452 
453     private synchronized static void buildBootstrapClassLoader() {
454 //        if (bootstrapApplication == null) {
455 //            throw new RuntimeException("The Bootstrap Application has not been defined yet.");
456 //        }
457 
458         if (applicationClassLoader != null) {
459             throw new RuntimeException("The Bootstrap ClassLoader has already been built. Please report this problem.");
460         }
461 
462         //Build the new classloader to launch the CCS application
463         List<URL> classPathUrlList = new ArrayList<>();
464 
465         //Put all additional classpath entries from application definition file to classpath
466         if (bootstrapApplication != null) {
467             scanPropertiesForClassPathEntries(getBootstrapApplicationProperties());
468         }
469 
470         try {
471 
472             String userProvidedDistributionDirs = BootstrapUtils.getUserProvidedDistributionDirectories();
473             List<String> additionalDistributionJarDirs = BootstrapUtils.extractDirectoriesFromPath(userProvidedDistributionDirs, null, true);
474 
475             boolean checkForVersionIncompatibilities = additionalDistributionJarDirs.size() > 0;
476             ClassPathBuilderSupport cpSupport = new ClassPathBuilderSupport();
477 
478             //First add the distribution main jar file
479             String applicationMainJar = getDistributionMainJar();
480             File jar = null;
481             if (applicationMainJar != null) {
482                 jar = new File(applicationMainJar);
483                 classPathUrlList.add(jar.toURI().toURL());
484             }
485 
486             //Before moving this code, check https://jira.slac.stanford.edu/browse/LSSTCCS-284
487             //Add all the additional Entries to the classpath read from property files.        
488             for (String classPathEntry : additionalClassPathEntriesList) {
489                 File cpEntryFile = new File(classPathEntry);
490                 if (cpEntryFile.isDirectory()) {
491                     if (!quiet) {
492                         System.out.println("*** [WARNING] Directories cannot be added to the classpath as additional Classpath entries. Skipping " + classPathEntry);
493                     }
494                 } else {
495                     classPathUrlList.add(cpEntryFile.toURI().toURL());
496                 }
497             }
498 
499             if (checkForVersionIncompatibilities && jar != null) {
500                 scanManifestForClassPathElements(jar, cpSupport);
501             }
502 
503             //Then add the distribution main jar for any additional distribution
504             for (String dir : additionalDistributionJarDirs) {
505                 String additionalMainJar = getDistributionMainJar(dir);
506                 File mainjar = new File(additionalMainJar);
507                 classPathUrlList.add(mainjar.toURI().toURL());
508                 if (checkForVersionIncompatibilities) {
509                     scanManifestForClassPathElements(mainjar, cpSupport);
510                 }
511             }
512 
513         } catch (MalformedURLException mue) {
514             throw new RuntimeException("Failed to build URL when building the classpath: " + mue.getMessage());
515         }
516 
517         //Build the array of classpath URLs
518         URL[] classpathUrls = new URL[classPathUrlList.size()];
519         int count = 0;
520         for (URL classpathURl : classPathUrlList) {
521             classpathUrls[count++] = classpathURl;
522         }
523 
524         applicationClassLoader = new URLClassLoader(classpathUrls);
525     }
526 
527     /**
528      * Get the fully qualified path to the distribution main jar from the
529      * distribution definition file.
530      *
531      * @param distribution The distribution
532      *
533      * @return The main jar file path.
534      */
535     private static String getDistributionMainJar(String distribution) {
536         File distributionDefinitionFile = new File(BootstrapUtils.getDistributionResourcesDirectory(distribution) + "DIST-INF/distribution.xml");
537         if ( !distributionDefinitionFile.exists()) {
538             if ( "true".equals(System.getProperty("org.lsst.ccs.bootstrap.test")) ) {
539                 return null;
540             }
541             throw new RuntimeException("FATAL: Cannot run distribution " + distribution + " as it does not contain the distribution definition xml file");
542         }
543 
544         try {
545             DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
546             DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
547 
548             Document doc;
549             doc = dBuilder.parse(distributionDefinitionFile);
550 
551             Element rootEl = doc.getDocumentElement();
552 
553             return BootstrapUtils.getDistributionJarFilesDirectory(distribution) + rootEl.getAttribute("mainJar");
554 
555         } catch (ParserConfigurationException | SAXException | IOException pce) {
556             throw new RuntimeException(pce);
557         }
558     }
559 
560     private static String getDistributionMainJar() {
561         if (distributionMainJar == null) {
562             distributionMainJar = getDistributionMainJar(BootstrapUtils.getCCSDistributionRootDirectory());
563         }
564         return distributionMainJar;
565     }
566 
567     /**
568      * Launch the CCS Application for the given name.
569      *
570      * @param applicationName The name of the CCS application.
571      */
572     private void launchCCSApplication(String applicationName) {
573 
574         Properties applicationProperties = BootstrapUtils.getApplicationDefinitionProperties(applicationName);
575 
576         if (doPrintHelp()) {
577             String appDescription = applicationProperties.getProperty(APPLICATION_DESCRIPTION_PROPERTY, "");
578             System.out.println("\n\tCCS Application " + applicationName + " " + appDescription + "\n");
579         }
580 
581         //Get the main Class to launch.
582         String applicationMainClassName = applicationProperties.getProperty(BootstrapUtils.APPLICATION_MAINCLASS_PROPERTY);
583 
584         if (applicationMainClassName == null) {
585             throw new RuntimeException("*** Application " + applicationName + " must contain define the main Class to lauch. "
586                     + "This can be done either in the Manifest of the main jar or by defining the property "
587                     + BootstrapUtils.APPLICATION_MAINCLASS_PROPERTY
588                     + " in its definition file. ");
589         }
590 
591         if (verbose() && isQuiet()) {
592             System.out.println("*** Distribution Root: " + BootstrapUtils.getCCSDistributionRootDirectory());
593             System.out.println("*** Application name: " + applicationName);
594             System.out.println("*** MainClass: " + applicationMainClassName);
595             System.out.println("*** LD_LIBRARY_PATH: " + System.getenv("LD_LIBRARY_PATH"));
596         }
597 
598         // The LD_LIBRARY_PATH environment variable is now set in the CCSbootstrap.sh script.
599         //Set up the LD_LIBRARY_PATH
600 //        try {
601 //            //The following is a hack to make it work. 
602 //            //What follows is from http://blog.cedarsoft.com/2010/11/setting-java-library-path-programmatically/
603 //            //the only way to set the property java.libary.path is to add a system property *before* the application is started:
604 //            //
605 //            //java -Djava.library.path=/path/to/libs
606 //            //
607 //            //Changing the system property later doesn’t have any effect, since the property is evaluated very early and cached.
608 //            //But the Classloader has a static field (sys_paths) that contains the paths. 
609 //            //If that field is set to null, it is initialized automatically. 
610 //            //Therefore forcing that field to null will result into the reevaluation of the library path as soon as loadLibrary() is called…
611 //            System.setProperty("java.library.path", BootstrapUtils.getDistributionJniDirectory() + File.pathSeparator + BootstrapUtils.getDistributionLibDirectory());
612 //            Field fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths");
613 //            fieldSysPath.setAccessible(true);
614 //            fieldSysPath.set(null, null);
615 //        } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
616 //            System.out.println("*** Failed to set java.library.path");
617 //        }
618         try {
619             Class applicationMainClass = Class.forName(applicationMainClassName);
620             try {
621                 //Get the application main method.
622                 Method applicationMainMethod = applicationMainClass.getMethod("main",
623                         new Class[]{String[].class});
624 
625                 //Build the array containing the arguments to be passed to the main
626                 String[] mainArgs = getApplicationArguments(applicationProperties);
627 
628                 //Print the classpath
629                 if (verbose() && !isQuiet()) {
630                     printBootstrapClassLoader();
631                 }
632 
633                 try {
634                     //Invoke the main method on of the main Class.
635                     applicationMainMethod.invoke(null, new Object[]{mainArgs});
636                 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
637                     System.out.println("*** Failed to invoke main method in class " + applicationMainClassName + "\n" + e.getMessage());
638                     throw new RuntimeException(e);
639                 }
640 
641             } catch (NoSuchMethodException | SecurityException | ParseException e) {
642                 System.out.println("*** Could not access the main method in class " + applicationMainClassName + "\n" + e.getMessage());
643             }
644 
645         } catch (ClassNotFoundException cnfe) {
646             System.out.println("*** Could not find class " + applicationMainClassName + " in the following classpath: ");
647             printBootstrapClassLoader();
648             System.out.println("*************************************************************************\n" + cnfe.getMessage());
649         }
650 
651     }
652 
653     public static void main(String[] args) throws Exception {
654 
655         System.setProperty(BOOTSTRAP_ENVIRONMENT_PROPERTY, "true");
656         Bootstrap bootstrap = new Bootstrap();
657         bootstrap.parseCommandLineArguments(args);
658 
659         String applicationName = getBootstrapApplication();
660 
661         if (bootstrap.showClasspath) {
662             if (getBootstrapApplication() == null) {
663                 throw new IllegalArgumentException("To display the content of the classpath you have to provide an application with the --app option");
664             } else {
665                 printBootstrapClassLoader();
666             }
667         } else if (bootstrap.showProperties != null) {
668             if (getBootstrapApplication() == null) {
669                 throw new IllegalArgumentException("To display the content of a properties file you have to provide an application with the --app option");
670             } else {
671 //                ResourcesUtils.printProperties(BootstrapUtils.getMergedProperties(bootstrap.showProperties));
672                 ResourcesUtils.printProperties(BootstrapResourceUtils.getBootstrapProperties(bootstrap.showProperties));
673             }
674         } else if (bootstrap.showDistributionInfo) {
675             printDistributionInfo();
676         } else if (bootstrap.listApplications) {
677 
678             List<String> availableApplications = BootstrapUtils.getBootstrapListOfApplications();
679             if (availableApplications.isEmpty()) {
680                 System.out.println("No CCS applications are defined in the current distribution.");
681                 if (bootstrap.verbose() && !bootstrap.isQuiet()) {
682                     System.out.println(BootstrapUtils.getDistributionResourcesDirectory());
683                 }
684             } else {
685                 System.out.println("Available CCS applications :");
686                 if (bootstrap.verbose() && !bootstrap.isQuiet()) {
687                     System.out.println(BootstrapUtils.getDistributionResourcesDirectory());
688                 }
689                 for (String application : availableApplications) {
690                     Properties applicationProps = BootstrapUtils.getApplicationDefinitionProperties(application);
691                     System.out.println("\t" + application + "\t" + applicationProps.getProperty(APPLICATION_DESCRIPTION_PROPERTY));
692                 }
693             }
694 
695         } else if (bootstrap.doPrintHelp() && applicationName == null) {
696             printHelp(bootstrap.getBootstrapCommandLineOptions());
697         } else if (applicationName != null) {
698             bootstrap.launchCCSApplication(applicationName);
699         } else {
700             printHelp(bootstrap.getBootstrapCommandLineOptions());
701         }
702 
703     }
704 
705     private static void printHelp(Options o) {
706         HelpFormatter formatter = new HelpFormatter();
707         formatter.printHelp(100, "CCSbootstrap", "", o, "", true);
708     }
709 
710     private static void printDistributionInfo() {
711         System.out.println("\n*** Distribution info");
712         System.out.println("\tDistribution path: " + BootstrapUtils.getCCSDistributionRootDirectory());
713         System.out.println("\tResources ordered search path: ");
714         List<String> distSearchPathList = BootstrapUtils.getOrderedListOfResourceDirectories();
715         String resourcesDirList = "";
716         StringBuffer b = new StringBuffer();
717         for (String dir : distSearchPathList) {
718             b.append("\t\t" + dir + "\n");
719         }
720         resourcesDirList += b.toString();
721         System.out.print(resourcesDirList);
722 
723     }
724 
725     /*
726      * This method is used to identify if we are running within the bootstrap environment.
727      * It should be used to make sure that WARNING/ERROR messages that are related to the 
728      * bootstrap environment don't propagate outside of it.
729      * See https://jira.slac.stanford.edu/browse/LSSTCCS-209
730      * 
731      */
732     public static boolean isBootstrapEnvironment() {
733         return "true".equals(System.getProperty(BOOTSTRAP_ENVIRONMENT_PROPERTY));
734     }
735 
736     private static void scanManifestForClassPathElements(File file, ClassPathBuilderSupport cp) {
737         JarFile jarFile = null;
738         try {
739             jarFile = new JarFile(file);
740             Manifest manifest = jarFile.getManifest();
741             String manifestClassPath = manifest.getMainAttributes().getValue("Class-Path").trim();
742             File parentDir = file.getParentFile();
743             StringTokenizer classPathTokens = new StringTokenizer(manifestClassPath, " ");
744             while (classPathTokens.hasMoreTokens()) {
745                 String manifestClassPathJar = classPathTokens.nextToken();
746                 cp.addClasspathEntry(parentDir.getAbsolutePath(), manifestClassPathJar);
747             }
748         } catch (IOException ioe) {
749             throw new RuntimeException(ioe);
750         } finally {
751             if (jarFile != null) {
752                 try {
753                     jarFile.close();
754                 } catch (IOException e) {
755                     throw new RuntimeException(e);
756                 }
757             }
758         }
759 
760     }
761 
762     private static class ClassPathBuilderSupport {
763 
764         private HashMap<String, ClassPathElementWithVersion> classPathEntries = new HashMap<>();
765 
766         void addClasspathEntry(String resourceDir, String fileName) {
767 
768             String jarName = VersionComparator.stripVersion(fileName);
769 
770             ClassPathElementWithVersion existingEntry = classPathEntries.get(jarName);
771             ClassPathElementWithVersion newEntry = new ClassPathElementWithVersion(resourceDir, fileName);
772             if (existingEntry == null) {
773                 classPathEntries.put(jarName, newEntry);
774             } else if (!existingEntry.fileName.equals(newEntry.fileName) && !quiet) {
775                 System.out.println("WARNING Classpath entry version conflict: ");
776                 System.out.println("\t " + newEntry.resourceDir + BootstrapUtils.FILE_SEPARATOR + newEntry.fileName);
777                 System.out.println("\t " + existingEntry.resourceDir + BootstrapUtils.FILE_SEPARATOR + existingEntry.fileName);
778             }
779         }
780 
781         Collection<ClassPathElementWithVersion> getListOfClasspathElements() {
782             return classPathEntries.values();
783         }
784     }
785 
786     private static class ClassPathElementWithVersion {
787 
788         private String fileName, resourceDir;
789         private URL elementURL;
790         private Version version;
791 
792         public ClassPathElementWithVersion(String resourceDir, String fileName) {
793 
794             this.fileName = fileName;
795             this.resourceDir = resourceDir;
796 
797         }
798 
799         Version getVersion() {
800             if (version == null) {
801                 version = VersionComparator.getVersionFromFileName(fileName);
802             }
803             return version;
804         }
805 
806         String getFileName() {
807             return fileName;
808         }
809 
810         URL getElementUrl() {
811             File element = new File(resourceDir, fileName);
812             if (element.exists()) {
813                 try {
814                     return element.toURI().toURL();
815                 } catch (Exception e) {
816                     e.printStackTrace();
817                     return null;
818                 }
819             } else {
820                 System.out.println("The following file does not exist : " + element.getAbsolutePath());
821                 System.out.println("Something went wrong with its version in the Bootstrap");
822                 return null;
823             }
824         }
825     }
826 }