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.Context;
22  import org.apache.catalina.Host;
23  import org.apache.catalina.LifecycleException;
24  import org.apache.catalina.Wrapper;
25  import org.apache.catalina.connector.Connector;
26  import org.apache.catalina.core.StandardContext;
27  import org.apache.catalina.loader.WebappLoader;
28  import org.apache.catalina.realm.MemoryRealm;
29  import org.apache.catalina.servlets.DefaultServlet;
30  import org.apache.catalina.startup.Catalina;
31  import org.apache.catalina.startup.CatalinaProperties;
32  import org.apache.catalina.startup.Tomcat;
33  import org.apache.catalina.valves.AccessLogValve;
34  import org.apache.commons.io.IOUtils;
35  import org.apache.commons.lang.StringUtils;
36  import org.apache.maven.artifact.Artifact;
37  import org.apache.maven.artifact.factory.ArtifactFactory;
38  import org.apache.maven.artifact.repository.ArtifactRepository;
39  import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
40  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
41  import org.apache.maven.artifact.resolver.ArtifactResolver;
42  import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
43  import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
44  import org.apache.maven.artifact.versioning.VersionRange;
45  import org.apache.maven.execution.MavenSession;
46  import org.apache.maven.plugin.MojoExecutionException;
47  import org.apache.maven.plugin.MojoFailureException;
48  import org.apache.maven.plugins.annotations.Component;
49  import org.apache.maven.plugins.annotations.Parameter;
50  import org.apache.maven.project.MavenProject;
51  import org.apache.maven.shared.filtering.MavenFileFilter;
52  import org.apache.maven.shared.filtering.MavenFileFilterRequest;
53  import org.apache.maven.shared.filtering.MavenFilteringException;
54  import org.apache.naming.NamingEntry;
55  import org.apache.naming.resources.FileDirContext;
56  import org.apache.tomcat.maven.common.config.AbstractWebapp;
57  import org.apache.tomcat.maven.common.run.EmbeddedRegistry;
58  import org.apache.tomcat.maven.common.run.ExternalRepositoriesReloadableWebappLoader;
59  import org.apache.tomcat.maven.plugin.tomcat7.AbstractTomcat7Mojo;
60  import org.codehaus.plexus.archiver.ArchiverException;
61  import org.codehaus.plexus.archiver.UnArchiver;
62  import org.codehaus.plexus.archiver.manager.ArchiverManager;
63  import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
64  import org.codehaus.plexus.classworlds.ClassWorld;
65  import org.codehaus.plexus.classworlds.realm.ClassRealm;
66  import org.codehaus.plexus.classworlds.realm.DuplicateRealmException;
67  import org.codehaus.plexus.util.DirectoryScanner;
68  import org.codehaus.plexus.util.FileUtils;
69  import org.w3c.dom.Document;
70  import org.w3c.dom.NamedNodeMap;
71  import org.w3c.dom.Node;
72  import org.xml.sax.SAXException;
73  
74  import javax.naming.NamingException;
75  import javax.servlet.ServletException;
76  import javax.xml.parsers.DocumentBuilder;
77  import javax.xml.parsers.DocumentBuilderFactory;
78  import javax.xml.parsers.ParserConfigurationException;
79  import javax.xml.stream.XMLInputFactory;
80  import javax.xml.stream.XMLStreamConstants;
81  import javax.xml.stream.XMLStreamException;
82  import javax.xml.stream.XMLStreamReader;
83  import java.io.File;
84  import java.io.FileInputStream;
85  import java.io.FileNotFoundException;
86  import java.io.FileOutputStream;
87  import java.io.IOException;
88  import java.net.MalformedURLException;
89  import java.net.URL;
90  import java.util.ArrayList;
91  import java.util.Collection;
92  import java.util.Collections;
93  import java.util.Iterator;
94  import java.util.List;
95  import java.util.Map;
96  import java.util.Properties;
97  import java.util.Set;
98  
99  /**
100  * @author Olivier Lamy
101  * @since 2.0
102  */
103 public abstract class AbstractRunMojo
104     extends AbstractTomcat7Mojo
105 {
106     // ---------------------------------------------------------------------
107     // Mojo Components
108     // ---------------------------------------------------------------------
109 
110     /**
111      * Used to look up Artifacts in the remote repository.
112      */
113     @Component
114     protected ArtifactFactory factory;
115 
116     /**
117      * Location of the local repository.
118      */
119     @Parameter( defaultValue = "${localRepository}", required = true, readonly = true )
120     private ArtifactRepository local;
121 
122     /**
123      * Used to look up Artifacts in the remote repository.
124      */
125     @Component
126     protected ArtifactResolver resolver;
127 
128     // ----------------------------------------------------------------------
129     // Mojo Parameters
130     // ----------------------------------------------------------------------
131 
132     /**
133      * The packaging of the Maven project that this goal operates upon.
134      */
135     @Parameter( defaultValue = "${project.packaging}", required = true, readonly = true )
136     private String packaging;
137 
138     /**
139      * The directory to create the Tomcat server configuration under.
140      */
141     @Parameter( defaultValue = "${project.build.directory}/tomcat" )
142     private File configurationDir;
143 
144     /**
145      * The port to run the Tomcat server on.
146      * Will be exposed as System props and session.executionProperties with key tomcat.maven.http.port
147      */
148     @Parameter( property = "maven.tomcat.port", defaultValue = "8080" )
149     private int port;
150 
151     /**
152      * The AJP port to run the Tomcat server on.
153      * By default it's 0 this means won't be started.
154      * The ajp connector will be started only for value > 0.
155      * Will be exposed as System props and session.executionProperties with key tomcat.maven.ajp.port
156      *
157      * @since 2.0
158      */
159     @Parameter( property = "maven.tomcat.ajp.port", defaultValue = "0" )
160     private int ajpPort;
161 
162     /**
163      * The AJP protocol to run the Tomcat server on.
164      * By default it's ajp.
165      * NOTE The ajp connector will be started only if {@link #ajpPort} > 0.
166      * possible values are:
167      * <ul>
168      * <li>org.apache.coyote.ajp.AjpProtocol - new blocking Java connector that supports an executor</li>
169      * <li>org.apache.coyote.ajp.AjpAprProtocol - the APR/native connector.</li>
170      * </ul>
171      *
172      * @since 2.0
173      */
174     @Parameter( property = "maven.tomcat.ajp.protocol", defaultValue = "org.apache.coyote.ajp.AjpProtocol" )
175     private String ajpProtocol;
176 
177     /**
178      * The https port to run the Tomcat server on.
179      * By default it's 0 this means won't be started.
180      * The https connector will be started only for value > 0.
181      * Will be exposed as System props and session.executionProperties with key tomcat.maven.https.port
182      *
183      * @since 1.0
184      */
185     @Parameter( property = "maven.tomcat.httpsPort", defaultValue = "0" )
186     private int httpsPort;
187 
188     /**
189      * The character encoding to use for decoding URIs.
190      *
191      * @since 1.0
192      */
193     @Parameter( property = "maven.tomcat.uriEncoding", defaultValue = "ISO-8859-1" )
194     private String uriEncoding;
195 
196     /**
197      * List of System properties to pass to the Tomcat Server.
198      *
199      * @since 1.0-alpha-2
200      */
201     @Parameter
202     private Map<String, String> systemProperties;
203 
204     /**
205      * The directory contains additional configuration Files that copied in the Tomcat conf Directory.
206      *
207      * @since 1.0-alpha-2
208      */
209     @Parameter( property = "maven.tomcat.additionalConfigFilesDir", defaultValue = "${basedir}/src/main/tomcatconf" )
210     private File additionalConfigFilesDir;
211 
212     /**
213      * server.xml to use <b>Note if you use this you must configure in this file your webapp paths</b>.
214      *
215      * @since 1.0-alpha-2
216      */
217     @Parameter( property = "maven.tomcat.serverXml" )
218     private File serverXml;
219 
220     /**
221      * overriding the providing web.xml to run tomcat
222      * <b>This override the global Tomcat web.xml located in $CATALINA_HOME/conf/</b>
223      *
224      * @since 1.0-alpha-2
225      */
226     @Parameter( property = "maven.tomcat.webXml" )
227     private File tomcatWebXml;
228 
229     /**
230      * Set this to true to allow Maven to continue to execute after invoking
231      * the run goal.
232      *
233      * @since 1.0
234      */
235     @Parameter( property = "maven.tomcat.fork", defaultValue = "false" )
236     private boolean fork;
237 
238     /**
239      * Will create a tomcat context for each dependencies of war type with 'scope' set to 'tomcat'.
240      * In other words, dependencies with:
241      * <pre>
242      *    &lt;type&gt;war&lt;/type&gt;
243      *    &lt;scope&gt;tomcat&lt;/scope&gt;
244      * </pre>
245      * To preserve backward compatibility it's false by default.
246      *
247      * @since 1.0
248      * @deprecated use webapps instead
249      */
250     @Parameter( property = "maven.tomcat.addContextWarDependencies", defaultValue = "false" )
251     private boolean addContextWarDependencies;
252 
253     /**
254      * The maven project.
255      *
256      * @since 1.0
257      */
258     @Component
259     protected MavenProject project;
260 
261     /**
262      * The archive manager.
263      *
264      * @since 1.0
265      */
266     @Component
267     private ArchiverManager archiverManager;
268 
269     /**
270      * if <code>true</code> a new classLoader separated from maven core will be created to start tomcat.
271      *
272      * @since 1.0
273      */
274     @Parameter( property = "tomcat.useSeparateTomcatClassLoader", defaultValue = "false" )
275     protected boolean useSeparateTomcatClassLoader;
276 
277     /**
278      * @since 1.0
279      */
280     @Parameter( defaultValue = "${plugin.artifacts}", required = true )
281     private List<Artifact> pluginArtifacts;
282 
283     /**
284      * If set to true ignore if packaging of project is not 'war'.
285      *
286      * @since 1.0
287      */
288     @Parameter( property = "tomcat.ignorePackaging", defaultValue = "false" )
289     private boolean ignorePackaging;
290 
291     /**
292      * Override the default keystoreFile for the HTTPS connector (if enabled)
293      *
294      * @since 1.1
295      */
296     @Parameter
297     private String keystoreFile;
298 
299     /**
300      * Override the default keystorePass for the HTTPS connector (if enabled)
301      *
302      * @since 1.1
303      */
304     @Parameter
305     private String keystorePass;
306 
307     /**
308      * Override the type of keystore file to be used for the server certificate. If not specified, the default value is "JKS".
309      *
310      * @since 2.0
311      */
312     @Parameter( defaultValue = "JKS" )
313     private String keystoreType;
314 
315     /**
316      * <p>
317      * Enables or disables naming support for the embedded Tomcat server.
318      * </p>
319      * <p>
320      * <strong>Note:</strong> This setting is ignored if you provide a <code>server.xml</code> for your
321      * Tomcat. Instead please configure naming in the <code>server.xml</code>.
322      * </p>
323      *
324      * @see <a href="http://tomcat.apache.org/tomcat-6.0-doc/api/org/apache/catalina/startup/Embedded.html">org.apache.catalina.startup.Embedded</a>
325      * @see <a href="http://tomcat.apache.org/tomcat-7.0-doc/api/org/apache/catalina/startup/Tomcat.html">org.apache.catalina.startup.Tomcat</a>
326      * @since 2.0
327      */
328     @Parameter( property = "maven.tomcat.useNaming", defaultValue = "true" )
329     private boolean useNaming;
330 
331     /**
332      * Force context scanning if you don't use a context file with reloadable = "true".
333      * The other way to use contextReloadable is to add attribute reloadable = "true"
334      * in your context file.
335      *
336      * @since 2.0
337      */
338     @Parameter( property = "maven.tomcat.contextReloadable", defaultValue = "false" )
339     protected boolean contextReloadable;
340 
341     /**
342      * represents the delay in seconds between each classPathScanning change invocation
343      *
344      * @see <a href="http://tomcat.apache.org/tomcat-7.0-doc/config/context.html">http://tomcat.apache.org/tomcat-7.0-doc/config/context.html</a>
345      */
346     @Parameter( property = "maven.tomcat.backgroundProcessorDelay", defaultValue = "-1" )
347     protected int backgroundProcessorDelay = -1;
348 
349 
350     /**
351      * <p>The path of the Tomcat context XML file.</p>
352      * <p>Since release 2.0, the file is filtered as a maven resource so you can use
353      * interpolation tokens ${ }</p>
354      */
355     @Parameter( property = "maven.tomcat.contextFile" )
356     protected File contextFile;
357 
358     /**
359      * The default context file to check for if contextFile not configured.
360      * If no contextFile configured and the below default not present, no
361      * contextFile will be sent to Tomcat, resulting in the latter's default
362      * context configuration being used instead.
363      */
364     @Parameter( defaultValue = "${project.build.directory}/${project.build.finalName}/META-INF/context.xml",
365                 readonly = true )
366     private File defaultContextFile;
367 
368     /**
369      * The protocol to run the Tomcat server on.
370      * By default it's HTTP/1.1.
371      *
372      * @since 2.0
373      */
374     @Parameter( property = "maven.tomcat.protocol", defaultValue = "HTTP/1.1" )
375     private String protocol;
376 
377     /**
378      * The path of the Tomcat users XML file.
379      *
380      * @since 2.0
381      */
382     @Parameter( property = "maven.tomcat.tomcatUsers.file" )
383     private File tomcatUsers;
384 
385     /**
386      * The path of the Tomcat logging configuration.
387      *
388      * @since 2.0
389      */
390     @Parameter( property = "maven.tomcat.tomcatLogging.file" )
391     private File tomcatLoggingFile;
392 
393     /**
394      * Skip execution
395      *
396      * @since 2.0
397      */
398     @Parameter( property = "maven.tomcat.skip", defaultValue = "false" )
399     protected boolean skip;
400 
401     /**
402      * @see {@link Webapp}
403      * @since 2.0
404      */
405     @Parameter
406     private List<Webapp> webapps;
407 
408     /**
409      * The static context
410      *
411      * @since 2.0
412      */
413     @Parameter( property = "maven.tomcat.staticContextPath", defaultValue = "/" )
414     private String staticContextPath;
415 
416     /**
417      * The static context docroot base fully qualified path
418      * if <code>null</code> static context won't be added
419      *
420      * @since 2.0
421      */
422     @Parameter( property = "maven.tomcat.staticContextDocbase" )
423     private String staticContextDocbase;
424 
425     /**
426      * Class loader class to set.
427      *
428      * @since 2.0
429      */
430     @Parameter
431     protected String classLoaderClass;
432 
433     @Parameter( defaultValue = "${session}", readonly = true, required = true )
434     protected MavenSession session;
435 
436     /**
437      * Will dump port in a properties file (see ports for property names).
438      * If empty no file generated
439      */
440     @Parameter( property = "maven.tomcat.propertiesPortFilePath" )
441     protected String propertiesPortFilePath;
442 
443     /**
444      * configure host name
445      *
446      * @since 2.0
447      */
448     @Parameter( property = "maven.tomcat.hostName", defaultValue = "localhost" )
449     protected String hostName;
450 
451     /**
452      * configure aliases
453      * see <a href="http://tomcat.apache.org/tomcat-7.0-doc/config/host.html#Host_Name_Aliases">Host Name aliases</a>
454      *
455      * @since 2.0
456      */
457     @Parameter
458     protected String[] aliases;
459 
460     /**
461      * enable client authentication for https (if configured)
462      * see <a href="http://tomcat.apache.org/tomcat-7.0-doc/config/http.html#SSL_Support_-_BIO_and_NIO">http://tomcat.apache.org/tomcat-7.0-doc/config/http.html#SSL_Support_-_BIO_and_NIO</a>
463      *
464      * @since 2.1
465      */
466     @Parameter( property = "maven.tomcat.https.clientAuth", defaultValue = "false" )
467     protected String clientAuth = "false";
468 
469     @Component( role = MavenFileFilter.class, hint = "default" )
470     protected MavenFileFilter mavenFileFilter;
471 
472     // ----------------------------------------------------------------------
473     // Fields
474     // ----------------------------------------------------------------------
475 
476     /**
477      * @since 1.0
478      */
479     private ClassRealm tomcatRealm;
480 
481     // ----------------------------------------------------------------------
482     // Mojo Implementation
483     // ----------------------------------------------------------------------
484 
485     /**
486      * {@inheritDoc}
487      */
488     public void execute()
489         throws MojoExecutionException, MojoFailureException
490     {
491         if ( skip )
492         {
493             getLog().info( "skip execution" );
494             return;
495         }
496         // ensure project is a web application
497         if ( !isWar() && !addContextWarDependencies && getAdditionalWebapps().isEmpty() )
498         {
499             getLog().info( messagesProvider.getMessage( "AbstractRunMojo.nonWar" ) );
500             return;
501         }
502         ClassLoader originalClassLoader = null;
503         if ( useSeparateTomcatClassLoader )
504         {
505             originalClassLoader = Thread.currentThread().getContextClassLoader();
506         }
507         try
508         {
509             getLog().info( messagesProvider.getMessage( "AbstractRunMojo.runningWar", getWebappUrl() ) );
510 
511             initConfiguration();
512             startContainer();
513             if ( !fork )
514             {
515                 waitIndefinitely();
516             }
517         }
518         catch ( LifecycleException exception )
519         {
520             throw new MojoExecutionException( messagesProvider.getMessage( "AbstractRunMojo.cannotStart" ), exception );
521         }
522         catch ( IOException exception )
523         {
524             throw new MojoExecutionException(
525                 messagesProvider.getMessage( "AbstractRunMojo.cannotCreateConfiguration" ), exception );
526         }
527         catch ( ServletException e )
528         {
529             throw new MojoExecutionException( e.getMessage(), e );
530         }
531         catch ( MavenFilteringException e )
532         {
533             throw new MojoExecutionException( "filtering issue: " + e.getMessage(), e );
534         }
535         finally
536         {
537             if ( useSeparateTomcatClassLoader )
538             {
539                 Thread.currentThread().setContextClassLoader( originalClassLoader );
540             }
541         }
542     }
543 
544     // ----------------------------------------------------------------------
545     // Protected Methods
546     // ----------------------------------------------------------------------
547 
548     /**
549      * Gets the webapp context path to use for the web application being run.
550      *
551      * @return the webapp context path
552      */
553     protected String getPath()
554     {
555         return path;
556     }
557 
558     /**
559      * Gets the context to run this web application under for the specified embedded Tomcat.
560      *
561      * @param container the embedded Tomcat container being used
562      * @return the context to run this web application under
563      * @throws IOException            if the context could not be created
564      * @throws MojoExecutionException in case of an error creating the context
565      */
566     protected Context createContext( Tomcat container )
567         throws IOException, MojoExecutionException, ServletException
568     {
569         String contextPath = getPath();
570 
571         String baseDir = getDocBase().getAbsolutePath();
572 
573         File overriddenContextFile = getContextFile();
574 
575         StandardContext standardContext = null;
576 
577         if ( overriddenContextFile != null && overriddenContextFile.exists() )
578         {
579             standardContext = parseContextFile( overriddenContextFile );
580         }
581         else if ( defaultContextFile.exists() )
582         {
583             standardContext = parseContextFile( defaultContextFile );
584         }
585 
586         if ( standardContext != null )
587         {
588             if ( standardContext.getPath() != null )
589             {
590                 contextPath = standardContext.getPath();
591             }
592             if ( standardContext.getDocBase() != null )
593             {
594                 baseDir = standardContext.getDocBase();
595             }
596         }
597 
598         contextPath = "/".equals( contextPath ) ? "" : contextPath;
599 
600         getLog().info( "create webapp with contextPath: " + contextPath );
601 
602         Context context = container.addWebapp( contextPath, baseDir );
603 
604         context.setResources(
605             new MyDirContext( new File( project.getBuild().getOutputDirectory() ).getAbsolutePath() ) );
606 
607         if ( useSeparateTomcatClassLoader )
608         {
609             context.setParentClassLoader( getTomcatClassLoader() );
610         }
611 
612         final WebappLoader loader = createWebappLoader();
613 
614         context.setLoader( loader );
615 
616         if ( overriddenContextFile != null )
617         {
618             // here, send file to Tomcat for it to complain if missing
619             context.setConfigFile( overriddenContextFile.toURI().toURL() );
620         }
621         else if ( defaultContextFile.exists() )
622         {
623             // here, only sending default file if it indeed exists
624             // otherwise Tomcat will create a default context
625             context.setConfigFile( defaultContextFile.toURI().toURL() );
626         }
627 
628         if ( classLoaderClass != null )
629         {
630             loader.setLoaderClass( classLoaderClass );
631         }
632 
633         return context;
634 
635     }
636 
637     protected StandardContext parseContextFile( File file )
638         throws MojoExecutionException
639     {
640         try
641         {
642             StandardContext standardContext = new StandardContext();
643             XMLStreamReader reader = XMLInputFactory.newFactory().createXMLStreamReader( new FileInputStream( file ) );
644 
645             int tag = reader.next();
646 
647             while ( true )
648             {
649                 if ( tag == XMLStreamConstants.START_ELEMENT && StringUtils.equals( "Context", reader.getLocalName() ) )
650                 {
651                     String path = reader.getAttributeValue( null, "path" );
652                     if ( StringUtils.isNotBlank( path ) )
653                     {
654                         standardContext.setPath( path );
655                     }
656 
657                     String docBase = reader.getAttributeValue( null, "docBase" );
658                     if ( StringUtils.isNotBlank( docBase ) )
659                     {
660                         standardContext.setDocBase( docBase );
661                     }
662                 }
663                 if ( !reader.hasNext() )
664                 {
665                     break;
666                 }
667                 tag = reader.next();
668             }
669 
670             return standardContext;
671         }
672         catch ( XMLStreamException e )
673         {
674             throw new MojoExecutionException( e.getMessage(), e );
675         }
676         catch ( FileNotFoundException e )
677         {
678             throw new MojoExecutionException( e.getMessage(), e );
679         }
680     }
681 
682 
683     private static class MyDirContext
684         extends FileDirContext
685     {
686         String buildOutputDirectory;
687 
688         MyDirContext( String buildOutputDirectory )
689         {
690             this.buildOutputDirectory = buildOutputDirectory;
691         }
692 
693         @Override
694         protected List<NamingEntry> doListBindings( String name )
695             throws NamingException
696         {
697             if ( "/WEB-INF/classes".equals( name ) )
698             {
699                 if ( !new File( buildOutputDirectory ).exists() )
700                 {
701                     return Collections.emptyList();
702                 }
703                 FileDirContext fileDirContext = new FileDirContext();
704                 fileDirContext.setDocBase( buildOutputDirectory );
705                 NamingEntry namingEntry = new NamingEntry( "/WEB-INF/classes", fileDirContext, -1 );
706                 return Collections.singletonList( namingEntry );
707             }
708 
709             return super.doListBindings( name );
710         }
711     }
712 
713     /**
714      * Gets the webapp loader to run this web application under.
715      *
716      * @return the webapp loader to use
717      * @throws IOException            if the webapp loader could not be created
718      * @throws MojoExecutionException in case of an error creating the webapp loader
719      */
720     protected WebappLoader createWebappLoader()
721         throws IOException, MojoExecutionException
722     {
723         if ( useSeparateTomcatClassLoader )
724         {
725             return ( isContextReloadable() )
726                 ? new ExternalRepositoriesReloadableWebappLoader( getTomcatClassLoader(), getLog() )
727                 : new WebappLoader( getTomcatClassLoader() );
728         }
729 
730         return ( isContextReloadable() )
731             ? new ExternalRepositoriesReloadableWebappLoader( Thread.currentThread().getContextClassLoader(), getLog() )
732             : new WebappLoader( Thread.currentThread().getContextClassLoader() );
733     }
734 
735     /**
736      * Determine whether the passed context.xml file declares the context as reloadable or not.
737      *
738      * @return false by default, true if  reloadable="true" in context.xml.
739      */
740     protected boolean isContextReloadable()
741         throws MojoExecutionException
742     {
743         if ( contextReloadable || backgroundProcessorDelay > 0 )
744         {
745             return true;
746         }
747         // determine whether to use a reloadable Loader or not (default is false).
748         boolean reloadable = false;
749         try
750         {
751             if ( contextFile != null && contextFile.exists() )
752             {
753                 DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
754                 DocumentBuilder builder = builderFactory.newDocumentBuilder();
755                 Document contextDoc = builder.parse( contextFile );
756                 contextDoc.getDocumentElement().normalize();
757 
758                 NamedNodeMap nodeMap = contextDoc.getDocumentElement().getAttributes();
759                 Node reloadableAttribute = nodeMap.getNamedItem( "reloadable" );
760 
761                 reloadable =
762                     ( reloadableAttribute != null ) ? Boolean.valueOf( reloadableAttribute.getNodeValue() ) : false;
763             }
764             getLog().debug( "context reloadable: " + reloadable );
765         }
766         catch ( IOException ioe )
767         {
768             getLog().error( "Could not parse file: [" + contextFile.getAbsolutePath() + "]", ioe );
769         }
770         catch ( ParserConfigurationException pce )
771         {
772             getLog().error( "Could not configure XML parser", pce );
773         }
774         catch ( SAXException se )
775         {
776             getLog().error( "Could not parse file: [" + contextFile.getAbsolutePath() + "]", se );
777         }
778 
779         return reloadable;
780     }
781 
782 
783     /**
784      * Gets the webapp directory to run.
785      *
786      * @return the webapp directory
787      */
788     protected abstract File getDocBase();
789 
790     /**
791      * Gets the Tomcat context XML file to use.
792      *
793      * @return the context XML file
794      */
795     protected abstract File getContextFile()
796         throws MojoExecutionException;
797 
798     // ----------------------------------------------------------------------
799     // Private Methods
800     // ----------------------------------------------------------------------
801 
802     /**
803      * Gets whether this project uses WAR packaging.
804      *
805      * @return whether this project uses WAR packaging
806      */
807     protected boolean isWar()
808     {
809         return "war".equals( packaging ) || ignorePackaging;
810     }
811 
812     /**
813      * Gets the URL of the running webapp.
814      *
815      * @return the URL of the running webapp
816      * @throws java.net.MalformedURLException if the running webapp URL is invalid
817      */
818     private URL getWebappUrl()
819         throws MalformedURLException
820     {
821         return new URL( "http", "localhost", port, getPath() );
822     }
823 
824     /**
825      * FIXME not sure we need all of those files with tomcat7
826      * Creates the Tomcat configuration directory with the necessary resources.
827      *
828      * @throws IOException            if the Tomcat configuration could not be created
829      * @throws MojoExecutionException if the Tomcat configuration could not be created
830      */
831     private void initConfiguration()
832         throws IOException, MojoExecutionException, MavenFilteringException
833     {
834         if ( configurationDir.exists() )
835         {
836             getLog().info( messagesProvider.getMessage( "AbstractRunMojo.usingConfiguration", configurationDir ) );
837         }
838         else
839         {
840             getLog().info( messagesProvider.getMessage( "AbstractRunMojo.creatingConfiguration", configurationDir ) );
841 
842             configurationDir.mkdirs();
843 
844             File confDir = new File( configurationDir, "conf" );
845             confDir.mkdir();
846 
847             if ( tomcatLoggingFile != null )
848             {
849                 FileUtils.copyFile( tomcatLoggingFile, new File( confDir, "logging.properties" ) );
850             }
851             else
852             {
853                 copyFile( "/conf/logging.properties", new File( confDir, "logging.properties" ) );
854             }
855 
856             copyFile( "/conf/tomcat-users.xml", new File( confDir, "tomcat-users.xml" ) );
857             if ( tomcatWebXml != null )
858             {
859                 if ( !tomcatWebXml.exists() )
860                 {
861                     throw new MojoExecutionException( " tomcatWebXml " + tomcatWebXml.getPath() + " not exists" );
862                 }
863                 //MTOMCAT-42  here it's a real file resources not a one coming with the mojo
864                 //MTOMCAT-128 apply filtering
865                 MavenFileFilterRequest mavenFileFilterRequest = new MavenFileFilterRequest();
866                 mavenFileFilterRequest.setFrom( tomcatWebXml );
867                 mavenFileFilterRequest.setTo( new File( confDir, "web.xml" ) );
868                 mavenFileFilterRequest.setMavenProject( project );
869                 mavenFileFilterRequest.setMavenSession( session );
870                 mavenFileFilterRequest.setFiltering( true );
871 
872                 mavenFileFilter.copyFile( mavenFileFilterRequest );
873 
874             }
875             else
876             {
877                 copyFile( "/conf/web.xml", new File( confDir, "web.xml" ) );
878             }
879 
880             File logDir = new File( configurationDir, "logs" );
881             logDir.mkdir();
882 
883             File webappsDir = new File( configurationDir, "webapps" );
884             webappsDir.mkdir();
885 
886             if ( additionalConfigFilesDir != null && additionalConfigFilesDir.exists() )
887             {
888                 DirectoryScanner scanner = new DirectoryScanner();
889                 scanner.addDefaultExcludes();
890                 scanner.setBasedir( additionalConfigFilesDir.getPath() );
891                 scanner.scan();
892 
893                 String[] files = scanner.getIncludedFiles();
894 
895                 if ( files != null && files.length > 0 )
896                 {
897                     getLog().info( "Coping additional tomcat config files" );
898 
899                     for ( int i = 0; i < files.length; i++ )
900                     {
901                         File file = new File( additionalConfigFilesDir, files[i] );
902 
903                         getLog().info( " copy " + file.getName() );
904 
905                         FileUtils.copyFileToDirectory( file, confDir );
906                     }
907                 }
908             }
909         }
910     }
911 
912     /**
913      * Copies the specified class resource to the specified file.
914      *
915      * @param fromPath the path of the class resource to copy
916      * @param toFile   the file to copy to
917      * @throws IOException if the file could not be copied
918      */
919     private void copyFile( String fromPath, File toFile )
920         throws IOException
921     {
922         URL fromURL = getClass().getResource( fromPath );
923 
924         if ( fromURL == null )
925         {
926             throw new FileNotFoundException( fromPath );
927         }
928 
929         FileUtils.copyURLToFile( fromURL, toFile );
930     }
931 
932     /**
933      * Starts the embedded Tomcat server.
934      *
935      * @throws IOException            if the server could not be configured
936      * @throws LifecycleException     if the server could not be started
937      * @throws MojoExecutionException if the server could not be configured
938      */
939     private void startContainer()
940         throws IOException, LifecycleException, MojoExecutionException, ServletException
941     {
942         String previousCatalinaBase = System.getProperty( "catalina.base" );
943 
944         try
945         {
946 
947             // Set the system properties
948             setupSystemProperties();
949 
950             System.setProperty( "catalina.base", configurationDir.getAbsolutePath() );
951 
952             if ( serverXml != null )
953             {
954                 if ( !serverXml.exists() )
955                 {
956                     throw new MojoExecutionException( serverXml.getPath() + " not exists" );
957                 }
958 
959                 Catalina container = new Catalina();
960 
961                 if ( useSeparateTomcatClassLoader )
962                 {
963                     Thread.currentThread().setContextClassLoader( getTomcatClassLoader() );
964                     container.setParentClassLoader( getTomcatClassLoader() );
965                 }
966 
967                 container.setUseNaming( this.useNaming );
968                 container.setConfig( serverXml.getAbsolutePath() );
969                 container.start();
970                 EmbeddedRegistry.getInstance().register( container );
971             }
972             else
973             {
974 
975                 System.setProperty( "java.util.logging.manager", "org.apache.juli.ClassLoaderLogManager" );
976                 System.setProperty( "java.util.logging.config.file",
977                                     new File( configurationDir, "conf/logging.properties" ).toString() );
978 
979                 // Trigger loading of catalina.properties
980                 CatalinaProperties.getProperty( "foo" );
981 
982                 Tomcat embeddedTomcat = new ExtendedTomcat( configurationDir );
983 
984                 embeddedTomcat.setBaseDir( configurationDir.getAbsolutePath() );
985                 MemoryRealm memoryRealm = new MemoryRealm();
986 
987                 if ( tomcatUsers != null )
988                 {
989                     if ( !tomcatUsers.exists() )
990                     {
991                         throw new MojoExecutionException( " tomcatUsers " + tomcatUsers.getPath() + " not exists" );
992                     }
993                     getLog().info( "use tomcat-users.xml from " + tomcatUsers.getAbsolutePath() );
994                     memoryRealm.setPathname( tomcatUsers.getAbsolutePath() );
995                 }
996 
997                 embeddedTomcat.setDefaultRealm( memoryRealm );
998 
999                 Context ctx = createContext( embeddedTomcat );
1000 
1001                 if ( useNaming )
1002                 {
1003                     embeddedTomcat.enableNaming();
1004                 }
1005 
1006                 embeddedTomcat.getHost().setAppBase( new File( configurationDir, "webapps" ).getAbsolutePath() );
1007 
1008                 if ( hostName != null )
1009                 {
1010                     embeddedTomcat.getHost().setName( hostName );
1011                 }
1012                 if ( aliases != null )
1013                 {
1014                     for ( String alias : aliases )
1015                     {
1016                         embeddedTomcat.getHost().addAlias( alias );
1017                     }
1018 
1019                 }
1020                 createStaticContext( embeddedTomcat, ctx, embeddedTomcat.getHost() );
1021 
1022                 Connector connector = new Connector( protocol );
1023                 connector.setPort( port );
1024 
1025                 if ( httpsPort > 0 )
1026                 {
1027                     connector.setRedirectPort( httpsPort );
1028                 }
1029 
1030                 connector.setURIEncoding( uriEncoding );
1031 
1032                 embeddedTomcat.getService().addConnector( connector );
1033 
1034                 embeddedTomcat.setConnector( connector );
1035 
1036                 AccessLogValve alv = new AccessLogValve();
1037                 alv.setDirectory( new File( configurationDir, "logs" ).getAbsolutePath() );
1038                 alv.setPattern( "%h %l %u %t \"%r\" %s %b %I %D" );
1039                 embeddedTomcat.getHost().getPipeline().addValve( alv );
1040 
1041                 // create https connector
1042                 Connector httpsConnector = null;
1043                 if ( httpsPort > 0 )
1044                 {
1045                     httpsConnector = new Connector( protocol );
1046                     httpsConnector.setPort( httpsPort );
1047                     httpsConnector.setSecure( true );
1048                     httpsConnector.setProperty( "SSLEnabled", "true" );
1049                     // should be default but configure it anyway
1050                     httpsConnector.setProperty( "sslProtocol", "TLS" );
1051                     if ( keystoreFile != null )
1052                     {
1053                         httpsConnector.setAttribute( "keystoreFile", keystoreFile );
1054                     }
1055                     if ( keystorePass != null )
1056                     {
1057                         httpsConnector.setAttribute( "keystorePass", keystorePass );
1058                     }
1059                     if ( keystoreType != null )
1060                     {
1061                         httpsConnector.setAttribute( "keystoreType", keystoreType );
1062                     }
1063 
1064                     httpsConnector.setAttribute( "clientAuth", clientAuth );
1065 
1066                     embeddedTomcat.getEngine().getService().addConnector( httpsConnector );
1067 
1068                 }
1069 
1070                 // create ajp connector
1071                 Connector ajpConnector = null;
1072                 if ( ajpPort > 0 )
1073                 {
1074                     ajpConnector = new Connector( ajpProtocol );
1075                     ajpConnector.setPort( ajpPort );
1076                     ajpConnector.setURIEncoding( uriEncoding );
1077                     embeddedTomcat.getEngine().getService().addConnector( ajpConnector );
1078                 }
1079 
1080                 if ( addContextWarDependencies || !getAdditionalWebapps().isEmpty() )
1081                 {
1082                     createDependencyContexts( embeddedTomcat );
1083                 }
1084 
1085                 if ( useSeparateTomcatClassLoader )
1086                 {
1087                     Thread.currentThread().setContextClassLoader( getTomcatClassLoader() );
1088                     embeddedTomcat.getEngine().setParentClassLoader( getTomcatClassLoader() );
1089                 }
1090 
1091                 embeddedTomcat.start();
1092 
1093                 Properties portProperties = new Properties();
1094 
1095                 portProperties.put( "tomcat.maven.http.port", Integer.toString( connector.getLocalPort() ) );
1096 
1097                 session.getExecutionProperties().put( "tomcat.maven.http.port",
1098                                                       Integer.toString( connector.getLocalPort() ) );
1099                 System.setProperty( "tomcat.maven.http.port", Integer.toString( connector.getLocalPort() ) );
1100 
1101                 if ( httpsConnector != null )
1102                 {
1103                     session.getExecutionProperties().put( "tomcat.maven.https.port",
1104                                                           Integer.toString( httpsConnector.getLocalPort() ) );
1105                     portProperties.put( "tomcat.maven.https.port", Integer.toString( httpsConnector.getLocalPort() ) );
1106                     System.setProperty( "tomcat.maven.https.port", Integer.toString( httpsConnector.getLocalPort() ) );
1107                 }
1108 
1109                 if ( ajpConnector != null )
1110                 {
1111                     session.getExecutionProperties().put( "tomcat.maven.ajp.port",
1112                                                           Integer.toString( ajpConnector.getLocalPort() ) );
1113                     portProperties.put( "tomcat.maven.ajp.port", Integer.toString( ajpConnector.getLocalPort() ) );
1114                     System.setProperty( "tomcat.maven.ajp.port", Integer.toString( ajpConnector.getLocalPort() ) );
1115                 }
1116                 if ( propertiesPortFilePath != null )
1117                 {
1118                     File propertiesPortsFile = new File( propertiesPortFilePath );
1119                     if ( propertiesPortsFile.exists() )
1120                     {
1121                         propertiesPortsFile.delete();
1122                     }
1123                     FileOutputStream fileOutputStream = new FileOutputStream( propertiesPortsFile );
1124                     try
1125                     {
1126                         portProperties.store( fileOutputStream, "Apache Tomcat Maven plugin port used" );
1127                     }
1128                     finally
1129                     {
1130                         IOUtils.closeQuietly( fileOutputStream );
1131                     }
1132                 }
1133 
1134                 EmbeddedRegistry.getInstance().register( embeddedTomcat );
1135 
1136             }
1137 
1138 
1139         }
1140         finally
1141         {
1142             if ( previousCatalinaBase != null )
1143             {
1144                 System.setProperty( "catalina.base", previousCatalinaBase );
1145             }
1146         }
1147     }
1148 
1149     private List<Webapp> getAdditionalWebapps()
1150     {
1151         if ( webapps == null )
1152         {
1153             return Collections.emptyList();
1154         }
1155         return webapps;
1156     }
1157 
1158     protected ClassRealm getTomcatClassLoader()
1159         throws MojoExecutionException
1160     {
1161         if ( this.tomcatRealm != null )
1162         {
1163             return tomcatRealm;
1164         }
1165         try
1166         {
1167             ClassWorld world = new ClassWorld();
1168             ClassRealm root = world.newRealm( "tomcat", Thread.currentThread().getContextClassLoader() );
1169 
1170             for ( @SuppressWarnings( "rawtypes" ) Iterator i = pluginArtifacts.iterator(); i.hasNext(); )
1171             {
1172                 Artifact pluginArtifact = (Artifact) i.next();
1173                 // add all plugin artifacts see https://issues.apache.org/jira/browse/MTOMCAT-122
1174                 if ( pluginArtifact.getFile() != null )
1175                 {
1176                     root.addURL( pluginArtifact.getFile().toURI().toURL() );
1177                 }
1178 
1179             }
1180             tomcatRealm = root;
1181             return root;
1182         }
1183         catch ( DuplicateRealmException e )
1184         {
1185             throw new MojoExecutionException( e.getMessage(), e );
1186         }
1187         catch ( MalformedURLException e )
1188         {
1189             throw new MojoExecutionException( e.getMessage(), e );
1190         }
1191     }
1192 
1193     @SuppressWarnings( "unchecked" )
1194     public Set<Artifact> getProjectArtifacts()
1195     {
1196         return project.getArtifacts();
1197     }
1198 
1199     /**
1200      * Causes the current thread to wait indefinitely. This method does not return.
1201      */
1202     private void waitIndefinitely()
1203     {
1204         Object lock = new Object();
1205 
1206         synchronized ( lock )
1207         {
1208             try
1209             {
1210                 lock.wait();
1211             }
1212             catch ( InterruptedException exception )
1213             {
1214                 getLog().warn( messagesProvider.getMessage( "AbstractRunMojo.interrupted" ), exception );
1215             }
1216         }
1217     }
1218 
1219 
1220     /**
1221      * Set the SystemProperties from the configuration.
1222      */
1223     private void setupSystemProperties()
1224     {
1225         if ( systemProperties != null && !systemProperties.isEmpty() )
1226         {
1227             getLog().info( "setting SystemProperties:" );
1228 
1229             for ( String key : systemProperties.keySet() )
1230             {
1231                 String value = systemProperties.get( key );
1232 
1233                 if ( value != null )
1234                 {
1235                     getLog().info( " " + key + "=" + value );
1236                     System.setProperty( key, value );
1237                 }
1238                 else
1239                 {
1240                     getLog().info( "skip sysProps " + key + " with empty value" );
1241                 }
1242             }
1243         }
1244     }
1245 
1246 
1247     /**
1248      * Allows the startup of additional webapps in the tomcat container by declaration with scope
1249      * "tomcat".
1250      *
1251      * @param container tomcat
1252      * @return dependency tomcat contexts of warfiles in scope "tomcat"
1253      */
1254     private Collection<Context> createDependencyContexts( Tomcat container )
1255         throws MojoExecutionException, MalformedURLException, ServletException, IOException
1256     {
1257         getLog().info( "Deploying dependency wars" );
1258         // Let's add other modules
1259         List<Context> contexts = new ArrayList<Context>();
1260 
1261         ScopeArtifactFilter filter = new ScopeArtifactFilter( "tomcat" );
1262         @SuppressWarnings( "unchecked" ) Set<Artifact> artifacts = project.getArtifacts();
1263         for ( Artifact artifact : artifacts )
1264         {
1265 
1266             // Artifact is not yet registered and it has neither test, nor a
1267             // provided scope, not is it optional
1268             if ( "war".equals( artifact.getType() ) && !artifact.isOptional() && filter.include( artifact ) )
1269             {
1270                 addContextFromArtifact( container, contexts, artifact, "/" + artifact.getArtifactId(), null, false );
1271             }
1272         }
1273 
1274         for ( AbstractWebapp additionalWebapp : getAdditionalWebapps() )
1275         {
1276             String contextPath = additionalWebapp.getContextPath();
1277             if ( !contextPath.startsWith( "/" ) )
1278             {
1279                 contextPath = "/" + contextPath;
1280             }
1281             addContextFromArtifact( container, contexts, getArtifact( additionalWebapp ), contextPath,
1282                                     additionalWebapp.getContextFile(), additionalWebapp.isAsWebapp() );
1283         }
1284         return contexts;
1285     }
1286 
1287 
1288     private void addContextFromArtifact( Tomcat container, List<Context> contexts, Artifact artifact,
1289                                          String contextPath, File contextXml, boolean asWebApp )
1290         throws MojoExecutionException, MalformedURLException, ServletException, IOException
1291     {
1292         getLog().info( "Deploy warfile: " + String.valueOf( artifact.getFile() ) + " to contextPath: " + contextPath );
1293         File webapps = new File( configurationDir, "webapps" );
1294         File artifactWarDir = new File( webapps, artifact.getArtifactId() );
1295         if ( !artifactWarDir.exists() )
1296         {
1297             //dont extract if exists
1298             artifactWarDir.mkdir();
1299             try
1300             {
1301                 UnArchiver unArchiver = archiverManager.getUnArchiver( "zip" );
1302                 unArchiver.setSourceFile( artifact.getFile() );
1303                 unArchiver.setDestDirectory( artifactWarDir );
1304 
1305                 // Extract the module
1306                 unArchiver.extract();
1307             }
1308             catch ( NoSuchArchiverException e )
1309             {
1310                 getLog().error( e );
1311                 return;
1312             }
1313             catch ( ArchiverException e )
1314             {
1315                 getLog().error( e );
1316                 return;
1317             }
1318         }
1319         // TODO make that configurable ?
1320         //WebappLoader webappLoader = new WebappLoader( Thread.currentThread().getContextClassLoader() );
1321         WebappLoader webappLoader = createWebappLoader();
1322         Context context = null;
1323         if ( asWebApp )
1324         {
1325             context = container.addWebapp( contextPath, artifactWarDir.getAbsolutePath() );
1326         }
1327         else
1328         {
1329             context = container.addContext( contextPath, artifactWarDir.getAbsolutePath() );
1330         }
1331         context.setLoader( webappLoader );
1332 
1333         File contextFile = contextXml != null ? contextXml : getContextFile();
1334         if ( contextFile != null )
1335         {
1336             context.setConfigFile( contextFile.toURI().toURL() );
1337         }
1338 
1339         contexts.add( context );
1340 //        container.getHost().addChild(context);
1341     }
1342 
1343     private void createStaticContext( final Tomcat container, Context context, Host host )
1344     {
1345         if ( staticContextDocbase != null )
1346         {
1347             Context staticContext = container.addContext( staticContextPath, staticContextDocbase );
1348             staticContext.setPrivileged( true );
1349             Wrapper servlet = context.createWrapper();
1350             servlet.setServletClass( DefaultServlet.class.getName() );
1351             servlet.setName( "staticContent" );
1352             staticContext.addChild( servlet );
1353             staticContext.addServletMapping( "/", "staticContent" );
1354             host.addChild( staticContext );
1355         }
1356     }
1357 
1358 
1359     /**
1360      * Resolves the Artifact from the remote repository if necessary. If no version is specified, it will be retrieved
1361      * from the dependency list or from the DependencyManagement section of the pom.
1362      *
1363      * @param additionalWebapp containing information about artifact from plugin configuration.
1364      * @return Artifact object representing the specified file.
1365      * @throws MojoExecutionException with a message if the version can't be found in DependencyManagement.
1366      */
1367     protected Artifact getArtifact( AbstractWebapp additionalWebapp )
1368         throws MojoExecutionException
1369     {
1370 
1371         Artifact artifact;
1372         VersionRange vr;
1373         try
1374         {
1375             vr = VersionRange.createFromVersionSpec( additionalWebapp.getVersion() );
1376         }
1377         catch ( InvalidVersionSpecificationException e )
1378         {
1379             getLog().warn( "fail to create versionRange from version: " + additionalWebapp.getVersion(), e );
1380             vr = VersionRange.createFromVersion( additionalWebapp.getVersion() );
1381         }
1382 
1383         if ( StringUtils.isEmpty( additionalWebapp.getClassifier() ) )
1384         {
1385             artifact =
1386                 factory.createDependencyArtifact( additionalWebapp.getGroupId(), additionalWebapp.getArtifactId(), vr,
1387                                                   additionalWebapp.getType(), null, Artifact.SCOPE_COMPILE );
1388         }
1389         else
1390         {
1391             artifact =
1392                 factory.createDependencyArtifact( additionalWebapp.getGroupId(), additionalWebapp.getArtifactId(), vr,
1393                                                   additionalWebapp.getType(), additionalWebapp.getClassifier(),
1394                                                   Artifact.SCOPE_COMPILE );
1395         }
1396 
1397         try
1398         {
1399             resolver.resolve( artifact, project.getRemoteArtifactRepositories(), this.local );
1400         }
1401         catch ( ArtifactResolutionException e )
1402         {
1403             throw new MojoExecutionException( "Unable to resolve artifact.", e );
1404         }
1405         catch ( ArtifactNotFoundException e )
1406         {
1407             throw new MojoExecutionException( "Unable to find artifact.", e );
1408         }
1409 
1410         return artifact;
1411     }
1412 }