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.MavenFileFilter;
33  import org.apache.maven.shared.filtering.MavenFileFilterRequest;
34  import org.apache.maven.shared.filtering.MavenFilteringException;
35  import org.apache.tomcat.maven.common.run.ClassLoaderEntriesCalculator;
36  import org.apache.tomcat.maven.common.run.ClassLoaderEntriesCalculatorRequest;
37  import org.apache.tomcat.maven.common.run.ClassLoaderEntriesCalculatorResult;
38  import org.apache.tomcat.maven.common.run.TomcatRunException;
39  import org.codehaus.plexus.util.IOUtil;
40  import org.codehaus.plexus.util.xml.Xpp3Dom;
41  import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
42  import org.codehaus.plexus.util.xml.Xpp3DomWriter;
43  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
44  
45  import java.io.File;
46  import java.io.FileReader;
47  import java.io.FileWriter;
48  import java.io.IOException;
49  import java.io.StringWriter;
50  import java.util.List;
51  import java.util.Set;
52  
53  /**
54   * Runs the current project as a dynamic web application using an embedded Tomcat server.
55   *
56   * @author Olivier Lamy
57   * @since 2.0
58   */
59  @Mojo( name = "run", requiresDependencyResolution = ResolutionScope.TEST )
60  @Execute( phase = LifecyclePhase.PROCESS_CLASSES )
61  public class RunMojo
62      extends AbstractRunMojo
63  {
64      // ----------------------------------------------------------------------
65      // Mojo Parameters
66      // ----------------------------------------------------------------------
67  
68  
69      /**
70       * The set of dependencies for the web application being run.
71       */
72      @Parameter( defaultValue = "${project.artifacts}", required = true, readonly = true )
73      private Set<Artifact> dependencies;
74  
75      /**
76       * The web resources directory for the web application being run.
77       */
78      @Parameter( defaultValue = "${basedir}/src/main/webapp", property = "tomcat.warSourceDirectory" )
79      private File warSourceDirectory;
80  
81  
82      /**
83       * Set the "follow standard delegation model" flag used to configure our ClassLoader.
84       *
85       * @see http://tomcat.apache.org/tomcat-7.0-doc/api/org/apache/catalina/loader/WebappLoader.html#setDelegate(boolean)
86       * @since 1.0
87       */
88      @Parameter( property = "tomcat.delegate", defaultValue = "true" )
89      private boolean delegate = true;
90  
91      /**
92       * @since 2.0
93       */
94      @Component
95      private ClassLoaderEntriesCalculator classLoaderEntriesCalculator;
96  
97      /**
98       * will add /WEB-INF/lib/*.jar and /WEB-INF/classes from war dependencies in the webappclassloader
99       *
100      * @since 2.0
101      */
102     @Parameter( property = "maven.tomcat.addWarDependenciesInClassloader", defaultValue = "true" )
103     private boolean addWarDependenciesInClassloader;
104 
105     /**
106      * will use the test classpath rather than the compile one and will add test dependencies too
107      *
108      * @since 2.0
109      */
110     @Parameter( property = "maven.tomcat.useTestClasspath", defaultValue = "false" )
111     private boolean useTestClasspath;
112 
113     /**
114      * Additional optional directories to add to the embedded tomcat classpath.
115      *
116      * @since 2.0
117      */
118     @Parameter( alias = "additionalClassesDirs" )
119     private List<String> additionalClasspathDirs;
120 
121     /**
122      * {@inheritDoc}
123      */
124     @Override
125     protected File getDocBase()
126     {
127         return warSourceDirectory;
128     }
129 
130     /**
131      * {@inheritDoc}
132      */
133     @Override
134     protected File getContextFile()
135         throws MojoExecutionException
136     {
137         File temporaryContextFile = null;
138 
139         //----------------------------------------------------------------------------
140         // context attributes backgroundProcessorDelay reloadable cannot be modified at runtime.
141         // It looks only values from the file are used
142         // so here we create a temporary file with values modified
143         //----------------------------------------------------------------------------
144         FileReader fr = null;
145         FileWriter fw = null;
146         StringWriter sw = new StringWriter();
147         try
148         {
149             temporaryContextFile = File.createTempFile( "tomcat-maven-plugin", "temp-ctx-file" );
150             temporaryContextFile.deleteOnExit();
151 
152             // format to modify/create <Context backgroundProcessorDelay="5" reloadable="false">
153             if ( contextFile != null && contextFile.exists() )
154             {
155                 MavenFileFilterRequest mavenFileFilterRequest = new MavenFileFilterRequest();
156                 mavenFileFilterRequest.setFrom( contextFile );
157                 mavenFileFilterRequest.setTo( temporaryContextFile );
158                 mavenFileFilterRequest.setMavenProject( project );
159                 mavenFileFilterRequest.setMavenSession( session );
160                 mavenFileFilterRequest.setFiltering( true );
161 
162                 mavenFileFilter.copyFile( mavenFileFilterRequest );
163 
164                 fr = new FileReader( temporaryContextFile );
165                 Xpp3Dom xpp3Dom = Xpp3DomBuilder.build( fr );
166                 xpp3Dom.setAttribute( "backgroundProcessorDelay", Integer.toString( backgroundProcessorDelay ) );
167                 xpp3Dom.setAttribute( "reloadable", Boolean.toString( isContextReloadable() ) );
168                 fw = new FileWriter( temporaryContextFile );
169                 Xpp3DomWriter.write( fw, xpp3Dom );
170                 Xpp3DomWriter.write( sw, xpp3Dom );
171                 getLog().debug( " generated context file " + sw.toString() );
172             }
173             else
174             {
175                 if ( contextReloadable )
176                 {
177                     // don't care about using a complicated xml api to create one xml line :-)
178                     StringBuilder sb = new StringBuilder( "<Context " ).append( "backgroundProcessorDelay=\"" ).append(
179                         Integer.toString( backgroundProcessorDelay ) ).append( "\"" ).append(
180                         " reloadable=\"" + Boolean.toString( isContextReloadable() ) + "\"/>" );
181 
182                     getLog().debug( " generated context file " + sb.toString() );
183                     fw = new FileWriter( temporaryContextFile );
184                     fw.write( sb.toString() );
185                 }
186                 else
187                 {
188                     // no user context file and contextReloadable false so no need about creating a hack one
189                     return null;
190                 }
191             }
192         }
193         catch ( IOException e )
194         {
195             getLog().error( "error creating fake context.xml : " + e.getMessage(), e );
196             throw new MojoExecutionException( "error creating fake context.xml : " + e.getMessage(), e );
197         }
198         catch ( XmlPullParserException e )
199         {
200             getLog().error( "error creating fake context.xml : " + e.getMessage(), e );
201             throw new MojoExecutionException( "error creating fake context.xml : " + e.getMessage(), e );
202         }
203         catch ( MavenFilteringException e )
204         {
205             getLog().error( "error filtering context.xml : " + e.getMessage(), e );
206             throw new MojoExecutionException( "error filtering context.xml : " + e.getMessage(), e );
207         }
208         finally
209         {
210             IOUtil.close( fw );
211             IOUtil.close( fr );
212             IOUtil.close( sw );
213         }
214 
215         return temporaryContextFile;
216     }
217 
218     /**
219      * {@inheritDoc}
220      *
221      * @throws MojoExecutionException
222      */
223     @Override
224     protected WebappLoader createWebappLoader()
225         throws IOException, MojoExecutionException
226     {
227         WebappLoader loader = super.createWebappLoader();
228         if ( useSeparateTomcatClassLoader )
229         {
230             loader.setDelegate( delegate );
231         }
232 
233         try
234         {
235             ClassLoaderEntriesCalculatorRequest request =
236                 new ClassLoaderEntriesCalculatorRequest().setDependencies( dependencies ).setLog(
237                     getLog() ).setMavenProject( project ).setAddWarDependenciesInClassloader(
238                     addWarDependenciesInClassloader ).setUseTestClassPath( useTestClasspath );
239             ClassLoaderEntriesCalculatorResult classLoaderEntriesCalculatorResult =
240                 classLoaderEntriesCalculator.calculateClassPathEntries( request );
241             List<String> classLoaderEntries = classLoaderEntriesCalculatorResult.getClassPathEntries();
242             final List<File> tmpDirectories = classLoaderEntriesCalculatorResult.getTmpDirectories();
243 
244             Runtime.getRuntime().addShutdownHook( new Thread()
245             {
246                 @Override
247                 public void run()
248                 {
249                     for ( File tmpDir : tmpDirectories )
250                     {
251                         try
252                         {
253                             FileUtils.deleteDirectory( tmpDir );
254                         }
255                         catch ( IOException e )
256                         {
257                             // ignore
258                         }
259                     }
260                 }
261             } );
262 
263             if ( classLoaderEntries != null )
264             {
265                 for ( String classLoaderEntry : classLoaderEntries )
266                 {
267                     loader.addRepository( classLoaderEntry );
268                 }
269             }
270 
271             if ( additionalClasspathDirs != null && !additionalClasspathDirs.isEmpty() )
272             {
273                 for ( String additionalClasspathDir : additionalClasspathDirs )
274                 {
275                     if ( StringUtils.isNotBlank( additionalClasspathDir ) )
276                     {
277                         File file = new File( additionalClasspathDir );
278                         if ( file.exists() )
279                         {
280                             String fileUri = file.toURI().toString();
281                             getLog().debug( "add file:" + fileUri + " as a additionalClasspathDir" );
282                             loader.addRepository( fileUri );
283                         }
284                     }
285                 }
286             }
287         }
288         catch ( TomcatRunException e )
289         {
290             throw new MojoExecutionException( e.getMessage(), e );
291         }
292 
293         return loader;
294     }
295 }