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