1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.start;
20
21 import static org.eclipse.jetty.start.UsageException.ERR_INVOKE_MAIN;
22 import static org.eclipse.jetty.start.UsageException.ERR_NOT_STOPPED;
23 import static org.eclipse.jetty.start.UsageException.ERR_UNKNOWN;
24
25 import java.io.BufferedReader;
26 import java.io.File;
27 import java.io.FileOutputStream;
28 import java.io.FileWriter;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.InputStreamReader;
32 import java.io.LineNumberReader;
33 import java.io.OutputStream;
34 import java.io.PrintWriter;
35 import java.lang.reflect.InvocationTargetException;
36 import java.lang.reflect.Method;
37 import java.net.ConnectException;
38 import java.net.InetAddress;
39 import java.net.Socket;
40 import java.net.SocketTimeoutException;
41 import java.net.URL;
42 import java.util.ArrayList;
43 import java.util.Collection;
44 import java.util.Collections;
45 import java.util.List;
46 import java.util.Locale;
47 import java.util.regex.Matcher;
48 import java.util.regex.Pattern;
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77 public class Main
78 {
79 private static final int EXIT_USAGE = 1;
80
81 public static String join(Collection<?> objs, String delim)
82 {
83 StringBuilder str = new StringBuilder();
84 boolean needDelim = false;
85 for (Object obj : objs)
86 {
87 if (needDelim)
88 {
89 str.append(delim);
90 }
91 str.append(obj);
92 needDelim = true;
93 }
94 return str.toString();
95 }
96
97 public static void main(String[] args)
98 {
99 try
100 {
101 Main main = new Main();
102 StartArgs startArgs = main.processCommandLine(args);
103 main.start(startArgs);
104 }
105 catch (UsageException e)
106 {
107 System.err.println(e.getMessage());
108 usageExit(e.getCause(),e.getExitCode());
109 }
110 catch (Throwable e)
111 {
112 usageExit(e,UsageException.ERR_UNKNOWN);
113 }
114 }
115
116 static void usageExit(int exit)
117 {
118 usageExit(null,exit);
119 }
120
121 static void usageExit(Throwable t, int exit)
122 {
123 if (t != null)
124 {
125 t.printStackTrace(System.err);
126 }
127 System.err.println();
128 System.err.println("Usage: java -jar start.jar [options] [properties] [configs]");
129 System.err.println(" java -jar start.jar --help # for more information");
130 System.exit(exit);
131 }
132
133 private final BaseHome baseHome;
134
135 Main() throws IOException
136 {
137 baseHome = new BaseHome();
138 }
139
140 private void copyInThread(final InputStream in, final OutputStream out)
141 {
142 new Thread(new Runnable()
143 {
144 @Override
145 public void run()
146 {
147 try
148 {
149 byte[] buf = new byte[1024];
150 int len = in.read(buf);
151 while (len > 0)
152 {
153 out.write(buf,0,len);
154 len = in.read(buf);
155 }
156 }
157 catch (IOException e)
158 {
159
160 }
161 }
162
163 }).start();
164 }
165
166 private void initFile(FileArg arg)
167 {
168 try
169 {
170 File file = baseHome.getBaseFile(arg.location);
171
172 StartLog.debug("Module file %s %s",file.getAbsolutePath(),(file.exists()?"[Exists!]":""));
173 if (file.exists())
174 {
175 return;
176 }
177
178 if (arg.uri!=null)
179 {
180 URL url = new URL(arg.uri);
181
182 System.err.println("DOWNLOAD: " + url + " to " + arg.location);
183
184 FS.ensureDirectoryExists(file.getParentFile());
185
186 byte[] buf = new byte[8192];
187 try (InputStream in = url.openStream(); OutputStream out = new FileOutputStream(file);)
188 {
189 while (true)
190 {
191 int len = in.read(buf);
192
193 if (len > 0)
194 {
195 out.write(buf,0,len);
196 }
197 if (len < 0)
198 {
199 break;
200 }
201 }
202 }
203 }
204 else if (arg.location.endsWith("/"))
205 {
206 System.err.println("MKDIR: " + baseHome.toShortForm(file));
207 file.mkdirs();
208 }
209 else
210 StartLog.warn("MISSING: required file "+ baseHome.toShortForm(file));
211
212 }
213 catch (Exception e)
214 {
215 StartLog.warn("ERROR: processing %s%n%s",arg,e);
216 StartLog.warn(e);
217 usageExit(EXIT_USAGE);
218 }
219 }
220
221 private void dumpClasspathWithVersions(Classpath classpath)
222 {
223 System.out.println();
224 System.out.println("Jetty Server Classpath:");
225 System.out.println("-----------------------");
226 if (classpath.count() == 0)
227 {
228 System.out.println("No classpath entries and/or version information available show.");
229 return;
230 }
231
232 System.out.println("Version Information on " + classpath.count() + " entr" + ((classpath.count() > 1)?"ies":"y") + " in the classpath.");
233 System.out.println("Note: order presented here is how they would appear on the classpath.");
234 System.out.println(" changes to the --module=name command line options will be reflected here.");
235
236 int i = 0;
237 for (File element : classpath.getElements())
238 {
239 System.out.printf("%2d: %24s | %s\n",i++,getVersion(element),baseHome.toShortForm(element));
240 }
241 }
242
243 public BaseHome getBaseHome()
244 {
245 return baseHome;
246 }
247
248 private String getVersion(File element)
249 {
250 if (element.isDirectory())
251 {
252 return "(dir)";
253 }
254
255 if (element.isFile())
256 {
257 String name = element.getName().toLowerCase(Locale.ENGLISH);
258 if (name.endsWith(".jar"))
259 {
260 return JarVersion.getVersion(element);
261 }
262 }
263
264 return "";
265 }
266
267 public void invokeMain(StartArgs args) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException, IOException
268 {
269 Class<?> invoked_class = null;
270 ClassLoader classloader = args.getClasspath().getClassLoader();
271 String mainclass = args.getMainClassname();
272
273 try
274 {
275 invoked_class = classloader.loadClass(mainclass);
276 }
277 catch (ClassNotFoundException e)
278 {
279 System.out.println("WARNING: Nothing to start, exiting ...");
280 StartLog.debug(e);
281 usageExit(ERR_INVOKE_MAIN);
282 return;
283 }
284
285 StartLog.debug("%s - %s",invoked_class,invoked_class.getPackage().getImplementationVersion());
286
287 CommandLineBuilder cmd = args.getMainArgs(baseHome,false);
288 String argArray[] = cmd.getArgs().toArray(new String[0]);
289 StartLog.debug("Command Line Args: %s",cmd.toString());
290
291 Class<?>[] method_param_types = new Class[]
292 { argArray.getClass() };
293
294 Method main = invoked_class.getDeclaredMethod("main",method_param_types);
295 Object[] method_params = new Object[]
296 { argArray };
297 main.invoke(null,method_params);
298 }
299
300 public void listConfig(StartArgs args)
301 {
302
303 args.dumpEnvironment();
304
305
306 args.dumpJvmArgs();
307
308
309 args.dumpSystemProperties();
310
311
312 args.dumpProperties();
313
314
315 dumpClasspathWithVersions(args.getClasspath());
316
317
318 args.dumpActiveXmls(baseHome);
319 }
320
321 private void listModules(StartArgs args)
322 {
323 System.out.println();
324 System.out.println("Jetty All Available Modules:");
325 System.out.println("----------------------------");
326 args.getAllModules().dump();
327
328
329 System.out.println();
330 System.out.println("Jetty Active Module Tree:");
331 System.out.println("-------------------------");
332 Modules modules = args.getAllModules();
333 modules.dumpEnabledTree();
334 }
335
336 private void moduleIni(StartArgs args, String name, boolean topLevel, boolean appendStartIni) throws IOException
337 {
338
339 File start_d = baseHome.getBaseFile("start.d");
340
341
342 Modules modules = args.getAllModules();
343 Module module = modules.get(name);
344 if (module == null)
345 {
346 StartLog.warn("ERROR: No known module for %s",name);
347 return;
348 }
349
350
351 File start_ini = baseHome.getBaseFile("start.ini");
352 String short_start_ini = baseHome.toShortForm(start_ini);
353 File ini = new File(start_d,name + ".ini");
354 String short_ini = baseHome.toShortForm(ini);
355 StartIni module_ini = null;
356 if (ini.exists())
357 {
358 module_ini = new StartIni(ini);
359 if (module_ini.getLineMatches(Pattern.compile("--module=(.*, *)*" + name)).size() == 0)
360 {
361 StartLog.warn("ERROR: %s is not enabled in %s!",name,short_ini);
362 return;
363 }
364 }
365
366 boolean transitive = module.isEnabled() && (module.getSources().size() == 0);
367 boolean has_ini_lines = module.getInitialise().size() > 0;
368
369
370 if (!module.isEnabled() || (transitive && has_ini_lines) || (topLevel && !ini.exists() && !appendStartIni))
371 {
372 String source = null;
373 PrintWriter out = null;
374 try
375 {
376 if (appendStartIni)
377 {
378 if ((!start_ini.exists() && !start_ini.createNewFile()) || !start_ini.canWrite())
379 {
380 StartLog.warn("ERROR: Bad %s! ",start_ini);
381 return;
382 }
383 source = short_start_ini;
384 StartLog.warn("%-15s initialised in %s (appended)",name,source);
385 out = new PrintWriter(new FileWriter(start_ini,true));
386 }
387 else
388 {
389
390 if (!start_d.exists())
391 {
392 start_d.mkdirs();
393 }
394 if (!start_d.isDirectory() || !start_d.canWrite())
395 {
396 StartLog.warn("ERROR: Bad start.d %s! ",start_d);
397 return;
398 }
399
400 if (!ini.createNewFile())
401 {
402 StartLog.warn("ERROR: %s cannot be initialised in %s! ",name,short_ini);
403 return;
404 }
405 source = short_ini;
406 StartLog.warn("%-15s initialised in %s (created)",name,source);
407 out = new PrintWriter(ini);
408 }
409
410 if (appendStartIni)
411 {
412 out.println();
413 }
414 out.println("#");
415 out.println("# Initialize module " + name);
416 out.println("#");
417 Pattern p = Pattern.compile("--module=([^,]+)(,([^,]+))*");
418
419 out.println("--module=" + name);
420 args.parse("--module=" + name,source);
421 modules.enable(name,Collections.singletonList(source));
422 for (String line : module.getInitialise())
423 {
424 out.println(line);
425 args.parse(line,source);
426 Matcher m = p.matcher(line);
427 if (m.matches())
428 {
429 for (int i = 1; i <= m.groupCount(); i++)
430 {
431 String n = m.group(i);
432 if (n == null)
433 {
434 continue;
435 }
436 n = n.trim();
437 if ((n.length() == 0) || n.startsWith(","))
438 {
439 continue;
440 }
441
442 modules.enable(n,Collections.singletonList(source));
443 }
444 }
445 }
446 }
447 finally
448 {
449 if (out != null)
450 {
451 out.close();
452 }
453 }
454 }
455 else if (ini.exists())
456 {
457 StartLog.info("%-15s initialised in %s",name,short_ini);
458 }
459
460
461 for (String source : module.getSources())
462 {
463 if (!short_ini.equals(source))
464 {
465 StartLog.warn("%-15s enabled in %s",name,baseHome.toShortForm(source));
466 }
467 }
468
469
470 for (String file : module.getFiles())
471 {
472 initFile(new FileArg(file));
473 }
474
475
476 if (topLevel)
477 {
478 List<Module> parents = new ArrayList<>();
479 for (String parent : modules.resolveParentModulesOf(name))
480 {
481 if (!name.equals(parent))
482 {
483 Module m = modules.get(parent);
484 m.setEnabled(true);
485 parents.add(m);
486 }
487 }
488 Collections.sort(parents,Collections.reverseOrder(new Module.DepthComparator()));
489 for (Module m : parents)
490 {
491 moduleIni(args,m.getName(),false,appendStartIni);
492 }
493 }
494 }
495
496
497
498
499 public StartArgs processCommandLine(List<String> cmdLine) throws Exception
500 {
501 return this.processCommandLine(cmdLine.toArray(new String[cmdLine.size()]));
502 }
503
504 public StartArgs processCommandLine(String[] cmdLine) throws Exception
505 {
506 StartArgs args = new StartArgs(cmdLine);
507
508
509
510
511
512
513
514 baseHome.initialize(args);
515
516
517
518 StartLog.getInstance().initialize(baseHome,args);
519
520 StartLog.debug("jetty.home=%s",baseHome.getHome());
521 StartLog.debug("jetty.base=%s",baseHome.getBase());
522 args.addSystemProperty("jetty.home",baseHome.getHome());
523 args.addSystemProperty("jetty.base",baseHome.getBase());
524
525
526
527 File start_ini = baseHome.getBaseFile("start.ini");
528 if (FS.canReadFile(start_ini))
529 {
530 StartLog.debug("Reading ${jetty.base}/start.ini - %s",start_ini);
531 args.parse(baseHome,new StartIni(start_ini));
532 }
533
534 File start_d = baseHome.getBaseFile("start.d");
535 if (FS.canReadDirectory(start_d))
536 {
537 List<File> files = new ArrayList<>();
538 for (File file : start_d.listFiles(new FS.IniFilter()))
539 {
540 files.add(file);
541 }
542
543 Collections.sort(files,new NaturalSort.Files());
544 for (File file : files)
545 {
546 StartLog.debug("Reading ${jetty.base}/start.d/%s - %s",file.getName(),file);
547 args.parse(baseHome,new StartIni(file));
548 }
549 }
550
551
552
553
554
555 StartLog.debug("Parsing collected arguments");
556 args.parseCommandLine();
557
558
559 Modules modules = new Modules();
560 StartLog.debug("Registering all modules");
561 modules.registerAll(baseHome);
562
563
564 for (String enabledModule : args.getEnabledModules())
565 {
566 List<String> sources = args.getSources(enabledModule);
567 modules.enable(enabledModule,sources);
568 }
569
570 StartLog.debug("Building Module Graph");
571 modules.buildGraph();
572
573 args.setAllModules(modules);
574 List<Module> activeModules = modules.resolveEnabled();
575
576
577 args.expandModules(baseHome,activeModules);
578
579
580 args.resolveExtraXmls(baseHome);
581
582 return args;
583 }
584
585 public void start(StartArgs args) throws IOException, InterruptedException
586 {
587 StartLog.debug("StartArgs: %s",args);
588
589
590 Classpath classpath = args.getClasspath();
591
592 System.setProperty("java.class.path",classpath.toString());
593
594
595 if (args.isHelp())
596 {
597 usage(true);
598 }
599
600
601 if (args.isListClasspath())
602 {
603 dumpClasspathWithVersions(classpath);
604 }
605
606
607 if (args.isListConfig())
608 {
609 listConfig(args);
610 }
611
612
613 if (args.isListModules())
614 {
615 listModules(args);
616 }
617
618
619 if (args.getModuleGraphFilename() != null)
620 {
621 File outputFile = baseHome.getBaseFile(args.getModuleGraphFilename());
622 System.out.printf("Generating GraphViz Graph of Jetty Modules at %s%n",baseHome.toShortForm(outputFile));
623 ModuleGraphWriter writer = new ModuleGraphWriter();
624 writer.config(args.getProperties());
625 writer.write(args.getAllModules(),outputFile);
626 }
627
628
629 if (args.isDryRun())
630 {
631 CommandLineBuilder cmd = args.getMainArgs(baseHome,true);
632 System.out.println(cmd.toString());
633 }
634
635 if (args.isStopCommand())
636 {
637 int stopPort = Integer.parseInt(args.getProperties().getProperty("STOP.PORT"));
638 String stopKey = args.getProperties().getProperty("STOP.KEY");
639
640 if (args.getProperties().getProperty("STOP.WAIT") != null)
641 {
642 int stopWait = Integer.parseInt(args.getProperties().getProperty("STOP.PORT"));
643
644 stop(stopPort,stopKey,stopWait);
645 }
646 else
647 {
648 stop(stopPort,stopKey);
649 }
650 }
651
652
653 for (String module : args.getModuleStartIni())
654 {
655 moduleIni(args,module,true,true);
656 }
657
658
659 for (String module : args.getModuleStartdIni())
660 {
661 moduleIni(args,module,true,false);
662 }
663
664
665 if (!args.isRun())
666 {
667 return;
668 }
669
670
671 if (args.isExec())
672 {
673 CommandLineBuilder cmd = args.getMainArgs(baseHome,true);
674 ProcessBuilder pbuilder = new ProcessBuilder(cmd.getArgs());
675 final Process process = pbuilder.start();
676 Runtime.getRuntime().addShutdownHook(new Thread()
677 {
678 @Override
679 public void run()
680 {
681 StartLog.debug("Destroying " + process);
682 process.destroy();
683 }
684 });
685
686 copyInThread(process.getErrorStream(),System.err);
687 copyInThread(process.getInputStream(),System.out);
688 copyInThread(System.in,process.getOutputStream());
689 process.waitFor();
690 System.exit(0);
691 return;
692 }
693
694 if (args.hasJvmArgs() || args.hasSystemProperties())
695 {
696 System.err.println("WARNING: System properties and/or JVM args set. Consider using --dry-run or --exec");
697 }
698
699
700 ClassLoader cl = classpath.getClassLoader();
701 Thread.currentThread().setContextClassLoader(cl);
702
703
704 try
705 {
706 invokeMain(args);
707 }
708 catch (Exception e)
709 {
710 usageExit(e,ERR_INVOKE_MAIN);
711 }
712 }
713
714
715
716
717 public void stop(int port, String key)
718 {
719 stop(port,key,0);
720 }
721
722 public void stop(int port, String key, int timeout)
723 {
724 int _port = port;
725 String _key = key;
726
727 try
728 {
729 if (_port <= 0)
730 {
731 System.err.println("STOP.PORT system property must be specified");
732 }
733 if (_key == null)
734 {
735 _key = "";
736 System.err.println("STOP.KEY system property must be specified");
737 System.err.println("Using empty key");
738 }
739
740 try (Socket s = new Socket(InetAddress.getByName("127.0.0.1"),_port))
741 {
742 if (timeout > 0)
743 {
744 s.setSoTimeout(timeout * 1000);
745 }
746
747 try (OutputStream out = s.getOutputStream())
748 {
749 out.write((_key + "\r\nstop\r\n").getBytes());
750 out.flush();
751
752 if (timeout > 0)
753 {
754 System.err.printf("Waiting %,d seconds for jetty to stop%n",timeout);
755 LineNumberReader lin = new LineNumberReader(new InputStreamReader(s.getInputStream()));
756 String response;
757 while ((response = lin.readLine()) != null)
758 {
759 StartLog.debug("Received \"%s\"",response);
760 if ("Stopped".equals(response))
761 {
762 StartLog.warn("Server reports itself as Stopped");
763 }
764 }
765 }
766 }
767 }
768 }
769 catch (SocketTimeoutException e)
770 {
771 System.err.println("Timed out waiting for stop confirmation");
772 System.exit(ERR_UNKNOWN);
773 }
774 catch (ConnectException e)
775 {
776 usageExit(e,ERR_NOT_STOPPED);
777 }
778 catch (Exception e)
779 {
780 usageExit(e,ERR_UNKNOWN);
781 }
782 }
783
784 public void usage(boolean exit)
785 {
786 String usageResource = "org/eclipse/jetty/start/usage.txt";
787 boolean usagePresented = false;
788 try (InputStream usageStream = getClass().getClassLoader().getResourceAsStream(usageResource))
789 {
790 if (usageStream != null)
791 {
792 try (InputStreamReader reader = new InputStreamReader(usageStream); BufferedReader buf = new BufferedReader(reader))
793 {
794 usagePresented = true;
795 String line;
796 while ((line = buf.readLine()) != null)
797 {
798 System.out.println(line);
799 }
800 }
801 }
802 else
803 {
804 System.out.println("No usage.txt ??");
805 }
806 }
807 catch (IOException e)
808 {
809 StartLog.warn(e);
810 }
811 if (!usagePresented)
812 {
813 System.err.println("ERROR: detailed usage resource unavailable");
814 }
815 if (exit)
816 {
817 System.exit(EXIT_USAGE);
818 }
819 }
820 }