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