View Javadoc

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