1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.maven.plugin;
20
21 import java.io.BufferedOutputStream;
22 import java.io.BufferedReader;
23 import java.io.File;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.InputStreamReader;
28 import java.io.LineNumberReader;
29 import java.io.OutputStream;
30 import java.net.MalformedURLException;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.HashMap;
34 import java.util.HashSet;
35 import java.util.Iterator;
36 import java.util.List;
37 import java.util.Locale;
38 import java.util.Map;
39 import java.util.Properties;
40 import java.util.Random;
41 import java.util.Set;
42
43 import org.apache.maven.artifact.Artifact;
44 import org.apache.maven.plugin.MojoExecutionException;
45 import org.apache.maven.plugin.MojoFailureException;
46 import org.apache.maven.plugin.descriptor.PluginDescriptor;
47 import org.eclipse.jetty.annotations.AnnotationConfiguration;
48 import org.eclipse.jetty.quickstart.QuickStartDescriptorGenerator;
49 import org.eclipse.jetty.util.IO;
50 import org.eclipse.jetty.util.resource.Resource;
51 import org.eclipse.jetty.util.resource.ResourceCollection;
52 import org.eclipse.jetty.util.thread.QueuedThreadPool;
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
78
79
80
81
82 public class JettyRunForkedMojo extends JettyRunMojo
83 {
84 public static final String DEFAULT_WEBAPP_SRC = "src"+File.separator+"main"+File.separator+"webapp";
85 public static final String FAKE_WEBAPP = "webapp-tmp";
86
87
88 public String PORT_SYSPROPERTY = "jetty.port";
89
90
91
92
93
94
95
96
97
98
99
100
101
102 protected File target;
103
104
105
106
107
108
109 protected File forkWebXml;
110
111
112
113
114
115
116 private String jvmArgs;
117
118
119
120
121
122
123 private List pluginArtifacts;
124
125
126
127
128
129
130 private PluginDescriptor plugin;
131
132
133
134
135
136 private boolean waitForChild;
137
138
139
140
141 private int maxStartupLines;
142
143
144
145
146
147
148
149 private Map<String,String> env = new HashMap<String,String>();
150
151
152
153
154 private Process forkedProcess;
155
156
157
158
159
160 private Random random;
161
162
163
164 private Resource originalBaseResource;
165
166
167
168
169
170
171
172 public class ShutdownThread extends Thread
173 {
174 public ShutdownThread()
175 {
176 super("RunForkedShutdown");
177 }
178
179 public void run ()
180 {
181 if (forkedProcess != null && waitForChild)
182 {
183 forkedProcess.destroy();
184 }
185 }
186 }
187
188
189
190
191
192
193
194
195
196 private static class ConsoleStreamer implements Runnable
197 {
198 private String mode;
199 private BufferedReader reader;
200
201 public ConsoleStreamer(String mode, InputStream is)
202 {
203 this.mode = mode;
204 this.reader = new BufferedReader(new InputStreamReader(is));
205 }
206
207
208 public void run()
209 {
210 String line;
211 try
212 {
213 while ((line = reader.readLine()) != (null))
214 {
215 System.out.println("[" + mode + "] " + line);
216 }
217 }
218 catch (IOException ignore)
219 {
220
221 }
222 finally
223 {
224 IO.close(reader);
225 }
226 }
227 }
228
229
230
231
232
233
234
235
236 public void execute() throws MojoExecutionException, MojoFailureException
237 {
238 Runtime.getRuntime().addShutdownHook(new ShutdownThread());
239 random = new Random();
240 super.execute();
241 }
242
243
244
245
246 @Override
247 public void startJetty() throws MojoExecutionException
248 {
249
250
251
252 if (forkWebXml == null)
253 forkWebXml = new File (target, "fork-web.xml");
254
255 try
256 {
257 printSystemProperties();
258
259
260
261
262 server.configureHandlers();
263
264
265 configureWebApplication();
266
267
268 originalBaseResource = webApp.getBaseResource();
269
270
271 webApp.setCopyWebDir(false);
272 webApp.setCopyWebInf(false);
273 webApp.setGenerateQuickStart(true);
274
275 if (!forkWebXml.getParentFile().exists())
276 forkWebXml.getParentFile().mkdirs();
277 if (!forkWebXml.exists())
278 forkWebXml.createNewFile();
279
280 webApp.setQuickStartWebDescriptor(Resource.newResource(forkWebXml));
281
282
283 server.addWebApplication(webApp);
284
285
286
287 QueuedThreadPool tpool = server.getBean(QueuedThreadPool.class);
288 if (tpool != null)
289 tpool.start();
290 else
291 webApp.setAttribute(AnnotationConfiguration.MULTI_THREADED, Boolean.FALSE.toString());
292
293 webApp.start();
294
295
296 File props = prepareConfiguration();
297
298 webApp.stop();
299
300 if (tpool != null)
301 tpool.stop();
302
303 List<String> cmd = new ArrayList<String>();
304 cmd.add(getJavaBin());
305
306 if (jvmArgs != null)
307 {
308 String[] args = jvmArgs.split(" ");
309 for (int i=0;args != null && i<args.length;i++)
310 {
311 if (args[i] !=null && !"".equals(args[i]))
312 cmd.add(args[i].trim());
313 }
314 }
315
316 String classPath = getContainerClassPath();
317 if (classPath != null && classPath.length() > 0)
318 {
319 cmd.add("-cp");
320 cmd.add(classPath);
321 }
322 cmd.add(Starter.class.getCanonicalName());
323
324 if (stopPort > 0 && stopKey != null)
325 {
326 cmd.add("--stop-port");
327 cmd.add(Integer.toString(stopPort));
328 cmd.add("--stop-key");
329 cmd.add(stopKey);
330 }
331 if (jettyXml != null)
332 {
333 cmd.add("--jetty-xml");
334 cmd.add(jettyXml);
335 }
336
337 if (contextXml != null)
338 {
339 cmd.add("--context-xml");
340 cmd.add(contextXml);
341 }
342
343 cmd.add("--props");
344 cmd.add(props.getAbsolutePath());
345
346 String token = createToken();
347 cmd.add("--token");
348 cmd.add(token);
349
350 ProcessBuilder builder = new ProcessBuilder(cmd);
351 builder.directory(project.getBasedir());
352
353 if (PluginLog.getLog().isDebugEnabled())
354 PluginLog.getLog().debug(Arrays.toString(cmd.toArray()));
355
356 PluginLog.getLog().info("Forked process starting");
357
358
359 if (!env.isEmpty())
360 {
361 builder.environment().putAll(env);
362 }
363
364 if (waitForChild)
365 {
366 forkedProcess = builder.start();
367 startPump("STDOUT",forkedProcess.getInputStream());
368 startPump("STDERR",forkedProcess.getErrorStream());
369 int exitcode = forkedProcess.waitFor();
370 PluginLog.getLog().info("Forked execution exit: "+exitcode);
371 }
372 else
373 {
374 builder.redirectErrorStream(true);
375 forkedProcess = builder.start();
376
377
378
379 try
380 {
381 String line = "";
382 try (InputStream is = forkedProcess.getInputStream();
383 LineNumberReader reader = new LineNumberReader(new InputStreamReader(is)))
384 {
385 int attempts = maxStartupLines;
386 while (attempts>0 && line != null)
387 {
388 --attempts;
389 line = reader.readLine();
390 if (line != null && line.startsWith(token))
391 break;
392 }
393
394 }
395
396 if (line != null && line.trim().equals(token))
397 PluginLog.getLog().info("Forked process started.");
398 else
399 {
400 String err = (line == null?"":(line.startsWith(token)?line.substring(token.length()):line));
401 PluginLog.getLog().info("Forked process startup errors"+(!"".equals(err)?", received: "+err:""));
402 }
403 }
404 catch (Exception e)
405 {
406 throw new MojoExecutionException ("Problem determining if forked process is ready: "+e.getMessage());
407 }
408
409 }
410 }
411 catch (InterruptedException ex)
412 {
413 if (forkedProcess != null && waitForChild)
414 forkedProcess.destroy();
415
416 throw new MojoExecutionException("Failed to start Jetty within time limit");
417 }
418 catch (Exception ex)
419 {
420 if (forkedProcess != null && waitForChild)
421 forkedProcess.destroy();
422
423 throw new MojoExecutionException("Failed to create Jetty process", ex);
424 }
425 }
426
427
428
429
430
431
432
433
434 public List<String> getProvidedJars() throws MojoExecutionException
435 {
436
437
438
439
440 if (useProvidedScope)
441 {
442
443 List<String> provided = new ArrayList<String>();
444 for ( Iterator<Artifact> iter = project.getArtifacts().iterator(); iter.hasNext(); )
445 {
446 Artifact artifact = iter.next();
447 if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope()) && !isPluginArtifact(artifact))
448 {
449 provided.add(artifact.getFile().getAbsolutePath());
450 if (getLog().isDebugEnabled()) { getLog().debug("Adding provided artifact: "+artifact);}
451 }
452 }
453 return provided;
454
455 }
456 else
457 return null;
458 }
459
460
461
462
463
464
465
466
467 public File prepareConfiguration() throws MojoExecutionException
468 {
469 try
470 {
471
472 File propsFile = new File (target, "fork.props");
473 if (propsFile.exists())
474 propsFile.delete();
475
476 propsFile.createNewFile();
477
478
479 Properties props = new Properties();
480
481
482
483 if (webApp.getDescriptor() != null)
484 {
485 props.put("web.xml", webApp.getDescriptor());
486 }
487
488 if (webApp.getQuickStartWebDescriptor() != null)
489 {
490 props.put("quickstart.web.xml", webApp.getQuickStartWebDescriptor().getFile().getAbsolutePath());
491 }
492
493
494 if (webApp.getContextPath() != null)
495 {
496 props.put("context.path", webApp.getContextPath());
497 }
498
499
500 props.put("tmp.dir", webApp.getTempDirectory().getAbsolutePath());
501 props.put("tmp.dir.persist", Boolean.toString(webApp.isPersistTempDirectory()));
502
503
504
505 if (originalBaseResource != null)
506 {
507 StringBuffer rb = new StringBuffer();
508 if (originalBaseResource instanceof ResourceCollection)
509 {
510 ResourceCollection resources = ((ResourceCollection)originalBaseResource);
511 for (Resource r:resources.getResources())
512 {
513 if (rb.length() > 0) rb.append(",");
514 rb.append(r.toString());
515 }
516 }
517 else
518 rb.append(originalBaseResource.toString());
519
520 props.put("base.dirs", rb.toString());
521 }
522
523
524 props.put("base.first", Boolean.toString(webApp.getBaseAppFirst()));
525
526
527 if (webApp.getClasses() != null)
528 {
529 props.put("classes.dir",webApp.getClasses().getAbsolutePath());
530 }
531
532 if (useTestScope && webApp.getTestClasses() != null)
533 {
534 props.put("testClasses.dir", webApp.getTestClasses().getAbsolutePath());
535 }
536
537
538 List<File> deps = webApp.getWebInfLib();
539 StringBuffer strbuff = new StringBuffer();
540 for (int i=0; i<deps.size(); i++)
541 {
542 File d = deps.get(i);
543 strbuff.append(d.getAbsolutePath());
544 if (i < deps.size()-1)
545 strbuff.append(",");
546 }
547 props.put("lib.jars", strbuff.toString());
548
549
550 List<Artifact> warArtifacts = getWarArtifacts();
551 for (int i=0; i<warArtifacts.size(); i++)
552 {
553 strbuff.setLength(0);
554 Artifact a = warArtifacts.get(i);
555 strbuff.append(a.getGroupId()+",");
556 strbuff.append(a.getArtifactId()+",");
557 strbuff.append(a.getFile().getAbsolutePath());
558 props.put("maven.war.artifact."+i, strbuff.toString());
559 }
560
561
562
563 WarPluginInfo warPlugin = new WarPluginInfo(project);
564
565
566 props.put("maven.war.includes", toCSV(warPlugin.getDependentMavenWarIncludes()));
567 props.put("maven.war.excludes", toCSV(warPlugin.getDependentMavenWarExcludes()));
568
569
570 List<OverlayConfig> configs = warPlugin.getMavenWarOverlayConfigs();
571 int i=0;
572 for (OverlayConfig c:configs)
573 {
574 props.put("maven.war.overlay."+(i++), c.toString());
575 }
576
577 try (OutputStream out = new BufferedOutputStream(new FileOutputStream(propsFile)))
578 {
579 props.store(out, "properties for forked webapp");
580 }
581 return propsFile;
582 }
583 catch (Exception e)
584 {
585 throw new MojoExecutionException("Prepare webapp configuration", e);
586 }
587 }
588
589
590
591
592
593
594
595
596
597
598
599 private List<Artifact> getWarArtifacts()
600 throws MalformedURLException, IOException
601 {
602 List<Artifact> warArtifacts = new ArrayList<Artifact>();
603 for ( Iterator<Artifact> iter = project.getArtifacts().iterator(); iter.hasNext(); )
604 {
605 Artifact artifact = (Artifact) iter.next();
606
607 if (artifact.getType().equals("war"))
608 warArtifacts.add(artifact);
609 }
610
611 return warArtifacts;
612 }
613
614
615
616
617
618
619
620
621
622
623 public boolean isPluginArtifact(Artifact artifact)
624 {
625 if (pluginArtifacts == null || pluginArtifacts.isEmpty())
626 return false;
627
628 boolean isPluginArtifact = false;
629 for (Iterator<Artifact> iter = pluginArtifacts.iterator(); iter.hasNext() && !isPluginArtifact; )
630 {
631 Artifact pluginArtifact = iter.next();
632 if (getLog().isDebugEnabled()) { getLog().debug("Checking "+pluginArtifact);}
633 if (pluginArtifact.getGroupId().equals(artifact.getGroupId()) && pluginArtifact.getArtifactId().equals(artifact.getArtifactId()))
634 isPluginArtifact = true;
635 }
636
637 return isPluginArtifact;
638 }
639
640
641
642
643
644
645
646
647 private Set<Artifact> getExtraJars()
648 throws Exception
649 {
650 Set<Artifact> extraJars = new HashSet<Artifact>();
651
652
653 List l = pluginArtifacts;
654 Artifact pluginArtifact = null;
655
656 if (l != null)
657 {
658 Iterator itor = l.iterator();
659 while (itor.hasNext() && pluginArtifact == null)
660 {
661 Artifact a = (Artifact)itor.next();
662 if (a.getArtifactId().equals(plugin.getArtifactId()))
663 {
664 extraJars.add(a);
665 }
666 }
667 }
668
669 return extraJars;
670 }
671
672
673
674
675
676
677
678
679
680
681 public String getContainerClassPath() throws Exception
682 {
683 StringBuilder classPath = new StringBuilder();
684 for (Object obj : pluginArtifacts)
685 {
686 Artifact artifact = (Artifact) obj;
687 if ("jar".equals(artifact.getType()))
688 {
689 if (classPath.length() > 0)
690 {
691 classPath.append(File.pathSeparator);
692 }
693 classPath.append(artifact.getFile().getAbsolutePath());
694
695 }
696 }
697
698
699 Set<Artifact> extraJars = getExtraJars();
700 for (Artifact a:extraJars)
701 {
702 classPath.append(File.pathSeparator);
703 classPath.append(a.getFile().getAbsolutePath());
704 }
705
706
707
708 List<String> providedJars = getProvidedJars();
709 if (providedJars != null && !providedJars.isEmpty())
710 {
711 for (String jar:providedJars)
712 {
713 classPath.append(File.pathSeparator);
714 classPath.append(jar);
715 if (getLog().isDebugEnabled()) getLog().debug("Adding provided jar: "+jar);
716 }
717 }
718
719 return classPath.toString();
720 }
721
722
723
724
725
726
727
728 private String getJavaBin()
729 {
730 String javaexes[] = new String[]
731 { "java", "java.exe" };
732
733 File javaHomeDir = new File(System.getProperty("java.home"));
734 for (String javaexe : javaexes)
735 {
736 File javabin = new File(javaHomeDir,fileSeparators("bin/" + javaexe));
737 if (javabin.exists() && javabin.isFile())
738 {
739 return javabin.getAbsolutePath();
740 }
741 }
742
743 return "java";
744 }
745
746
747
748
749
750
751
752
753 public static String fileSeparators(String path)
754 {
755 StringBuilder ret = new StringBuilder();
756 for (char c : path.toCharArray())
757 {
758 if ((c == '/') || (c == '\\'))
759 {
760 ret.append(File.separatorChar);
761 }
762 else
763 {
764 ret.append(c);
765 }
766 }
767 return ret.toString();
768 }
769
770
771
772
773
774
775
776
777 public static String pathSeparators(String path)
778 {
779 StringBuilder ret = new StringBuilder();
780 for (char c : path.toCharArray())
781 {
782 if ((c == ',') || (c == ':'))
783 {
784 ret.append(File.pathSeparatorChar);
785 }
786 else
787 {
788 ret.append(c);
789 }
790 }
791 return ret.toString();
792 }
793
794
795
796
797
798
799
800 private String createToken ()
801 {
802 return Long.toString(random.nextLong()^System.currentTimeMillis(), 36).toUpperCase(Locale.ENGLISH);
803 }
804
805
806
807
808
809
810
811
812 private void startPump(String mode, InputStream inputStream)
813 {
814 ConsoleStreamer pump = new ConsoleStreamer(mode,inputStream);
815 Thread thread = new Thread(pump,"ConsoleStreamer/" + mode);
816 thread.setDaemon(true);
817 thread.start();
818 }
819
820
821
822
823
824
825
826
827 private String toCSV (List<String> strings)
828 {
829 if (strings == null)
830 return "";
831 StringBuffer strbuff = new StringBuffer();
832 Iterator<String> itor = strings.iterator();
833 while (itor.hasNext())
834 {
835 strbuff.append(itor.next());
836 if (itor.hasNext())
837 strbuff.append(",");
838 }
839 return strbuff.toString();
840 }
841 }