View Javadoc

1   package org.apache.tomcat.maven.plugin.tomcat7.run;
2   /*
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   */
20  
21  import org.apache.catalina.loader.WebappLoader;
22  import org.apache.commons.io.FileUtils;
23  import org.apache.commons.lang.StringUtils;
24  import org.apache.maven.artifact.Artifact;
25  import org.apache.maven.plugin.MojoExecutionException;
26  import org.apache.maven.plugins.annotations.Component;
27  import org.apache.maven.plugins.annotations.Execute;
28  import org.apache.maven.plugins.annotations.LifecyclePhase;
29  import org.apache.maven.plugins.annotations.Mojo;
30  import org.apache.maven.plugins.annotations.Parameter;
31  import org.apache.maven.plugins.annotations.ResolutionScope;
32  import org.apache.maven.shared.filtering.MavenFileFilterRequest;
33  import org.apache.maven.shared.filtering.MavenFilteringException;
34  import org.apache.tomcat.maven.common.run.ClassLoaderEntriesCalculator;
35  import org.apache.tomcat.maven.common.run.ClassLoaderEntriesCalculatorRequest;
36  import org.apache.tomcat.maven.common.run.ClassLoaderEntriesCalculatorResult;
37  import org.apache.tomcat.maven.common.run.TomcatRunException;
38  import org.codehaus.plexus.util.IOUtil;
39  import org.codehaus.plexus.util.xml.Xpp3Dom;
40  import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
41  import org.codehaus.plexus.util.xml.Xpp3DomWriter;
42  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
43  
44  import java.io.File;
45  import java.io.FileReader;
46  import java.io.FileWriter;
47  import java.io.IOException;
48  import java.io.StringWriter;
49  import java.util.List;
50  import java.util.Set;
51  
52  /**
53   * Runs the current project as a dynamic web application using an embedded Tomcat server.
54   *
55   * @author Olivier Lamy
56   * @since 2.0
57   */
58  @Mojo( name = "run", requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true )
59  @Execute( phase = LifecyclePhase.PROCESS_CLASSES )
60  public class RunMojo
61      extends AbstractRunMojo
62  {
63      // ----------------------------------------------------------------------
64      // Mojo Parameters
65      // ----------------------------------------------------------------------
66  
67  
68      /**
69       * The set of dependencies for the web application being run.
70       */
71      @Parameter( defaultValue = "${project.artifacts}", required = true, readonly = true )
72      private Set<Artifact> dependencies;
73  
74      /**
75       * The web resources directory for the web application being run.
76       */
77      @Parameter( defaultValue = "${basedir}/src/main/webapp", property = "tomcat.warSourceDirectory" )
78      private File warSourceDirectory;
79  
80  
81      /**
82       * Set the "follow standard delegation model" flag used to configure our ClassLoader.
83       *
84       * @see http://tomcat.apache.org/tomcat-7.0-doc/api/org/apache/catalina/loader/WebappLoader.html#setDelegate(boolean)
85       * @since 1.0
86       */
87      @Parameter( property = "tomcat.delegate", defaultValue = "true" )
88      private boolean delegate = true;
89  
90      /**
91       * @since 2.0
92       */
93      @Component
94      private ClassLoaderEntriesCalculator classLoaderEntriesCalculator;
95  
96      /**
97       * will add /WEB-INF/lib/*.jar and /WEB-INF/classes from war dependencies in the webappclassloader
98       *
99       * @since 2.0
100      */
101     @Parameter( property = "maven.tomcat.addWarDependenciesInClassloader", defaultValue = "true" )
102     private boolean addWarDependenciesInClassloader;
103 
104     /**
105      * will use the test classpath rather than the compile one and will add test dependencies too
106      *
107      * @since 2.0
108      */
109     @Parameter( property = "maven.tomcat.useTestClasspath", defaultValue = "false" )
110     private boolean useTestClasspath;
111 
112     /**
113      * Additional optional directories to add to the embedded tomcat classpath.
114      *
115      * @since 2.0
116      */
117     @Parameter( alias = "additionalClassesDirs" )
118     private List<String> additionalClasspathDirs;
119 
120     /**
121      * {@inheritDoc}
122      */
123     @Override
124     protected File getDocBase()
125         throws IOException
126     {
127         // https://issues.apache.org/jira/browse/MTOMCAT-239
128         // when running a jar docBase doesn't exists so create a fake one
129         if ( !warSourceDirectory.exists() )
130         {
131             // we create a temporary file in build.directory
132             final File tempDocBase = createTempDirectory( new File( project.getBuild().getDirectory() ) );
133             Runtime.getRuntime().addShutdownHook( new Thread()
134             {
135                 @Override
136                 public void run()
137                 {
138                     try
139                     {
140                         FileUtils.deleteDirectory( tempDocBase );
141                     }
142                     catch ( Exception e )
143                     {
144                         // we can consider as safe to ignore as it's located in build directory
145                     }
146                 }
147             } );
148             return tempDocBase;
149         }
150         return warSourceDirectory;
151     }
152 
153     private static File createTempDirectory( File baseTmpDirectory )
154         throws IOException
155     {
156         final File temp = File.createTempFile( "temp", Long.toString( System.nanoTime() ), baseTmpDirectory );
157 
158         if ( !( temp.delete() ) )
159         {
160             throw new IOException( "Could not delete temp file: " + temp.getAbsolutePath() );
161         }
162 
163         if ( !( temp.mkdir() ) )
164         {
165             throw new IOException( "Could not create temp directory: " + temp.getAbsolutePath() );
166         }
167 
168         return temp;
169     }
170 
171     /**
172      * {@inheritDoc}
173      */
174     @Override
175     protected File getContextFile()
176         throws MojoExecutionException
177     {
178         File temporaryContextFile = null;
179 
180         //----------------------------------------------------------------------------
181         // context attributes backgroundProcessorDelay reloadable cannot be modified at runtime.
182         // It looks only values from the file are used
183         // so here we create a temporary file with values modified
184         //----------------------------------------------------------------------------
185         FileReader fr = null;
186         FileWriter fw = null;
187         StringWriter sw = new StringWriter();
188         try
189         {
190             temporaryContextFile = File.createTempFile( "tomcat-maven-plugin", "temp-ctx-file" );
191             temporaryContextFile.deleteOnExit();
192 
193             // format to modify/create <Context backgroundProcessorDelay="5" reloadable="false">
194             if ( contextFile != null && contextFile.exists() )
195             {
196                 MavenFileFilterRequest mavenFileFilterRequest = new MavenFileFilterRequest();
197                 mavenFileFilterRequest.setFrom( contextFile );
198                 mavenFileFilterRequest.setTo( temporaryContextFile );
199                 mavenFileFilterRequest.setMavenProject( project );
200                 mavenFileFilterRequest.setMavenSession( session );
201                 mavenFileFilterRequest.setFiltering( true );
202 
203                 mavenFileFilter.copyFile( mavenFileFilterRequest );
204 
205                 fr = new FileReader( temporaryContextFile );
206                 Xpp3Dom xpp3Dom = Xpp3DomBuilder.build( fr );
207                 xpp3Dom.setAttribute( "backgroundProcessorDelay", Integer.toString( backgroundProcessorDelay ) );
208                 xpp3Dom.setAttribute( "reloadable", Boolean.toString( isContextReloadable() ) );
209                 fw = new FileWriter( temporaryContextFile );
210                 Xpp3DomWriter.write( fw, xpp3Dom );
211                 Xpp3DomWriter.write( sw, xpp3Dom );
212                 getLog().debug( " generated context file " + sw.toString() );
213             }
214             else
215             {
216                 if ( contextReloadable )
217                 {
218                     // don't care about using a complicated xml api to create one xml line :-)
219                     StringBuilder sb = new StringBuilder( "<Context " ).append( "backgroundProcessorDelay=\"" ).append(
220                         Integer.toString( backgroundProcessorDelay ) ).append( "\"" ).append(
221                         " reloadable=\"" + Boolean.toString( isContextReloadable() ) + "\"/>" );
222 
223                     getLog().debug( " generated context file " + sb.toString() );
224                     fw = new FileWriter( temporaryContextFile );
225                     fw.write( sb.toString() );
226                 }
227                 else
228                 {
229                     // no user context file and contextReloadable false so no need about creating a hack one
230                     return null;
231                 }
232             }
233         }
234         catch ( IOException e )
235         {
236             getLog().error( "error creating fake context.xml : " + e.getMessage(), e );
237             throw new MojoExecutionException( "error creating fake context.xml : " + e.getMessage(), e );
238         }
239         catch ( XmlPullParserException e )
240         {
241             getLog().error( "error creating fake context.xml : " + e.getMessage(), e );
242             throw new MojoExecutionException( "error creating fake context.xml : " + e.getMessage(), e );
243         }
244         catch ( MavenFilteringException e )
245         {
246             getLog().error( "error filtering context.xml : " + e.getMessage(), e );
247             throw new MojoExecutionException( "error filtering context.xml : " + e.getMessage(), e );
248         }
249         finally
250         {
251             IOUtil.close( fw );
252             IOUtil.close( fr );
253             IOUtil.close( sw );
254         }
255 
256         return temporaryContextFile;
257     }
258 
259     /**
260      * {@inheritDoc}
261      *
262      * @throws MojoExecutionException
263      */
264     @Override
265     protected WebappLoader createWebappLoader()
266         throws IOException, MojoExecutionException
267     {
268         WebappLoader loader = super.createWebappLoader();
269 
270         if ( useSeparateTomcatClassLoader )
271         {
272             loader.setDelegate( delegate );
273         }
274 
275         try
276         {
277             ClassLoaderEntriesCalculatorRequest request =
278                 new ClassLoaderEntriesCalculatorRequest().setDependencies( dependencies ).setLog(
279                     getLog() ).setMavenProject( project ).setAddWarDependenciesInClassloader(
280                     addWarDependenciesInClassloader ).setUseTestClassPath( useTestClasspath );
281             ClassLoaderEntriesCalculatorResult classLoaderEntriesCalculatorResult =
282                 classLoaderEntriesCalculator.calculateClassPathEntries( request );
283             List<String> classLoaderEntries = classLoaderEntriesCalculatorResult.getClassPathEntries();
284             final List<File> tmpDirectories = classLoaderEntriesCalculatorResult.getTmpDirectories();
285 
286             Runtime.getRuntime().addShutdownHook( new Thread()
287             {
288                 @Override
289                 public void run()
290                 {
291                     for ( File tmpDir : tmpDirectories )
292                     {
293                         try
294                         {
295                             FileUtils.deleteDirectory( tmpDir );
296                         }
297                         catch ( IOException e )
298                         {
299                             // ignore
300                         }
301                     }
302                 }
303             } );
304 
305             if ( classLoaderEntries != null )
306             {
307                 for ( String classLoaderEntry : classLoaderEntries )
308                 {
309                     loader.addRepository( classLoaderEntry );
310                 }
311             }
312 
313             if ( additionalClasspathDirs != null && !additionalClasspathDirs.isEmpty() )
314             {
315                 for ( String additionalClasspathDir : additionalClasspathDirs )
316                 {
317                     if ( StringUtils.isNotBlank( additionalClasspathDir ) )
318                     {
319                         File file = new File( additionalClasspathDir );
320                         if ( file.exists() )
321                         {
322                             String fileUri = file.toURI().toString();
323                             getLog().debug( "add file:" + fileUri + " as a additionalClasspathDir" );
324                             loader.addRepository( fileUri );
325                         }
326                     }
327                 }
328             }
329         }
330         catch ( TomcatRunException e )
331         {
332             throw new MojoExecutionException( e.getMessage(), e );
333         }
334 
335         return loader;
336     }
337 }