001: // ========================================================================
002: // Copyright 2003-2005 Mort Bay Consulting Pty. Ltd.
003: // ------------------------------------------------------------------------
004: // Licensed under the Apache License, Version 2.0 (the "License");
005: // you may not use this file except in compliance with the License.
006: // You may obtain a copy of the License at
007: // http://www.apache.org/licenses/LICENSE-2.0
008: // Unless required by applicable law or agreed to in writing, software
009: // distributed under the License is distributed on an "AS IS" BASIS,
010: // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011: // See the License for the specific language governing permissions and
012: // limitations under the License.
013: // ========================================================================
014: package org.mortbay.start;
015:
016: import java.io.BufferedReader;
017: import java.io.File;
018: import java.io.FileInputStream;
019: import java.io.IOException;
020: import java.io.InputStream;
021: import java.io.InputStreamReader;
022: import java.io.OutputStream;
023: import java.lang.reflect.InvocationTargetException;
024: import java.lang.reflect.Method;
025: import java.net.InetAddress;
026: import java.net.Socket;
027: import java.security.Policy;
028: import java.util.ArrayList;
029: import java.util.Hashtable;
030: import java.util.StringTokenizer;
031:
032: /*-------------------------------------------*/
033: /**
034: * Main start class. This class is intended to be the main class listed in the MANIFEST.MF of the
035: * start.jar archive. It allows an application to be started with the command "java -jar
036: * start.jar". The behaviour of Main is controlled by the "org/mortbay/start/start.config" file
037: * obtained as a resource or file. This can be overridden with the START system property. The
038: * format of each line in this file is:
039: *
040: * <PRE>
041: *
042: * SUBJECT [ [!] CONDITION [AND|OR] ]*
043: *
044: * </PRE>
045: *
046: * where SUBJECT:
047: *
048: * <PRE>
049: * ends with ".class" is the Main class to run.
050: * ends with ".xml" is a configuration file for the command line
051: * ends with "/" is a directory from which add all jar and zip files from.
052: * ends with "/*" is a directory from which add all unconsidered jar and zip files from.
053: * Containing = are used to assign system properties.
054: * all other subjects are treated as files to be added to the classpath.
055: * </PRE>
056: *
057: * Subjects may include system properties with $(propertyname) syntax. File subjects starting with
058: * "/" are considered absolute, all others are relative to the home directory.
059: * <P>
060: * CONDITION is one of:
061: *
062: * <PRE>
063: *
064: * always
065: * never
066: * available package.class
067: * java OPERATOR n.n
068: * nargs OPERATOR n
069: * OPERATOR := one of "<",">"," <=",">=","==","!="
070: *
071: * </PRE>
072: *
073: * CONTITIONS can be combined with AND OR or !, with AND being the assume operator for a list of
074: * CONDITIONS. Classpath operations are evaluated on the fly, so once a class or jar is added to
075: * the classpath, subsequent available conditions will see that class. The system parameter
076: * CLASSPATH, if set is given to the start classloader before any paths from the configuration
077: * file. Programs started with start.jar may be stopped with the stop.jar, which connects via a
078: * local port to stop the server. The default port can be set with the STOP.PORT system property (a
079: * port of < 0 disables the stop mechanism). If the STOP.KEY system property is set, then a random
080: * key is generated and written to stdout. This key must be passed to the stop.jar.
081: *
082: * @author Jan Hlavaty (hlavac@code.cz)
083: * @author Greg Wilkins
084: */
085: public class Main {
086: static boolean _debug = System.getProperty("DEBUG", null) != null;
087: private String _classname = null;
088: private Classpath _classpath = new Classpath();
089: private String _config = System.getProperty("START",
090: "org/mortbay/start/start.config");
091: private ArrayList _xml = new ArrayList();
092: private boolean _version = false;
093:
094: public static void main(String[] args) {
095: try {
096: if (args.length > 0 && args[0].equalsIgnoreCase("--help")) {
097: System.err
098: .println("Usage: java [-DDEBUG] [-DSTART=start.config] [-Dmain.class=org.MyMain] -jar start.jar [--help|--stop|--version] [config ...]");
099: System.exit(1);
100: } else if (args.length > 0
101: && args[0].equalsIgnoreCase("--stop")) {
102: new Main().stop();
103: } else if (args.length > 0
104: && args[0].equalsIgnoreCase("--version")) {
105: String[] nargs = new String[args.length - 1];
106: System.arraycopy(args, 1, nargs, 0, nargs.length);
107: Main main = new Main();
108: main._version = true;
109: main.start(nargs);
110: } else {
111: new Main().start(args);
112: }
113: } catch (Exception e) {
114: e.printStackTrace();
115: }
116: }
117:
118: static File getDirectory(String name) {
119: try {
120: if (name != null) {
121: File dir = new File(name).getCanonicalFile();
122: if (dir.isDirectory()) {
123: return dir;
124: }
125: }
126: } catch (IOException e) {
127: }
128: return null;
129: }
130:
131: boolean isAvailable(String classname) {
132: try {
133: Class.forName(classname);
134: return true;
135: } catch (NoClassDefFoundError e) {
136: } catch (ClassNotFoundException e) {
137: }
138: ClassLoader loader = _classpath.getClassLoader();
139: try {
140: loader.loadClass(classname);
141: return true;
142: } catch (NoClassDefFoundError e) {
143: } catch (ClassNotFoundException e) {
144: }
145: return false;
146: }
147:
148: public void invokeMain(ClassLoader classloader, String classname,
149: String[] args) throws IllegalAccessException,
150: InvocationTargetException, NoSuchMethodException,
151: ClassNotFoundException {
152: Class invoked_class = null;
153: invoked_class = classloader.loadClass(classname);
154:
155: if (_version) {
156: System.err.println(invoked_class.getPackage()
157: .getImplementationTitle()
158: + " "
159: + invoked_class.getPackage()
160: .getImplementationVersion());
161: System.exit(0);
162: }
163:
164: Class[] method_param_types = new Class[1];
165: method_param_types[0] = args.getClass();
166: Method main = null;
167: main = invoked_class.getDeclaredMethod("main",
168: method_param_types);
169: Object[] method_params = new Object[1];
170: method_params[0] = args;
171:
172: main.invoke(null, method_params);
173: }
174:
175: /* ------------------------------------------------------------ */
176: String expand(String s) {
177: int i1 = 0;
178: int i2 = 0;
179: while (s != null) {
180: i1 = s.indexOf("$(", i2);
181: if (i1 < 0)
182: break;
183: i2 = s.indexOf(")", i1 + 2);
184: if (i2 < 0)
185: break;
186: String property = System.getProperty(s
187: .substring(i1 + 2, i2), "");
188: s = s.substring(0, i1) + property + s.substring(i2 + 1);
189: }
190: return s;
191: }
192:
193: /* ------------------------------------------------------------ */
194: void configure(InputStream config, int nargs) throws Exception {
195: BufferedReader cfg = new BufferedReader(new InputStreamReader(
196: config, "ISO-8859-1"));
197: Version java_version = new Version(System
198: .getProperty("java.version"));
199: Version ver = new Version();
200: // JAR's already processed
201: java.util.Hashtable done = new Hashtable();
202: // Initial classpath
203: String classpath = System.getProperty("CLASSPATH");
204: if (classpath != null) {
205: StringTokenizer tok = new StringTokenizer(classpath,
206: File.pathSeparator);
207: while (tok.hasMoreTokens())
208: _classpath.addComponent(tok.nextToken());
209: }
210: // Handle line by line
211: String line = null;
212: while (true) {
213: line = cfg.readLine();
214: if (line == null)
215: break;
216: if (line.length() == 0 || line.startsWith("#"))
217: continue;
218: try {
219: StringTokenizer st = new StringTokenizer(line);
220: String subject = st.nextToken();
221: boolean expression = true;
222: boolean not = false;
223: String condition = null;
224: // Evaluate all conditions
225: while (st.hasMoreTokens()) {
226: condition = st.nextToken();
227: if (condition.equalsIgnoreCase("!")) {
228: not = true;
229: continue;
230: }
231: if (condition.equalsIgnoreCase("OR")) {
232: if (expression)
233: break;
234: expression = true;
235: continue;
236: }
237: if (condition.equalsIgnoreCase("AND")) {
238: if (!expression)
239: break;
240: continue;
241: }
242: boolean eval = true;
243: if (condition.equals("true")
244: || condition.equals("always")) {
245: eval = true;
246: } else if (condition.equals("false")
247: || condition.equals("never")) {
248: eval = false;
249: } else if (condition.equals("available")) {
250: String class_to_check = st.nextToken();
251: eval = isAvailable(class_to_check);
252: } else if (condition.equals("exists")) {
253: try {
254: eval = false;
255: File file = new File(expand(st.nextToken()));
256: eval = file.exists();
257: } catch (Exception e) {
258: if (_debug)
259: e.printStackTrace();
260: }
261: } else if (condition.equals("property")) {
262: String property = System.getProperty(st
263: .nextToken());
264: eval = property != null
265: && property.length() > 0;
266: } else if (condition.equals("java")) {
267: String operator = st.nextToken();
268: String version = st.nextToken();
269: ver.parse(version);
270: eval = (operator.equals("<") && java_version
271: .compare(ver) < 0)
272: || (operator.equals(">") && java_version
273: .compare(ver) > 0)
274: || (operator.equals("<=") && java_version
275: .compare(ver) <= 0)
276: || (operator.equals("=<") && java_version
277: .compare(ver) <= 0)
278: || (operator.equals("=>") && java_version
279: .compare(ver) >= 0)
280: || (operator.equals(">=") && java_version
281: .compare(ver) >= 0)
282: || (operator.equals("==") && java_version
283: .compare(ver) == 0)
284: || (operator.equals("!=") && java_version
285: .compare(ver) != 0);
286: } else if (condition.equals("nargs")) {
287: String operator = st.nextToken();
288: int number = Integer.parseInt(st.nextToken());
289: eval = (operator.equals("<") && nargs < number)
290: || (operator.equals(">") && nargs > number)
291: || (operator.equals("<=") && nargs <= number)
292: || (operator.equals("=<") && nargs <= number)
293: || (operator.equals("=>") && nargs >= number)
294: || (operator.equals(">=") && nargs >= number)
295: || (operator.equals("==") && nargs == number)
296: || (operator.equals("!=") && nargs != number);
297: } else {
298: System.err.println("ERROR: Unknown condition: "
299: + condition);
300: eval = false;
301: }
302: expression &= not ? !eval : eval;
303: not = false;
304: }
305: String file = expand(subject).replace('/',
306: File.separatorChar);
307: if (_debug)
308: System.err.println((expression ? "T " : "F ")
309: + line);
310: if (!expression) {
311: done.put(file, file);
312: continue;
313: }
314: // Handle the subject
315: if (subject.indexOf("=") > 0) {
316: int i = file.indexOf("=");
317: String property = file.substring(0, i);
318: String value = file.substring(i + 1);
319: if (_debug)
320: System.err.println(" " + property + "="
321: + value);
322: System.setProperty(property, value);
323: } else if (subject.endsWith("/*")) {
324: // directory of JAR files - only add jars and zips
325: // within the directory
326: File dir = new File(file.substring(0,
327: file.length() - 1));
328: addJars(dir, done, false);
329: } else if (subject.endsWith("/**")) {
330: //directory hierarchy of jar files - recursively add all
331: //jars and zips in the hierarchy
332: File dir = new File(file.substring(0,
333: file.length() - 2));
334: addJars(dir, done, true);
335: } else if (subject.endsWith("/")) {
336: // class directory
337: File cd = new File(file);
338: String d = cd.getCanonicalPath();
339: if (!done.containsKey(d)) {
340: done.put(d, d);
341: boolean added = _classpath.addComponent(d);
342: if (_debug)
343: System.err.println((added ? " CLASSPATH+="
344: : " !")
345: + d);
346: }
347: } else if (subject.toLowerCase().endsWith(".xml")) {
348: // Config file
349: File f = new File(file);
350: if (f.exists())
351: _xml.add(f.getCanonicalPath());
352: if (_debug)
353: System.err.println(" ARGS+=" + f);
354: } else if (subject.toLowerCase().endsWith(".class")) {
355: // Class
356: String cn = expand(subject.substring(0, subject
357: .length() - 6));
358: if (cn != null && cn.length() > 0) {
359: if (_debug)
360: System.err.println(" CLASS=" + cn);
361: _classname = cn;
362: }
363: } else {
364: // single JAR file
365: File f = new File(file);
366: String d = f.getCanonicalPath();
367: if (!done.containsKey(d)) {
368: done.put(d, d);
369: boolean added = _classpath.addComponent(d);
370: if (!added) {
371: added = _classpath
372: .addClasspath(expand(subject));
373: if (_debug)
374: System.err
375: .println((added ? " CLASSPATH+="
376: : " !")
377: + d);
378: } else if (_debug)
379: System.err.println((added ? " CLASSPATH+="
380: : " !")
381: + d);
382: }
383: }
384: } catch (Exception e) {
385: System.err.println("on line: '" + line + "'");
386: e.printStackTrace();
387: }
388: }
389: }
390:
391: /* ------------------------------------------------------------ */
392: public void start(String[] args) {
393: ArrayList al = new ArrayList();
394: for (int i = 0; i < args.length; i++) {
395: if (args[i] == null)
396: continue;
397: else
398: al.add(args[i]);
399: }
400: args = (String[]) al.toArray(new String[al.size()]);
401: // set up classpath:
402: InputStream cpcfg = null;
403: try {
404: Monitor.monitor();
405:
406: cpcfg = getClass().getClassLoader().getResourceAsStream(
407: _config);
408: if (_debug)
409: System.err.println("config=" + _config);
410: if (cpcfg == null)
411: cpcfg = new FileInputStream(_config);
412: configure(cpcfg, args.length);
413: File file = new File(System.getProperty("jetty.home"));
414: String canonical = file.getCanonicalPath();
415: System.setProperty("jetty.home", canonical);
416: } catch (Exception e) {
417: e.printStackTrace();
418: System.exit(1);
419: } finally {
420: try {
421: cpcfg.close();
422: } catch (Exception e) {
423: e.printStackTrace();
424: }
425: }
426: // okay, classpath complete.
427: System.setProperty("java.class.path", _classpath.toString());
428: ClassLoader cl = _classpath.getClassLoader();
429: if (_debug) {
430: System.err.println("java.class.path="
431: + System.getProperty("java.class.path"));
432: System.err.println("jetty.home="
433: + System.getProperty("jetty.home"));
434: System.err.println("java.io.tmpdir="
435: + System.getProperty("java.io.tmpdir"));
436: System.err.println("java.class.path=" + _classpath);
437: System.err.println("classloader=" + cl);
438: System.err.println("classloader.parent=" + cl.getParent());
439: }
440: // Invoke main(args) using new classloader.
441: Thread.currentThread().setContextClassLoader(cl);
442: // re-eval the policy now that env is set
443: try {
444: Policy policy = Policy.getPolicy();
445: if (policy != null)
446: policy.refresh();
447: } catch (Exception e) {
448: e.printStackTrace();
449: }
450: try {
451: for (int i = 0; i < args.length; i++) {
452: if (args[i] == null)
453: continue;
454: _xml.add(args[i]);
455: }
456: args = (String[]) _xml.toArray(args);
457: //check for override of start class
458: String mainClass = System.getProperty("jetty.server");
459: if (mainClass != null)
460: _classname = mainClass;
461: mainClass = System.getProperty("main.class");
462: if (mainClass != null)
463: _classname = mainClass;
464: if (_debug)
465: System.err.println("main.class=" + _classname);
466: invokeMain(cl, _classname, args);
467: } catch (Exception e) {
468: e.printStackTrace();
469: }
470: }
471:
472: /**
473: * Stop a running jetty instance.
474: */
475: public void stop() {
476: int _port = Integer.getInteger("STOP.PORT", -1).intValue();
477: String _key = System.getProperty("STOP.KEY", null);
478:
479: try {
480: if (_port <= 0)
481: System.err
482: .println("STOP.PORT system property must be specified");
483: if (_key == null) {
484: _key = "";
485: System.err
486: .println("STOP.KEY system property must be specified");
487: System.err.println("Using empty key");
488: }
489:
490: Socket s = new Socket(InetAddress.getByName("127.0.0.1"),
491: _port);
492: OutputStream out = s.getOutputStream();
493: out.write((_key + "\r\nstop\r\n").getBytes());
494: out.flush();
495: s.close();
496: } catch (Exception e) {
497: e.printStackTrace();
498: }
499: }
500:
501: private void addJars(File dir, Hashtable table, boolean recurse)
502: throws IOException {
503: File[] entries = dir.listFiles();
504:
505: for (int i = 0; entries != null && i < entries.length; i++) {
506: File entry = entries[i];
507:
508: if (entry.isDirectory() && recurse)
509: addJars(entry, table, recurse);
510: else {
511: String name = entry.getName().toLowerCase();
512: if (name.endsWith(".jar") || name.endsWith(".zip")) {
513: String jar = entry.getCanonicalPath();
514: if (!table.containsKey(jar)) {
515: table.put(jar, jar);
516: boolean added = _classpath.addComponent(jar);
517: if (_debug)
518: System.err.println((added ? " CLASSPATH+="
519: : " !")
520: + jar);
521: }
522: }
523: }
524: }
525: }
526: }
|