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