1   // ========================================================================
2   // Copyright 2003-2005 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // Licensed under the Apache License, Version 2.0 (the "License");
5   // you may not use this file except in compliance with the License.
6   // You may obtain a copy of the License at 
7   // http://www.apache.org/licenses/LICENSE-2.0
8   // Unless required by applicable law or agreed to in writing, software
9   // distributed under the License is distributed on an "AS IS" BASIS,
10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  // See the License for the specific language governing permissions and
12  // limitations under the License.
13  // ========================================================================
14  package org.mortbay.start;
15  
16  import java.io.BufferedReader;
17  import java.io.File;
18  import java.io.FileInputStream;
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.io.InputStreamReader;
22  import java.io.OutputStream;
23  import java.lang.reflect.InvocationTargetException;
24  import java.lang.reflect.Method;
25  import java.net.ConnectException;
26  import java.net.InetAddress;
27  import java.net.Socket;
28  import java.security.Policy;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.Collections;
32  import java.util.HashSet;
33  import java.util.Hashtable;
34  import java.util.List;
35  import java.util.Set;
36  import java.util.StringTokenizer;
37  
38  /*-------------------------------------------*/
39  /**
40   * Main start class. This class is intended to be the main class listed in the MANIFEST.MF of the
41   * start.jar archive. It allows an application to be started with the command "java -jar
42   * start.jar". The behaviour of Main is controlled by the "org/mortbay/start/start.config" file
43   * obtained as a resource or file. This can be overridden with the START system property. The
44   * format of each line in this file is:
45   * 
46   * <PRE>
47   * 
48   * SUBJECT [ [!] CONDITION [AND|OR] ]*
49   * 
50   * </PRE>
51   * 
52   * where SUBJECT:
53   * 
54   * <PRE>
55   * ends with ".class" is the Main class to run.
56   * ends with ".xml" is a configuration file for the command line
57   * ends with "/" is a directory from which add all jar and zip files from.
58   * ends with "/*" is a directory from which add all unconsidered jar and zip files from.
59   * Containing = are used to assign system properties.
60   * all other subjects are treated as files to be added to the classpath.
61   * </PRE>
62   * 
63   * Subjects may include system properties with $(propertyname) syntax. File subjects starting with
64   * "/" are considered absolute, all others are relative to the home directory.
65   * <P>
66   * CONDITION is one of:
67   * 
68   * <PRE>
69   * 
70   * always
71   * never
72   * available package.class 
73   * java OPERATOR n.n 
74   * nargs OPERATOR n
75   * OPERATOR := one of "<",">"," <=",">=","==","!="
76   * 
77   * </PRE>
78   * 
79   * CONTITIONS can be combined with AND OR or !, with AND being the assume operator for a list of
80   * CONDITIONS. Classpath operations are evaluated on the fly, so once a class or jar is added to
81   * the classpath, subsequent available conditions will see that class. The system parameter
82   * CLASSPATH, if set is given to the start classloader before any paths from the configuration
83   * file. Programs started with start.jar may be stopped with the stop.jar, which connects via a
84   * local port to stop the server. The default port can be set with the STOP.PORT system property (a
85   * port of < 0 disables the stop mechanism). If the STOP.KEY system property is set, then a random
86   * key is generated and written to stdout. This key must be passed to the stop.jar.
87   * <p>
88   * The configuration file may be divided into sections with option names like:
89   * <pre>
90   * [ssl,default]
91   * </pre>
92   * Clauses after a section header will only be included if they match one of the tags in the 
93   * options property.  By default options are set to "default,*" or the OPTIONS property may
94   * be used to pass in a list of tags, eg. :
95   * <pre>
96   *  java -DOPTIONS=jetty,jsp,ssl -jar start.jar
97   * </pre>
98   * The tag '*' is always appended to the options, so any section with the * tag is always 
99   * applied.
100  *
101  * 
102  * @author Jan Hlavaty (hlavac@code.cz)
103  * @author Greg Wilkins
104  */
105 public class Main
106 {
107     static boolean _debug=System.getProperty("DEBUG",null)!=null;
108     private String _classname=null;
109     private Classpath _classpath=new Classpath();
110     private String _config=System.getProperty("START","org/mortbay/start/start.config");
111     private ArrayList _xml=new ArrayList();
112     private boolean _version=false;
113 
114     public static void main(String[] args)
115     {
116         try
117         {
118             if (args.length>0&&args[0].equalsIgnoreCase("--help"))
119             {
120                 usage();
121             }
122             else if (args.length>0&&args[0].equalsIgnoreCase("--stop"))
123             {
124                 new Main().stop();
125             }
126             else if (args.length>0&&args[0].equalsIgnoreCase("--version"))
127             {
128                 String[] nargs=new String[args.length-1];
129                 System.arraycopy(args,1,nargs,0,nargs.length);
130                 Main main=new Main();
131                 main._version=true;
132                 main.start(nargs);
133             }
134             else
135             {
136                 new Main().start(args);
137             }
138         }
139         catch (Exception e)
140         {
141             e.printStackTrace();
142             usage();
143         }
144     }
145     
146     private static void usage()
147     {
148         System.err.println("Usage: java [-DDEBUG] [-DSTART=start.config] [-DOPTIONS=opts] [-Dmain.class=org.MyMain] -jar start.jar [--help|--stop|--version] [config ...]");        
149         System.exit(1);
150     }
151 
152     static File getDirectory(String name)
153     {
154         try
155         {
156             if (name!=null)
157             {
158                 File dir=new File(name).getCanonicalFile();
159                 if (dir.isDirectory())
160                 {
161                     return dir;
162                 }
163             }
164         }
165         catch (IOException e)
166         {
167         }
168         return null;
169     }
170 
171     boolean isAvailable(String classname)
172     {
173         try
174         {
175             Class.forName(classname);
176             return true;
177         }
178         catch (NoClassDefFoundError e)
179         {
180         }
181         catch (ClassNotFoundException e)
182         {
183         }
184         ClassLoader loader=_classpath.getClassLoader();
185         try
186         {
187             loader.loadClass(classname);
188             return true;
189         }
190         catch (NoClassDefFoundError e)
191         {
192         }
193         catch (ClassNotFoundException e)
194         {
195         }
196         return false;
197     }
198 
199     public void invokeMain(ClassLoader classloader, String classname, String[] args) throws IllegalAccessException, InvocationTargetException,
200             NoSuchMethodException, ClassNotFoundException
201     {
202         Class invoked_class=null;
203         invoked_class=classloader.loadClass(classname);
204 
205         if (_version)
206         {
207             System.err.println(invoked_class.getPackage().getImplementationTitle()+" "+invoked_class.getPackage().getImplementationVersion());
208             System.exit(0);
209         }
210 
211         Class[] method_param_types=new Class[1];
212         method_param_types[0]=args.getClass();
213         Method main=null;
214         main=invoked_class.getDeclaredMethod("main",method_param_types);
215         Object[] method_params=new Object[1];
216         method_params[0]=args;
217 
218         main.invoke(null,method_params);
219     }
220 
221     /* ------------------------------------------------------------ */
222     String expand(String s)
223     {
224         int i1=0;
225         int i2=0;
226         while (s!=null)
227         {
228             i1=s.indexOf("$(",i2);
229             if (i1<0)
230                 break;
231             i2=s.indexOf(")",i1+2);
232             if (i2<0)
233                 break;
234             String property=System.getProperty(s.substring(i1+2,i2),"");
235             s=s.substring(0,i1)+property+s.substring(i2+1);
236         }
237         return s;
238     }
239 
240     /* ------------------------------------------------------------ */
241     void configure(InputStream config, int nargs) throws Exception
242     {
243         BufferedReader cfg=new BufferedReader(new InputStreamReader(config,"ISO-8859-1"));
244         Version java_version=new Version(System.getProperty("java.version"));
245         Version ver=new Version();
246         // JAR's already processed
247         Hashtable done=new Hashtable();
248         // Initial classpath
249         String classpath=System.getProperty("CLASSPATH");
250         if (classpath!=null)
251         {
252             StringTokenizer tok=new StringTokenizer(classpath,File.pathSeparator);
253             while (tok.hasMoreTokens())
254                 _classpath.addComponent(tok.nextToken());
255         }
256 
257         List section=null;
258         List options=null;
259         String o=System.getProperty("OPTIONS");
260         if (o==null)
261             o="default";
262         options=Arrays.asList((o.toString()+",*").split("[ ,]")); 
263         
264         // Handle line by line
265         String line=null;
266         while (true)
267         {
268             line=cfg.readLine();
269             if (line==null)
270                 break;
271             String trim=line.trim();
272             if (trim.length()==0||trim.startsWith("#"))
273                 continue;
274             
275             // handle options
276             if (trim.startsWith("[") && trim.endsWith("]"))
277                 section = Arrays.asList(trim.substring(1,trim.length()-1).split("[ ,]"));  
278             
279             if (section!=null && Collections.disjoint(section,options))
280                 continue;
281             
282             try
283             {
284                 StringTokenizer st=new StringTokenizer(line);
285                 String subject=st.nextToken();
286                 boolean expression=true;
287                 boolean not=false;
288                 String condition=null;
289                 // Evaluate all conditions
290                 while (st.hasMoreTokens())
291                 {
292                     condition=st.nextToken();
293                     if (condition.equalsIgnoreCase("!"))
294                     {
295                         not=true;
296                         continue;
297                     }
298                     if (condition.equalsIgnoreCase("OR"))
299                     {
300                         if (expression)
301                             break;
302                         expression=true;
303                         continue;
304                     }
305                     if (condition.equalsIgnoreCase("AND"))
306                     {
307                         if (!expression)
308                             break;
309                         continue;
310                     }
311                     boolean eval=true;
312                     if (condition.equals("true")||condition.equals("always"))
313                     {
314                         eval=true;
315                     }
316                     else if (condition.equals("false")||condition.equals("never"))
317                     {
318                         eval=false;
319                     }
320                     else if (condition.equals("available"))
321                     {
322                         String class_to_check=st.nextToken();
323                         eval=isAvailable(class_to_check);
324                     }
325                     else if (condition.equals("exists"))
326                     {
327                         try
328                         {
329                             eval=false;
330                             File file=new File(expand(st.nextToken()));
331                             eval=file.exists();
332                         }
333                         catch (Exception e)
334                         {
335                             if (_debug)
336                                 e.printStackTrace();
337                         }
338                     }
339                     else if (condition.equals("property"))
340                     {
341                         String property=System.getProperty(st.nextToken());
342                         eval=property!=null&&property.length()>0;
343                     }
344                     else if (condition.equals("java"))
345                     {
346                         String operator=st.nextToken();
347                         String version=st.nextToken();
348                         ver.parse(version);
349                         eval=(operator.equals("<")&&java_version.compare(ver)<0)||(operator.equals(">")&&java_version.compare(ver)>0)
350                                 ||(operator.equals("<=")&&java_version.compare(ver)<=0)||(operator.equals("=<")&&java_version.compare(ver)<=0)
351                                 ||(operator.equals("=>")&&java_version.compare(ver)>=0)||(operator.equals(">=")&&java_version.compare(ver)>=0)
352                                 ||(operator.equals("==")&&java_version.compare(ver)==0)||(operator.equals("!=")&&java_version.compare(ver)!=0);
353                     }
354                     else if (condition.equals("nargs"))
355                     {
356                         String operator=st.nextToken();
357                         int number=Integer.parseInt(st.nextToken());
358                         eval=(operator.equals("<")&&nargs<number)||(operator.equals(">")&&nargs>number)||(operator.equals("<=")&&nargs<=number)
359                                 ||(operator.equals("=<")&&nargs<=number)||(operator.equals("=>")&&nargs>=number)||(operator.equals(">=")&&nargs>=number)
360                                 ||(operator.equals("==")&&nargs==number)||(operator.equals("!=")&&nargs!=number);
361                     }
362                     else
363                     {
364                         System.err.println("ERROR: Unknown condition: "+condition);
365                         eval=false;
366                     }
367                     expression&=not?!eval:eval;
368                     not=false;
369                 }
370                 String file=expand(subject).replace('/',File.separatorChar);
371                 if (_debug)
372                     System.err.println((expression?"T ":"F ")+line);
373                 if (!expression)
374                 {
375                     done.put(file,file);
376                     continue;
377                 }
378                 // Handle the subject
379                 if (subject.indexOf("=")>0)
380                 {
381                     int i=file.indexOf("=");
382                     String property=file.substring(0,i);
383                     String value=file.substring(i+1);
384                     if (_debug)
385                         System.err.println("  "+property+"="+value);
386                     System.setProperty(property,value);
387                 }
388                 else if (subject.endsWith("/*"))
389                 {
390                     // directory of JAR files - only add jars and zips
391                     // within the directory
392                     File dir=new File(file.substring(0,file.length()-1));
393                     addJars(dir,done,false);
394                 }
395                 else if (subject.endsWith("/**"))
396                 {
397                     //directory hierarchy of jar files - recursively add all
398                     //jars and zips in the hierarchy
399                     File dir=new File(file.substring(0,file.length()-2));
400                     addJars(dir,done,true);
401                 }
402                 else if (subject.endsWith("/"))
403                 {
404                     // class directory
405                     File cd=new File(file);
406                     String d=cd.getCanonicalPath();
407                     if (!done.containsKey(d))
408                     {
409                         done.put(d,d);
410                         boolean added=_classpath.addComponent(d);
411                         if (_debug)
412                             System.err.println((added?"  CLASSPATH+=":"  !")+d);
413                     }
414                 }
415                 else if (subject.toLowerCase().endsWith(".xml"))
416                 {
417                     // Config file
418                     File f=new File(file);
419                     if (f.exists())
420                         _xml.add(f.getCanonicalPath());
421                     if (_debug)
422                         System.err.println("  ARGS+="+f);
423                 }
424                 else if (subject.toLowerCase().endsWith(".class"))
425                 {
426                     // Class
427                     String cn=expand(subject.substring(0,subject.length()-6));
428                     if (cn!=null&&cn.length()>0)
429                     {
430                         if (_debug)
431                             System.err.println("  CLASS="+cn);
432                         _classname=cn;
433                     }
434                 }
435                 else if (subject.toLowerCase().endsWith(".path"))
436                 {
437                     //classpath (jetty.class.path?) to add to runtime classpath
438                     String cn=expand(subject.substring(0,subject.length()-5));
439                     if (cn!=null&&cn.length()>0)
440                     {
441                         if (_debug)
442                             System.err.println("  PATH="+cn);
443                         _classpath.addClasspath(cn);
444                     }                  
445                 }
446                 else
447                 {
448                     // single JAR file
449                     File f=new File(file);
450                     if(f.exists())
451                     {
452                         String d=f.getCanonicalPath();
453                         if (!done.containsKey(d))
454                         {
455                             done.put(d,d);
456                             boolean added=_classpath.addComponent(d);
457                             if (!added)
458                             {
459                                 added=_classpath.addClasspath(expand(subject));
460                                 if (_debug)
461                                     System.err.println((added?"  CLASSPATH+=":"  !")+d);
462                             }
463                             else if (_debug)
464                                 System.err.println((added?"  CLASSPATH+=":"  !")+d);
465                         }
466                     }
467                 }
468             }
469             catch (Exception e)
470             {
471                 System.err.println("on line: '"+line+"'");
472                 e.printStackTrace();
473             }
474         }
475     }
476 
477     /* ------------------------------------------------------------ */
478     public void start(String[] args)
479     {
480         ArrayList al=new ArrayList();
481         for (int i=0; i<args.length; i++)
482         {
483             if (args[i]==null)
484                 continue;
485             else
486                 al.add(args[i]);
487         }
488         args=(String[])al.toArray(new String[al.size()]);
489         // set up classpath:
490         InputStream cpcfg=null;
491         try
492         {
493             Monitor.monitor();
494 
495             cpcfg=getClass().getClassLoader().getResourceAsStream(_config);
496             if (_debug)
497                 System.err.println("config="+_config);
498             if (cpcfg==null)
499                 cpcfg=new FileInputStream(_config);
500             configure(cpcfg,args.length);
501             File file=new File(System.getProperty("jetty.home"));
502             String canonical=file.getCanonicalPath();
503             System.setProperty("jetty.home",canonical);
504         }
505         catch (Exception e)
506         {
507             e.printStackTrace();
508             System.exit(1);
509         }
510         finally
511         {
512             try
513             {
514                 cpcfg.close();
515             }
516             catch (Exception e)
517             {
518                 e.printStackTrace();
519             }
520         }
521         // okay, classpath complete.
522         System.setProperty("java.class.path",_classpath.toString());
523         ClassLoader cl=_classpath.getClassLoader();
524         if (_debug)
525         {
526             System.err.println("java.class.path="+System.getProperty("java.class.path"));
527             System.err.println("jetty.home="+System.getProperty("jetty.home"));
528             System.err.println("java.io.tmpdir="+System.getProperty("java.io.tmpdir"));
529             System.err.println("java.class.path="+_classpath);
530             System.err.println("classloader="+cl);
531             System.err.println("classloader.parent="+cl.getParent());
532         }
533         // Invoke main(args) using new classloader.
534         Thread.currentThread().setContextClassLoader(cl);
535         // re-eval the policy now that env is set
536         try
537         {
538             Policy policy=Policy.getPolicy();
539             if (policy!=null)
540                 policy.refresh();
541         }
542         catch (Exception e)
543         {
544             e.printStackTrace();
545         }
546         try
547         {
548             for (int i=0; i<args.length; i++)
549             {
550                 if (args[i]==null)
551                     continue;
552                 _xml.add(args[i]);
553             }
554             args=(String[])_xml.toArray(args);
555             //check for override of start class
556             String mainClass=System.getProperty("jetty.server");
557             if (mainClass!=null)
558                 _classname=mainClass;
559             mainClass=System.getProperty("main.class");
560             if (mainClass!=null)
561                 _classname=mainClass;
562             if (_debug)
563                 System.err.println("main.class="+_classname);
564             invokeMain(cl,_classname,args);
565         }
566         catch (Exception e)
567         {
568             e.printStackTrace();
569         }
570     }
571 
572     /**
573      * Stop a running jetty instance.
574      */
575     public void stop()
576     {
577         int _port=Integer.getInteger("STOP.PORT",-1).intValue();
578         String _key=System.getProperty("STOP.KEY",null);
579 
580         try
581         {
582             if (_port<=0)
583                 System.err.println("STOP.PORT system property must be specified");
584             if (_key==null)
585             {
586                 _key="";
587                 System.err.println("STOP.KEY system property must be specified");
588                 System.err.println("Using empty key");
589             }
590 
591             Socket s=new Socket(InetAddress.getByName("127.0.0.1"),_port);
592             OutputStream out=s.getOutputStream();
593             out.write((_key+"\r\nstop\r\n").getBytes());
594             out.flush();
595             s.close();
596         }
597         catch (ConnectException e)
598         {
599             System.err.println("ERROR: Not running!");
600         }
601         catch (Exception e)
602         {
603             e.printStackTrace();
604         }
605     }
606 
607     private void addJars(File dir, Hashtable table, boolean recurse) throws IOException
608     {
609         File[] entries=dir.listFiles();
610 
611         for (int i=0; entries!=null&&i<entries.length; i++)
612         {
613             File entry=entries[i];
614 
615             if (entry.isDirectory()&&recurse)
616                 addJars(entry,table,recurse);
617             else
618             {
619                 String name=entry.getName().toLowerCase();
620                 if (name.endsWith(".jar")||name.endsWith(".zip"))
621                 {
622                     String jar=entry.getCanonicalPath();
623                     if (!table.containsKey(jar))
624                     {
625                         table.put(jar,jar);
626                         boolean added=_classpath.addComponent(jar);
627                         if (_debug)
628                             System.err.println((added?"  CLASSPATH+=":"  !")+jar);
629                     }
630                 }
631             }
632         }
633     }
634 }