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