View Javadoc

1   //========================================================================
2   //$Id: AbstractJettyRunMojo.java 5224 2009-05-29 07:56:29Z dyu $
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  package org.mortbay.jetty.plugin;
17  
18  import java.io.File;
19  import java.io.IOException;
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.Date;
23  import java.util.Iterator;
24  import java.util.List;
25  
26  import org.apache.maven.artifact.Artifact;
27  import org.apache.maven.plugin.MojoExecutionException;
28  import org.apache.maven.plugin.MojoFailureException;
29  import org.codehaus.plexus.util.FileUtils;
30  import org.mortbay.jetty.plugin.util.ScanTargetPattern;
31  import org.mortbay.resource.Resource;
32  import org.mortbay.resource.ResourceCollection;
33  import org.mortbay.util.Scanner;
34  
35  /**
36   * AbstractJettyRunMojo
37   * 
38   * 
39   * Base class for all jetty versions for the "run" mojo.
40   * 
41   */
42  public abstract class AbstractJettyRunMojo extends AbstractJettyMojo
43  {
44  
45      /**
46       * If true, the <testOutputDirectory>
47       * and the dependencies of <scope>test<scope>
48       * will be put first on the runtime classpath.
49       * @parameter default-value="false"
50       */
51      private boolean useTestClasspath;
52      
53      
54      /**
55       * The location of a jetty-env.xml file. Optional.
56       * @parameter
57       */
58      private File jettyEnvXml;
59      
60      /**
61       * The location of the web.xml file. If not
62       * set then it is assumed it is in ${basedir}/src/main/webapp/WEB-INF
63       * 
64       * @parameter expression="${maven.war.webxml}"
65       */
66      private File webXml;
67      
68      /**
69       * The directory containing generated classes.
70       *
71       * @parameter expression="${project.build.outputDirectory}"
72       * @required
73       * 
74       */
75      private File classesDirectory;
76      
77      
78      
79      /**
80       * The directory containing generated test classes.
81       * 
82       * @parameter expression="${project.build.testOutputDirectory}"
83       * @required
84       */
85      private File testClassesDirectory;
86      
87      /**
88       * Root directory for all html/jsp etc files
89       *
90       * @parameter expression="${basedir}/src/main/webapp"
91       * @required
92       */
93      private File webAppSourceDirectory;
94      
95      /**
96       * @parameter expression="${plugin.artifacts}"
97       * @readonly
98       */
99      private List pluginArtifacts;
100     
101     /**
102      * List of files or directories to additionally periodically scan for changes. Optional.
103      * @parameter
104      */
105     private File[] scanTargets;
106     
107     
108     /**
109      * List of directories with ant-style <include> and <exclude> patterns
110      * for extra targets to periodically scan for changes. Can be used instead of,
111      * or in conjunction with <scanTargets>.Optional.
112      * @parameter
113      */
114     private ScanTargetPattern[] scanTargetPatterns;
115 
116     /**
117      * web.xml as a File
118      */
119     private File webXmlFile;
120     
121     
122     /**
123      * jetty-env.xml as a File
124      */
125     private File jettyEnvXmlFile;
126 
127     /**
128      * List of files on the classpath for the webapp
129      */
130     private List classPathFiles;
131     
132     
133     /**
134      * Extra scan targets as a list
135      */
136     private List extraScanTargets;
137     
138     /**
139      * overlays (resources)
140      */
141     private List _overlays;
142 
143     public File getWebXml()
144     {
145         return this.webXml;
146     }
147     
148     public File getJettyEnvXml ()
149     {
150         return this.jettyEnvXml;
151     }
152 
153     public File getClassesDirectory()
154     {
155         return this.classesDirectory;
156     }
157 
158     public File getWebAppSourceDirectory()
159     {
160         return this.webAppSourceDirectory;
161     }
162 
163     public void setWebXmlFile (File f)
164     {
165         this.webXmlFile = f;
166     }
167     
168     public File getWebXmlFile ()
169     {
170         return this.webXmlFile;
171     }
172     
173     public File getJettyEnvXmlFile ()
174     {
175         return this.jettyEnvXmlFile;
176     }
177     
178     public void setJettyEnvXmlFile (File f)
179     {
180         this.jettyEnvXmlFile = f;
181     }
182     
183     public void setClassPathFiles (List list)
184     {
185         this.classPathFiles = new ArrayList(list);
186     }
187 
188     public List getClassPathFiles ()
189     {
190         return this.classPathFiles;
191     }
192 
193 
194     public List getExtraScanTargets ()
195     {
196         return this.extraScanTargets;
197     }
198     
199     public void setExtraScanTargets(List list)
200     {
201         this.extraScanTargets = list;
202     }
203 
204     /**
205      * Run the mojo
206      * @see org.apache.maven.plugin.Mojo#execute()
207      */
208     public void execute() throws MojoExecutionException, MojoFailureException
209     {
210        super.execute();
211     }
212     
213     
214     /**
215      * Verify the configuration given in the pom.
216      * 
217      * @see org.mortbay.jetty.plugin.AbstractJettyMojo#checkPomConfiguration()
218      */
219     public void checkPomConfiguration () throws MojoExecutionException
220     {
221         File buildDir = new File(getProject().getBuild().getDirectory());
222         if(!buildDir.exists())
223             buildDir.mkdir();
224         // check the location of the static content/jsps etc
225         try
226         {
227             if ((getWebAppSourceDirectory() == null) || !getWebAppSourceDirectory().exists())
228                 throw new MojoExecutionException("Webapp source directory "
229                         + (getWebAppSourceDirectory() == null ? "null" : getWebAppSourceDirectory().getCanonicalPath())
230                         + " does not exist");
231             else
232                 getLog().info( "Webapp source directory = "
233                         + getWebAppSourceDirectory().getCanonicalPath());
234         }
235         catch (IOException e)
236         {
237             throw new MojoExecutionException("Webapp source directory does not exist", e);
238         }
239         
240         // check reload mechanic
241         if ( !"automatic".equalsIgnoreCase( reload ) && !"manual".equalsIgnoreCase( reload ) )
242         {
243             throw new MojoExecutionException( "invalid reload mechanic specified, must be 'automatic' or 'manual'" );
244         }
245         else
246         {
247             getLog().info("Reload Mechanic: " + reload );
248         }
249       
250         //check if a jetty-env.xml location has been provided, if so, it must exist
251         if  (getJettyEnvXml() != null)
252         {
253             setJettyEnvXmlFile(jettyEnvXml);
254             
255             try
256             {
257                 if (!getJettyEnvXmlFile().exists())
258                     throw new MojoExecutionException("jetty-env.xml file does not exist at location "+jettyEnvXml);
259                 else
260                     getLog().info(" jetty-env.xml = "+getJettyEnvXmlFile().getCanonicalPath());
261             }
262             catch (IOException e)
263             {
264                 throw new MojoExecutionException("jetty-env.xml does not exist");
265             }
266         }
267         
268         
269         // check the classes to form a classpath with
270         try
271         {
272             //allow a webapp with no classes in it (just jsps/html)
273             if (getClassesDirectory() != null)
274             {
275                 if (!getClassesDirectory().exists())
276                     getLog().info( "Classes directory "+ getClassesDirectory().getCanonicalPath()+ " does not exist");
277                 else
278                     getLog().info("Classes = " + getClassesDirectory().getCanonicalPath());
279             }
280             else
281                 getLog().info("Classes directory not set");         
282         }
283         catch (IOException e)
284         {
285             throw new MojoExecutionException("Location of classesDirectory does not exist");
286         }
287         
288         
289         setExtraScanTargets(new ArrayList());
290         if (scanTargets != null)
291         {            
292             for (int i=0; i< scanTargets.length; i++)
293             {
294                 getLog().info("Added extra scan target:"+ scanTargets[i]);
295                 getExtraScanTargets().add(scanTargets[i]);
296             }            
297         }
298         
299         
300         if (scanTargetPatterns!=null)
301         {
302             for (int i=0;i<scanTargetPatterns.length; i++)
303             {
304                 Iterator itor = scanTargetPatterns[i].getIncludes().iterator();
305                 StringBuffer strbuff = new StringBuffer();
306                 while (itor.hasNext())
307                 {
308                     strbuff.append((String)itor.next());
309                     if (itor.hasNext())
310                         strbuff.append(",");
311                 }
312                 String includes = strbuff.toString();
313                 
314                 itor = scanTargetPatterns[i].getExcludes().iterator();
315                 strbuff= new StringBuffer();
316                 while (itor.hasNext())
317                 {
318                     strbuff.append((String)itor.next());
319                     if (itor.hasNext())
320                         strbuff.append(",");
321                 }
322                 String excludes = strbuff.toString();
323 
324                 try
325                 {
326                     List files = FileUtils.getFiles(scanTargetPatterns[i].getDirectory(), includes, excludes);
327                     itor = files.iterator();
328                     while (itor.hasNext())
329                         getLog().info("Adding extra scan target from pattern: "+itor.next());
330                     List currentTargets = getExtraScanTargets();
331                     if(currentTargets!=null && !currentTargets.isEmpty())
332                         currentTargets.addAll(files);
333                     else
334                         setExtraScanTargets(files);
335                 }
336                 catch (IOException e)
337                 {
338                     throw new MojoExecutionException(e.getMessage());
339                 }
340             }
341             
342            
343         }
344     }
345 
346     private void checkWebXml() throws MojoExecutionException
347     {
348         // get the web.xml file if one has been provided, otherwise assume it is
349         // in the webapp src directory
350         if (getWebXml() == null )
351             webXml = new File(new File(getWebAppSourceDirectory(),"WEB-INF"), "web.xml");
352         setWebXmlFile(webXml);
353         
354         try
355         {
356             if (!getWebXmlFile().exists())
357             {
358                 Resource resource = webAppConfig.getBaseResource().addPath("WEB-INF/web.xml");
359                 if(!resource.exists())
360                 {
361                     
362                     throw new MojoExecutionException( "web.xml does not exist at location "
363                             + webXmlFile.getCanonicalPath());
364                 }
365                 getLog().info( "web.xml file = " + resource);
366             }
367             else
368                 getLog().info( "web.xml file = "
369                         + webXmlFile.getCanonicalPath());
370         }
371         catch (IOException e)
372         {
373             throw new MojoExecutionException("web.xml does not exist", e);
374         }
375     }
376 
377 
378 
379     public void configureWebApplication() throws Exception
380     {
381        super.configureWebApplication();
382         setClassPathFiles(setUpClassPath());
383         checkWebXml();
384         if(webAppConfig.getWebXmlFile()==null)
385             webAppConfig.setWebXmlFile(getWebXmlFile());
386         if(webAppConfig.getJettyEnvXmlFile()==null)
387             webAppConfig.setJettyEnvXmlFile(getJettyEnvXmlFile());
388         if(webAppConfig.getClassPathFiles()==null)
389             webAppConfig.setClassPathFiles(getClassPathFiles());
390         if(webAppConfig.getWar()==null)
391             webAppConfig.setWar(getWebAppSourceDirectory().getCanonicalPath());
392         getLog().info("Webapp directory = " + getWebAppSourceDirectory().getCanonicalPath());
393 
394         webAppConfig.configure();
395     }
396     
397     public void configureScanner ()
398     {
399         // start the scanner thread (if necessary) on the main webapp
400         final ArrayList scanList = new ArrayList();
401         scanList.add(getWebXmlFile());
402         if (getJettyEnvXmlFile() != null)
403             scanList.add(getJettyEnvXmlFile());
404         File jettyWebXmlFile = findJettyWebXmlFile(new File(getWebAppSourceDirectory(),"WEB-INF"));
405         if (jettyWebXmlFile != null)
406             scanList.add(jettyWebXmlFile);
407         scanList.addAll(getExtraScanTargets());
408         scanList.add(getProject().getFile());
409         scanList.addAll(getClassPathFiles());
410         setScanList(scanList);
411         ArrayList listeners = new ArrayList();
412         listeners.add(new Scanner.BulkListener()
413         {
414             public void filesChanged (List changes)
415             {
416                 try
417                 {
418                     boolean reconfigure = changes.contains(getProject().getFile().getCanonicalPath());
419                     restartWebApp(reconfigure);
420                 }
421                 catch (Exception e)
422                 {
423                     getLog().error("Error reconfiguring/restarting webapp after change in watched files",e);
424                 }
425             }
426 
427 
428         });
429         setScannerListeners(listeners);
430     }
431 
432     public void restartWebApp(boolean reconfigureScanner) throws Exception 
433     {
434         getLog().info("restarting "+webAppConfig);
435         getLog().debug("Stopping webapp ...");
436         webAppConfig.stop();
437         getLog().debug("Reconfiguring webapp ...");
438 
439         checkPomConfiguration();
440         configureWebApplication();
441 
442         // check if we need to reconfigure the scanner,
443         // which is if the pom changes
444         if (reconfigureScanner)
445         {
446             getLog().info("Reconfiguring scanner after change to pom.xml ...");
447             scanList.clear();
448             scanList.add(getWebXmlFile());
449             if (getJettyEnvXmlFile() != null)
450                 scanList.add(getJettyEnvXmlFile());
451             scanList.addAll(getExtraScanTargets());
452             scanList.add(getProject().getFile());
453             scanList.addAll(getClassPathFiles());
454             getScanner().setScanDirs(scanList);
455         }
456 
457         getLog().debug("Restarting webapp ...");
458         webAppConfig.start();
459         getLog().info("Restart completed at "+new Date().toString());
460     }
461     
462     private List getDependencyFiles ()
463     {
464         List dependencyFiles = new ArrayList();
465         List overlays = new ArrayList();
466         for ( Iterator iter = getProject().getArtifacts().iterator(); iter.hasNext(); )
467         {
468             Artifact artifact = (Artifact) iter.next();
469             // Include runtime and compile time libraries, and possibly test libs too
470             if(artifact.getType().equals("war"))
471             {
472                 try
473                 {
474                     Resource r = Resource.newResource("jar:" + artifact.getFile().toURL().toString() + "!/");
475                     overlays.add(r);
476                     getExtraScanTargets().add(artifact.getFile());
477                 }
478                 catch(Exception e)
479                 {
480                     throw new RuntimeException(e);
481                 }
482                 continue;
483             }
484             if (((!Artifact.SCOPE_PROVIDED.equals(artifact.getScope())) && (!Artifact.SCOPE_TEST.equals( artifact.getScope()))) 
485                     ||
486                 (useTestClasspath && Artifact.SCOPE_TEST.equals( artifact.getScope())))
487             {
488                 dependencyFiles.add(artifact.getFile());
489                 getLog().debug( "Adding artifact " + artifact.getFile().getName() + " for WEB-INF/lib " );   
490             }
491         }
492         if(!overlays.isEmpty() && !isEqual(overlays, _overlays))
493         {
494             try
495             {
496                 Resource resource = _overlays==null ? webAppConfig.getBaseResource() : null;
497                 ResourceCollection rc = new ResourceCollection();
498                 if(resource==null)
499                 {
500                     // nothing configured, so we automagically enable the overlays                    
501                     int size = overlays.size()+1;
502                     Resource[] resources = new Resource[size];
503                     resources[0] = Resource.newResource(getWebAppSourceDirectory().toURL());
504                     for(int i=1; i<size; i++)
505                     {
506                         resources[i] = (Resource)overlays.get(i-1);
507                         getLog().info("Adding overlay: " + resources[i]);
508                     }
509                     rc.setResources(resources);
510                 }                
511                 else
512                 {                    
513                     if(resource instanceof ResourceCollection)
514                     {
515                         // there was a preconfigured ResourceCollection ... append the artifact wars
516                         Resource[] old = ((ResourceCollection)resource).getResources();
517                         int size = old.length + overlays.size();
518                         Resource[] resources = new Resource[size];
519                         System.arraycopy(old, 0, resources, 0, old.length);
520                         for(int i=old.length,j=0; i<size; i++,j++)
521                         {
522                             resources[i] = (Resource)overlays.get(j);
523                             getLog().info("Adding overlay: " + resources[i]);
524                         }
525                         rc.setResources(resources);
526                     }
527                     else
528                     {
529                         // baseResource was already configured w/c could be src/main/webapp
530                         if(!resource.isDirectory() && String.valueOf(resource.getFile()).endsWith(".war"))
531                         {
532                             // its a war                            
533                             resource = Resource.newResource("jar:" + resource.getURL().toString() + "!/");
534                         }
535                         int size = overlays.size()+1;
536                         Resource[] resources = new Resource[size];
537                         resources[0] = resource;
538                         for(int i=1; i<size; i++)
539                         {
540                             resources[i] = (Resource)overlays.get(i-1);
541                             getLog().info("Adding overlay: " + resources[i]);
542                         }
543                         rc.setResources(resources);
544                     }
545                 }
546                 webAppConfig.setBaseResource(rc);
547                 _overlays = overlays;
548             }
549             catch(Exception e)
550             {
551                 throw new RuntimeException(e);
552             }
553         }
554         return dependencyFiles; 
555     }
556     
557     
558    
559 
560     private List setUpClassPath()
561     {
562         List classPathFiles = new ArrayList();       
563         
564         //if using the test classes, make sure they are first
565         //on the list
566         if (useTestClasspath && (testClassesDirectory != null))
567             classPathFiles.add(testClassesDirectory);
568         
569         if (getClassesDirectory() != null)
570             classPathFiles.add(getClassesDirectory());
571         
572         //now add all of the dependencies
573         classPathFiles.addAll(getDependencyFiles());
574         
575         if (getLog().isDebugEnabled())
576         {
577             for (int i = 0; i < classPathFiles.size(); i++)
578             {
579                 getLog().debug("classpath element: "+ ((File) classPathFiles.get(i)).getName());
580             }
581         }
582         return classPathFiles;
583     }
584     
585     static boolean isEqual(List overlays1, List overlays2)
586     {
587         if(overlays2==null || overlays1.size()!=overlays2.size())
588             return false;
589         
590         for(int i=0; i<overlays1.size(); i++)
591         {
592             if(!overlays1.get(i).equals(overlays2.get(i)))
593                 return false;
594         }
595         return true;
596     }
597 
598 }