View Javadoc

1   //========================================================================
2   //$Id: AbstractJettyMojo.java 5511 2009-09-08 03:25:47Z janb $
3   //Copyright 2000-2004 Mort Bay Consulting Pty. Ltd.
4   //------------------------------------------------------------------------
5   //Licensed under the Apache License, Version 2.0 (the "License");
6   //you may not use this file except in compliance with the License.
7   //You may obtain a copy of the License at
8   //http://www.apache.org/licenses/LICENSE-2.0
9   //Unless required by applicable law or agreed to in writing, software
10  //distributed under the License is distributed on an "AS IS" BASIS,
11  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  //See the License for the specific language governing permissions and
13  //limitations under the License.
14  //========================================================================
15  
16  
17  package org.mortbay.jetty.plugin;
18  
19  
20  import java.io.File;
21  import java.io.FileInputStream;
22  import java.util.ArrayList;
23  import java.util.Enumeration;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Properties;
27  
28  import org.apache.maven.plugin.AbstractMojo;
29  import org.apache.maven.plugin.MojoExecutionException;
30  import org.apache.maven.plugin.MojoFailureException;
31  import org.apache.maven.project.MavenProject;
32  import org.mortbay.jetty.Server;
33  import org.mortbay.jetty.plugin.util.ConsoleScanner;
34  import org.mortbay.jetty.plugin.util.JettyPluginServer;
35  import org.mortbay.jetty.plugin.util.PluginLog;
36  import org.mortbay.jetty.plugin.util.SystemProperties;
37  import org.mortbay.jetty.plugin.util.SystemProperty;
38  import org.mortbay.util.Scanner;
39  
40  
41  
42  /**
43   * AbstractJettyMojo
44   *
45   *
46   */
47  public abstract class AbstractJettyMojo extends AbstractMojo
48  {
49      /**
50       * The proxy for the Server object
51       */
52      protected JettyPluginServer server;
53  
54  
55      /**
56       * The "virtual" webapp created by the plugin
57       * @parameter
58       */
59      protected Jetty6PluginWebAppContext webAppConfig;
60  
61  
62  
63      /**
64       * The maven project.
65       *
66       * @parameter expression="${executedProject}"
67       * @required
68       * @readonly
69       */
70      protected MavenProject project;
71  
72  
73  
74      /**
75       * The context path for the webapp. Defaults to the
76       * name of the webapp's artifact.
77       *
78       * @parameter expression="/${project.artifactId}"
79       * @required
80       */
81      protected String contextPath;
82  
83  
84      /**
85       * The temporary directory to use for the webapp.
86       * Defaults to target/jetty-tmp
87       *
88       * @parameter expression="${project.build.directory}/work"
89       * @required
90       */
91      protected File tmpDirectory;
92  
93  
94  
95      /**
96       * A webdefault.xml file to use instead
97       * of the default for the webapp. Optional.
98       *
99       * @parameter
100      */
101     protected File webDefaultXml;
102 
103 
104     /**
105      * A web.xml file to be applied AFTER
106      * the webapp's web.xml file. Useful for
107      * applying different build profiles, eg
108      * test, production etc. Optional.
109      * @parameter
110      */
111     protected File overrideWebXml;
112     
113     /**
114      * The interval in seconds to scan the webapp for changes 
115      * and restart the context if necessary. Ignored if reload
116      * is enabled. Disabled by default.
117      * 
118      * @parameter expression="${jetty.scanIntervalSeconds}" default-value="0"
119      * @required
120      */
121     protected int scanIntervalSeconds;
122     
123     
124     /**
125      * reload can be set to either 'automatic' or 'manual'
126      *
127      * if 'manual' then the context can be reloaded by a linefeed in the console
128      * if 'automatic' then traditional reloading on changed files is enabled.
129      * 
130      * @parameter expression="${jetty.reload}" default-value="automatic"
131      */
132     protected String reload;
133     
134     /**
135      * File containing system properties to be set before execution
136      * 
137      * Note that these properties will NOT override System properties
138      * that have been set on the command line, by the JVM, or directly 
139      * in the POM via systemProperties. Optional.
140      * 
141      * @parameter expression="${jetty.systemPropertiesFile}"
142      */
143     protected File systemPropertiesFile;
144 
145     /**
146      * System properties to set before execution. 
147      * Note that these properties will NOT override System properties 
148      * that have been set on the command line or by the JVM. They WILL 
149      * override System properties that have been set via systemPropertiesFile.
150      * Optional.
151      * @parameter
152      */
153     protected SystemProperties systemProperties;
154     
155     
156     
157     /**
158      * Location of a jetty xml configuration file whose contents 
159      * will be applied before any plugin configuration. Optional.
160      * @parameter
161      */
162     protected File jettyConfig;
163     
164     /**
165      * Port to listen to stop jetty on executing -DSTOP.PORT=<stopPort> 
166      * -DSTOP.KEY=<stopKey> -jar start.jar --stop
167      * @parameter
168      */
169     protected int stopPort;
170     
171     /**
172      * Key to provide when stopping jetty on executing java -DSTOP.KEY=<stopKey> 
173      * -DSTOP.PORT=<stopPort> -jar start.jar --stop
174      * @parameter
175      */
176     protected String stopKey;
177 
178     /**
179  	 * <p>
180  	 * Determines whether or not the server blocks when started. The default
181  	 * behavior (daemon = false) will cause the server to pause other processes
182  	 * while it continues to handle web requests. This is useful when starting the
183  	 * server with the intent to work with it interactively.
184  	 * </p><p>
185  	 * Often, it is desirable to let the server start and continue running subsequent
186  	 * processes in an automated build environment. This can be facilitated by setting
187  	 * daemon to true.
188  	 * </p>
189  	 * @parameter expression="${jetty.daemon}" default-value="false"
190  	 */
191  	protected boolean daemon;
192     
193     /**
194      * A scanner to check for changes to the webapp
195      */
196     protected Scanner scanner;
197     
198     /**
199      *  List of files and directories to scan
200      */
201     protected ArrayList scanList;
202     
203     /**
204      * List of Listeners for the scanner
205      */
206     protected ArrayList scannerListeners;
207     
208     
209     /**
210      * A scanner to check ENTER hits on the console
211      */
212     protected Thread consoleScanner;
213 
214     
215     public String PORT_SYSPROPERTY = "jetty.port";
216     
217     /**
218      * @return Returns the realms configured in the pom
219      */
220     public abstract Object[] getConfiguredUserRealms();
221     
222     /**
223      * @return Returns the connectors configured in the pom
224      */
225     public abstract Object[] getConfiguredConnectors();
226 
227     public abstract Object getConfiguredRequestLog();
228     
229 
230     public abstract void checkPomConfiguration() throws MojoExecutionException;
231     
232     
233     
234     public abstract void configureScanner () throws MojoExecutionException;
235     
236     
237     public abstract void applyJettyXml () throws Exception;
238     
239     
240     /**
241      * create a proxy that wraps a particular jetty version Server object
242      * @return
243      */
244     public abstract JettyPluginServer createServer() throws Exception;
245     
246     
247     public abstract void finishConfigurationBeforeStart() throws Exception;
248     
249     
250     public MavenProject getProject()
251     {
252         return this.project;
253     }
254     
255     public File getTmpDirectory()
256     {
257         return this.tmpDirectory;
258     }
259 
260     
261     public File getWebDefaultXml()
262     {
263         return this.webDefaultXml;
264     }
265     
266     public File getOverrideWebXml()
267     {
268         return this.overrideWebXml;
269     }
270     
271     /**
272      * @return Returns the contextPath.
273      */
274     public String getContextPath()
275     {
276         return this.contextPath;
277     }
278 
279     /**
280      * @return Returns the scanIntervalSeconds.
281      */
282     public int getScanIntervalSeconds()
283     {
284         return this.scanIntervalSeconds;
285     }
286 
287     /**
288      * @return returns the path to the systemPropertiesFile
289      */
290     public File getSystemPropertiesFile()
291     {
292         return this.systemPropertiesFile;
293     }
294     
295     public void setSystemPropertiesFile(File file) throws Exception
296     {
297         this.systemPropertiesFile = file;
298         FileInputStream propFile = new FileInputStream(systemPropertiesFile);
299         Properties properties = new Properties();
300         properties.load(propFile);
301         
302         if (this.systemProperties == null )
303             this.systemProperties = new SystemProperties();
304         
305         for (Enumeration keys = properties.keys(); keys.hasMoreElements();  )
306         {
307             String key = (String)keys.nextElement();
308             if ( ! systemProperties.containsSystemProperty(key) )
309             {
310                 SystemProperty prop = new SystemProperty();
311                 prop.setKey(key);
312                 prop.setValue(properties.getProperty(key));
313                 
314                 this.systemProperties.setSystemProperty(prop);
315             }
316         }
317         
318     }
319     
320     public void setSystemProperties(SystemProperties systemProperties)
321     {
322         if (this.systemProperties == null)
323             this.systemProperties = systemProperties;
324         else
325         {
326             Iterator itor = systemProperties.getSystemProperties().iterator();
327             while (itor.hasNext())
328             {
329                 SystemProperty prop = (SystemProperty)itor.next();
330                 this.systemProperties.setSystemProperty(prop);
331             }   
332         }
333     }
334 
335     public File getJettyXmlFile ()
336     {
337         return this.jettyConfig;
338     }
339 
340 
341     public JettyPluginServer getServer ()
342     {
343         return this.server;
344     }
345 
346     public void setServer (JettyPluginServer server)
347     {
348         this.server = server;
349     }
350 
351 
352     public void setScanList (ArrayList list)
353     {
354         this.scanList = new ArrayList(list);
355     }
356 
357     public ArrayList getScanList ()
358     {
359         return this.scanList;
360     }
361 
362 
363     public void setScannerListeners (ArrayList listeners)
364     {
365         this.scannerListeners = new ArrayList(listeners);
366     }
367 
368     public ArrayList getScannerListeners ()
369     {
370         return this.scannerListeners;
371     }
372 
373     public Scanner getScanner ()
374     {
375         return scanner;
376     }
377 
378     public void execute() throws MojoExecutionException, MojoFailureException
379     {
380         getLog().info("Configuring Jetty for project: " + getProject().getName());
381         PluginLog.setLog(getLog());
382         checkPomConfiguration();
383         startJetty();
384     }
385 
386 
387     public void startJetty () throws MojoExecutionException
388     {
389         try
390         {
391             getLog().debug("Starting Jetty Server ...");
392 
393             printSystemProperties();
394             setServer(createServer());
395 
396             //apply any config from a jetty.xml file first which is able to
397             //be overwritten by config in the pom.xml
398             applyJettyXml ();
399 
400             JettyPluginServer plugin=getServer();
401 
402 
403             // if the user hasn't configured their project's pom to use a
404             // different set of connectors,
405             // use the default
406             Object[] configuredConnectors = getConfiguredConnectors();
407 
408             plugin.setConnectors(configuredConnectors);
409             Object[] connectors = plugin.getConnectors();
410 
411             if (connectors == null|| connectors.length == 0)
412             {
413                 //if a SystemProperty -Djetty.port=<portnum> has been supplied, use that as the default port
414                 configuredConnectors = new Object[] { plugin.createDefaultConnector(System.getProperty(PORT_SYSPROPERTY, null)) };
415                 plugin.setConnectors(configuredConnectors);
416             }
417 
418 
419             //set up a RequestLog if one is provided
420             if (getConfiguredRequestLog() != null)
421                 getServer().setRequestLog(getConfiguredRequestLog());
422 
423             //set up the webapp and any context provided
424             getServer().configureHandlers();
425             configureWebApplication();
426             getServer().addWebApplication(webAppConfig);
427 
428 
429             // set up security realms
430             Object[] configuredRealms = getConfiguredUserRealms();
431             for (int i = 0; (configuredRealms != null) && i < configuredRealms.length; i++)
432                 getLog().debug(configuredRealms[i].getClass().getName() + ": "+ configuredRealms[i].toString());
433 
434             plugin.setUserRealms(configuredRealms);
435 
436             //do any other configuration required by the
437             //particular Jetty version
438             finishConfigurationBeforeStart();
439 
440             // start Jetty
441             server.start();
442 
443             getLog().info("Started Jetty Server");
444             
445             if(stopPort>0 && stopKey!=null)
446             {
447                 org.mortbay.jetty.plugin.util.Monitor monitor = new org.mortbay.jetty.plugin.util.Monitor(stopPort, stopKey, new Server[]{(Server)server.getProxiedObject()}, !daemon);
448                 monitor.start();
449             }
450             
451             // start the scanner thread (if necessary) on the main webapp
452             configureScanner ();
453             startScanner();
454             
455             // start the new line scanner thread if necessary
456             startConsoleScanner();
457 
458             // keep the thread going if not in daemon mode
459             if (!daemon)
460             {
461                 server.join();
462             }
463         }
464         catch (Exception e)
465         {
466             throw new MojoExecutionException("Failure", e);
467         }
468         finally
469         {
470             if (!daemon)
471             {
472                 getLog().info("Jetty server exiting.");
473             }            
474         }
475         
476     }
477     
478     
479     public abstract void restartWebApp(boolean reconfigureScanner) throws Exception;
480 
481     /**
482      * Subclasses should invoke this to setup basic info
483      * on the webapp
484      * 
485      * @throws MojoExecutionException
486      */
487     public void configureWebApplication () throws Exception
488     {
489         //use EITHER a <webAppConfig> element or the now deprecated <contextPath>, <tmpDirectory>, <webDefaultXml>, <overrideWebXml>
490         //way of doing things
491         if (webAppConfig == null)
492         {
493             webAppConfig = new Jetty6PluginWebAppContext();
494             webAppConfig.setContextPath((getContextPath().startsWith("/") ? getContextPath() : "/"+ getContextPath()));
495             if (getTmpDirectory() != null)
496                 webAppConfig.setTempDirectory(getTmpDirectory());
497             if (getWebDefaultXml() != null)
498                 webAppConfig.setDefaultsDescriptor(getWebDefaultXml().getCanonicalPath());
499             if (getOverrideWebXml() != null)
500                 webAppConfig.setOverrideDescriptor(getOverrideWebXml().getCanonicalPath());
501         }
502 
503         if (webAppConfig.getContextPath() == null)
504         {
505             webAppConfig.setContextPath((getContextPath().startsWith("/") ? getContextPath() : "/"+ getContextPath()));
506         }
507 
508         getLog().info("Context path = " + webAppConfig.getContextPath());
509         getLog().info("Tmp directory = "+ " determined at runtime");
510         getLog().info("Web defaults = "+(webAppConfig.getDefaultsDescriptor()==null?" jetty default":webAppConfig.getDefaultsDescriptor()));
511         getLog().info("Web overrides = "+(webAppConfig.getOverrideDescriptor()==null?" none":webAppConfig.getOverrideDescriptor()));
512 
513     }
514 
515     /**
516      * Run a scanner thread on the given list of files and directories, calling
517      * stop/start on the given list of LifeCycle objects if any of the watched
518      * files change.
519      *
520      */
521     private void startScanner()
522     {
523 
524         // check if scanning is enabled
525         if (getScanIntervalSeconds() <= 0) return;
526 
527         // check if reload is manual. It disables file scanning
528         if ( "manual".equalsIgnoreCase( reload ) )
529         {
530             // issue a warning if both scanIntervalSeconds and reload
531             // are enabled
532             getLog().warn("scanIntervalSeconds is set to " + scanIntervalSeconds + " but will be IGNORED due to manual reloading");
533             return;
534         }
535 
536         scanner = new Scanner();
537         scanner.setReportExistingFilesOnStartup(false);
538         scanner.setScanInterval(getScanIntervalSeconds());
539         scanner.setScanDirs(getScanList());
540         scanner.setRecursive(true);
541         List listeners = getScannerListeners();
542         Iterator itor = (listeners==null?null:listeners.iterator());
543         while (itor!=null && itor.hasNext())
544             scanner.addListener((Scanner.Listener)itor.next());
545         getLog().info("Starting scanner at interval of " + getScanIntervalSeconds()+ " seconds.");
546         scanner.start();
547     }
548     
549     /**
550      * Run a thread that monitors the console input to detect ENTER hits.
551      */
552     protected void startConsoleScanner() 
553     {
554         if ( "manual".equalsIgnoreCase( reload ) )
555         {
556             getLog().info("Console reloading is ENABLED. Hit ENTER on the console to restart the context.");
557             consoleScanner = new ConsoleScanner(this);
558             consoleScanner.start();
559         }
560         
561     }
562 
563     private void printSystemProperties ()
564     {
565         // print out which system properties were set up
566         if (getLog().isDebugEnabled())
567         {
568             if (systemProperties != null)
569             {
570                 Iterator itor = systemProperties.getSystemProperties().iterator();
571                 while (itor.hasNext())
572                 {
573                     SystemProperty prop = (SystemProperty)itor.next();
574                     getLog().debug("Property "+prop.getName()+"="+prop.getValue()+" was "+ (prop.isSet() ? "set" : "skipped"));
575                 }
576             }
577         }
578     }
579 
580     /**
581      * Try and find a jetty-web.xml file, using some
582      * historical naming conventions if necessary.
583      * @param webInfDir
584      * @return
585      */
586     public File findJettyWebXmlFile (File webInfDir)
587     {
588         if (webInfDir == null)
589             return null;
590         if (!webInfDir.exists())
591             return null;
592 
593         File f = new File (webInfDir, "jetty-web.xml");
594         if (f.exists())
595             return f;
596 
597         //try some historical alternatives
598         f = new File (webInfDir, "web-jetty.xml");
599         if (f.exists())
600             return f;
601         f = new File (webInfDir, "jetty6-web.xml");
602         if (f.exists())
603             return f;
604         
605         return null;
606     }
607 }