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