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