001: /*
002: * <copyright>
003: *
004: * Copyright 1997-2004 BBNT Solutions, LLC
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: */
026:
027: package org.cougaar.bootstrap;
028:
029: import java.io.BufferedReader;
030: import java.io.File;
031: import java.io.FilenameFilter;
032: import java.io.IOException;
033: import java.io.InputStream;
034: import java.io.InputStreamReader;
035: import java.lang.reflect.Array;
036: import java.lang.reflect.Constructor;
037: import java.lang.reflect.Method;
038: import java.net.MalformedURLException;
039: import java.net.URL;
040: import java.util.ArrayList;
041: import java.util.Collections;
042: import java.util.HashMap;
043: import java.util.Iterator;
044: import java.util.List;
045: import java.util.Map;
046: import java.util.Properties;
047: import java.util.regex.Matcher;
048: import java.util.regex.Pattern;
049:
050: /**
051: * A <b>main(String[] args)</b> class that searches for classes and
052: * jar files in a path, creates an {@link XURLClassLoader} that uses
053: * the found jars, and launches an application class (typically a
054: * Cougaar <b>Node</b>).
055: * <p>
056: * The Bootstrapper simplifies the
057: * <code>$COUGAAR_INSTALL_PATH/bin/cougaar</code>
058: * script and similar run scripts, which use the bootstrapper's jar
059: * for their Java classpath, instead of listing many individual jar
060: * files. For example, instead of:<pre>
061: * java
062: * -classpath /tmp/classes:/jars/lib/a.jar:/jars/lib/b.jar:/jars/lib/c.jar
063: * MyClass
064: * </pre>
065: * one can write:<pre>
066: * java
067: * -Dorg.cougaar.class.path=/tmp/classes
068: * -Dorg.cougaar.install.path=/jars
069: * -jar bootstrap.jar
070: * MyClass
071: * </pre>
072: * or the equivalent:<pre>
073: * java
074: * -classpath bootstrap.jar
075: * -Dorg.cougaar.class.path=/tmp/classes
076: * -Dorg.cougaar.install.path=/jars
077: * org.cougaar.bootstrap.Bootstrapper
078: * MyClass
079: * </pre>
080: * <p>
081: * Note that the "-classpath" should <b>only</b> specify the
082: * Bootstrapper's jar. See the "Important Notes" below for further
083: * details.
084: * <p>
085: * The application classname can be specified by either the:<pre>
086: * -Dorg.cougaar.bootstrap.application=<i>CLASSNAME</i>
087: * </pre>
088: * system property or as the first command-line argument to this
089: * Bootstrapper class's {@link #main} method. The class must
090: * provide a <code>public static launch(String[])</code> or
091: * <code>main(String[])</code> method (searched for in that order).
092: * If additional command-line arguments are specified, they are
093: * passed along to the application class's method.
094: * <p>
095: * The default jar search path is specified by the:<pre>
096: * -Dorg.cougaar.jar.path=<i>{@link #DEFAULT_JAR_PATH}</i>
097: * </pre>
098: * system property. In standard configurations this path is
099: * expanded to (approximately) the following jars:<pre>
100: * $COUGAAR_INSTALL_PATH/lib/*.jar
101: * $COUGAAR_INSTALL_PATH/sys/*.jar
102: * </pre>
103: * <p>
104: * The exact list of jars is controlled by "-Dorg.cougaar.jar.path".
105: * The separator character is ":" on Linux, ";" on Windows, and ","
106: * on both. The jar path defaults to the {@link #DEFAULT_JAR_PATH}
107: * value of:<pre>
108: * -Dorg.cougaar.jar.path=\
109: * classpath($CLASSPATH):\
110: * $RUNTIME/lib:\
111: * $RUNTIME/sys:\
112: * $SOCIETY/lib:\
113: * $SOCIETY/sys:\
114: * $INSTALL/lib:\
115: * $INSTALL/plugins:\
116: * $SYS:\
117: * $INSTALL/sys
118: * </pre>
119: * where:<pre>
120: * $CLASSPATH is the optional -Dorg.cougaar.class.path
121: * $RUNTIME is the optional -Dorg.cougaar.runtime.path
122: * $SOCIETY is the optional -Dorg.cougaar.society.path
123: * $INSTALL is the optional -Dorg.cougaar.install.path
124: * $SYS is the optional -Dorg.cougaar.system.path
125: * </pre>
126: * If any of the above "$VARIABLE" system properties is not set, then the
127: * corresponding paths in the default jar path will be excluded. For example,
128: * if only the "-Dorg.cougaar.install.path" is set, then the default jar path
129: * will be:<pre>
130: * -Dorg.cougaar.jar.path=$INSTALL/lib:$INSTALL/plugins:$INSTALL/sys
131: * </pre>
132: * <p>
133: * The "classpath(..)" path wrapper is used to list jars and
134: * directories containing classes, similar to Java's CLASSPATH.
135: * For example:<pre>
136: * -Dorg.cougaar.class.path=/tmp/classes:/tmp/foo.jar
137: * </pre>
138: * The default path wrapper, "directory(..)", is used to find jars
139: * in a directory by listing "*.jar", "*.zip", and "*.plugin".
140: * For example:<pre>
141: * -Dorg.cougaar.system.path=/jars
142: * </pre>
143: * where "/jars" contains jar files, for example:<pre>
144: * /jars/a.jar
145: * /jars/b.jar
146: * /jars/c.jar
147: * </pre>
148: * If the "-Dorg.cougaar.jar.path" value ends in the separator character,
149: * then the {@link #DEFAULT_JAR_PATH} is appended to the end of the specified
150: * value. This can be used to easily prefix the jar path.
151: * <p>
152: * Note that the above "$" strings must be escaped to avoid Unix
153: * shell expansion, for example:<pre>
154: * -Dorg.cougaar.jar.path=\\\$INSTALL/lib:\\\$INSTALL/sys
155: * </pre>
156: * In practice the "$" strings are rarely used, since explicit paths
157: * are often specified, for example:<pre>
158: * -Dorg.cougaar.jar.path=/tmp/lib:classpath(/tmp/classes):/tmp/sys
159: * </pre>
160: * <p>
161: * The $CLASSPATH "-Dorg.cougaar.class.path" is primarily a developer
162: * mechanism to override the infrastructure and application jars with
163: * development code. Most packaged Cougaar applications do not use
164: * these optional system properties, and instead create jar files for
165: * $INSTALL/lib.
166: * <p>
167: * A couple jars are typically excluded by the jar finder, and
168: * instead are loaded by the Java system ClassLoader:<pre>
169: * bootstrap.jar <i>(contains this Bootstrapper class)</i>
170: * javaiopatch.jar <i>(contains the persistence I/O overrides)</i>
171: * </pre>
172: * These excluded jars are set by:<pre>
173: * -Dorg.cougaar.bootstrap.excludeJars=javaiopatch.jar:bootstrap.jar
174: * </pre>
175: * <p>
176: * <b>Important Notes:</b>
177: * If you use the Bootstrapper, do not put Cougaar classes on your
178: * Java classpath -- only specify:<pre>
179: * java -classpath bootstrapper.jar ..
180: * </pre>
181: * If the Java SystemClassloader loads a Cougaar class, it will refer
182: * to SystemClassloader-loaded core classes which exist in a different
183: * namespace than Bootstrapper-loaded classes. This problem will
184: * cause all sorts of loading errors.
185: * <p>
186: * A common problem is the attempt to use "patch" jar files to repair
187: * a few classes of some much larger archive. There are two problems
188: * with this use pattern:<ol>
189: * <li>The order that Bootstrapper will find jar files in a directory
190: * is undefined - there is no guarantee that the patch will take
191: * precedence over the original.</li>
192: * <li>Classloaders will refuse to load classes of a given package
193: * from multiple jar files - if the patch jar does not contain the
194: * whole package, the classloader will likely be unable to load the
195: * rest of the classes.</li>
196: * </ol>
197: * Both problems tend to crop up when you can least afford this confusion.
198: * <p>
199: *
200: * @property org.cougaar.bootstrap.Bootstrapper.loud=false
201: * Set to "true" to information about classloader path and order.
202: * Set to "shout" to get information about where each loaded class
203: * comes from.
204: *
205: * @property org.cougaar.jar.path
206: * Bootstrapper jar and class path, which defaults to the
207: * {@link #DEFAULT_JAR_PATH} documented in the Bootstrapper class.
208: *
209: * @property org.cougaar.runtime.path
210: * Optional directory where runtime-specific jars are installed
211: * in "lib/" and "sys/", which is usually supplied via the optional
212: * $COUGAAR_RUNTIME_PATH environment variable.
213: *
214: * @property org.cougaar.society.path
215: * Optional directory where application-specific jars are installed
216: * in "lib/" and "sys/", which is usually supplied via the optional
217: * $COUGAAR_SOCIETY_PATH environment variable.
218: *
219: * @property org.cougaar.install.path
220: * The directory where this Cougaar instance is installed, usually
221: * supplied by the $COUGAAR_INSTALL_PATH/bin/cougaar from the
222: * $COUGAAR_INSTALL_PATH environment variable. <b>REQUIRED</b>
223: *
224: * @property org.cougaar.class.path
225: * Optional classpath-like setting searched immediately before
226: * discovered $COUGAAR_INSTALL_PATH/lib jars, to load non-jarred
227: * classes.
228: *
229: * @property org.cougaar.system.path
230: * Optional directory searched immediately before discovered
231: * $COUGAAR_INSTALL_PATH/sys jars, typically not used in practice.
232: *
233: * @property org.cougaar.bootstrap.class
234: * Bootstrapper class to use to bootstrap the system. Defaults to
235: * the bootstrapper (org.cougaar.bootstrap.Bootstrapper).
236: *
237: * @property org.cougaar.bootstrap.excludeJars
238: * Allows exclusion of specific jar files from consideration by
239: * bootstrapper. Defaults to "javaiopatch.jar:bootstrap.jar".
240: *
241: * @property org.cougaar.bootstrap.application
242: * The name of the application class to bootstrap. If not
243: * specified, will use the Bootstrapper's first command-line argument.
244: * This property only applies when the Bootstrap is invoked as an
245: * application.
246: *
247: * @property org.cougaar.properties.url=URL
248: * Set to specify where an additional set of
249: * System Properties should be loaded from.
250: */
251: public class Bootstrapper {
252:
253: /** Operating specific path separator */
254: private static final char OS_SEP_CHAR = File.pathSeparatorChar;
255: private static final String OS_SEP = File.pathSeparator;
256:
257: /** Standardized path separator, which works on both Linux and Windows */
258: private static final char STD_SEP_CHAR = ',';
259: private static final String STD_SEP = "" + STD_SEP_CHAR;
260:
261: /**
262: * The default value for the "org.cougaar.jar.path" system property.
263: * <p>
264: * See the above class-level javadoc for details.
265: */
266: public static final String DEFAULT_JAR_PATH = "classpath($CLASSPATH)"
267: + STD_SEP
268: + "$RUNTIME/lib"
269: + STD_SEP
270: + "$RUNTIME/sys"
271: + STD_SEP
272: + "$SOCIETY/lib"
273: + STD_SEP
274: + "$SOCIETY/sys"
275: + STD_SEP
276: + "$INSTALL/lib"
277: + STD_SEP
278: + "$INSTALL/plugins"
279: + STD_SEP + "$SYS" + STD_SEP + "$INSTALL/sys";
280:
281: protected final static int loudness;
282: static {
283: String s = SystemProperties
284: .getProperty("org.cougaar.bootstrap.Bootstrapper.loud");
285: if ("true".equals(s)) {
286: loudness = 1;
287: } else if ("shout".equals(s)) {
288: loudness = 2;
289: } else {
290: loudness = 0;
291: }
292: }
293:
294: /** return the loudness value of the bootstrapper.
295: * 0 is quiet (normal), 1 is verbose, 2 is insanely verbose.
296: **/
297: public final static int getLoudness() {
298: return loudness;
299: }
300:
301: // cache of getExcludedJars
302: private List excludedJars;
303:
304: private static boolean isBootstrapped = false;
305:
306: /** @return true iff a bootstrapper has run in the current VM **/
307: public final static boolean isBootstrapped() {
308: return isBootstrapped;
309: }
310:
311: /** If no bootstrapper has run in the current vm, sets a flag and
312: * returns, otherwise throws an error.
313: **/
314: protected synchronized final static void setIsBootstrapped() {
315: if (isBootstrapped) {
316: throw new Error("Circular Bootstrap!");
317: }
318: isBootstrapped = true;
319: }
320:
321: /** Launch an application inside the context of a bootstrapper instance.
322: * Gets the application class to use from the org.cougaar.bootstrap.application
323: * system property or from the first argument and then calls launch(String, String[]).
324: * @property org.cougaar.bootstrap.application The name of the application class
325: * to bootstrap. If not specified, will use the first argument instead. Only applies
326: * when Bootstrap is being invoked as an application.
327: **/
328: public static void main(String[] args) {
329: launch(args);
330: }
331:
332: public static void launch(Object[] args) {
333: String classname = SystemProperties
334: .getProperty("org.cougaar.bootstrap.application");
335: Object[] launchArgs = args;
336: if (classname == null) {
337: classname = (String) args[0];
338: launchArgs = (Object[]) Array.newInstance(args.getClass()
339: .getComponentType(), args.length - 1);
340: System.arraycopy(args, 1, launchArgs, 0, launchArgs.length);
341: }
342: launch(classname, launchArgs);
343: }
344:
345: /** Make a note that the application is being bootstrapped,
346: * construct a Bootstrapper instance, and pass control to the instance.
347: * @param args the args are typically a String[], but an Object[] is
348: * supported to pass more complex data structures from a container into
349: * the application
350: * @see #launchApplication(String, Object[])
351: **/
352: public static void launch(String classname, Object[] args) {
353: setIsBootstrapped();
354: readProperties(SystemProperties
355: .getProperty("org.cougaar.properties.url"));
356: SystemProperties.expandProperties();
357:
358: getBootstrapper().launchApplication(classname, args);
359: }
360:
361: /** Construct a bootstrapper instance **/
362: private final static Bootstrapper getBootstrapper() {
363: String s = SystemProperties.getProperty(
364: "org.cougaar.bootstrap.class",
365: "org.cougaar.bootstrap.Bootstrapper");
366: try {
367: Class c = Class.forName(s);
368: return (Bootstrapper) c.newInstance();
369: } catch (Exception e) {
370: throw new Error("Cannot instantiate bootstrapper " + s, e);
371: }
372: }
373:
374: protected String applicationClassname;
375: protected Object[] applicationArguments;
376: protected ClassLoader applicationClassLoader;
377:
378: /** Primary instance entry point for bootstrapper.
379: * Essentially finds the right list of URLs to use,
380: * creates a Classloader, and then calls launchMain.
381: **/
382: protected void launchApplication(String classname, Object[] args) {
383: applicationClassname = classname;
384: applicationArguments = args;
385:
386: applicationClassLoader = prepareVM(classname, args);
387: Thread.currentThread().setContextClassLoader(
388: applicationClassLoader);
389:
390: launchMain(applicationClassLoader, classname, args);
391: }
392:
393: /** Called to prepare the VM environment for running the application.
394: * @return A ClassLoader instance to be used to load the application.
395: **/
396: protected ClassLoader prepareVM(String classname, Object[] args) {
397: List l = computeURLs();
398: return createClassLoader(l);
399: }
400:
401: /** construct the right classloader, given a list of URLs.
402: * The default method uses the value of the system property
403: * "org.cougaar.bootstrap.classloader.class" as a class to find,
404: * then calls the constructor with a URL[] as the single argument
405: * (e.g. like a URLClassLoader).
406: * <p>
407: * The default is to create an {@link XURLClassLoader}, but another
408: * good option is a {@link BootstrapClassLoader}.
409: * @property org.cougaar.bootstrap.classloader.class Specifies the classloader
410: * class to use.
411: **/
412: protected ClassLoader createClassLoader(List l) {
413: URL urls[] = (URL[]) l.toArray(new URL[l.size()]);
414:
415: String clname = getProperty(
416: "org.cougaar.bootstrap.classloader.class",
417: "org.cougaar.bootstrap.XURLClassLoader");
418: try {
419: Class clclazz = Class.forName(clname);
420: Constructor clconst = clclazz
421: .getConstructor(new Class[] { URL[].class });
422: ClassLoader cl = (ClassLoader) clconst
423: .newInstance(new Object[] { urls });
424: return cl;
425: } catch (Exception e) {
426: throw new Error("Could not bootstrap the classloader", e);
427: }
428: }
429:
430: /** Find the primary application entry point for the application class and call it.
431: * The default implementation will look for static void launch(String[]) and then
432: * static void main(String[]).
433: * This method contains all the reflection code for invoking the application.
434: **/
435: protected void launchMain(ClassLoader cl, String classname,
436: Object[] args) {
437: try {
438: Class appClass = cl.loadClass(classname);
439:
440: Method main = null;
441: for (int i = 0; main == null && i < 2; i++) {
442: String method_name = (i == 0 ? "launch" : "main");
443: for (Class argcl = args.getClass().getComponentType(); argcl != null; argcl = argcl
444: .getSuperclass()) {
445: try {
446: Class argscl = Array.newInstance(argcl, 0)
447: .getClass();
448: main = appClass.getMethod(method_name,
449: new Class[] { argscl });
450: break;
451: } catch (NoSuchMethodException nsm) {
452: // okay
453: }
454: }
455: }
456: if (main == null) {
457: throw new RuntimeException(
458: "Unable to find \"launch\" or \"main\" method");
459: }
460:
461: main.invoke(null, new Object[] { args });
462: } catch (Exception e) {
463: throw new Error("Failed to launch " + classname, e);
464: }
465: }
466:
467: /** Entry point for computing the list of URLs to pass to our classloader.
468: **/
469: protected List computeURLs() {
470: return filterURLs(findURLs());
471: }
472:
473: /** Find jars, etc in the documented places.
474: * Shouldn't actually load or check the jars for correctness at this point.
475: **/
476: protected List findURLs() {
477: List l = new ArrayList();
478: List paths = findURLPaths();
479: for (int i = 0, n = paths.size(); i < n; i++) {
480: String s = (String) paths.get(i);
481: l.addAll(findJarsIn(s));
482: }
483: return l;
484: }
485:
486: protected List findURLPaths() {
487: // based on org/cougaar/util/Configuration.java:
488:
489: // symbolic names
490: Map props = new HashMap();
491: String cp = getProperty("org.cougaar.class.path");
492: if (cp != null && cp.length() > 0) {
493: props.put("CLASSPATH", cp);
494: }
495: String runtime_path = getProperty("org.cougaar.runtime.path");
496: if (runtime_path != null && runtime_path.length() > 0) {
497: props.put("RUNTIME", runtime_path);
498: props.put("CRP", runtime_path); // alias for RUNTIME
499: props.put("COUGAAR_RUNTIME_PATH", runtime_path); // for completeness
500: }
501: String society_path = getProperty("org.cougaar.society.path");
502: if (society_path != null && society_path.length() > 0) {
503: props.put("SOCIETY", society_path);
504: props.put("CSP", society_path); // alias for SOCIETY
505: props.put("COUGAAR_SOCIETY_PATH", society_path); // for completeness
506: }
507: String install_path = getProperty("org.cougaar.install.path");
508: if (install_path != null && install_path.length() > 0) {
509: props.put("INSTALL", install_path);
510: props.put("CIP", install_path); // alias for INSTALL
511: props.put("COUGAAR_INSTALL_PATH", install_path); // for completeness
512: }
513: props.put("HOME", getProperty("user.home"));
514: props.put("CWD", getProperty("user.dir"));
515: String sys = getProperty("org.cougaar.system.path");
516: if (sys != null && sys.length() > 0) {
517: props.put("SYS", sys);
518: }
519:
520: // jar path
521: String jar_path = getProperty("org.cougaar.jar.path");
522: if (jar_path != null && jar_path.length() > 0
523: && jar_path.charAt(0) == '"'
524: && jar_path.charAt(jar_path.length() - 1) == '"') {
525: jar_path = jar_path.substring(1, jar_path.length() - 1);
526: }
527: boolean append_default = false;
528: if (jar_path == null) {
529: append_default = true;
530: jar_path = "";
531: } else if (jar_path.length() > 0) {
532: jar_path = jar_path.replace('\\', '/'); // Make sure its a URL and not a file path
533: char lastChar = jar_path.charAt(jar_path.length() - 1);
534: append_default = (lastChar == STD_SEP_CHAR || lastChar == OS_SEP_CHAR);
535: }
536: if (append_default) {
537: // append default path, but only include paths that contain known keys.
538: //
539: // For example, ignore "$RUNTIME/lib" if "$RUNTIME" is not set.
540: boolean needs_sep = true;
541: List l = tokenizeJarPath(DEFAULT_JAR_PATH);
542: for (int i = 0; i < l.size(); i++) {
543: String s = (String) l.get(i);
544: if (!canSubstituteProperties(s, props))
545: continue;
546: if (needs_sep) {
547: jar_path += STD_SEP;
548: } else {
549: needs_sep = true;
550: }
551: jar_path += s;
552: }
553: }
554:
555: // resolve symbols
556: String s = substituteProperties(jar_path, props);
557:
558: // tokenize the path and remove duplicates
559: return tokenizeJarPath(s);
560: }
561:
562: private static List tokenizeJarPath(String s) {
563: List l = new ArrayList();
564: for (int i = 0;;) {
565: int j;
566: int k;
567: if (s.startsWith("classpath(", i)
568: || s.startsWith("directory(", i)) {
569: j = s.indexOf(')', i);
570: k = j + 1;
571: } else {
572: j = s.indexOf(STD_SEP_CHAR, i);
573: if (STD_SEP_CHAR != OS_SEP_CHAR
574: && !(OS_SEP_CHAR == ':' && isURL(s, i))) {
575: int c = s.indexOf(OS_SEP_CHAR, i);
576: j = ((j >= 0 && c >= 0) ? Math.min(j, c) : Math
577: .max(j, c));
578: }
579: k = j;
580: }
581: if (j < 0) {
582: k = s.length();
583: }
584: String path = s.substring(i, k);
585: if (path.length() > 0 && !l.contains(path)) {
586: l.add(path);
587: }
588: if (j < 0) {
589: break;
590: }
591: i = j + 1;
592: }
593: return l;
594: }
595:
596: private static boolean isURL(String s, int i) {
597: int c = s.indexOf(':', i);
598: return (((c - i) >= 2) && (c == indexOfNonLetter(s, i))
599: && ((c + 1) < s.length()) && (s.charAt(c + 1) == '/' || ("jar"
600: .equals(s.substring(i, c)) && s.indexOf(':', c + 2) > 0)));
601: }
602:
603: private static int indexOfNonLetter(String s, int i) {
604: int l = s.length();
605: for (int j = i; j < l; j++) {
606: char c = s.charAt(j);
607: if (c < 'a' || c > 'z')
608: return j;
609: }
610: return -1;
611: }
612:
613: private static int indexOfNonAlpha(String s, int i) {
614: int l = s.length();
615: for (int j = i; j < l; j++) {
616: char c = s.charAt(j);
617: if (!Character.isLetterOrDigit(c) && c != '_')
618: return j;
619: }
620: return -1;
621: }
622:
623: private static boolean canSubstituteProperties(String s, Map props) {
624: return (s == null || (substituteProperties(s, props, false) != null));
625: }
626:
627: private static String substituteProperties(String s, Map props) {
628: return substituteProperties(s, props, false);
629: }
630:
631: private static String substituteProperties(String orig_s,
632: Map props, boolean failOnUnknownProperty) {
633: String s = orig_s;
634: while (true) {
635: int i = (s == null ? -1 : s.indexOf('$'));
636: if (i < 0) {
637: break;
638: }
639: int j = indexOfNonAlpha(s, i + 1);
640: String s0 = s.substring(0, i);
641: String s2 = (j < 0 ? "" : s.substring(j));
642: String key = s.substring(i + 1, (j < 0 ? s.length() : j));
643: Object val = props.get(key);
644: if (val == null) {
645: if (failOnUnknownProperty) {
646: throw new IllegalArgumentException(
647: "Unknown property \"" + key
648: + "\" in path: " + orig_s);
649: }
650: s = null;
651: break;
652: }
653: s = s0 + val + s2;
654: }
655: return s;
656: }
657:
658: protected List findJarsIn(String s) {
659: boolean isClasspath = s.startsWith("classpath(");
660: if (isClasspath || s.startsWith("directory(")) {
661: int end = s.length() - (s.endsWith(")") ? 1 : 0);
662: s = s.substring(s.indexOf('(') + 1, end);
663: }
664: if (s == null || s.length() == 0) {
665: return Collections.EMPTY_LIST;
666: }
667: if (isClasspath) {
668: return findJarsInClasspath(s);
669: }
670: return findJarsInDirectory(s);
671: }
672:
673: protected List findJarsInDirectory(String s) {
674: if (s.startsWith("file:/") || !isURL(s, 0)) {
675: return findJarsInDirectory(new File(s));
676: } else {
677: return findJarsInDirectoryURL(s);
678: }
679: }
680:
681: /** Gather jar files found in the directory specified by the argument **/
682: protected List findJarsInDirectory(File f) {
683: File[] files;
684: if (f.isDirectory()) {
685: files = f.listFiles(new FilenameFilter() {
686: public boolean accept(File dir, String name) {
687: return isJar(name);
688: }
689: });
690: } else if (f.isFile() && isJar(f.getName())) {
691: files = new File[1];
692: files[0] = f;
693: } else {
694: files = null;
695: }
696: if (files == null || files.length == 0)
697: return Collections.EMPTY_LIST;
698: List l = new ArrayList(files.length);
699: for (int i = 0; i < files.length; i++) {
700: try {
701: l.add(newURL("file:" + files[i].getCanonicalPath()));
702: } catch (Exception e) {
703: e.printStackTrace();
704: }
705: }
706: return l;
707: }
708:
709: protected List findJarsInDirectoryURL(String s) {
710: InputStream in = null;
711: List ret = null;
712: try {
713: URL url = new URL(s);
714: in = url.openStream();
715: if (isJar(s)) {
716: in.close();
717: return Collections.singletonList(url);
718: }
719: // <a href="foo.jar">
720: Pattern p = Pattern
721: .compile(
722: "^.*<\\s*a\\s+href\\s*=\\s*\"\\s*"
723: + "([a-z0-9_:/~\\.-]+\\.(jar|zip|plugin))\\s*"
724: + "\"\\s*>.*$",
725: Pattern.CASE_INSENSITIVE);
726: ret = new ArrayList();
727: BufferedReader br = new BufferedReader(
728: new InputStreamReader(in));
729: while (true) {
730: String line = br.readLine();
731: if (line == null)
732: break;
733: line = line.trim();
734: if (line.length() == 0)
735: continue;
736: Matcher m = p.matcher(line);
737: if (!m.matches())
738: continue;
739: String si = m.group(1);
740: if (si.indexOf(":/") < 0) {
741: if (!s.endsWith("/") && !si.startsWith("/")) {
742: si = "/" + si;
743: }
744: si = s + si;
745: }
746: ret.add(newURL(si));
747: }
748: br.close();
749: in = null;
750: } catch (Exception e) {
751: ret = Collections.EMPTY_LIST;
752: } finally {
753: if (in != null) {
754: try {
755: in.close();
756: } catch (Exception x) {
757: }
758: }
759: }
760: return ret;
761: }
762:
763: /** gather jar files listed in the classpath-like specification **/
764: protected List findJarsInClasspath(String path) {
765: if (path == null)
766: return Collections.EMPTY_LIST;
767: List l = tokenizeJarPath(path);
768: List ret = new ArrayList(l.size());
769: for (int i = 0; i < l.size(); i++) {
770: try {
771: String si = (String) l.get(i);
772: if (!isJar(si) && !si.endsWith(File.separator)) {
773: si += File.separator;
774: si = canonicalPath(si); // Convert to a canonical path, if possible
775: }
776: ret.add(newURL(si));
777: } catch (Exception e) {
778: e.printStackTrace();
779: }
780: }
781: return ret;
782: }
783:
784: /** convert a directory name to a canonical path **/
785: protected final String canonicalPath(String filename) {
786: String ret = filename;
787: if (!filename.startsWith("file:") && !isURL(filename, 0)) {
788: File f = new File(filename);
789: try {
790: ret = f.getCanonicalPath() + File.separator;
791: } catch (IOException ioe) {
792: // file must not exist...
793: }
794: }
795: return ret;
796: }
797:
798: /** @return true iff the argument appears to name a jar file **/
799: protected boolean isJar(String n) {
800: return (n.endsWith(".jar") || n.endsWith(".zip") || n
801: .endsWith(".plugin"));
802: }
803:
804: /** Convert the argument into a URL **/
805: protected URL newURL(String p) throws MalformedURLException {
806: try {
807: URL u = new URL(p);
808: return u;
809: } catch (MalformedURLException ex) {
810: return new File(p).toURI().toURL();
811: }
812: }
813:
814: /** Filter a set of URLs with whatever checks are required.
815: * @return a list of URLs suitable for passing to the classloader.
816: **/
817: protected List filterURLs(List l) {
818: List o = new ArrayList();
819: for (Iterator it = l.iterator(); it.hasNext();) {
820: URL u = (URL) it.next();
821: if (checkURL(u)) {
822: o.add(u);
823: } else {
824: }
825: }
826: return o;
827: }
828:
829: /** Check to see if a specific URL should be included in the bootstrap
830: * classloader's URLlist. The default implementation checks each url
831: * against the list of excluded jars.
832: **/
833: protected boolean checkURL(URL url) {
834: String u = url.toString();
835: List l = getExcludedJars();
836: for (int i = 0; i < l.size(); i++) {
837: String tail = (String) l.get(i);
838: if (u.endsWith(tail))
839: return false;
840: }
841: return true;
842: }
843:
844: /**
845: * Get the list of jar files to be ignored by bootstrapper, which
846: * typically includes javaiopatch and boostrap itself.
847: * @todo Replace this with something which examines the
848: * jars for dont-bootstrap-me flags.
849: **/
850: protected List getExcludedJars() {
851: if (excludedJars == null) {
852: excludedJars = new ArrayList();
853: String s = getProperty("org.cougaar.bootstrap.excludeJars");
854: if (s == null) {
855: s = "javaiopatch.jar:bootstrap.jar";
856: }
857: if (s.length() > 0) {
858: String files[] = s.split(":");
859: for (int i = 0; i < files.length; i++) {
860: excludedJars.add(files[i]);
861: }
862: }
863: }
864: return excludedJars;
865: }
866:
867: protected String getProperty(String key) {
868: return SystemProperties.getProperty(key);
869: }
870:
871: protected String getProperty(String key, String def) {
872: return SystemProperties.getProperty(key, def);
873: }
874:
875: protected Properties getProperties() {
876: return SystemProperties.getProperties();
877: }
878:
879: /**
880: * Reads the properties from specified url
881: **/
882: public static void readProperties(String propertiesURL) {
883: if (propertiesURL != null) {
884: readPropertiesFromURL(SystemProperties.getProperties(),
885: propertiesURL);
886: }
887: }
888:
889: protected void readPropertiesFromURL(String propertiesURL) {
890: if (propertiesURL != null) {
891: readPropertiesFromURL(getProperties(), propertiesURL);
892: }
893: }
894:
895: private static void readPropertiesFromURL(Properties props,
896: String propertiesURL) {
897: if (propertiesURL != null) {
898: try { // open url, load into props
899: URL url = new URL(propertiesURL);
900: InputStream stream = url.openStream();
901: props.load(stream);
902: stream.close();
903: } catch (MalformedURLException me) {
904: System.err.println(me);
905: } catch (IOException ioe) {
906: System.err.println(ioe);
907: }
908: }
909: }
910: }
|