View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.start;
20  
21  import java.io.BufferedReader;
22  import java.io.Closeable;
23  import java.io.File;
24  import java.io.FileFilter;
25  import java.io.FileInputStream;
26  import java.io.FileNotFoundException;
27  import java.io.FileOutputStream;
28  import java.io.FileReader;
29  import java.io.FilenameFilter;
30  import java.io.IOException;
31  import java.io.InputStream;
32  import java.io.InputStreamReader;
33  import java.io.LineNumberReader;
34  import java.io.OutputStream;
35  import java.io.PrintStream;
36  import java.lang.reflect.InvocationTargetException;
37  import java.lang.reflect.Method;
38  import java.net.ConnectException;
39  import java.net.InetAddress;
40  import java.net.Socket;
41  import java.net.SocketTimeoutException;
42  import java.text.SimpleDateFormat;
43  import java.util.ArrayList;
44  import java.util.Arrays;
45  import java.util.Collections;
46  import java.util.Date;
47  import java.util.HashSet;
48  import java.util.List;
49  import java.util.Locale;
50  import java.util.Properties;
51  import java.util.Set;
52  
53  /*-------------------------------------------*/
54  /**
55   * <p>
56   * Main start class. This class is intended to be the main class listed in the MANIFEST.MF of the start.jar archive. It allows an application to be started with
57   * the command "java -jar start.jar".
58   * </p>
59   *
60   * <p>
61   * The behaviour of Main is controlled by the parsing of the {@link Config} "org/eclipse/start/start.config" file obtained as a resource or file.
62   * </p>
63   */
64  public class Main
65  {
66      private static final String START_LOG_FILENAME = "start.log";
67      private static final SimpleDateFormat START_LOG_ROLLOVER_DATEFORMAT = new SimpleDateFormat("yyyy_MM_dd-HHmmSSSSS.'" + START_LOG_FILENAME + "'");
68  
69      private static final int EXIT_USAGE = 1;
70      private static final int ERR_LOGGING = -1;
71      private static final int ERR_INVOKE_MAIN = -2;
72      private static final int ERR_NOT_STOPPED = -4;
73      private static final int ERR_UNKNOWN = -5;
74      private boolean _showUsage = false;
75      private boolean _dumpVersions = false;
76      private boolean _listConfig = false;
77      private boolean _listOptions = false;
78      private boolean _dryRun = false;
79      private boolean _exec = false;
80      private final Config _config = new Config();
81      private final Set<String> _sysProps = new HashSet<String>();
82      private final List<String> _jvmArgs = new ArrayList<String>();
83      private String _startConfig = null;
84  
85      private String _jettyHome;
86  
87      public static void main(String[] args)
88      {
89          try
90          {
91              Main main = new Main();
92              List<String> arguments = main.expandCommandLine(args);
93              List<String> xmls = main.processCommandLine(arguments);
94              if (xmls!=null)
95                  main.start(xmls);
96          }
97          catch (Throwable e)
98          {
99              usageExit(e,ERR_UNKNOWN);
100         }
101     }
102 
103     Main() throws IOException
104     {
105         _jettyHome = System.getProperty("jetty.home",".");
106         _jettyHome = new File(_jettyHome).getCanonicalPath();
107     }
108 
109     public List<String> expandCommandLine(String[] args) throws Exception
110     {
111         List<String> arguments = new ArrayList<String>();
112 
113         // add the command line args and look for start.ini args
114         boolean ini = false;
115         for (String arg : args)
116         {
117             if (arg.startsWith("--ini=") || arg.equals("--ini"))
118             {
119                 ini = true;
120                 if (arg.length() > 6)
121                 {
122                     arguments.addAll(loadStartIni(new File(arg.substring(6))));
123                 }
124             }
125             else if (arg.startsWith("--config="))
126             {
127                 _startConfig = arg.substring(9);
128             }
129             else
130             {
131                 arguments.add(arg);
132             }
133         }
134 
135         // if no non-option inis, add the start.ini and start.d
136         if (!ini)
137         {
138             arguments.addAll(0,parseStartIniFiles());
139         }
140 
141         return arguments;
142     }
143 
144     List<String> parseStartIniFiles()
145     {
146         List<String> ini_args=new ArrayList<String>();
147         File start_ini = new File(_jettyHome,"start.ini");
148         if (start_ini.exists())
149             ini_args.addAll(loadStartIni(start_ini));
150 
151         File start_d = new File(_jettyHome,"start.d");
152         if (start_d.isDirectory())
153         {
154             File[] inis = start_d.listFiles(new FilenameFilter()
155             {
156                 public boolean accept(File dir, String name)
157                 {
158                     return name.toLowerCase(Locale.ENGLISH).endsWith(".ini");
159                 }
160             });
161             Arrays.sort(inis);
162             for (File i : inis)
163                 ini_args.addAll(loadStartIni(i));
164         }
165         return ini_args;
166     }
167 
168     public List<String> processCommandLine(List<String> arguments) throws Exception
169     {
170         // The XML Configuration Files to initialize with
171         List<String> xmls = new ArrayList<String>();
172 
173         // Process the arguments
174         int startup = 0;
175         for (String arg : arguments)
176         {
177             if ("--help".equals(arg) || "-?".equals(arg))
178             {
179                 _showUsage = true;
180                 continue;
181             }
182             
183             if ("--stop".equals(arg))
184             {
185                 int port = Integer.parseInt(Config.getProperty("STOP.PORT","-1"));
186                 String key = Config.getProperty("STOP.KEY",null);
187                 int timeout = Integer.parseInt(Config.getProperty("STOP.WAIT", "0"));
188                 stop(port,key,timeout);
189                 return null;  
190             }
191 
192             if ("--version".equals(arg) || "-v".equals(arg) || "--info".equals(arg))
193             {
194                 _dumpVersions = true;
195                 continue;
196             }
197 
198             if ("--list-modes".equals(arg) || "--list-options".equals(arg))
199             {
200                 _listOptions = true;
201                 continue;
202             }
203 
204             if ("--list-config".equals(arg))
205             {
206                 _listConfig = true;
207                 continue;
208             }
209 
210             if ("--exec-print".equals(arg) || "--dry-run".equals(arg))
211             {
212                 _dryRun = true;
213                 continue;
214             }
215 
216             if ("--exec".equals(arg))
217             {
218                 _exec = true;
219                 continue;
220             }
221 
222             // Special internal indicator that jetty was started by the jetty.sh Daemon
223             if ("--daemon".equals(arg))
224             {
225                 File startDir = new File(System.getProperty("jetty.logs","logs"));
226                 if (!startDir.exists() || !startDir.canWrite())
227                     startDir = new File(".");
228 
229                 File startLog = new File(startDir, START_LOG_ROLLOVER_DATEFORMAT.format(new Date()));
230 
231                 if (!startLog.exists() && !startLog.createNewFile())
232                 {
233                     // Output about error is lost in majority of cases.
234                     System.err.println("Unable to create: " + startLog.getAbsolutePath());
235                     // Toss a unique exit code indicating this failure.
236                     usageExit(ERR_LOGGING);
237                 }
238 
239                 if (!startLog.canWrite())
240                 {
241                     // Output about error is lost in majority of cases.
242                     System.err.println("Unable to write to: " + startLog.getAbsolutePath());
243                     // Toss a unique exit code indicating this failure.
244                     usageExit(ERR_LOGGING);
245                 }
246                 PrintStream logger = new PrintStream(new FileOutputStream(startLog,false));
247                 System.setOut(logger);
248                 System.setErr(logger);
249                 System.out.println("Establishing "+ START_LOG_FILENAME + " on " + new Date());
250                 continue;
251             }
252 
253             if (arg.startsWith("--pre="))
254             {
255                 xmls.add(startup++,arg.substring(6));
256                 continue;
257             }
258 
259             if (arg.startsWith("-D"))
260             {
261                 String[] assign = arg.substring(2).split("=",2);
262                 _sysProps.add(assign[0]);
263                 switch (assign.length)
264                 {
265                     case 2:
266                         System.setProperty(assign[0],assign[1]);
267                         break;
268                     case 1:
269                         System.setProperty(assign[0],"");
270                         break;
271                     default:
272                         break;
273                 }
274                 continue;
275             }
276 
277             if (arg.startsWith("-"))
278             {
279                 _jvmArgs.add(arg);
280                 continue;
281             }
282 
283             // Is this a Property?
284             if (arg.indexOf('=') >= 0)
285             {
286                 String[] assign = arg.split("=",2);
287 
288                 switch (assign.length)
289                 {
290                     case 2:
291                         if ("OPTIONS".equals(assign[0]))
292                         {
293                             String opts[] = assign[1].split(",");
294                             for (String opt : opts)
295                                 _config.addActiveOption(opt.trim());
296                         }
297                         else
298                         {
299                             this._config.setProperty(assign[0],assign[1]);
300                         }
301                         break;
302                     case 1:
303                         this._config.setProperty(assign[0],null);
304                         break;
305                     default:
306                         break;
307                 }
308 
309                 continue;
310             }
311 
312             // Anything else is considered an XML file.
313             if (xmls.contains(arg))
314             {
315                 System.out.println("WARN: Argument '" + arg + "' specified multiple times. Check start.ini?");
316                 System.out.println("Use \"java -jar start.jar --help\" for more information.");
317             }
318             xmls.add(arg);
319         }
320 
321         return xmls;
322     }
323 
324     private void usage()
325     {
326         String usageResource = "org/eclipse/jetty/start/usage.txt";
327         InputStream usageStream = getClass().getClassLoader().getResourceAsStream(usageResource);
328 
329         if (usageStream == null)
330         {
331             System.err.println("ERROR: detailed usage resource unavailable");
332             usageExit(EXIT_USAGE);
333         }
334 
335         BufferedReader buf = null;
336         try
337         {
338             buf = new BufferedReader(new InputStreamReader(usageStream));
339             String line;
340 
341             while ((line = buf.readLine()) != null)
342             {
343                 if (line.endsWith("@") && line.indexOf('@') != line.lastIndexOf('@'))
344                 {
345                     String indent = line.substring(0,line.indexOf("@"));
346                     String info = line.substring(line.indexOf('@'),line.lastIndexOf('@'));
347 
348                     if (info.equals("@OPTIONS"))
349                     {
350                         List<String> sortedOptions = new ArrayList<String>();
351                         sortedOptions.addAll(_config.getSectionIds());
352                         Collections.sort(sortedOptions);
353 
354                         for (String option : sortedOptions)
355                         {
356                             if ("*".equals(option) || option.trim().length() == 0)
357                                 continue;
358                             System.out.print(indent);
359                             System.out.println(option);
360                         }
361                     }
362                     else if (info.equals("@CONFIGS"))
363                     {
364                         File etc = new File(System.getProperty("jetty.home","."),"etc");
365                         if (!etc.exists() || !etc.isDirectory())
366                         {
367                             System.out.print(indent);
368                             System.out.println("Unable to find/list " + etc);
369                             continue;
370                         }
371 
372                         File configs[] = etc.listFiles(new FileFilter()
373                         {
374                             public boolean accept(File path)
375                             {
376                                 if (!path.isFile())
377                                 {
378                                     return false;
379                                 }
380 
381                                 String name = path.getName().toLowerCase(Locale.ENGLISH);
382                                 return (name.startsWith("jetty") && name.endsWith(".xml"));
383                             }
384                         });
385 
386                         List<File> configFiles = new ArrayList<File>();
387                         configFiles.addAll(Arrays.asList(configs));
388                         Collections.sort(configFiles);
389 
390                         for (File configFile : configFiles)
391                         {
392                             System.out.print(indent);
393                             System.out.print("etc/");
394                             System.out.println(configFile.getName());
395                         }
396                     }
397                     else if (info.equals("@STARTINI"))
398                     {
399                         List<String> ini = loadStartIni(new File(_jettyHome,"start.ini"));
400                         if (ini != null && ini.size() > 0)
401                         {
402                             for (String a : ini)
403                             {
404                                 System.out.print(indent);
405                                 System.out.println(a);
406                             }
407                         }
408                         else
409                         {
410                             System.out.print(indent);
411                             System.out.println("none");
412                         }
413                     }
414                 }
415                 else
416                 {
417                     System.out.println(line);
418                 }
419             }
420         }
421         catch (IOException e)
422         {
423             usageExit(e,EXIT_USAGE);
424         }
425         finally
426         {
427             close(buf);
428         }
429         System.exit(EXIT_USAGE);
430     }
431 
432     public void invokeMain(ClassLoader classloader, String classname, List<String> args) throws IllegalAccessException, InvocationTargetException,
433             NoSuchMethodException, ClassNotFoundException
434     {
435         Class<?> invoked_class = null;
436 
437         try
438         {
439             invoked_class = classloader.loadClass(classname);
440         }
441         catch (ClassNotFoundException e)
442         {
443             e.printStackTrace();
444         }
445 
446         if (Config.isDebug() || invoked_class == null)
447         {
448             if (invoked_class == null)
449             {
450                 System.err.println("ClassNotFound: " + classname);
451             }
452             else
453             {
454                 System.err.println(classname + " " + invoked_class.getPackage().getImplementationVersion());
455             }
456 
457             if (invoked_class == null)
458             {
459                 usageExit(ERR_INVOKE_MAIN);
460                 return;
461             }
462         }
463 
464         String argArray[] = args.toArray(new String[0]);
465 
466         Class<?>[] method_param_types = new Class[]
467         { argArray.getClass() };
468 
469         Method main = invoked_class.getDeclaredMethod("main",method_param_types);
470         Object[] method_params = new Object[]
471         { argArray };
472         main.invoke(null,method_params);
473     }
474 
475     /* ------------------------------------------------------------ */
476     public static void close(Closeable c)
477     {
478         if (c == null)
479         {
480             return;
481         }
482         try
483         {
484             c.close();
485         }
486         catch (IOException e)
487         {
488             e.printStackTrace(System.err);
489         }
490     }
491 
492     /* ------------------------------------------------------------ */
493     public void start(List<String> xmls) throws IOException, InterruptedException
494     {
495         // Setup Start / Stop Monitoring
496         int port = Integer.parseInt(Config.getProperty("STOP.PORT","-1"));
497         String key = Config.getProperty("STOP.KEY",null);
498         Monitor monitor = new Monitor(port,key);
499 
500         // Load potential Config (start.config)
501         List<String> configuredXmls = loadConfig(xmls);
502 
503         // No XML defined in start.config or command line. Can't execute.
504         if (configuredXmls.isEmpty())
505         {
506             throw new FileNotFoundException("No XML configuration files specified in start.config or command line.");
507         }
508 
509         // Normalize the XML config options passed on the command line.
510         configuredXmls = resolveXmlConfigs(configuredXmls);
511 
512         // Get Desired Classpath based on user provided Active Options.
513         Classpath classpath = _config.getActiveClasspath();
514 
515         System.setProperty("java.class.path",classpath.toString());
516         ClassLoader cl = classpath.getClassLoader();
517         if (Config.isDebug())
518         {
519             System.err.println("java.class.path=" + System.getProperty("java.class.path"));
520             System.err.println("jetty.home=" + System.getProperty("jetty.home"));
521             System.err.println("java.home=" + System.getProperty("java.home"));
522             System.err.println("java.io.tmpdir=" + System.getProperty("java.io.tmpdir"));
523             System.err.println("java.class.path=" + classpath);
524             System.err.println("classloader=" + cl);
525             System.err.println("classloader.parent=" + cl.getParent());
526             System.err.println("properties=" + Config.getProperties());
527         }
528 
529         // Show the usage information and return
530         if (_showUsage)
531         {
532             usage();
533             return;
534         }
535 
536         // Show the version information and return
537         if (_dumpVersions)
538         {
539             showClasspathWithVersions(classpath);
540             return;
541         }
542 
543         // Show all options with version information
544         if (_listOptions)
545         {
546             showAllOptionsWithVersions();
547             return;
548         }
549 
550         if (_listConfig)
551         {
552             listConfig();
553             return;
554         }
555 
556         // Show Command Line to execute Jetty
557         if (_dryRun)
558         {
559             CommandLineBuilder cmd = buildCommandLine(classpath,configuredXmls);
560             System.out.println(cmd.toString());
561             return;
562         }
563 
564         // execute Jetty in another JVM
565         if (_exec)
566         {
567             CommandLineBuilder cmd = buildCommandLine(classpath,configuredXmls);
568 
569             ProcessBuilder pbuilder = new ProcessBuilder(cmd.getArgs());
570             final Process process = pbuilder.start();
571             Runtime.getRuntime().addShutdownHook(new Thread()
572             {
573                 @Override
574                 public void run()
575                 {
576                     Config.debug("Destroying " + process);
577                     process.destroy();
578                 }
579             });
580 
581             copyInThread(process.getErrorStream(),System.err);
582             copyInThread(process.getInputStream(),System.out);
583             copyInThread(System.in,process.getOutputStream());
584             monitor.setProcess(process);
585             process.waitFor();
586 
587             return;
588         }
589 
590         if (_jvmArgs.size() > 0 || _sysProps.size() > 0)
591         {
592             System.err.println("WARNING: System properties and/or JVM args set.  Consider using --dry-run or --exec");
593         }
594 
595         // Set current context class loader to what is selected.
596         Thread.currentThread().setContextClassLoader(cl);
597 
598         // Invoke the Main Class
599         try
600         {
601             // Get main class as defined in start.config
602             String classname = _config.getMainClassname();
603 
604             // Check for override of start class (via "jetty.server" property)
605             String mainClass = System.getProperty("jetty.server");
606             if (mainClass != null)
607             {
608                 classname = mainClass;
609             }
610 
611             // Check for override of start class (via "main.class" property)
612             mainClass = System.getProperty("main.class");
613             if (mainClass != null)
614             {
615                 classname = mainClass;
616             }
617 
618             Config.debug("main.class=" + classname);
619 
620             invokeMain(cl,classname,configuredXmls);
621         }
622         catch (Exception e)
623         {
624             usageExit(e,ERR_INVOKE_MAIN);
625         }
626     }
627 
628     private void copyInThread(final InputStream in, final OutputStream out)
629     {
630         new Thread(new Runnable()
631         {
632             public void run()
633             {
634                 try
635                 {
636                     byte[] buf = new byte[1024];
637                     int len = in.read(buf);
638                     while (len > 0)
639                     {
640                         out.write(buf,0,len);
641                         len = in.read(buf);
642                     }
643                 }
644                 catch (IOException e)
645                 {
646                     // e.printStackTrace();
647                 }
648             }
649 
650         }).start();
651     }
652 
653     private String resolveXmlConfig(String xmlFilename) throws FileNotFoundException
654     {
655         if (!xmlFilename.toLowerCase(Locale.ENGLISH).endsWith(".xml"))
656         {
657             // Nothing to resolve.
658             return xmlFilename;
659         }
660 
661         File xml = new File(xmlFilename);
662         if (xml.exists() && xml.isFile())
663         {
664             return xml.getAbsolutePath();
665         }
666 
667         xml = new File(_jettyHome,fixPath(xmlFilename));
668         if (xml.exists() && xml.isFile())
669         {
670             return xml.getAbsolutePath();
671         }
672 
673         xml = new File(_jettyHome,fixPath("etc/" + xmlFilename));
674         if (xml.exists() && xml.isFile())
675         {
676             return xml.getAbsolutePath();
677         }
678 
679         throw new FileNotFoundException("Unable to find XML Config: " + xmlFilename);
680     }
681 
682     CommandLineBuilder buildCommandLine(Classpath classpath, List<String> xmls) throws IOException
683     {
684         CommandLineBuilder cmd = new CommandLineBuilder(findJavaBin());
685 
686         for (String x : _jvmArgs)
687         {
688             cmd.addArg(x);
689         }
690         cmd.addRawArg("-Djetty.home=" + _jettyHome);
691         for (String p : _sysProps)
692         {
693             String v = System.getProperty(p);
694             cmd.addEqualsArg("-D" + p,v);
695         }
696         cmd.addArg("-cp");
697         cmd.addRawArg(classpath.toString());
698         cmd.addRawArg(_config.getMainClassname());
699 
700         // Check if we need to pass properties as a file
701         Properties properties = Config.getProperties();
702         if (properties.size() > 0)
703         {
704             File prop_file = File.createTempFile("start",".properties");
705             if (!_dryRun)
706                 prop_file.deleteOnExit();
707             properties.store(new FileOutputStream(prop_file),"start.jar properties");
708             cmd.addArg(prop_file.getAbsolutePath());
709         }
710 
711         for (String xml : xmls)
712         {
713             cmd.addRawArg(xml);
714         }
715         return cmd;
716     }
717 
718     private String findJavaBin()
719     {
720         File javaHome = new File(System.getProperty("java.home"));
721         if (!javaHome.exists())
722         {
723             return null;
724         }
725 
726         File javabin = findExecutable(javaHome,"bin/java");
727         if (javabin != null)
728         {
729             return javabin.getAbsolutePath();
730         }
731 
732         javabin = findExecutable(javaHome,"bin/java.exe");
733         if (javabin != null)
734         {
735             return javabin.getAbsolutePath();
736         }
737 
738         return "java";
739     }
740 
741     private File findExecutable(File root, String path)
742     {
743         String npath = path.replace('/',File.separatorChar);
744         File exe = new File(root,npath);
745         if (!exe.exists())
746         {
747             return null;
748         }
749         return exe;
750     }
751 
752     private void showAllOptionsWithVersions()
753     {
754         Set<String> sectionIds = _config.getSectionIds();
755 
756         StringBuffer msg = new StringBuffer();
757         msg.append("There ");
758         if (sectionIds.size() > 1)
759         {
760             msg.append("are ");
761         }
762         else
763         {
764             msg.append("is ");
765         }
766         msg.append(String.valueOf(sectionIds.size()));
767         msg.append(" OPTION");
768         if (sectionIds.size() > 1)
769         {
770             msg.append("s");
771         }
772         msg.append(" available to use.");
773         System.out.println(msg);
774         System.out.println("Each option is listed along with associated available classpath entries,  in the order that they would appear from that mode.");
775         System.out.println("Note: If using multiple options (eg: 'Server,servlet,webapp,jms,jmx') "
776                 + "then overlapping entries will not be repeated in the eventual classpath.");
777         System.out.println();
778         System.out.printf("${jetty.home} = %s%n",_jettyHome);
779         System.out.println();
780 
781         for (String sectionId : sectionIds)
782         {
783             if (Config.DEFAULT_SECTION.equals(sectionId))
784             {
785                 System.out.println("GLOBAL option (Prepended Entries)");
786             }
787             else if ("*".equals(sectionId))
788             {
789                 System.out.println("GLOBAL option (Appended Entries) (*)");
790             }
791             else
792             {
793                 System.out.printf("Option [%s]",sectionId);
794                 if (Character.isUpperCase(sectionId.charAt(0)))
795                 {
796                     System.out.print(" (Aggregate)");
797                 }
798                 System.out.println();
799             }
800             System.out.println("-------------------------------------------------------------");
801 
802             Classpath sectionCP = _config.getSectionClasspath(sectionId);
803 
804             if (sectionCP.isEmpty())
805             {
806                 System.out.println("Empty option, no classpath entries active.");
807                 System.out.println();
808                 continue;
809             }
810 
811             int i = 0;
812             for (File element : sectionCP.getElements())
813             {
814                 String elementPath = element.getAbsolutePath();
815                 if (elementPath.startsWith(_jettyHome))
816                 {
817                     elementPath = "${jetty.home}" + elementPath.substring(_jettyHome.length());
818                 }
819                 System.out.printf("%2d: %20s | %s\n",i++,getVersion(element),elementPath);
820             }
821 
822             System.out.println();
823         }
824     }
825 
826     private void showClasspathWithVersions(Classpath classpath)
827     {
828         // Iterate through active classpath, and fetch Implementation Version from each entry (if present)
829         // to dump to end user.
830 
831         System.out.println("Active Options: " + _config.getActiveOptions());
832 
833         if (classpath.count() == 0)
834         {
835             System.out.println("No version information available show.");
836             return;
837         }
838 
839         System.out.println("Version Information on " + classpath.count() + " entr" + ((classpath.count() > 1)?"ies":"y") + " in the classpath.");
840         System.out.println("Note: order presented here is how they would appear on the classpath.");
841         System.out.println("      changes to the OPTIONS=[option,option,...] command line option will be reflected here.");
842 
843         int i = 0;
844         for (File element : classpath.getElements())
845         {
846             String elementPath = element.getAbsolutePath();
847             if (elementPath.startsWith(_jettyHome))
848             {
849                 elementPath = "${jetty.home}" + elementPath.substring(_jettyHome.length());
850             }
851             System.out.printf("%2d: %20s | %s\n",i++,getVersion(element),elementPath);
852         }
853     }
854 
855     private String fixPath(String path)
856     {
857         return path.replace('/',File.separatorChar);
858     }
859 
860     private String getVersion(File element)
861     {
862         if (element.isDirectory())
863         {
864             return "(dir)";
865         }
866 
867         if (element.isFile())
868         {
869             String name = element.getName().toLowerCase(Locale.ENGLISH);
870             if (name.endsWith(".jar"))
871             {
872                 return JarVersion.getVersion(element);
873             }
874 
875             if (name.endsWith(".zip"))
876             {
877                 return getZipVersion(element);
878             }
879         }
880 
881         return "";
882     }
883 
884     private String getZipVersion(File element)
885     {
886         // TODO - find version in zip file. Look for META-INF/MANIFEST.MF ?
887         return "";
888     }
889 
890     private List<String> resolveXmlConfigs(List<String> xmls) throws FileNotFoundException
891     {
892         List<String> ret = new ArrayList<String>();
893         for (String xml : xmls)
894         {
895             ret.add(resolveXmlConfig(xml));
896         }
897 
898         return ret;
899     }
900 
901     private void listConfig()
902     {
903         InputStream cfgstream = null;
904         try
905         {
906             cfgstream = getConfigStream();
907             byte[] buf = new byte[4096];
908 
909             int len = 0;
910 
911             while (len >= 0)
912             {
913                 len = cfgstream.read(buf);
914                 if (len > 0)
915                     System.out.write(buf,0,len);
916             }
917         }
918         catch (Exception e)
919         {
920             usageExit(e,ERR_UNKNOWN);
921         }
922         finally
923         {
924             close(cfgstream);
925         }
926     }
927 
928     /**
929      * Load Configuration.
930      *
931      * No specific configuration is real until a {@link Config#getCombinedClasspath(java.util.Collection)} is used to execute the {@link Class} specified by
932      * {@link Config#getMainClassname()} is executed.
933      *
934      * @param xmls
935      *            the command line specified xml configuration options.
936      * @return the list of xml configurations arriving via command line and start.config choices.
937      */
938     private List<String> loadConfig(List<String> xmls)
939     {
940         InputStream cfgstream = null;
941         try
942         {
943             // Pass in xmls.size into Config so that conditions based on "nargs" work.
944             _config.setArgCount(xmls.size());
945 
946             cfgstream = getConfigStream();
947 
948             // parse the config
949             _config.parse(cfgstream);
950 
951             _jettyHome = Config.getProperty("jetty.home",_jettyHome);
952             if (_jettyHome != null)
953             {
954                 _jettyHome = new File(_jettyHome).getCanonicalPath();
955                 System.setProperty("jetty.home",_jettyHome);
956             }
957 
958             // Collect the configured xml configurations.
959             List<String> ret = new ArrayList<String>();
960             ret.addAll(xmls); // add command line provided xmls first.
961             for (String xmlconfig : _config.getXmlConfigs())
962             {
963                 // add xmlconfigs arriving via start.config
964                 if (!ret.contains(xmlconfig))
965                 {
966                     ret.add(xmlconfig);
967                 }
968             }
969 
970             return ret;
971         }
972         catch (Exception e)
973         {
974             usageExit(e,ERR_UNKNOWN);
975             return null; // never executed (just here to satisfy javac compiler)
976         }
977         finally
978         {
979             close(cfgstream);
980         }
981     }
982 
983     private InputStream getConfigStream() throws FileNotFoundException
984     {
985         String config = _startConfig;
986         if (config == null || config.length() == 0)
987         {
988             config = System.getProperty("START","org/eclipse/jetty/start/start.config");
989         }
990 
991         Config.debug("config=" + config);
992 
993         // Look up config as resource first.
994         InputStream cfgstream = getClass().getClassLoader().getResourceAsStream(config);
995 
996         // resource not found, try filesystem next
997         if (cfgstream == null)
998         {
999             cfgstream = new FileInputStream(config);
1000         }
1001 
1002         return cfgstream;
1003     }
1004 
1005     /**
1006      * Stop a running jetty instance.
1007      */
1008     public void stop(int port, String key)
1009     {
1010         stop (port,key, 0);
1011     }
1012 
1013     
1014     public void stop (int port, String key, int timeout)
1015     {
1016         int _port = port;
1017         String _key = key;
1018 
1019         try
1020         {
1021             if (_port <= 0)
1022             {
1023                 System.err.println("STOP.PORT system property must be specified");
1024             }
1025             if (_key == null)
1026             {
1027                 _key = "";
1028                 System.err.println("STOP.KEY system property must be specified");
1029                 System.err.println("Using empty key");
1030             }
1031 
1032             Socket s = new Socket(InetAddress.getByName("127.0.0.1"),_port);
1033             if (timeout > 0)
1034                 s.setSoTimeout(timeout*1000);
1035             try
1036             {
1037                 OutputStream out = s.getOutputStream();
1038                 out.write((_key + "\r\nstop\r\n").getBytes());
1039                 out.flush();
1040 
1041                 if (timeout > 0)
1042                 {
1043                     System.err.println("Waiting"+(timeout > 0 ? (" "+timeout+"sec") : "")+" for jetty to stop");
1044                     LineNumberReader lin = new LineNumberReader(new InputStreamReader(s.getInputStream()));
1045                     String response=lin.readLine();
1046                     if ("Stopped".equals(response))
1047                         System.err.println("Stopped");
1048                 }
1049             }
1050             finally
1051             {
1052                 s.close();
1053             }
1054         }
1055         catch (SocketTimeoutException e)
1056         {
1057             System.err.println("Timed out waiting for stop confirmation");
1058             System.exit(ERR_UNKNOWN);
1059         }
1060         catch (ConnectException e)
1061         {
1062             usageExit(e,ERR_NOT_STOPPED);
1063         }
1064         catch (Exception e)
1065         {
1066             usageExit(e,ERR_UNKNOWN);
1067         }
1068     }
1069 
1070     static void usageExit(Throwable t, int exit)
1071     {
1072         t.printStackTrace(System.err);
1073         System.err.println();
1074         System.err.println("Usage: java -jar start.jar [options] [properties] [configs]");
1075         System.err.println("       java -jar start.jar --help  # for more information");
1076         System.exit(exit);
1077     }
1078 
1079     static void usageExit(int exit)
1080     {
1081         System.err.println();
1082         System.err.println("Usage: java -jar start.jar [options] [properties] [configs]");
1083         System.err.println("       java -jar start.jar --help  # for more information");
1084         System.exit(exit);
1085     }
1086 
1087     /**
1088      * Convert a start.ini format file into an argument list.
1089      */
1090     static List<String> loadStartIni(File ini)
1091     {
1092         if (!ini.exists())
1093         {
1094             System.err.println("Warning - can't find ini file: " + ini);
1095             // No start.ini found, skip load.
1096             return Collections.emptyList();
1097         }
1098 
1099         List<String> args = new ArrayList<String>();
1100 
1101         FileReader reader = null;
1102         BufferedReader buf = null;
1103         try
1104         {
1105             reader = new FileReader(ini);
1106             buf = new BufferedReader(reader);
1107 
1108             String arg;
1109             while ((arg = buf.readLine()) != null)
1110             {
1111                 arg = arg.trim();
1112                 if (arg.length() == 0 || arg.startsWith("#"))
1113                 {
1114                     continue;
1115                 }
1116                 args.add(arg);
1117             }
1118         }
1119         catch (IOException e)
1120         {
1121             usageExit(e,ERR_UNKNOWN);
1122         }
1123         finally
1124         {
1125             Main.close(buf);
1126             Main.close(reader);
1127         }
1128 
1129         return args;
1130     }
1131 
1132     void addJvmArgs(List<String> jvmArgs)
1133     {
1134         _jvmArgs.addAll(jvmArgs);
1135     }
1136 }