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
22 import java.io.File;
23 import java.io.FileInputStream;
24 import java.io.InputStream;
25 import java.net.MalformedURLException;
26 import java.net.URL;
27 import java.net.URLClassLoader;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Enumeration;
31 import java.util.Iterator;
32 import java.util.List;
33 import java.util.Properties;
34 import java.util.Set;
35
36 import org.apache.maven.artifact.Artifact;
37 import org.apache.maven.plugin.AbstractMojo;
38 import org.apache.maven.plugin.MojoExecutionException;
39 import org.apache.maven.plugin.MojoFailureException;
40 import org.apache.maven.project.MavenProject;
41 import org.codehaus.plexus.util.FileUtils;
42 import org.eclipse.jetty.security.LoginService;
43 import org.eclipse.jetty.server.Connector;
44 import org.eclipse.jetty.server.RequestLog;
45 import org.eclipse.jetty.server.ShutdownMonitor;
46 import org.eclipse.jetty.server.handler.ContextHandler;
47 import org.eclipse.jetty.server.handler.ContextHandlerCollection;
48 import org.eclipse.jetty.server.handler.HandlerCollection;
49 import org.eclipse.jetty.util.Scanner;
50 import org.eclipse.jetty.util.resource.Resource;
51 import org.eclipse.jetty.xml.XmlConfiguration;
52
53
54
55
56
57
58
59
60
61
62 public abstract class AbstractJettyMojo extends AbstractMojo
63 {
64
65
66
67 public String PORT_SYSPROPERTY = "jetty.port";
68
69
70
71
72
73
74
75
76
77 protected boolean useProvidedScope;
78
79
80
81
82
83
84
85
86 protected String[] excludedGoals;
87
88
89
90
91
92
93
94
95
96
97
98
99 protected ContextHandler[] contextHandlers;
100
101
102
103
104
105
106
107
108
109
110 protected LoginService[] loginServices;
111
112
113
114
115
116
117
118
119
120
121 protected RequestLog requestLog;
122
123
124
125
126
127
128
129
130
131
132 protected JettyWebAppContext webApp;
133
134
135
136
137
138
139
140
141
142
143 protected int scanIntervalSeconds;
144
145
146
147
148
149
150
151
152
153
154 protected String reload;
155
156
157
158
159
160
161
162
163
164
165
166 protected File systemPropertiesFile;
167
168
169
170
171
172
173
174
175
176
177 protected SystemProperties systemProperties;
178
179
180
181
182
183
184
185
186
187 protected String jettyXml;
188
189
190
191
192
193
194
195
196 protected int stopPort;
197
198
199
200
201
202
203
204
205 protected String stopKey;
206
207
208
209
210
211
212 protected boolean dumpOnStart;
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228 protected boolean daemon;
229
230
231
232
233
234
235
236 protected boolean skip;
237
238
239
240
241
242
243
244
245
246 protected String contextXml;
247
248
249
250
251
252
253
254
255 protected MavenProject project;
256
257
258
259
260
261
262
263
264 protected Set projectArtifacts;
265
266
267
268
269
270
271 protected org.apache.maven.plugin.MojoExecution execution;
272
273
274
275
276
277
278
279
280 protected List pluginArtifacts;
281
282
283
284
285
286
287
288
289 protected MavenServerConnector httpConnector;
290
291
292
293
294
295 protected JettyServer server = new JettyServer();
296
297
298
299
300
301 protected Scanner scanner;
302
303
304
305
306
307 protected ArrayList<File> scanList;
308
309
310
311
312
313 protected ArrayList<Scanner.BulkListener> scannerListeners;
314
315
316
317
318
319 protected Thread consoleScanner;
320
321
322
323
324
325
326 public abstract void restartWebApp(boolean reconfigureScanner) throws Exception;
327
328
329 public abstract void checkPomConfiguration() throws MojoExecutionException;
330
331
332 public abstract void configureScanner () throws MojoExecutionException;
333
334
335
336
337
338
339
340
341 public void execute() throws MojoExecutionException, MojoFailureException
342 {
343 getLog().info("Configuring Jetty for project: " + this.project.getName());
344 if (skip)
345 {
346 getLog().info("Skipping Jetty start: jetty.skip==true");
347 return;
348 }
349
350 if (isExcluded(execution.getMojoDescriptor().getGoal()))
351 {
352 getLog().info("The goal \""+execution.getMojoDescriptor().getFullGoalName()+
353 "\" has been made unavailable for this web application by an <excludedGoal> configuration.");
354 return;
355 }
356
357 configurePluginClasspath();
358 PluginLog.setLog(getLog());
359 checkPomConfiguration();
360 startJetty();
361 }
362
363
364
365
366
367
368
369 public void configurePluginClasspath() throws MojoExecutionException
370 {
371
372
373
374
375 if (useProvidedScope)
376 {
377 try
378 {
379 List<URL> provided = new ArrayList<URL>();
380 URL[] urls = null;
381
382 for ( Iterator<Artifact> iter = projectArtifacts.iterator(); iter.hasNext(); )
383 {
384 Artifact artifact = iter.next();
385 if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope()) && !isPluginArtifact(artifact))
386 {
387 provided.add(artifact.getFile().toURI().toURL());
388 if (getLog().isDebugEnabled()) { getLog().debug("Adding provided artifact: "+artifact);}
389 }
390 }
391
392 if (!provided.isEmpty())
393 {
394 urls = new URL[provided.size()];
395 provided.toArray(urls);
396 URLClassLoader loader = new URLClassLoader(urls, getClass().getClassLoader());
397 Thread.currentThread().setContextClassLoader(loader);
398 getLog().info("Plugin classpath augmented with <scope>provided</scope> dependencies: "+Arrays.toString(urls));
399 }
400 }
401 catch (MalformedURLException e)
402 {
403 throw new MojoExecutionException("Invalid url", e);
404 }
405 }
406 }
407
408
409
410
411
412
413
414
415 public boolean isPluginArtifact(Artifact artifact)
416 {
417 if (pluginArtifacts == null || pluginArtifacts.isEmpty())
418 return false;
419
420 boolean isPluginArtifact = false;
421 for (Iterator<Artifact> iter = pluginArtifacts.iterator(); iter.hasNext() && !isPluginArtifact; )
422 {
423 Artifact pluginArtifact = iter.next();
424 if (getLog().isDebugEnabled()) { getLog().debug("Checking "+pluginArtifact);}
425 if (pluginArtifact.getGroupId().equals(artifact.getGroupId()) && pluginArtifact.getArtifactId().equals(artifact.getArtifactId()))
426 isPluginArtifact = true;
427 }
428
429 return isPluginArtifact;
430 }
431
432
433
434
435
436
437
438 public void finishConfigurationBeforeStart() throws Exception
439 {
440 HandlerCollection contexts = (HandlerCollection)server.getChildHandlerByClass(ContextHandlerCollection.class);
441 if (contexts==null)
442 contexts = (HandlerCollection)server.getChildHandlerByClass(HandlerCollection.class);
443
444 for (int i=0; (this.contextHandlers != null) && (i < this.contextHandlers.length); i++)
445 {
446 contexts.addHandler(this.contextHandlers[i]);
447 }
448 }
449
450
451
452
453
454
455
456 public void applyJettyXml() throws Exception
457 {
458 if (getJettyXmlFiles() == null)
459 return;
460
461 XmlConfiguration last = null;
462 for ( File xmlFile : getJettyXmlFiles() )
463 {
464 getLog().info( "Configuring Jetty from xml configuration file = " + xmlFile.getCanonicalPath() );
465 XmlConfiguration xmlConfiguration = new XmlConfiguration(Resource.toURL(xmlFile));
466
467
468 if (last == null)
469 xmlConfiguration.getIdMap().put("Server", this.server);
470 else
471 xmlConfiguration.getIdMap().putAll(last.getIdMap());
472
473
474 Enumeration<?> ensysprop = System.getProperties().propertyNames();
475 while (ensysprop.hasMoreElements())
476 {
477 String name = (String)ensysprop.nextElement();
478 xmlConfiguration.getProperties().put(name,System.getProperty(name));
479 }
480 last = xmlConfiguration;
481 xmlConfiguration.configure();
482 }
483 }
484
485
486
487
488
489
490
491 public void startJetty () throws MojoExecutionException
492 {
493 try
494 {
495 getLog().debug("Starting Jetty Server ...");
496
497 if(stopPort>0 && stopKey!=null)
498 {
499 ShutdownMonitor monitor = ShutdownMonitor.getInstance();
500 monitor.setPort(stopPort);
501 monitor.setKey(stopKey);
502 monitor.setExitVm(!daemon);
503 }
504
505 printSystemProperties();
506
507
508
509 applyJettyXml ();
510
511
512 if (httpConnector != null)
513 {
514
515 if (httpConnector.getPort() <= 0)
516 {
517
518 String tmp = System.getProperty(PORT_SYSPROPERTY, MavenServerConnector.DEFAULT_PORT_STR);
519 httpConnector.setPort(Integer.parseInt(tmp.trim()));
520 }
521 if (httpConnector.getServer() == null)
522 httpConnector.setServer(this.server);
523 this.server.addConnector(httpConnector);
524 }
525
526
527 Connector[] connectors = this.server.getConnectors();
528 if (connectors == null|| connectors.length == 0)
529 {
530
531 if (httpConnector == null)
532 {
533 httpConnector = new MavenServerConnector();
534
535 String tmp = System.getProperty(PORT_SYSPROPERTY, MavenServerConnector.DEFAULT_PORT_STR);
536 httpConnector.setPort(Integer.parseInt(tmp.trim()));
537 }
538 if (httpConnector.getServer() == null)
539 httpConnector.setServer(this.server);
540 this.server.setConnectors(new Connector[] {httpConnector});
541 }
542
543
544 if (this.requestLog != null)
545 this.server.setRequestLog(this.requestLog);
546
547
548 this.server.configureHandlers();
549 configureWebApplication();
550 this.server.addWebApplication(webApp);
551
552
553 for (int i = 0; (this.loginServices != null) && i < this.loginServices.length; i++)
554 {
555 getLog().debug(this.loginServices[i].getClass().getName() + ": "+ this.loginServices[i].toString());
556 this.server.addBean(this.loginServices[i]);
557 }
558
559
560
561 finishConfigurationBeforeStart();
562
563
564 this.server.start();
565
566 getLog().info("Started Jetty Server");
567
568
569 if ( dumpOnStart )
570 {
571 getLog().info(this.server.dump());
572 }
573
574
575 configureScanner ();
576 startScanner();
577
578
579 startConsoleScanner();
580
581
582 if (!daemon )
583 {
584 server.join();
585 }
586 }
587 catch (Exception e)
588 {
589 throw new MojoExecutionException("Failure", e);
590 }
591 finally
592 {
593 if (!daemon )
594 {
595 getLog().info("Jetty server exiting.");
596 }
597 }
598 }
599
600
601
602
603
604
605
606
607
608
609 public void configureWebApplication () throws Exception
610 {
611
612 if (webApp == null)
613 webApp = new JettyWebAppContext();
614
615
616
617
618 if (contextXml != null)
619 {
620 File file = FileUtils.getFile(contextXml);
621 XmlConfiguration xmlConfiguration = new XmlConfiguration(Resource.toURL(file));
622 getLog().info("Applying context xml file "+contextXml);
623 xmlConfiguration.configure(webApp);
624 }
625
626
627 String cp = webApp.getContextPath();
628 if (cp == null || "".equals(cp))
629 {
630 cp = "/"+project.getArtifactId();
631 webApp.setContextPath(cp);
632 }
633
634
635 if (webApp.getTempDirectory() == null)
636 {
637 File target = new File(project.getBuild().getDirectory());
638 File tmp = new File(target,"tmp");
639 if (!tmp.exists())
640 tmp.mkdirs();
641 webApp.setTempDirectory(tmp);
642 }
643
644 getLog().info("Context path = " + webApp.getContextPath());
645 getLog().info("Tmp directory = "+ (webApp.getTempDirectory()== null? " determined at runtime": webApp.getTempDirectory()));
646 getLog().info("Web defaults = "+(webApp.getDefaultsDescriptor()==null?" jetty default":webApp.getDefaultsDescriptor()));
647 getLog().info("Web overrides = "+(webApp.getOverrideDescriptor()==null?" none":webApp.getOverrideDescriptor()));
648 }
649
650
651
652
653
654
655
656
657
658
659 private void startScanner() throws Exception
660 {
661
662 if (scanIntervalSeconds <= 0) return;
663
664
665 if ( "manual".equalsIgnoreCase( reload ) )
666 {
667
668
669 getLog().warn("scanIntervalSeconds is set to " + scanIntervalSeconds + " but will be IGNORED due to manual reloading");
670 return;
671 }
672
673 scanner = new Scanner();
674 scanner.setReportExistingFilesOnStartup(false);
675 scanner.setScanInterval(scanIntervalSeconds);
676 scanner.setScanDirs(scanList);
677 scanner.setRecursive(true);
678 Iterator itor = (this.scannerListeners==null?null:this.scannerListeners.iterator());
679 while (itor!=null && itor.hasNext())
680 scanner.addListener((Scanner.Listener)itor.next());
681 getLog().info("Starting scanner at interval of " + scanIntervalSeconds + " seconds.");
682 scanner.start();
683 }
684
685
686
687
688
689
690
691 protected void startConsoleScanner() throws Exception
692 {
693 if ( "manual".equalsIgnoreCase( reload ) )
694 {
695 getLog().info("Console reloading is ENABLED. Hit ENTER on the console to restart the context.");
696 consoleScanner = new ConsoleScanner(this);
697 consoleScanner.start();
698 }
699 }
700
701
702
703
704
705
706
707 private void printSystemProperties ()
708 {
709
710 if (getLog().isDebugEnabled())
711 {
712 if (systemProperties != null)
713 {
714 Iterator itor = systemProperties.getSystemProperties().iterator();
715 while (itor.hasNext())
716 {
717 SystemProperty prop = (SystemProperty)itor.next();
718 getLog().debug("Property "+prop.getName()+"="+prop.getValue()+" was "+ (prop.isSet() ? "set" : "skipped"));
719 }
720 }
721 }
722 }
723
724
725
726
727
728
729
730
731
732
733 public File findJettyWebXmlFile (File webInfDir)
734 {
735 if (webInfDir == null)
736 return null;
737 if (!webInfDir.exists())
738 return null;
739
740 File f = new File (webInfDir, "jetty-web.xml");
741 if (f.exists())
742 return f;
743
744
745 f = new File (webInfDir, "web-jetty.xml");
746 if (f.exists())
747 return f;
748
749 return null;
750 }
751
752
753
754
755
756
757
758
759 public void setSystemPropertiesFile(File file) throws Exception
760 {
761 this.systemPropertiesFile = file;
762 Properties properties = new Properties();
763 try (InputStream propFile = new FileInputStream(systemPropertiesFile))
764 {
765 properties.load(propFile);
766 }
767 if (this.systemProperties == null )
768 this.systemProperties = new SystemProperties();
769
770 for (Enumeration<?> keys = properties.keys(); keys.hasMoreElements(); )
771 {
772 String key = (String)keys.nextElement();
773 if ( ! systemProperties.containsSystemProperty(key) )
774 {
775 SystemProperty prop = new SystemProperty();
776 prop.setKey(key);
777 prop.setValue(properties.getProperty(key));
778
779 this.systemProperties.setSystemProperty(prop);
780 }
781 }
782 }
783
784
785
786
787
788
789
790 public void setSystemProperties(SystemProperties systemProperties)
791 {
792 if (this.systemProperties == null)
793 this.systemProperties = systemProperties;
794 else
795 {
796 for (SystemProperty prop: systemProperties.getSystemProperties())
797 {
798 this.systemProperties.setSystemProperty(prop);
799 }
800 }
801 }
802
803
804
805
806
807
808
809
810
811
812 public List<File> getJettyXmlFiles()
813 {
814 if ( this.jettyXml == null )
815 {
816 return null;
817 }
818
819 List<File> jettyXmlFiles = new ArrayList<File>();
820
821 if ( this.jettyXml.indexOf(',') == -1 )
822 {
823 jettyXmlFiles.add( new File( this.jettyXml ) );
824 }
825 else
826 {
827 String[] files = this.jettyXml.split(",");
828
829 for ( String file : files )
830 {
831 jettyXmlFiles.add( new File(file) );
832 }
833 }
834
835 return jettyXmlFiles;
836 }
837
838
839
840
841
842
843
844 public boolean isExcluded (String goal)
845 {
846 if (excludedGoals == null || goal == null)
847 return false;
848
849 goal = goal.trim();
850 if ("".equals(goal))
851 return false;
852
853 boolean excluded = false;
854 for (int i=0; i<excludedGoals.length && !excluded; i++)
855 {
856 if (excludedGoals[i].equalsIgnoreCase(goal))
857 excluded = true;
858 }
859
860 return excluded;
861 }
862 }