blob: fff9cb5315fb90f2b0d8e3fc8bfd199d6bf0b4a3 [file] [log] [blame]
package org.apache.tomcat.maven.plugin.tomcat7.run;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import org.apache.catalina.Context;
import org.apache.catalina.Host;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappLoader;
import org.apache.catalina.realm.MemoryRealm;
import org.apache.catalina.servlets.DefaultServlet;
import org.apache.catalina.startup.Catalina;
import org.apache.catalina.startup.CatalinaProperties;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.valves.AccessLogValve;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.filtering.MavenFileFilter;
import org.apache.maven.shared.filtering.MavenFileFilterRequest;
import org.apache.maven.shared.filtering.MavenFilteringException;
import org.apache.naming.NamingEntry;
import org.apache.naming.resources.FileDirContext;
import org.apache.tomcat.JarScanner;
import org.apache.tomcat.maven.common.config.AbstractWebapp;
import org.apache.tomcat.maven.common.run.EmbeddedRegistry;
import org.apache.tomcat.maven.common.run.ExternalRepositoriesReloadableWebappLoader;
import org.apache.tomcat.maven.plugin.tomcat7.AbstractTomcat7Mojo;
import org.apache.tomcat.util.scan.StandardJarScanner;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.UnArchiver;
import org.codehaus.plexus.archiver.manager.ArchiverManager;
import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
import org.codehaus.plexus.classworlds.ClassWorld;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.codehaus.plexus.classworlds.realm.DuplicateRealmException;
import org.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.FileUtils;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* @author Olivier Lamy
* @since 2.0
*/
public abstract class AbstractRunMojo
extends AbstractTomcat7Mojo
{
// ---------------------------------------------------------------------
// Mojo Components
// ---------------------------------------------------------------------
/**
* Used to look up Artifacts in the remote repository.
*/
@Component
protected ArtifactFactory factory;
/**
* Location of the local repository.
*/
@Parameter(defaultValue = "${localRepository}", required = true, readonly = true)
private ArtifactRepository local;
/**
* Used to look up Artifacts in the remote repository.
*/
@Component
protected ArtifactResolver resolver;
// ----------------------------------------------------------------------
// Mojo Parameters
// ----------------------------------------------------------------------
/**
* The packaging of the Maven project that this goal operates upon.
*/
@Parameter(defaultValue = "${project.packaging}", required = true, readonly = true)
private String packaging;
/**
* The directory to create the Tomcat server configuration under.
*/
@Parameter(defaultValue = "${project.build.directory}/tomcat")
private File configurationDir;
/**
* The port to run the Tomcat server on.
* Will be exposed as System props and session.executionProperties with key tomcat.maven.http.port
*/
@Parameter(property = "maven.tomcat.port", defaultValue = "8080")
private int port;
/**
* this IP address will be used on all ports
*
* @since 2.2
*/
@Parameter(property = "maven.tomcat.address")
private String address;
/**
* The AJP port to run the Tomcat server on.
* By default it's 0 this means won't be started.
* The ajp connector will be started only for value > 0.
* Will be exposed as System props and session.executionProperties with key tomcat.maven.ajp.port
*
* @since 2.0
*/
@Parameter(property = "maven.tomcat.ajp.port", defaultValue = "0")
private int ajpPort;
/**
* The AJP protocol to run the Tomcat server on.
* By default it's ajp.
* NOTE The ajp connector will be started only if {@link #ajpPort} > 0.
* possible values are:
* <ul>
* <li>org.apache.coyote.ajp.AjpProtocol - new blocking Java connector that supports an executor</li>
* <li>org.apache.coyote.ajp.AjpAprProtocol - the APR/native connector.</li>
* </ul>
*
* @since 2.0
*/
@Parameter(property = "maven.tomcat.ajp.protocol", defaultValue = "org.apache.coyote.ajp.AjpProtocol")
private String ajpProtocol;
/**
* The https port to run the Tomcat server on.
* By default it's 0 this means won't be started.
* The https connector will be started only for value > 0.
* Will be exposed as System props and session.executionProperties with key tomcat.maven.https.port
*
* @since 1.0
*/
@Parameter(property = "maven.tomcat.httpsPort", defaultValue = "0")
private int httpsPort;
/**
* The max post size to run the Tomcat server on.
* By default it's 2097152 bytes. That's the default Tomcat configuration.
* Set this value to 0 or less to disable the post size limit.
*
* @since 2.3
*/
@Parameter(property = "maven.tomcat.maxPostSize", defaultValue = "2097152")
private int maxPostSize;
/**
* The character encoding to use for decoding URIs.
*
* @since 1.0
*/
@Parameter(property = "maven.tomcat.uriEncoding", defaultValue = "ISO-8859-1")
private String uriEncoding;
/**
* List of System properties to pass to the Tomcat Server.
*
* @since 1.0-alpha-2
*/
@Parameter
private Map<String, String> systemProperties;
/**
* The directory contains additional configuration Files that copied in the Tomcat conf Directory.
*
* @since 1.0-alpha-2
*/
@Parameter(property = "maven.tomcat.additionalConfigFilesDir", defaultValue = "${basedir}/src/main/tomcatconf")
private File additionalConfigFilesDir;
/**
* server.xml to use <b>Note if you use this you must configure in this file your webapp paths</b>.
*
* @since 1.0-alpha-2
*/
@Parameter(property = "maven.tomcat.serverXml")
private File serverXml;
/**
* overriding the providing web.xml to run tomcat
* <b>This override the global Tomcat web.xml located in $CATALINA_HOME/conf/</b>
*
* @since 1.0-alpha-2
*/
@Parameter(property = "maven.tomcat.webXml")
private File tomcatWebXml;
/**
* Set this to true to allow Maven to continue to execute after invoking
* the run goal.
*
* @since 1.0
*/
@Parameter(property = "maven.tomcat.fork", defaultValue = "false")
private boolean fork;
/**
* Will create a tomcat context for each dependencies of war type with 'scope' set to 'tomcat'.
* In other words, dependencies with:
* <pre>
* &lt;type&gt;war&lt;/type&gt;
* &lt;scope&gt;tomcat&lt;/scope&gt;
* </pre>
* To preserve backward compatibility it's false by default.
*
* @since 1.0
* @deprecated use webapps instead
*/
@Parameter(property = "maven.tomcat.addContextWarDependencies", defaultValue = "false")
private boolean addContextWarDependencies;
/**
* The maven project.
*
* @since 1.0
*/
@Parameter( defaultValue = "${project}", readonly = true )
protected MavenProject project;
/**
* The archive manager.
*
* @since 1.0
*/
@Component
private ArchiverManager archiverManager;
/**
* if <code>true</code> a new classLoader separated from maven core will be created to start tomcat.
*
* @since 1.0
*/
@Parameter(property = "tomcat.useSeparateTomcatClassLoader", defaultValue = "false")
protected boolean useSeparateTomcatClassLoader;
/**
* @since 1.0
*/
@Parameter(defaultValue = "${plugin.artifacts}", required = true)
private List<Artifact> pluginArtifacts;
/**
* If set to true ignore if packaging of project is not 'war'.
*
* @since 1.0
*/
@Parameter(property = "tomcat.ignorePackaging", defaultValue = "false")
private boolean ignorePackaging;
/**
* Override the default keystoreFile for the HTTPS connector (if enabled)
*
* @since 1.1
*/
@Parameter
private String keystoreFile;
/**
* Override the default keystorePass for the HTTPS connector (if enabled)
*
* @since 1.1
*/
@Parameter
private String keystorePass;
/**
* Override the type of keystore file to be used for the server certificate. If not specified, the default value is "JKS".
*
* @since 2.0
*/
@Parameter(defaultValue = "JKS")
private String keystoreType;
/**
* <p>
* Enables or disables naming support for the embedded Tomcat server.
* </p>
* <p>
* <strong>Note:</strong> This setting is ignored if you provide a <code>server.xml</code> for your
* Tomcat. Instead please configure naming in the <code>server.xml</code>.
* </p>
*
* @see <a href="http://tomcat.apache.org/tomcat-7.0-doc/api/org/apache/catalina/startup/Embedded.html">org.apache.catalina.startup.Embedded</a>
* @see <a href="http://tomcat.apache.org/tomcat-8.0-doc/api/org/apache/catalina/startup/Tomcat.html">org.apache.catalina.startup.Tomcat</a>
* @since 2.0
*/
@Parameter(property = "maven.tomcat.useNaming", defaultValue = "true")
private boolean useNaming;
/**
* Force context scanning if you don't use a context file with reloadable = "true".
* The other way to use contextReloadable is to add attribute reloadable = "true"
* in your context file.
*
* @since 2.0
*/
@Parameter(property = "maven.tomcat.contextReloadable", defaultValue = "false")
protected boolean contextReloadable;
/**
* represents the delay in seconds between each classPathScanning change invocation
*
* @see <a href="http://tomcat.apache.org/tomcat-7.0-doc/config/context.html">http://tomcat.apache.org/tomcat-7.0-doc/config/context.html</a>
*/
@Parameter(property = "maven.tomcat.backgroundProcessorDelay", defaultValue = "-1")
protected int backgroundProcessorDelay = -1;
/**
* <p>The path of the Tomcat context XML file.</p>
* <p>Since release 2.0, the file is filtered as a maven resource so you can use
* interpolation tokens ${ }</p>
*/
@Parameter(property = "maven.tomcat.contextFile")
protected File contextFile;
/**
* The default context file to check for if contextFile not configured.
* If no contextFile configured and the below default not present, no
* contextFile will be sent to Tomcat, resulting in the latter's default
* context configuration being used instead.
*/
@Parameter(defaultValue = "${project.build.directory}/${project.build.finalName}/META-INF/context.xml",
readonly = true)
private File defaultContextFile;
/**
* The protocol to run the Tomcat server on.
* By default it's HTTP/1.1.
* See possible values <a href="http://tomcat.apache.org/tomcat-7.0-doc/config/http.html">HTTP Connector</a>
* protocol attribute
*
* @since 2.0
*/
@Parameter(property = "maven.tomcat.protocol", defaultValue = "HTTP/1.1")
private String protocol;
/**
* The path of the Tomcat users XML file.
*
* @since 2.0
*/
@Parameter(property = "maven.tomcat.tomcatUsers.file")
private File tomcatUsers;
/**
* The path of the Tomcat logging configuration.
*
* @since 2.0
*/
@Parameter(property = "maven.tomcat.tomcatLogging.file")
private File tomcatLoggingFile;
/**
* Skip execution
*
* @since 2.0
*/
@Parameter(property = "maven.tomcat.skip", defaultValue = "false")
protected boolean skip;
/**
* Collection of webapp artifacts to be deployed. Elements are &lt;webapp&gt; and contain
* usual GAVC plus contextPath and/or contextFile elements.<p>
*
* @see {@link Webapp}
* @since 2.0
*/
@Parameter
private List<Webapp> webapps;
/**
* The static context
*
* @since 2.0
*/
@Parameter(property = "maven.tomcat.staticContextPath", defaultValue = "/")
private String staticContextPath;
/**
* The static context docroot base fully qualified path
* if <code>null</code> static context won't be added
*
* @since 2.0
*/
@Parameter(property = "maven.tomcat.staticContextDocbase")
private String staticContextDocbase;
/**
* Class loader class to set.
*
* @since 2.0
*/
@Parameter
protected String classLoaderClass;
@Parameter(defaultValue = "${session}", readonly = true, required = true)
protected MavenSession session;
/**
* Will dump port in a properties file (see ports for property names).
* If empty no file generated
*/
@Parameter(property = "maven.tomcat.propertiesPortFilePath")
protected String propertiesPortFilePath;
/**
* configure host name
*
* @since 2.0
*/
@Parameter(property = "maven.tomcat.hostName", defaultValue = "localhost")
protected String hostName;
/**
* configure aliases
* see <a href="http://tomcat.apache.org/tomcat-7.0-doc/config/host.html#Host_Name_Aliases">Host Name aliases</a>
*
* @since 2.0
*/
@Parameter
protected String[] aliases;
/**
* enable client authentication for https (if configured)
* see <a href="http://tomcat.apache.org/tomcat-7.0-doc/config/http.html#SSL_Support_-_BIO_and_NIO">http://tomcat.apache.org/tomcat-7.0-doc/config/http.html#SSL_Support_-_BIO_and_NIO</a>
*
* @since 2.1
*/
@Parameter(property = "maven.tomcat.https.clientAuth", defaultValue = "false")
protected String clientAuth = "false";
@Component(role = MavenFileFilter.class, hint = "default")
protected MavenFileFilter mavenFileFilter;
/**
* In case a module in your reactors has some web-fragments they will be read.
* If you don't need that for performance reasons, you can deactivate it.
*
* @since 2.2
*/
@Parameter(property = "maven.tomcat.jarScan.allDirectories", defaultValue = "true")
protected boolean jarScanAllDirectories = true;
/**
* @since 2.2
*/
@Parameter(property = "maven.tomcat.useBodyEncodingForURI", defaultValue = "false")
protected boolean useBodyEncodingForURI;
/**
* @since 2.2
*/
@Parameter
protected String trustManagerClassName;
/**
* @since 2.2
*/
@Parameter
protected String trustMaxCertLength;
/**
* @since 2.2
*/
@Parameter
protected String truststoreAlgorithm;
/**
* @since 2.2
*/
@Parameter
protected String truststoreFile;
/**
* @since 2.2
*/
@Parameter
protected String truststorePass;
/**
* @since 2.2
*/
@Parameter
protected String truststoreProvider;
/**
* @since 2.2
*/
@Parameter
protected String truststoreType;
// ----------------------------------------------------------------------
// Fields
// ----------------------------------------------------------------------
/**
* @since 1.0
*/
private ClassRealm tomcatRealm;
// ----------------------------------------------------------------------
// Mojo Implementation
// ----------------------------------------------------------------------
/**
* {@inheritDoc}
*/
public void execute()
throws MojoExecutionException, MojoFailureException
{
if ( skip )
{
getLog().info( "skip execution" );
return;
}
// ensure project is a web application
if ( !isWar() && !addContextWarDependencies && getAdditionalWebapps().isEmpty() )
{
getLog().info( messagesProvider.getMessage( "AbstractRunMojo.nonWar" ) );
return;
}
ClassLoader originalClassLoader = null;
if ( useSeparateTomcatClassLoader )
{
originalClassLoader = Thread.currentThread().getContextClassLoader();
}
try
{
getLog().info( messagesProvider.getMessage( "AbstractRunMojo.runningWar", getWebappUrl() ) );
initConfiguration();
startContainer();
if ( !fork )
{
waitIndefinitely();
}
}
catch ( LifecycleException exception )
{
throw new MojoExecutionException( messagesProvider.getMessage( "AbstractRunMojo.cannotStart" ), exception );
}
catch ( IOException exception )
{
throw new MojoExecutionException(
messagesProvider.getMessage( "AbstractRunMojo.cannotCreateConfiguration" ), exception );
}
catch ( ServletException e )
{
throw new MojoExecutionException( e.getMessage(), e );
}
catch ( MavenFilteringException e )
{
throw new MojoExecutionException( "filtering issue: " + e.getMessage(), e );
}
finally
{
if ( useSeparateTomcatClassLoader )
{
Thread.currentThread().setContextClassLoader( originalClassLoader );
}
}
}
// ----------------------------------------------------------------------
// Protected Methods
// ----------------------------------------------------------------------
/**
* Gets the webapp context path to use for the web application being run.
*
* @return the webapp context path
*/
protected String getPath()
{
return path;
}
/**
* Gets the context to run this web application under for the specified embedded Tomcat.
*
* @param container the embedded Tomcat container being used
* @return the context to run this web application under
* @throws IOException if the context could not be created
* @throws MojoExecutionException in case of an error creating the context
*/
protected Context createContext( Tomcat container )
throws IOException, MojoExecutionException, ServletException
{
String contextPath = getPath();
String baseDir = getDocBase().getAbsolutePath();
File overriddenContextFile = getContextFile();
StandardContext standardContext = null;
if ( overriddenContextFile != null && overriddenContextFile.exists() )
{
standardContext = parseContextFile( overriddenContextFile );
}
else if ( defaultContextFile.exists() )
{
standardContext = parseContextFile( defaultContextFile );
}
if ( standardContext != null )
{
if ( standardContext.getPath() != null )
{
contextPath = standardContext.getPath();
}
if ( standardContext.getDocBase() != null )
{
baseDir = standardContext.getDocBase();
}
}
contextPath = "/".equals( contextPath ) ? "" : contextPath;
getLog().info( "create webapp with contextPath: " + contextPath );
Context context = container.addWebapp( contextPath, baseDir );
context.setResources(
new MyDirContext( new File( project.getBuild().getOutputDirectory() ).getAbsolutePath() ) );
if ( useSeparateTomcatClassLoader )
{
context.setParentClassLoader( getTomcatClassLoader() );
}
final WebappLoader loader = createWebappLoader();
context.setLoader( loader );
if ( overriddenContextFile != null )
{
// here, send file to Tomcat for it to complain if missing
context.setConfigFile( overriddenContextFile.toURI().toURL() );
}
else if ( defaultContextFile.exists() )
{
// here, only sending default file if it indeed exists
// otherwise Tomcat will create a default context
context.setConfigFile( defaultContextFile.toURI().toURL() );
}
if ( classLoaderClass != null )
{
loader.setLoaderClass( classLoaderClass );
}
// https://issues.apache.org/jira/browse/MTOMCAT-239
// get the jar scanner to configure scanning directories as we can run a jar or a reactor project with a jar so
// the entries is a directory (target/classes)
JarScanner jarScanner = context.getJarScanner();
// normally this one only but just in case ...
if ( jarScanner instanceof StandardJarScanner )
{
( (StandardJarScanner) jarScanner ).setScanAllDirectories( jarScanAllDirectories );
}
// https://issues.apache.org/jira/browse/MTOMCAT-255
if(context instanceof StandardContext)
{
((StandardContext) context).setAddWebinfClassesResources(true);
((StandardContext) context).setAliases( "/WEB-INF/classes=" + project.getBuild().getOutputDirectory());
}
return context;
}
protected StandardContext parseContextFile( File file )
throws MojoExecutionException
{
try
{
StandardContext standardContext = new StandardContext();
XMLStreamReader reader = XMLInputFactory.newFactory().createXMLStreamReader( new FileInputStream( file ) );
int tag = reader.next();
while ( true )
{
if ( tag == XMLStreamConstants.START_ELEMENT && StringUtils.equals( "Context", reader.getLocalName() ) )
{
String path = reader.getAttributeValue( null, "path" );
if ( StringUtils.isNotBlank( path ) )
{
standardContext.setPath( path );
}
String docBase = reader.getAttributeValue( null, "docBase" );
if ( StringUtils.isNotBlank( docBase ) )
{
standardContext.setDocBase( docBase );
}
}
if ( !reader.hasNext() )
{
break;
}
tag = reader.next();
}
return standardContext;
}
catch ( XMLStreamException e )
{
throw new MojoExecutionException( e.getMessage(), e );
}
catch ( FileNotFoundException e )
{
throw new MojoExecutionException( e.getMessage(), e );
}
}
private static class MyDirContext
extends FileDirContext
{
String buildOutputDirectory;
MyDirContext( String buildOutputDirectory )
{
this.buildOutputDirectory = buildOutputDirectory;
}
@Override
protected List<NamingEntry> doListBindings( String name )
throws NamingException
{
if ( "/WEB-INF/classes".equals( name ) )
{
if ( !new File( buildOutputDirectory ).exists() )
{
return Collections.emptyList();
}
FileDirContext fileDirContext = new FileDirContext();
fileDirContext.setDocBase( buildOutputDirectory );
NamingEntry namingEntry = new NamingEntry( "/WEB-INF/classes", fileDirContext, -1 );
return Collections.singletonList( namingEntry );
}
return super.doListBindings( name );
}
}
/**
* Gets the webapp loader to run this web application under.
*
* @return the webapp loader to use
* @throws IOException if the webapp loader could not be created
* @throws MojoExecutionException in case of an error creating the webapp loader
*/
protected WebappLoader createWebappLoader()
throws IOException, MojoExecutionException
{
if ( useSeparateTomcatClassLoader )
{
return ( isContextReloadable() )
? new ExternalRepositoriesReloadableWebappLoader( getTomcatClassLoader(), getLog() )
: new WebappLoader( getTomcatClassLoader() );
}
return ( isContextReloadable() )
? new ExternalRepositoriesReloadableWebappLoader( Thread.currentThread().getContextClassLoader(), getLog() )
: new WebappLoader( Thread.currentThread().getContextClassLoader() );
}
/**
* Determine whether the passed context.xml file declares the context as reloadable or not.
*
* @return false by default, true if reloadable="true" in context.xml.
*/
protected boolean isContextReloadable()
throws MojoExecutionException
{
if ( contextReloadable || backgroundProcessorDelay > 0 )
{
return true;
}
// determine whether to use a reloadable Loader or not (default is false).
boolean reloadable = false;
try
{
if ( contextFile != null && contextFile.exists() )
{
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder();
Document contextDoc = builder.parse( contextFile );
contextDoc.getDocumentElement().normalize();
NamedNodeMap nodeMap = contextDoc.getDocumentElement().getAttributes();
Node reloadableAttribute = nodeMap.getNamedItem( "reloadable" );
reloadable =
( reloadableAttribute != null ) ? Boolean.valueOf( reloadableAttribute.getNodeValue() ) : false;
}
getLog().debug( "context reloadable: " + reloadable );
}
catch ( IOException ioe )
{
getLog().error( "Could not parse file: [" + contextFile.getAbsolutePath() + "]", ioe );
}
catch ( ParserConfigurationException pce )
{
getLog().error( "Could not configure XML parser", pce );
}
catch ( SAXException se )
{
getLog().error( "Could not parse file: [" + contextFile.getAbsolutePath() + "]", se );
}
return reloadable;
}
/**
* Gets the webapp directory to run.
*
* @return the webapp directory
*/
protected abstract File getDocBase()
throws IOException;
/**
* Gets the Tomcat context XML file to use.
*
* @return the context XML file
*/
protected abstract File getContextFile()
throws MojoExecutionException;
// ----------------------------------------------------------------------
// Private Methods
// ----------------------------------------------------------------------
/**
* Gets whether this project uses WAR packaging.
*
* @return whether this project uses WAR packaging
*/
protected boolean isWar()
{
return "war".equals( packaging ) || ignorePackaging;
}
/**
* Gets the URL of the running webapp.
*
* @return the URL of the running webapp
* @throws java.net.MalformedURLException if the running webapp URL is invalid
*/
private URL getWebappUrl()
throws MalformedURLException
{
return new URL( "http", "localhost", port, getPath() );
}
/**
* FIXME not sure we need all of those files with tomcat7
* Creates the Tomcat configuration directory with the necessary resources.
*
* @throws IOException if the Tomcat configuration could not be created
* @throws MojoExecutionException if the Tomcat configuration could not be created
*/
private void initConfiguration()
throws IOException, MojoExecutionException, MavenFilteringException
{
if ( configurationDir.exists() )
{
getLog().info( messagesProvider.getMessage( "AbstractRunMojo.usingConfiguration", configurationDir ) );
}
else
{
getLog().info( messagesProvider.getMessage( "AbstractRunMojo.creatingConfiguration", configurationDir ) );
configurationDir.mkdirs();
File confDir = new File( configurationDir, "conf" );
confDir.mkdir();
if ( tomcatLoggingFile != null )
{
FileUtils.copyFile( tomcatLoggingFile, new File( confDir, "logging.properties" ) );
}
else
{
copyFile( "/conf/logging.properties", new File( confDir, "logging.properties" ) );
}
copyFile( "/conf/tomcat-users.xml", new File( confDir, "tomcat-users.xml" ) );
if ( tomcatWebXml != null )
{
if ( !tomcatWebXml.exists() )
{
throw new MojoExecutionException( " tomcatWebXml " + tomcatWebXml.getPath() + " not exists" );
}
//MTOMCAT-42 here it's a real file resources not a one coming with the mojo
//MTOMCAT-128 apply filtering
MavenFileFilterRequest mavenFileFilterRequest = new MavenFileFilterRequest();
mavenFileFilterRequest.setFrom( tomcatWebXml );
mavenFileFilterRequest.setTo( new File( confDir, "web.xml" ) );
mavenFileFilterRequest.setMavenProject( project );
mavenFileFilterRequest.setMavenSession( session );
mavenFileFilterRequest.setFiltering( true );
mavenFileFilter.copyFile( mavenFileFilterRequest );
}
else
{
copyFile( "/conf/web.xml", new File( confDir, "web.xml" ) );
}
File logDir = new File( configurationDir, "logs" );
logDir.mkdir();
File webappsDir = new File( configurationDir, "webapps" );
webappsDir.mkdir();
if ( additionalConfigFilesDir != null && additionalConfigFilesDir.exists() )
{
DirectoryScanner scanner = new DirectoryScanner();
scanner.addDefaultExcludes();
scanner.setBasedir( additionalConfigFilesDir.getPath() );
scanner.scan();
String[] files = scanner.getIncludedFiles();
if ( files != null && files.length > 0 )
{
getLog().info( "Coping additional tomcat config files" );
for ( int i = 0; i < files.length; i++ )
{
File file = new File( additionalConfigFilesDir, files[i] );
getLog().info( " copy " + file.getName() );
FileUtils.copyFileToDirectory( file, confDir );
}
}
}
}
}
/**
* Copies the specified class resource to the specified file.
*
* @param fromPath the path of the class resource to copy
* @param toFile the file to copy to
* @throws IOException if the file could not be copied
*/
private void copyFile( String fromPath, File toFile )
throws IOException
{
URL fromURL = getClass().getResource( fromPath );
if ( fromURL == null )
{
throw new FileNotFoundException( fromPath );
}
FileUtils.copyURLToFile( fromURL, toFile );
}
/**
* Starts the embedded Tomcat server.
*
* @throws IOException if the server could not be configured
* @throws LifecycleException if the server could not be started
* @throws MojoExecutionException if the server could not be configured
*/
private void startContainer()
throws IOException, LifecycleException, MojoExecutionException, ServletException
{
String previousCatalinaBase = System.getProperty( "catalina.base" );
try
{
// Set the system properties
setupSystemProperties();
System.setProperty( "catalina.base", configurationDir.getAbsolutePath() );
if ( serverXml != null )
{
if ( !serverXml.exists() )
{
throw new MojoExecutionException( serverXml.getPath() + " not exists" );
}
Catalina container = new Catalina();
if ( useSeparateTomcatClassLoader )
{
Thread.currentThread().setContextClassLoader( getTomcatClassLoader() );
container.setParentClassLoader( getTomcatClassLoader() );
}
container.setUseNaming( this.useNaming );
container.setConfig( serverXml.getAbsolutePath() );
container.start();
EmbeddedRegistry.getInstance().register( container );
}
else
{
System.setProperty( "java.util.logging.manager", "org.apache.juli.ClassLoaderLogManager" );
System.setProperty( "java.util.logging.config.file",
new File( configurationDir, "conf/logging.properties" ).toString() );
// Trigger loading of catalina.properties
CatalinaProperties.getProperty( "foo" );
Tomcat embeddedTomcat = new ExtendedTomcat( configurationDir );
embeddedTomcat.setBaseDir( configurationDir.getAbsolutePath() );
MemoryRealm memoryRealm = new MemoryRealm();
if ( tomcatUsers != null )
{
if ( !tomcatUsers.exists() )
{
throw new MojoExecutionException( " tomcatUsers " + tomcatUsers.getPath() + " not exists" );
}
getLog().info( "use tomcat-users.xml from " + tomcatUsers.getAbsolutePath() );
memoryRealm.setPathname( tomcatUsers.getAbsolutePath() );
}
embeddedTomcat.setDefaultRealm( memoryRealm );
Context ctx = createContext( embeddedTomcat );
if ( useNaming )
{
embeddedTomcat.enableNaming();
}
embeddedTomcat.getHost().setAppBase( new File( configurationDir, "webapps" ).getAbsolutePath() );
if ( hostName != null )
{
embeddedTomcat.getHost().setName( hostName );
}
if ( aliases != null )
{
for ( String alias : aliases )
{
embeddedTomcat.getHost().addAlias( alias );
}
}
createStaticContext( embeddedTomcat, ctx, embeddedTomcat.getHost() );
Connector connector = new Connector( protocol );
connector.setPort( port );
connector.setMaxPostSize( maxPostSize );
if ( httpsPort > 0 )
{
connector.setRedirectPort( httpsPort );
}
if ( address != null )
{
connector.setAttribute( "address", address );
}
connector.setURIEncoding( uriEncoding );
connector.setUseBodyEncodingForURI( this.useBodyEncodingForURI );
embeddedTomcat.getService().addConnector( connector );
embeddedTomcat.setConnector( connector );
AccessLogValve alv = new AccessLogValve();
alv.setDirectory( new File( configurationDir, "logs" ).getAbsolutePath() );
alv.setPattern( "%h %l %u %t \"%r\" %s %b %I %D" );
embeddedTomcat.getHost().getPipeline().addValve( alv );
// create https connector
Connector httpsConnector = null;
if ( httpsPort > 0 )
{
httpsConnector = new Connector( protocol );
httpsConnector.setPort( httpsPort );
httpsConnector.setMaxPostSize( maxPostSize );
httpsConnector.setSecure( true );
httpsConnector.setProperty( "SSLEnabled", "true" );
// should be default but configure it anyway
httpsConnector.setProperty( "sslProtocol", "TLS" );
if ( keystoreFile != null )
{
httpsConnector.setAttribute( "keystoreFile", keystoreFile );
}
if ( keystorePass != null )
{
httpsConnector.setAttribute( "keystorePass", keystorePass );
}
if ( keystoreType != null )
{
httpsConnector.setAttribute( "keystoreType", keystoreType );
}
if ( trustManagerClassName != null )
{
httpsConnector.setAttribute( "trustManagerClassName", trustManagerClassName );
}
if ( trustMaxCertLength != null )
{
httpsConnector.setAttribute( "trustMaxCertLength", trustMaxCertLength );
}
if ( truststoreAlgorithm != null )
{
httpsConnector.setAttribute( "truststoreAlgorithm", truststoreAlgorithm );
}
if ( truststoreFile != null )
{
httpsConnector.setAttribute( "truststoreFile", truststoreFile );
}
if ( truststorePass != null )
{
httpsConnector.setAttribute( "truststorePass", truststorePass );
}
if ( truststoreProvider != null )
{
httpsConnector.setAttribute( "truststoreProvider", truststoreProvider );
}
if ( truststoreType != null )
{
httpsConnector.setAttribute( "truststoreType", truststoreType );
}
httpsConnector.setAttribute( "clientAuth", clientAuth );
httpsConnector.setUseBodyEncodingForURI( this.useBodyEncodingForURI );
if ( address != null )
{
httpsConnector.setAttribute( "address", address );
}
embeddedTomcat.getEngine().getService().addConnector( httpsConnector );
}
// create ajp connector
Connector ajpConnector = null;
if ( ajpPort > 0 )
{
ajpConnector = new Connector( ajpProtocol );
ajpConnector.setPort( ajpPort );
ajpConnector.setURIEncoding( uriEncoding );
ajpConnector.setUseBodyEncodingForURI( this.useBodyEncodingForURI );
if ( address != null )
{
ajpConnector.setAttribute( "address", address );
}
embeddedTomcat.getEngine().getService().addConnector( ajpConnector );
}
if ( addContextWarDependencies || !getAdditionalWebapps().isEmpty() )
{
createDependencyContexts( embeddedTomcat );
}
if ( useSeparateTomcatClassLoader )
{
Thread.currentThread().setContextClassLoader( getTomcatClassLoader() );
embeddedTomcat.getEngine().setParentClassLoader( getTomcatClassLoader() );
}
embeddedTomcat.start();
Properties portProperties = new Properties();
portProperties.put( "tomcat.maven.http.port", Integer.toString( connector.getLocalPort() ) );
session.getExecutionProperties().put( "tomcat.maven.http.port",
Integer.toString( connector.getLocalPort() ) );
System.setProperty( "tomcat.maven.http.port", Integer.toString( connector.getLocalPort() ) );
if ( httpsConnector != null )
{
session.getExecutionProperties().put( "tomcat.maven.https.port",
Integer.toString( httpsConnector.getLocalPort() ) );
portProperties.put( "tomcat.maven.https.port", Integer.toString( httpsConnector.getLocalPort() ) );
System.setProperty( "tomcat.maven.https.port", Integer.toString( httpsConnector.getLocalPort() ) );
}
if ( ajpConnector != null )
{
session.getExecutionProperties().put( "tomcat.maven.ajp.port",
Integer.toString( ajpConnector.getLocalPort() ) );
portProperties.put( "tomcat.maven.ajp.port", Integer.toString( ajpConnector.getLocalPort() ) );
System.setProperty( "tomcat.maven.ajp.port", Integer.toString( ajpConnector.getLocalPort() ) );
}
if ( propertiesPortFilePath != null )
{
File propertiesPortsFile = new File( propertiesPortFilePath );
if ( propertiesPortsFile.exists() )
{
propertiesPortsFile.delete();
}
FileOutputStream fileOutputStream = new FileOutputStream( propertiesPortsFile );
try
{
portProperties.store( fileOutputStream, "Apache Tomcat Maven plugin port used" );
}
finally
{
IOUtils.closeQuietly( fileOutputStream );
}
}
EmbeddedRegistry.getInstance().register( embeddedTomcat );
}
}
finally
{
if ( previousCatalinaBase != null )
{
System.setProperty( "catalina.base", previousCatalinaBase );
}
}
}
private List<Webapp> getAdditionalWebapps()
{
if ( webapps == null )
{
return Collections.emptyList();
}
return webapps;
}
protected ClassRealm getTomcatClassLoader()
throws MojoExecutionException
{
if ( this.tomcatRealm != null )
{
return tomcatRealm;
}
try
{
ClassWorld world = new ClassWorld();
ClassRealm root = world.newRealm( "tomcat", Thread.currentThread().getContextClassLoader() );
for ( @SuppressWarnings("rawtypes") Iterator i = pluginArtifacts.iterator(); i.hasNext(); )
{
Artifact pluginArtifact = (Artifact) i.next();
// add all plugin artifacts see https://issues.apache.org/jira/browse/MTOMCAT-122
if ( pluginArtifact.getFile() != null )
{
root.addURL( pluginArtifact.getFile().toURI().toURL() );
}
}
tomcatRealm = root;
return root;
}
catch ( DuplicateRealmException e )
{
throw new MojoExecutionException( e.getMessage(), e );
}
catch ( MalformedURLException e )
{
throw new MojoExecutionException( e.getMessage(), e );
}
}
@SuppressWarnings("unchecked")
public Set<Artifact> getProjectArtifacts()
{
return project.getArtifacts();
}
/**
* Causes the current thread to wait indefinitely. This method does not return.
*/
private void waitIndefinitely()
{
Object lock = new Object();
synchronized ( lock )
{
try
{
lock.wait();
}
catch ( InterruptedException exception )
{
getLog().warn( messagesProvider.getMessage( "AbstractRunMojo.interrupted" ), exception );
}
}
}
/**
* Set the SystemProperties from the configuration.
*/
private void setupSystemProperties()
{
if ( systemProperties != null && !systemProperties.isEmpty() )
{
getLog().info( "setting SystemProperties:" );
for ( String key : systemProperties.keySet() )
{
String value = systemProperties.get( key );
if ( value != null )
{
getLog().info( " " + key + "=" + value );
System.setProperty( key, value );
}
else
{
getLog().info( "skip sysProps " + key + " with empty value" );
}
}
}
}
/**
* Allows the startup of additional webapps in the tomcat container by declaration with scope
* "tomcat".
*
* @param container tomcat
* @return dependency tomcat contexts of warfiles in scope "tomcat"
*/
private Collection<Context> createDependencyContexts( Tomcat container )
throws MojoExecutionException, MalformedURLException, ServletException, IOException
{
getLog().info( "Deploying dependency wars" );
// Let's add other modules
List<Context> contexts = new ArrayList<Context>();
ScopeArtifactFilter filter = new ScopeArtifactFilter( "tomcat" );
@SuppressWarnings("unchecked") Set<Artifact> artifacts = project.getArtifacts();
for ( Artifact artifact : artifacts )
{
// Artifact is not yet registered and it has neither test, nor a
// provided scope, not is it optional
if ( "war".equals( artifact.getType() ) && !artifact.isOptional() && filter.include( artifact ) )
{
addContextFromArtifact( container, contexts, artifact, "/" + artifact.getArtifactId(), null, false );
}
}
for ( AbstractWebapp additionalWebapp : getAdditionalWebapps() )
{
String contextPath = additionalWebapp.getContextPath();
if ( !contextPath.startsWith( "/" ) )
{
contextPath = "/" + contextPath;
}
addContextFromArtifact( container, contexts, getArtifact( additionalWebapp ), contextPath,
additionalWebapp.getContextFile(), additionalWebapp.isAsWebapp() );
}
return contexts;
}
private void addContextFromArtifact( Tomcat container, List<Context> contexts, Artifact artifact,
String contextPath, File contextXml, boolean asWebApp )
throws MojoExecutionException, ServletException, IOException
{
getLog().info( "Deploy warfile: " + String.valueOf( artifact.getFile() ) + " to contextPath: " + contextPath );
File webapps = new File( configurationDir, "webapps" );
File artifactWarDir = new File( webapps, artifact.getArtifactId() );
if ( !artifactWarDir.exists() )
{
//dont extract if exists
artifactWarDir.mkdir();
try
{
UnArchiver unArchiver = archiverManager.getUnArchiver( "zip" );
unArchiver.setSourceFile( artifact.getFile() );
unArchiver.setDestDirectory( artifactWarDir );
// Extract the module
unArchiver.extract();
}
catch ( NoSuchArchiverException e )
{
getLog().error( e );
return;
}
catch ( ArchiverException e )
{
getLog().error( e );
return;
}
}
// TODO make that configurable ?
//WebappLoader webappLoader = new WebappLoader( Thread.currentThread().getContextClassLoader() );
WebappLoader webappLoader = createWebappLoader();
Context context = null;
if ( asWebApp )
{
context = container.addWebapp( contextPath, artifactWarDir.getAbsolutePath() );
}
else
{
context = container.addContext( contextPath, artifactWarDir.getAbsolutePath() );
}
context.setLoader( webappLoader );
File contextFile = contextXml != null ? contextXml : getContextFile();
if ( contextFile != null )
{
context.setConfigFile( contextFile.toURI().toURL() );
}
contexts.add( context );
// container.getHost().addChild(context);
}
private void createStaticContext( final Tomcat container, Context context, Host host )
{
if ( staticContextDocbase != null )
{
Context staticContext = container.addContext( staticContextPath, staticContextDocbase );
Tomcat.initWebappDefaults(staticContext);
staticContext.setPrivileged( true );
Wrapper servlet = context.createWrapper();
servlet.setServletClass( DefaultServlet.class.getName() );
servlet.setName( "staticContent" );
staticContext.addChild( servlet );
staticContext.addServletMapping( "/", "staticContent" );
// see https://issues.apache.org/jira/browse/MTOMCAT-238
//host.addChild( staticContext );
}
}
/**
* Resolves the Artifact from the remote repository if necessary. If no version is specified, it will be retrieved
* from the dependency list or from the DependencyManagement section of the pom.
*
* @param additionalWebapp containing information about artifact from plugin configuration.
* @return Artifact object representing the specified file.
* @throws MojoExecutionException with a message if the version can't be found in DependencyManagement.
*/
protected Artifact getArtifact( AbstractWebapp additionalWebapp )
throws MojoExecutionException
{
Artifact artifact;
VersionRange vr;
try
{
vr = VersionRange.createFromVersionSpec( additionalWebapp.getVersion() );
}
catch ( InvalidVersionSpecificationException e )
{
getLog().warn( "fail to create versionRange from version: " + additionalWebapp.getVersion(), e );
vr = VersionRange.createFromVersion( additionalWebapp.getVersion() );
}
if ( StringUtils.isEmpty( additionalWebapp.getClassifier() ) )
{
artifact =
factory.createDependencyArtifact( additionalWebapp.getGroupId(), additionalWebapp.getArtifactId(), vr,
additionalWebapp.getType(), null, Artifact.SCOPE_COMPILE );
}
else
{
artifact =
factory.createDependencyArtifact( additionalWebapp.getGroupId(), additionalWebapp.getArtifactId(), vr,
additionalWebapp.getType(), additionalWebapp.getClassifier(),
Artifact.SCOPE_COMPILE );
}
try
{
resolver.resolve( artifact, project.getRemoteArtifactRepositories(), this.local );
}
catch ( ArtifactResolutionException e )
{
throw new MojoExecutionException( "Unable to resolve artifact.", e );
}
catch ( ArtifactNotFoundException e )
{
throw new MojoExecutionException( "Unable to find artifact.", e );
}
return artifact;
}
}