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