View Javadoc

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