View Javadoc

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