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