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 ServerSupport.configureDefaultConfigurationClasses(server);
249
250
251 configureWebApplication();
252
253
254 originalBaseResource = webApp.getBaseResource();
255
256
257 originalPersistTemp = webApp.isPersistTempDirectory();
258
259
260 webApp.setCopyWebDir(false);
261 webApp.setCopyWebInf(false);
262 webApp.setGenerateQuickStart(true);
263
264 if (webApp.getQuickStartWebDescriptor() == null)
265 {
266 if (forkWebXml == null)
267 forkWebXml = new File (target, "fork-web.xml");
268
269 if (!forkWebXml.getParentFile().exists())
270 forkWebXml.getParentFile().mkdirs();
271 if (!forkWebXml.exists())
272 forkWebXml.createNewFile();
273
274 webApp.setQuickStartWebDescriptor(Resource.newResource(forkWebXml));
275 }
276
277
278 ServerSupport.addWebApplication(server, webApp);
279
280
281
282 QueuedThreadPool tpool = server.getBean(QueuedThreadPool.class);
283 if (tpool != null)
284 tpool.start();
285 else
286 webApp.setAttribute(AnnotationConfiguration.MULTI_THREADED, Boolean.FALSE.toString());
287
288
289 webApp.setPersistTempDirectory(true);
290
291 webApp.start();
292
293
294 File props = prepareConfiguration();
295
296 webApp.stop();
297
298 if (tpool != null)
299 tpool.stop();
300
301 List<String> cmd = new ArrayList<String>();
302 cmd.add(getJavaBin());
303
304 if (jvmArgs != null)
305 {
306 String[] args = jvmArgs.split(" ");
307 for (int i=0;args != null && i<args.length;i++)
308 {
309 if (args[i] !=null && !"".equals(args[i]))
310 cmd.add(args[i].trim());
311 }
312 }
313
314 String classPath = getContainerClassPath();
315 if (classPath != null && classPath.length() > 0)
316 {
317 cmd.add("-cp");
318 cmd.add(classPath);
319 }
320 cmd.add(Starter.class.getCanonicalName());
321
322 if (stopPort > 0 && stopKey != null)
323 {
324 cmd.add("--stop-port");
325 cmd.add(Integer.toString(stopPort));
326 cmd.add("--stop-key");
327 cmd.add(stopKey);
328 }
329 if (jettyXml != null)
330 {
331 cmd.add("--jetty-xml");
332 cmd.add(jettyXml);
333 }
334
335 if (contextXml != null)
336 {
337 cmd.add("--context-xml");
338 cmd.add(contextXml);
339 }
340
341 cmd.add("--props");
342 cmd.add(props.getAbsolutePath());
343
344 String token = createToken();
345 cmd.add("--token");
346 cmd.add(token);
347
348 ProcessBuilder builder = new ProcessBuilder(cmd);
349 builder.directory(project.getBasedir());
350
351 if (PluginLog.getLog().isDebugEnabled())
352 PluginLog.getLog().debug(Arrays.toString(cmd.toArray()));
353
354 PluginLog.getLog().info("Forked process starting");
355
356
357 if (!env.isEmpty())
358 {
359 builder.environment().putAll(env);
360 }
361
362 if (waitForChild)
363 {
364 forkedProcess = builder.start();
365 startPump("STDOUT",forkedProcess.getInputStream());
366 startPump("STDERR",forkedProcess.getErrorStream());
367 int exitcode = forkedProcess.waitFor();
368 PluginLog.getLog().info("Forked execution exit: "+exitcode);
369 }
370 else
371 {
372 builder.redirectErrorStream(true);
373 forkedProcess = builder.start();
374
375
376
377 try
378 {
379 String line = "";
380 try (InputStream is = forkedProcess.getInputStream();
381 LineNumberReader reader = new LineNumberReader(new InputStreamReader(is)))
382 {
383 int attempts = maxStartupLines;
384 while (attempts>0 && line != null)
385 {
386 --attempts;
387 line = reader.readLine();
388 if (line != null && line.startsWith(token))
389 break;
390 }
391
392 }
393
394 if (line != null && line.trim().equals(token))
395 PluginLog.getLog().info("Forked process started.");
396 else
397 {
398 String err = (line == null?"":(line.startsWith(token)?line.substring(token.length()):line));
399 PluginLog.getLog().info("Forked process startup errors"+(!"".equals(err)?", received: "+err:""));
400 }
401 }
402 catch (Exception e)
403 {
404 throw new MojoExecutionException ("Problem determining if forked process is ready: "+e.getMessage());
405 }
406
407 }
408 }
409 catch (InterruptedException ex)
410 {
411 if (forkedProcess != null && waitForChild)
412 forkedProcess.destroy();
413
414 throw new MojoExecutionException("Failed to start Jetty within time limit");
415 }
416 catch (Exception ex)
417 {
418 if (forkedProcess != null && waitForChild)
419 forkedProcess.destroy();
420
421 throw new MojoExecutionException("Failed to create Jetty process", ex);
422 }
423 }
424
425 public List<String> getProvidedJars() throws MojoExecutionException
426 {
427
428
429
430
431 if (useProvidedScope)
432 {
433
434 List<String> provided = new ArrayList<String>();
435 for ( Iterator<Artifact> iter = project.getArtifacts().iterator(); iter.hasNext(); )
436 {
437 Artifact artifact = iter.next();
438 if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope()) && !isPluginArtifact(artifact))
439 {
440 provided.add(artifact.getFile().getAbsolutePath());
441 if (getLog().isDebugEnabled()) { getLog().debug("Adding provided artifact: "+artifact);}
442 }
443 }
444 return provided;
445
446 }
447 else
448 return null;
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(originalPersistTemp));
486
487
488 if (originalBaseResource instanceof ResourceCollection)
489 props.put("base.dirs.orig", toCSV(((ResourceCollection)originalBaseResource).getResources()));
490 else
491 props.put("base.dirs.orig", originalBaseResource.toString());
492
493
494
495 Resource postOverlayResources = (Resource)webApp.getAttribute(MavenWebInfConfiguration.RESOURCE_BASES_POST_OVERLAY);
496 if (postOverlayResources instanceof ResourceCollection)
497 props.put("base.dirs", toCSV(((ResourceCollection)postOverlayResources).getResources()));
498 else
499 props.put("base.dirs", postOverlayResources.toString());
500
501
502
503 if (webApp.getClasses() != null)
504 {
505 props.put("classes.dir",webApp.getClasses().getAbsolutePath());
506 }
507
508 if (useTestScope && webApp.getTestClasses() != null)
509 {
510 props.put("testClasses.dir", webApp.getTestClasses().getAbsolutePath());
511 }
512
513
514 List<File> deps = webApp.getWebInfLib();
515 StringBuffer strbuff = new StringBuffer();
516 for (int i=0; i<deps.size(); i++)
517 {
518 File d = deps.get(i);
519 strbuff.append(d.getAbsolutePath());
520 if (i < deps.size()-1)
521 strbuff.append(",");
522 }
523 props.put("lib.jars", strbuff.toString());
524
525
526 List<Artifact> warArtifacts = getWarArtifacts();
527 for (int i=0; i<warArtifacts.size(); i++)
528 {
529 strbuff.setLength(0);
530 Artifact a = warArtifacts.get(i);
531 strbuff.append(a.getGroupId()+",");
532 strbuff.append(a.getArtifactId()+",");
533 strbuff.append(a.getFile().getAbsolutePath());
534 props.put("maven.war.artifact."+i, strbuff.toString());
535 }
536
537
538
539 WarPluginInfo warPlugin = new WarPluginInfo(project);
540
541
542 props.put("maven.war.includes", toCSV(warPlugin.getDependentMavenWarIncludes()));
543 props.put("maven.war.excludes", toCSV(warPlugin.getDependentMavenWarExcludes()));
544
545
546 List<OverlayConfig> configs = warPlugin.getMavenWarOverlayConfigs();
547 int i=0;
548 for (OverlayConfig c:configs)
549 {
550 props.put("maven.war.overlay."+(i++), c.toString());
551 }
552
553 try (OutputStream out = new BufferedOutputStream(new FileOutputStream(propsFile)))
554 {
555 props.store(out, "properties for forked webapp");
556 }
557 return propsFile;
558 }
559 catch (Exception e)
560 {
561 throw new MojoExecutionException("Prepare webapp configuration", e);
562 }
563 }
564
565
566
567
568
569
570
571
572
573
574
575 private List<Artifact> getWarArtifacts()
576 throws MalformedURLException, IOException
577 {
578 List<Artifact> warArtifacts = new ArrayList<Artifact>();
579 for ( Iterator<Artifact> iter = project.getArtifacts().iterator(); iter.hasNext(); )
580 {
581 Artifact artifact = (Artifact) iter.next();
582
583 if (artifact.getType().equals("war"))
584 warArtifacts.add(artifact);
585 }
586
587 return warArtifacts;
588 }
589
590 public boolean isPluginArtifact(Artifact artifact)
591 {
592 if (pluginArtifacts == null || pluginArtifacts.isEmpty())
593 return false;
594
595 boolean isPluginArtifact = false;
596 for (Iterator<Artifact> iter = pluginArtifacts.iterator(); iter.hasNext() && !isPluginArtifact; )
597 {
598 Artifact pluginArtifact = iter.next();
599 if (getLog().isDebugEnabled()) { getLog().debug("Checking "+pluginArtifact);}
600 if (pluginArtifact.getGroupId().equals(artifact.getGroupId()) && pluginArtifact.getArtifactId().equals(artifact.getArtifactId()))
601 isPluginArtifact = true;
602 }
603
604 return isPluginArtifact;
605 }
606
607 private Set<Artifact> getExtraJars()
608 throws Exception
609 {
610 Set<Artifact> extraJars = new HashSet<Artifact>();
611
612
613 List l = pluginArtifacts;
614 Artifact pluginArtifact = null;
615
616 if (l != null)
617 {
618 Iterator itor = l.iterator();
619 while (itor.hasNext() && pluginArtifact == null)
620 {
621 Artifact a = (Artifact)itor.next();
622 if (a.getArtifactId().equals(plugin.getArtifactId()))
623 {
624 extraJars.add(a);
625 }
626 }
627 }
628
629 return extraJars;
630 }
631
632 public String getContainerClassPath() throws Exception
633 {
634 StringBuilder classPath = new StringBuilder();
635 for (Object obj : pluginArtifacts)
636 {
637 Artifact artifact = (Artifact) obj;
638 if ("jar".equals(artifact.getType()))
639 {
640 if (classPath.length() > 0)
641 {
642 classPath.append(File.pathSeparator);
643 }
644 classPath.append(artifact.getFile().getAbsolutePath());
645
646 }
647 }
648
649
650 Set<Artifact> extraJars = getExtraJars();
651 for (Artifact a:extraJars)
652 {
653 classPath.append(File.pathSeparator);
654 classPath.append(a.getFile().getAbsolutePath());
655 }
656
657
658
659 List<String> providedJars = getProvidedJars();
660 if (providedJars != null && !providedJars.isEmpty())
661 {
662 for (String jar:providedJars)
663 {
664 classPath.append(File.pathSeparator);
665 classPath.append(jar);
666 if (getLog().isDebugEnabled()) getLog().debug("Adding provided jar: "+jar);
667 }
668 }
669
670 return classPath.toString();
671 }
672
673
674
675
676
677
678
679 private String getJavaBin()
680 {
681 String javaexes[] = new String[]
682 { "java", "java.exe" };
683
684 File javaHomeDir = new File(System.getProperty("java.home"));
685 for (String javaexe : javaexes)
686 {
687 File javabin = new File(javaHomeDir,fileSeparators("bin/" + javaexe));
688 if (javabin.exists() && javabin.isFile())
689 {
690 return javabin.getAbsolutePath();
691 }
692 }
693
694 return "java";
695 }
696
697 public static String fileSeparators(String path)
698 {
699 StringBuilder ret = new StringBuilder();
700 for (char c : path.toCharArray())
701 {
702 if ((c == '/') || (c == '\\'))
703 {
704 ret.append(File.separatorChar);
705 }
706 else
707 {
708 ret.append(c);
709 }
710 }
711 return ret.toString();
712 }
713
714 public static String pathSeparators(String path)
715 {
716 StringBuilder ret = new StringBuilder();
717 for (char c : path.toCharArray())
718 {
719 if ((c == ',') || (c == ':'))
720 {
721 ret.append(File.pathSeparatorChar);
722 }
723 else
724 {
725 ret.append(c);
726 }
727 }
728 return ret.toString();
729 }
730
731 private String createToken ()
732 {
733 return Long.toString(random.nextLong()^System.currentTimeMillis(), 36).toUpperCase(Locale.ENGLISH);
734 }
735
736 private void startPump(String mode, InputStream inputStream)
737 {
738 ConsoleStreamer pump = new ConsoleStreamer(mode,inputStream);
739 Thread thread = new Thread(pump,"ConsoleStreamer/" + mode);
740 thread.setDaemon(true);
741 thread.start();
742 }
743
744 private String toCSV (List<String> strings)
745 {
746 if (strings == null)
747 return "";
748 StringBuffer strbuff = new StringBuffer();
749 Iterator<String> itor = strings.iterator();
750 while (itor.hasNext())
751 {
752 strbuff.append(itor.next());
753 if (itor.hasNext())
754 strbuff.append(",");
755 }
756 return strbuff.toString();
757 }
758
759 private String toCSV (Resource[] resources)
760 {
761 StringBuffer rb = new StringBuffer();
762
763 for (Resource r:resources)
764 {
765 if (rb.length() > 0) rb.append(",");
766 rb.append(r.toString());
767 }
768
769 return rb.toString();
770 }
771 }