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