View Javadoc

1   // ========================================================================
2   // Copyright (c) 2003-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  package org.eclipse.jetty.start;
14  
15  import java.io.BufferedReader;
16  import java.io.File;
17  import java.io.FileInputStream;
18  import java.io.IOException;
19  import java.io.InputStream;
20  import java.io.InputStreamReader;
21  import java.io.OutputStream;
22  import java.lang.reflect.InvocationTargetException;
23  import java.lang.reflect.Method;
24  import java.net.ConnectException;
25  import java.net.InetAddress;
26  import java.net.Socket;
27  import java.security.Policy;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Collections;
31  import java.util.HashSet;
32  import java.util.Hashtable;
33  import java.util.List;
34  import java.util.Set;
35  import java.util.StringTokenizer;
36  
37  /*-------------------------------------------*/
38  /**
39   * Main start class. This class is intended to be the main class listed in the MANIFEST.MF of the
40   * start.jar archive. It allows an application to be started with the command "java -jar
41   * start.jar". The behaviour of Main is controlled by the "org/eclipse/start/start.config" file
42   * obtained as a resource or file. This can be overridden with the START system property. The
43   * format of each line in this file is:
44   * 
45   * <PRE>
46   * 
47   * SUBJECT [ [!] CONDITION [AND|OR] ]*
48   * 
49   * </PRE>
50   * 
51   * where SUBJECT:
52   * 
53   * <PRE>
54   * ends with ".class" is the Main class to run.
55   * ends with ".xml" is a configuration file for the command line
56   * ends with "/" is a directory from which add all jar and zip files from.
57   * ends with "/*" is a directory from which add all unconsidered jar and zip files from.
58   * Containing = are used to assign system properties.
59   * all other subjects are treated as files to be added to the classpath.
60   * </PRE>
61   * 
62   * Subjects may include system properties with $(propertyname) syntax. The $(version) property is
63   * defined as the maven version of the start.jar. 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 public class Main
103 {
104     private static final String _version = (Main.class.getPackage()!=null && Main.class.getPackage().getImplementationVersion()!=null)
105         ?Main.class.getPackage().getImplementationVersion()
106         :"Unknown";
107         
108     static boolean _debug=System.getProperty("DEBUG",null)!=null;
109     private String _classname=null;
110     private Classpath _classpath=new Classpath();
111     private String _config=System.getProperty("START","org/eclipse/jetty/start/start.config");
112     private ArrayList _xml=new ArrayList();
113     private boolean _showVersions=false;
114     private Set _options = new HashSet();
115 
116     public static void main(String[] args)
117     {
118         try
119         {
120             if (args.length>0&&args[0].equalsIgnoreCase("--help"))
121             {
122                 usage();
123             }
124             else if (args.length>0&&args[0].equalsIgnoreCase("--stop"))
125             {
126                 new Main().stop();
127             }
128             else if (args.length>0&&(args[0].equalsIgnoreCase("--version")||args[0].equalsIgnoreCase("--info")))
129             {
130                 String[] nargs=new String[args.length-1];
131                 System.arraycopy(args,1,nargs,0,nargs.length);
132                 Main main=new Main();
133                 main._showVersions=true;
134                 main.start(nargs);
135             }
136             else
137             {
138                 new Main().start(args);
139             }
140         }
141         catch (Exception e)
142         {
143             e.printStackTrace();
144             usage();
145         }
146     }
147     
148     private static void usage()
149     {
150         System.err.println("Usage: java [-DDEBUG] [-DSTART=start.config] [-DOPTIONS=opts] [-Dmain.class=org.MyMain] -jar start.jar [--help|--stop|--version|--info] [config ...]");        
151         System.exit(1);
152     }
153 
154     static File getDirectory(String name)
155     {
156         try
157         {
158             if (name!=null)
159             {
160                 File dir=new File(name).getCanonicalFile();
161                 if (dir.isDirectory())
162                 {
163                     return dir;
164                 }
165             }
166         }
167         catch (IOException e)
168         {
169         }
170         return null;
171     }
172 
173     boolean isAvailable(String classname)
174     {
175         try
176         {
177             Class.forName(classname);
178             return true;
179         }
180         catch (NoClassDefFoundError e)
181         {
182             if (_debug)
183                 System.err.println(e);
184         }
185         catch (ClassNotFoundException e)
186         {            
187             if (_debug)
188                 System.err.println(e);
189         }
190         ClassLoader loader=_classpath.getClassLoader();
191         try
192         {
193             loader.loadClass(classname);
194             return true;
195         }
196         catch (NoClassDefFoundError e)
197         {
198             if (_debug)
199                 System.err.println(e);
200         }
201         catch (ClassNotFoundException e)
202         {
203             if (_debug)
204                 System.err.println(e);
205         }
206         return false;
207     }
208 
209     public void invokeMain(ClassLoader classloader, String classname, String[] args) throws IllegalAccessException, InvocationTargetException,
210             NoSuchMethodException, ClassNotFoundException
211     {
212         Class invoked_class=null;
213         
214         try
215         {
216             invoked_class=classloader.loadClass(classname);
217         }
218         catch(ClassNotFoundException e)
219         {
220             //ignored
221         }
222         
223         if (_debug || _showVersions || invoked_class==null)
224         {
225             if (invoked_class==null)
226                 System.err.println("ClassNotFound: "+classname);
227             else
228                 System.err.println(classname+" "+invoked_class.getPackage().getImplementationVersion());
229             File[] elements = _classpath.getElements();
230             for (int i=0;i<elements.length;i++)
231                 System.err.println("  "+elements[i].getAbsolutePath());
232             if (_showVersions || invoked_class==null)
233             {
234                 List opts = new ArrayList(_options);
235                 Collections.sort(opts);
236                 System.err.println("OPTIONS: "+opts);
237 	        usage();
238             }
239         }
240 
241         Class[] method_param_types=new Class[1];
242         method_param_types[0]=args.getClass();
243         Method main=null;
244         main=invoked_class.getDeclaredMethod("main",method_param_types);
245         Object[] method_params=new Object[1];
246         method_params[0]=args;
247 
248         main.invoke(null,method_params);
249     }
250 
251     /* ------------------------------------------------------------ */
252     String expand(String s)
253     {
254         int i1=0;
255         int i2=0;
256         while (s!=null)
257         {
258             i1=s.indexOf("$(",i2);
259             if (i1<0)
260                 break;
261             i2=s.indexOf(")",i1+2);
262             if (i2<0)
263                 break;
264             String name=s.substring(i1+2,i2);
265             String property;
266             if ("version".equalsIgnoreCase(name))
267                 property=_version;
268             else
269                 property=System.getProperty(s.substring(i1+2,i2),"");
270             s=s.substring(0,i1)+property+s.substring(i2+1);
271         }
272         return s;
273     }
274 
275     /* ------------------------------------------------------------ */
276     void configure(InputStream config, int nargs) throws Exception
277     {
278         BufferedReader cfg=new BufferedReader(new InputStreamReader(config,"ISO-8859-1"));
279         Version java_version=new Version(System.getProperty("java.version"));
280         Version ver=new Version();
281         // JAR's already processed
282         Hashtable done=new Hashtable();
283         // Initial classpath
284         String classpath=System.getProperty("CLASSPATH");
285         if (classpath!=null)
286         {
287             StringTokenizer tok=new StringTokenizer(classpath,File.pathSeparator);
288             while (tok.hasMoreTokens())
289                 _classpath.addComponent(tok.nextToken());
290         }
291 
292         List section=null;
293         List options=null;
294         String o=System.getProperty("OPTIONS");
295         if (o==null)
296             o="default";
297         options=Arrays.asList((o.toString()+",*").split("[ ,]")); 
298         ArrayList unsatisfiedOptions = new ArrayList(options);
299         
300         // Handle line by line
301         String line=null;
302         while (true)
303         {
304             line=cfg.readLine();
305             if (line==null)
306                 break;
307             String trim=line.trim();
308             if (trim.length()==0||trim.startsWith("#"))
309                 continue;
310             
311             // handle options
312             if (trim.startsWith("[") && trim.endsWith("]"))
313             {
314                 section = Arrays.asList(trim.substring(1,trim.length()-1).split("[ ,]"));  
315                 _options.addAll(section);
316             }
317             
318             if (section!=null && Collections.disjoint(section,options))
319                 continue;
320             if (section!=null)
321                 unsatisfiedOptions.removeAll(section);
322             try
323             {
324                 StringTokenizer st=new StringTokenizer(line);
325                 String subject=st.nextToken();
326                 boolean expression=true;
327                 boolean not=false;
328                 String condition=null;
329                 // Evaluate all conditions
330                 while (st.hasMoreTokens())
331                 {
332                     condition=st.nextToken();
333                     if (condition.equalsIgnoreCase("!"))
334                     {
335                         not=true;
336                         continue;
337                     }
338                     if (condition.equalsIgnoreCase("OR"))
339                     {
340                         if (expression)
341                             break;
342                         expression=true;
343                         continue;
344                     }
345                     if (condition.equalsIgnoreCase("AND"))
346                     {
347                         if (!expression)
348                             break;
349                         continue;
350                     }
351                     boolean eval=true;
352                     if (condition.equals("true")||condition.equals("always"))
353                     {
354                         eval=true;
355                     }
356                     else if (condition.equals("false")||condition.equals("never"))
357                     {
358                         eval=false;
359                     }
360                     else if (condition.equals("available"))
361                     {
362                         String class_to_check=st.nextToken();
363                         eval=isAvailable(class_to_check);
364                     }
365                     else if (condition.equals("exists"))
366                     {
367                         try
368                         {
369                             eval=false;
370                             File file=new File(expand(st.nextToken()));
371                             eval=file.exists();
372                         }
373                         catch (Exception e)
374                         {
375                             if (_debug)
376                                 e.printStackTrace();
377                         }
378                     }
379                     else if (condition.equals("property"))
380                     {
381                         String property=System.getProperty(st.nextToken());
382                         eval=property!=null&&property.length()>0;
383                     }
384                     else if (condition.equals("java"))
385                     {
386                         String operator=st.nextToken();
387                         String version=st.nextToken();
388                         ver.parse(version);
389                         eval=(operator.equals("<")&&java_version.compare(ver)<0)||(operator.equals(">")&&java_version.compare(ver)>0)
390                                 ||(operator.equals("<=")&&java_version.compare(ver)<=0)||(operator.equals("=<")&&java_version.compare(ver)<=0)
391                                 ||(operator.equals("=>")&&java_version.compare(ver)>=0)||(operator.equals(">=")&&java_version.compare(ver)>=0)
392                                 ||(operator.equals("==")&&java_version.compare(ver)==0)||(operator.equals("!=")&&java_version.compare(ver)!=0);
393                     }
394                     else if (condition.equals("nargs"))
395                     {
396                         String operator=st.nextToken();
397                         int number=Integer.parseInt(st.nextToken());
398                         eval=(operator.equals("<")&&nargs<number)||(operator.equals(">")&&nargs>number)||(operator.equals("<=")&&nargs<=number)
399                                 ||(operator.equals("=<")&&nargs<=number)||(operator.equals("=>")&&nargs>=number)||(operator.equals(">=")&&nargs>=number)
400                                 ||(operator.equals("==")&&nargs==number)||(operator.equals("!=")&&nargs!=number);
401                     }
402                     else
403                     {
404                         System.err.println("ERROR: Unknown condition: "+condition);
405                         eval=false;
406                     }
407                     expression&=not?!eval:eval;
408                     not=false;
409                 }
410                 String file=expand(subject).replace('/',File.separatorChar);
411                 if (_debug)
412                     System.err.println((expression?"T ":"F ")+line);
413                 if (!expression)
414                 {
415                     done.put(file,file);
416                     continue;
417                 }
418                 // Handle the subject
419                 if (subject.indexOf("=")>0)
420                 {
421                     int i=file.indexOf("=");
422                     String property=file.substring(0,i);
423                     String value=file.substring(i+1);
424                     if (_debug)
425                         System.err.println("  "+property+"="+value);
426                     System.setProperty(property,value);
427                 }
428                 else if (subject.endsWith("/*"))
429                 {
430                     // directory of JAR files - only add jars and zips
431                     // within the directory
432                     File dir=new File(file.substring(0,file.length()-1));
433                     addJars(dir,done,false);
434                 }
435                 else if (subject.endsWith("/**"))
436                 {
437                     //directory hierarchy of jar files - recursively add all
438                     //jars and zips in the hierarchy
439                     File dir=new File(file.substring(0,file.length()-2));
440                     addJars(dir,done,true);
441                 }
442                 else if (subject.endsWith("/"))
443                 {
444                     // class directory
445                     File cd=new File(file);
446                     String d=cd.getCanonicalPath();
447                     if (!done.containsKey(d))
448                     {
449                         done.put(d,d);
450                         boolean added=_classpath.addComponent(d);
451                         if (_debug)
452                             System.err.println((added?"  CLASSPATH+=":"  !")+d);
453                     }
454                 }
455                 else if (subject.toLowerCase().endsWith(".xml"))
456                 {
457                     // Config file
458                     File f=new File(file);
459                     if (f.exists())
460                         _xml.add(f.getCanonicalPath());
461                     if (_debug)
462                         System.err.println("  ARGS+="+f);
463                 }
464                 else if (subject.toLowerCase().endsWith(".class"))
465                 {
466                     // Class
467                     String cn=expand(subject.substring(0,subject.length()-6));
468                     if (cn!=null&&cn.length()>0)
469                     {
470                         if (_debug)
471                             System.err.println("  CLASS="+cn);
472                         _classname=cn;
473                     }
474                 }
475                 else if (subject.toLowerCase().endsWith(".path"))
476                 {
477                     //classpath (jetty.class.path?) to add to runtime classpath
478                     String cn=expand(subject.substring(0,subject.length()-5));
479                     if (cn!=null&&cn.length()>0)
480                     {
481                         if (_debug)
482                             System.err.println("  PATH="+cn);
483                         _classpath.addClasspath(cn);
484                     }                  
485                 }
486                 else
487                 {
488                     // single JAR file
489                     File f=new File(file);
490                     if(f.exists())
491                     {
492                         String d=f.getCanonicalPath();
493                         if (!done.containsKey(d))
494                         {
495                             done.put(d,d);
496                             boolean added=_classpath.addComponent(d);
497                             if (!added)
498                             {
499                                 added=_classpath.addClasspath(expand(subject));
500                                 if (_debug)
501                                     System.err.println((added?"  CLASSPATH+=":"  !")+d);
502                             }
503                             else if (_debug)
504                                 System.err.println((added?"  CLASSPATH+=":"  !")+d);
505                         }
506                     }
507                 }
508             }
509             catch (Exception e)
510             {
511                 System.err.println("on line: '"+line+"'");
512                 e.printStackTrace();
513             }
514         }
515 
516         if (unsatisfiedOptions!=null && unsatisfiedOptions.size()>0)
517         {
518             String home = System.getProperty("jetty.home");
519             String lib = System.getProperty("jetty.lib");
520             File libDir = null;
521             if (lib!=null)
522             {
523                 libDir = new File (lib);
524             }
525             else if (home != null)
526             {
527                 libDir = new File (home, "lib");
528             }
529 
530             for (int i=0; i< unsatisfiedOptions.size(); i++)
531             {   
532                 if (libDir != null)
533                 {
534                     File dir = new File (libDir, (String)unsatisfiedOptions.get(i));
535                     if (dir.exists())
536                         addJars(dir,done,true);
537                     else
538                         System.err.println("Unsatisfied option:"+unsatisfiedOptions.get(i));
539                 }
540                 else
541                     System.err.println("Unsatisfied option:"+unsatisfiedOptions.get(i));
542             }
543         }
544     }
545 
546     /* ------------------------------------------------------------ */
547     public void start(String[] args)
548     {
549         ArrayList al=new ArrayList();
550         for (int i=0; i<args.length; i++)
551         {
552             if (args[i]==null)
553                 continue;
554             else
555                 al.add(args[i]);
556         }
557         args=(String[])al.toArray(new String[al.size()]);
558         // set up classpath:
559         InputStream cpcfg=null;
560         try
561         {
562             Monitor.monitor();
563 
564             cpcfg=getClass().getClassLoader().getResourceAsStream(_config);
565             if (_debug)
566                 System.err.println("config="+_config);
567             if (cpcfg==null)
568                 cpcfg=new FileInputStream(_config);
569             configure(cpcfg,args.length);
570             String jetty_home=System.getProperty("jetty.home");
571             if (jetty_home!=null)
572             {
573                 File file=new File(jetty_home);
574                 String canonical=file.getCanonicalPath();
575                 System.setProperty("jetty.home",canonical);
576             }
577         }
578         catch (Exception e)
579         {
580             e.printStackTrace();
581             System.exit(1);
582         }
583         finally
584         {
585             try
586             {
587                 cpcfg.close();
588             }
589             catch (Exception e)
590             {
591                 e.printStackTrace();
592             }
593         }
594         // okay, classpath complete.
595         System.setProperty("java.class.path",_classpath.toString());
596         ClassLoader cl=_classpath.getClassLoader();
597         if (_debug)
598         {
599             System.err.println("java.class.path="+System.getProperty("java.class.path"));
600             System.err.println("jetty.home="+System.getProperty("jetty.home"));
601             System.err.println("java.io.tmpdir="+System.getProperty("java.io.tmpdir"));
602             System.err.println("java.class.path="+_classpath);
603             System.err.println("classloader="+cl);
604             System.err.println("classloader.parent="+cl.getParent());
605         }
606         // Invoke main(args) using new classloader.
607         Thread.currentThread().setContextClassLoader(cl);
608         // re-eval the policy now that env is set
609         try
610         {
611             Policy policy=Policy.getPolicy();
612             if (policy!=null)
613                 policy.refresh();
614         }
615         catch (Exception e)
616         {
617             e.printStackTrace();
618         }
619         try
620         {
621             for (int i=0; i<args.length; i++)
622             {
623                 if (args[i]==null)
624                     continue;
625                 _xml.add(args[i]);
626             }
627             args=(String[])_xml.toArray(args);
628             //check for override of start class
629             String mainClass=System.getProperty("jetty.server");
630             if (mainClass!=null)
631                 _classname=mainClass;
632             mainClass=System.getProperty("main.class");
633             if (mainClass!=null)
634                 _classname=mainClass;
635             if (_debug)
636                 System.err.println("main.class="+_classname);
637             invokeMain(cl,_classname,args);
638         }
639         catch (Exception e)
640         {
641             e.printStackTrace();
642         }
643     }
644 
645     /**
646      * Stop a running jetty instance.
647      */
648     public void stop()
649     {
650         int _port=Integer.getInteger("STOP.PORT",-1).intValue();
651         String _key=System.getProperty("STOP.KEY",null);
652 
653         try
654         {
655             if (_port<=0)
656                 System.err.println("STOP.PORT system property must be specified");
657             if (_key==null)
658             {
659                 _key="";
660                 System.err.println("STOP.KEY system property must be specified");
661                 System.err.println("Using empty key");
662             }
663 
664             Socket s=new Socket(InetAddress.getByName("127.0.0.1"),_port);
665             OutputStream out=s.getOutputStream();
666             out.write((_key+"\r\nstop\r\n").getBytes());
667             out.flush();
668             s.close();
669         }
670         catch (ConnectException e)
671         {
672             System.err.println("ERROR: Not running!");
673         }
674         catch (Exception e)
675         {
676             e.printStackTrace();
677         }
678     }
679 
680     private void addJars(File dir, Hashtable table, boolean recurse) throws IOException
681     {
682         File[] entries=dir.listFiles();
683 
684         for (int i=0; entries!=null&&i<entries.length; i++)
685         {
686             File entry=entries[i];
687 
688             if (entry.isDirectory()&&recurse)
689                 addJars(entry,table,recurse);
690             else
691             {
692                 String name=entry.getName().toLowerCase();
693                 if (name.endsWith(".jar")||name.endsWith(".zip"))
694                 {
695                     String jar=entry.getCanonicalPath();
696                     if (!table.containsKey(jar))
697                     {
698                         table.put(jar,jar);
699                         boolean added=_classpath.addComponent(jar);
700                         if (_debug)
701                             System.err.println((added?"  CLASSPATH+=":"  !")+jar);
702                     }
703                 }
704             }
705         }
706     }
707 }