View Javadoc

1   //========================================================================
2   //$Id: AbstractJettyMojo.java 6194 2010-09-27 07:24:19Z 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      * <p>
195      * If true, execution of the plugin is skipped.
196      * </p>
197      * @parameter expression="${jetty.skip}" default-value="false"
198      */
199     protected boolean skip;
200 
201     /**
202      * A scanner to check for changes to the webapp
203      */
204     protected Scanner scanner;
205     
206     /**
207      *  List of files and directories to scan
208      */
209     protected ArrayList scanList;
210     
211     /**
212      * List of Listeners for the scanner
213      */
214     protected ArrayList scannerListeners;
215     
216     
217     /**
218      * A scanner to check ENTER hits on the console
219      */
220     protected Thread consoleScanner;
221 
222     
223     public String PORT_SYSPROPERTY = "jetty.port";
224     
225     /**
226      * @return Returns the realms configured in the pom
227      */
228     public abstract Object[] getConfiguredUserRealms();
229     
230     /**
231      * @return Returns the connectors configured in the pom
232      */
233     public abstract Object[] getConfiguredConnectors();
234 
235     public abstract Object getConfiguredRequestLog();
236     
237 
238     public abstract void checkPomConfiguration() throws MojoExecutionException;
239     
240     
241     
242     public abstract void configureScanner () throws MojoExecutionException;
243     
244     
245     public abstract void applyJettyXml () throws Exception;
246     
247     
248     /**
249      * create a proxy that wraps a particular jetty version Server object
250      * @return
251      */
252     public abstract JettyPluginServer createServer() throws Exception;
253     
254     
255     public abstract void finishConfigurationBeforeStart() throws Exception;
256     
257     
258     public MavenProject getProject()
259     {
260         return this.project;
261     }
262     
263     public File getTmpDirectory()
264     {
265         return this.tmpDirectory;
266     }
267 
268     
269     public File getWebDefaultXml()
270     {
271         return this.webDefaultXml;
272     }
273     
274     public File getOverrideWebXml()
275     {
276         return this.overrideWebXml;
277     }
278     
279     /**
280      * @return Returns the contextPath.
281      */
282     public String getContextPath()
283     {
284         return this.contextPath;
285     }
286 
287     /**
288      * @return Returns the scanIntervalSeconds.
289      */
290     public int getScanIntervalSeconds()
291     {
292         return this.scanIntervalSeconds;
293     }
294 
295     /**
296      * @return returns the path to the systemPropertiesFile
297      */
298     public File getSystemPropertiesFile()
299     {
300         return this.systemPropertiesFile;
301     }
302     
303     public void setSystemPropertiesFile(File file) throws Exception
304     {
305         this.systemPropertiesFile = file;
306         FileInputStream propFile = new FileInputStream(systemPropertiesFile);
307         Properties properties = new Properties();
308         properties.load(propFile);
309         
310         if (this.systemProperties == null )
311             this.systemProperties = new SystemProperties();
312         
313         for (Enumeration keys = properties.keys(); keys.hasMoreElements();  )
314         {
315             String key = (String)keys.nextElement();
316             if ( ! systemProperties.containsSystemProperty(key) )
317             {
318                 SystemProperty prop = new SystemProperty();
319                 prop.setKey(key);
320                 prop.setValue(properties.getProperty(key));
321                 
322                 this.systemProperties.setSystemProperty(prop);
323             }
324         }
325         
326     }
327     
328     public void setSystemProperties(SystemProperties systemProperties)
329     {
330         if (this.systemProperties == null)
331             this.systemProperties = systemProperties;
332         else
333         {
334             Iterator itor = systemProperties.getSystemProperties().iterator();
335             while (itor.hasNext())
336             {
337                 SystemProperty prop = (SystemProperty)itor.next();
338                 this.systemProperties.setSystemProperty(prop);
339             }   
340         }
341     }
342 
343     public File getJettyXmlFile ()
344     {
345         return this.jettyConfig;
346     }
347 
348 
349     public JettyPluginServer getServer ()
350     {
351         return this.server;
352     }
353 
354     public void setServer (JettyPluginServer server)
355     {
356         this.server = server;
357     }
358 
359 
360     public void setScanList (ArrayList list)
361     {
362         this.scanList = new ArrayList(list);
363     }
364 
365     public ArrayList getScanList ()
366     {
367         return this.scanList;
368     }
369 
370 
371     public void setScannerListeners (ArrayList listeners)
372     {
373         this.scannerListeners = new ArrayList(listeners);
374     }
375 
376     public ArrayList getScannerListeners ()
377     {
378         return this.scannerListeners;
379     }
380 
381     public Scanner getScanner ()
382     {
383         return scanner;
384     }
385 
386     public void execute() throws MojoExecutionException, MojoFailureException
387     {
388         getLog().info("Configuring Jetty for project: " + getProject().getName());
389         if (skip)
390         {
391             getLog().info("Skipping jetty: jetty.skip==true");
392             return;
393         }
394         PluginLog.setLog(getLog());
395         checkPomConfiguration();
396         startJetty();
397     }
398 
399 
400     public void startJetty () throws MojoExecutionException
401     {
402         try
403         {
404             getLog().debug("Starting Jetty Server ...");
405 
406             printSystemProperties();
407             setServer(createServer());
408 
409             //apply any config from a jetty.xml file first which is able to
410             //be overwritten by config in the pom.xml
411             applyJettyXml ();
412 
413             JettyPluginServer plugin=getServer();
414 
415 
416             // if the user hasn't configured their project's pom to use a
417             // different set of connectors,
418             // use the default
419             Object[] configuredConnectors = getConfiguredConnectors();
420 
421             plugin.setConnectors(configuredConnectors);
422             Object[] connectors = plugin.getConnectors();
423 
424             if (connectors == null|| connectors.length == 0)
425             {
426                 //if a SystemProperty -Djetty.port=<portnum> has been supplied, use that as the default port
427                 configuredConnectors = new Object[] { plugin.createDefaultConnector(System.getProperty(PORT_SYSPROPERTY, null)) };
428                 plugin.setConnectors(configuredConnectors);
429             }
430 
431 
432             //set up a RequestLog if one is provided
433             if (getConfiguredRequestLog() != null)
434                 getServer().setRequestLog(getConfiguredRequestLog());
435 
436             //set up the webapp and any context provided
437             getServer().configureHandlers();
438             configureWebApplication();
439             getServer().addWebApplication(webAppConfig);
440 
441 
442             // set up security realms
443             Object[] configuredRealms = getConfiguredUserRealms();
444             for (int i = 0; (configuredRealms != null) && i < configuredRealms.length; i++)
445                 getLog().debug(configuredRealms[i].getClass().getName() + ": "+ configuredRealms[i].toString());
446 
447             plugin.setUserRealms(configuredRealms);
448 
449             //do any other configuration required by the
450             //particular Jetty version
451             finishConfigurationBeforeStart();
452 
453             // start Jetty
454             server.start();
455 
456             getLog().info("Started Jetty Server");
457             
458             if(stopPort>0 && stopKey!=null)
459             {
460                 org.mortbay.jetty.plugin.util.Monitor monitor = new org.mortbay.jetty.plugin.util.Monitor(stopPort, stopKey, new Server[]{(Server)server.getProxiedObject()}, !daemon);
461                 monitor.start();
462             }
463             
464             // start the scanner thread (if necessary) on the main webapp
465             configureScanner ();
466             startScanner();
467             
468             // start the new line scanner thread if necessary
469             startConsoleScanner();
470 
471             // keep the thread going if not in daemon mode
472             if (!daemon)
473             {
474                 server.join();
475             }
476         }
477         catch (Exception e)
478         {
479             throw new MojoExecutionException("Failure", e);
480         }
481         finally
482         {
483             if (!daemon)
484             {
485                 getLog().info("Jetty server exiting.");
486             }            
487         }
488         
489     }
490     
491     
492     public abstract void restartWebApp(boolean reconfigureScanner) throws Exception;
493 
494     /**
495      * Subclasses should invoke this to setup basic info
496      * on the webapp
497      * 
498      * @throws MojoExecutionException
499      */
500     public void configureWebApplication () throws Exception
501     {
502         //use EITHER a <webAppConfig> element or the now deprecated <contextPath>, <tmpDirectory>, <webDefaultXml>, <overrideWebXml>
503         //way of doing things
504         if (webAppConfig == null)
505         {
506             webAppConfig = new Jetty6PluginWebAppContext();
507             webAppConfig.setContextPath((getContextPath().startsWith("/") ? getContextPath() : "/"+ getContextPath()));
508             if (getTmpDirectory() != null)
509                 webAppConfig.setTempDirectory(getTmpDirectory());
510             if (getWebDefaultXml() != null)
511                 webAppConfig.setDefaultsDescriptor(getWebDefaultXml().getCanonicalPath());
512             if (getOverrideWebXml() != null)
513                 webAppConfig.setOverrideDescriptor(getOverrideWebXml().getCanonicalPath());
514         }
515 
516         if (webAppConfig.getContextPath() == null)
517         {
518             webAppConfig.setContextPath((getContextPath().startsWith("/") ? getContextPath() : "/"+ getContextPath()));
519         }
520 
521         getLog().info("Context path = " + webAppConfig.getContextPath());
522         getLog().info("Tmp directory = "+ " determined at runtime");
523         getLog().info("Web defaults = "+(webAppConfig.getDefaultsDescriptor()==null?" jetty default":webAppConfig.getDefaultsDescriptor()));
524         getLog().info("Web overrides = "+(webAppConfig.getOverrideDescriptor()==null?" none":webAppConfig.getOverrideDescriptor()));
525 
526     }
527 
528     /**
529      * Run a scanner thread on the given list of files and directories, calling
530      * stop/start on the given list of LifeCycle objects if any of the watched
531      * files change.
532      *
533      */
534     private void startScanner()
535     {
536 
537         // check if scanning is enabled
538         if (getScanIntervalSeconds() <= 0) return;
539 
540         // check if reload is manual. It disables file scanning
541         if ( "manual".equalsIgnoreCase( reload ) )
542         {
543             // issue a warning if both scanIntervalSeconds and reload
544             // are enabled
545             getLog().warn("scanIntervalSeconds is set to " + scanIntervalSeconds + " but will be IGNORED due to manual reloading");
546             return;
547         }
548 
549         scanner = new Scanner();
550         scanner.setReportExistingFilesOnStartup(false);
551         scanner.setScanInterval(getScanIntervalSeconds());
552         scanner.setScanDirs(getScanList());
553         scanner.setRecursive(true);
554         List listeners = getScannerListeners();
555         Iterator itor = (listeners==null?null:listeners.iterator());
556         while (itor!=null && itor.hasNext())
557             scanner.addListener((Scanner.Listener)itor.next());
558         getLog().info("Starting scanner at interval of " + getScanIntervalSeconds()+ " seconds.");
559         scanner.start();
560     }
561     
562     /**
563      * Run a thread that monitors the console input to detect ENTER hits.
564      */
565     protected void startConsoleScanner() 
566     {
567         if ( "manual".equalsIgnoreCase( reload ) )
568         {
569             getLog().info("Console reloading is ENABLED. Hit ENTER on the console to restart the context.");
570             consoleScanner = new ConsoleScanner(this);
571             consoleScanner.start();
572         }
573         
574     }
575 
576     private void printSystemProperties ()
577     {
578         // print out which system properties were set up
579         if (getLog().isDebugEnabled())
580         {
581             if (systemProperties != null)
582             {
583                 Iterator itor = systemProperties.getSystemProperties().iterator();
584                 while (itor.hasNext())
585                 {
586                     SystemProperty prop = (SystemProperty)itor.next();
587                     getLog().debug("Property "+prop.getName()+"="+prop.getValue()+" was "+ (prop.isSet() ? "set" : "skipped"));
588                 }
589             }
590         }
591     }
592 
593     /**
594      * Try and find a jetty-web.xml file, using some
595      * historical naming conventions if necessary.
596      * @param webInfDir
597      * @return
598      */
599     public File findJettyWebXmlFile (File webInfDir)
600     {
601         if (webInfDir == null)
602             return null;
603         if (!webInfDir.exists())
604             return null;
605 
606         File f = new File (webInfDir, "jetty-web.xml");
607         if (f.exists())
608             return f;
609 
610         //try some historical alternatives
611         f = new File (webInfDir, "web-jetty.xml");
612         if (f.exists())
613             return f;
614         f = new File (webInfDir, "jetty6-web.xml");
615         if (f.exists())
616             return f;
617         
618         return null;
619     }
620 }