blob: 64262ec6013c9f1db0ce6b47b9f86e0923ecbf95 [file] [log] [blame]
package org.apache.tomcat.maven.plugin.tomcat8.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.commons.compress.archivers.ArchiveException;
import org.apache.commons.compress.archivers.ArchiveOutputStream;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.apache.commons.compress.archivers.jar.JarArchiveEntry;
import org.apache.commons.io.FileUtils;
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.model.Dependency;
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.project.MavenProjectHelper;
import org.apache.tomcat.maven.plugin.tomcat8.AbstractTomcat8Mojo;
import org.apache.tomcat.maven.runner.Tomcat8Runner;
import org.apache.tomcat.maven.runner.Tomcat8RunnerCli;
import org.codehaus.plexus.archiver.jar.Manifest;
import org.codehaus.plexus.archiver.jar.ManifestException;
import org.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.SelectorUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* @author Olivier Lamy
* @since 2.0
*/
public abstract class AbstractExecWarMojo
extends AbstractTomcat8Mojo
{
@Parameter( defaultValue = "${project.artifact}", required = true, readonly = true )
protected Artifact projectArtifact;
/**
* The maven project.
*/
@Parameter( defaultValue = "${project}", required = true, readonly = true )
protected MavenProject project;
@Parameter( defaultValue = "${plugin.artifacts}", required = true )
protected List<Artifact> pluginArtifacts;
@Parameter( defaultValue = "${project.build.directory}" )
protected File buildDirectory;
/**
* Path under {@link #buildDirectory} where this mojo may do temporary work.
*/
@Parameter( defaultValue = "${project.build.directory}/tomcat8-maven-plugin-exec" )
private File pluginWorkDirectory;
@Parameter( property = "maven.tomcat.exec.war.tomcatConf", defaultValue = "src/main/tomcatconf" )
protected File tomcatConfigurationFilesDirectory;
@Parameter( defaultValue = "src/main/tomcatconf/server.xml", property = "maven.tomcat.exec.war.serverXml" )
protected File serverXml;
/**
* Name of the generated exec JAR.
*/
@Parameter( property = "tomcat.jar.finalName",
defaultValue = "${project.artifactId}-${project.version}-war-exec.jar", required = true )
protected String finalName;
/**
* Skip the execution
*
* @since 2.2
*/
@Parameter( property = "maven.tomcat.skip", defaultValue = "false" )
private boolean skip;
/**
* The webapp context path to use for the web application being run.
* The name to store webapp in exec jar. Do not use /
*/
@Parameter( property = "maven.tomcat.path", defaultValue = "${project.artifactId}", required = true )
protected String path;
@Parameter
protected List<WarRunDependency> warRunDependencies;
@Component
protected ArtifactResolver artifactResolver;
/**
* Maven Artifact Factory component.
*/
@Component
protected ArtifactFactory artifactFactory;
/**
* Location of the local repository.
*/
@Parameter( defaultValue = "${localRepository}", required = true, readonly = true )
protected ArtifactRepository local;
/**
* List of Remote Repositories used by the resolver
*/
@Parameter( defaultValue = "${project.remoteArtifactRepositories}", required = true, readonly = true )
protected List<ArtifactRepository> remoteRepos;
@Component
protected MavenProjectHelper projectHelper;
/**
* Attach or not the generated artifact to the build (use true if you want to install or deploy it)
*/
@Parameter( property = "maven.tomcat.exec.war.attachArtifact", defaultValue = "true", required = true )
protected boolean attachArtifact;
/**
* the classifier to use for the attached/generated artifact
*/
@Parameter( property = "maven.tomcat.exec.war.attachArtifactClassifier", defaultValue = "exec-war",
required = true )
protected String attachArtifactClassifier;
/**
* the type to use for the attached/generated artifact
*/
@Parameter( property = "maven.tomcat.exec.war.attachArtifactType", defaultValue = "jar", required = true )
protected String attachArtifactClassifierType;
/**
* to enable naming when starting tomcat
*/
@Parameter( property = "maven.tomcat.exec.war.enableNaming", defaultValue = "false", required = true )
protected boolean enableNaming;
/**
* see http://tomcat.apache.org/tomcat-7.0-doc/config/valve.html
*/
@Parameter( property = "maven.tomcat.exec.war.accessLogValveFormat", defaultValue = "%h %l %u %t %r %s %b %I %D",
required = true )
protected String accessLogValveFormat;
/**
* list of extra dependencies to add in the standalone tomcat jar: your jdbc driver, mail.jar etc..
* <b>Those dependencies will be in root classloader.</b>
*/
@Parameter
protected List<ExtraDependency> extraDependencies;
/**
* list of extra resources to add in the standalone tomcat jar: your logger configuration etc
*/
@Parameter
protected List<ExtraResource> extraResources;
/**
* Main class to use for starting the standalone jar.
*/
@Parameter( property = "maven.tomcat.exec.war.mainClass",
defaultValue = "org.apache.tomcat.maven.runner.Tomcat8RunnerCli", required = true )
protected String mainClass;
/**
* which connector protocol to use HTTP/1.1 or org.apache.coyote.http11.Http11NioProtocol
*/
@Parameter( property = "maven.tomcat.exec.war.connectorHttpProtocol", defaultValue = "HTTP/1.1", required = true )
protected String connectorHttpProtocol;
/**
* configure a default http port for the standalone jar
*
* @since 2.2
*/
@Parameter( property = "maven.tomcat.exec.war.httpPort" )
protected String httpPort;
/**
* File patterns to exclude from extraDependencies
*
* @since 2.2
*/
@Parameter
protected String[] excludes;
public void execute()
throws MojoExecutionException, MojoFailureException
{
if ( this.skip )
{
getLog().info( "skip execution" );
return;
}
//project.addAttachedArtifact( );
File warExecFile = new File( buildDirectory, finalName );
if ( warExecFile.exists() )
{
warExecFile.delete();
}
File execWarJar = new File( buildDirectory, finalName );
FileOutputStream execWarJarOutputStream = null;
ArchiveOutputStream os = null;
File tmpPropertiesFile = null;
File tmpManifestFile = null;
FileOutputStream tmpPropertiesFileOutputStream = null;
PrintWriter tmpManifestWriter = null;
try
{
tmpPropertiesFile = new File( buildDirectory, "war-exec.properties" );
if ( tmpPropertiesFile.exists() )
{
tmpPropertiesFile.delete();
}
tmpPropertiesFile.getParentFile().mkdirs();
tmpManifestFile = new File( buildDirectory, "war-exec.manifest" );
if ( tmpManifestFile.exists() )
{
tmpManifestFile.delete();
}
tmpPropertiesFileOutputStream = new FileOutputStream( tmpPropertiesFile );
execWarJar.getParentFile().mkdirs();
execWarJar.createNewFile();
execWarJarOutputStream = new FileOutputStream( execWarJar );
tmpManifestWriter = new PrintWriter( tmpManifestFile );
// store :
//* wars in the root: foo.war
//* tomcat jars
//* file tomcat.standalone.properties with possible values :
// * useServerXml=true/false to use directly the one provided
// * enableNaming=true/false
// * wars=foo.war|contextpath;bar.war ( |contextpath is optionnal if empty use the war name )
// * accessLogValveFormat=
// * connectorhttpProtocol: HTTP/1.1 or org.apache.coyote.http11.Http11NioProtocol
//* optionnal: conf/ with usual tomcat configuration files
//* MANIFEST with Main-Class
Properties properties = new Properties();
properties.put( Tomcat8Runner.ARCHIVE_GENERATION_TIMESTAMP_KEY,
Long.toString( System.currentTimeMillis() ) );
properties.put( Tomcat8Runner.ENABLE_NAMING_KEY, Boolean.toString( enableNaming ) );
properties.put( Tomcat8Runner.ACCESS_LOG_VALVE_FORMAT_KEY, accessLogValveFormat );
properties.put( Tomcat8Runner.HTTP_PROTOCOL_KEY, connectorHttpProtocol );
if ( httpPort != null )
{
properties.put( Tomcat8Runner.HTTP_PORT_KEY, httpPort );
}
os = new ArchiveStreamFactory().createArchiveOutputStream( ArchiveStreamFactory.JAR,
execWarJarOutputStream );
if ( "war".equals( project.getPackaging() ) )
{
os.putArchiveEntry( new JarArchiveEntry( StringUtils.removeStart( path, "/" ) + ".war" ) );
IOUtils.copy( new FileInputStream( projectArtifact.getFile() ), os );
os.closeArchiveEntry();
properties.put( Tomcat8Runner.WARS_KEY, StringUtils.removeStart( path, "/" ) + ".war|" + path );
}
else if ( warRunDependencies != null && !warRunDependencies.isEmpty() )
{
for ( WarRunDependency warRunDependency : warRunDependencies )
{
if ( warRunDependency.dependency != null )
{
Dependency dependency = warRunDependency.dependency;
String version = dependency.getVersion();
if ( StringUtils.isEmpty( version ) )
{
version = findArtifactVersion( dependency );
}
if ( StringUtils.isEmpty( version ) )
{
throw new MojoExecutionException(
"Dependency '" + dependency.getGroupId() + "':'" + dependency.getArtifactId()
+ "' does not have version specified" );
}
Artifact artifact = artifactFactory.createArtifactWithClassifier( dependency.getGroupId(), //
dependency.getArtifactId(), //
version, //
dependency.getType(), //
dependency.getClassifier() );
artifactResolver.resolve( artifact, this.remoteRepos, this.local );
File warFileToBundle = new File( resolvePluginWorkDir(), artifact.getFile().getName() );
FileUtils.copyFile( artifact.getFile(), warFileToBundle );
if ( warRunDependency.contextXml != null )
{
warFileToBundle = addContextXmlToWar( warRunDependency.contextXml, warFileToBundle );
}
final String warFileName = artifact.getFile().getName();
os.putArchiveEntry( new JarArchiveEntry( warFileName ) );
IOUtils.copy( new FileInputStream( warFileToBundle ), os );
os.closeArchiveEntry();
String propertyWarValue = properties.getProperty( Tomcat8Runner.WARS_KEY );
String contextPath =
StringUtils.isEmpty( warRunDependency.contextPath ) ? "/" : warRunDependency.contextPath;
if ( propertyWarValue != null )
{
properties.put( Tomcat8Runner.WARS_KEY,
propertyWarValue + ";" + warFileName + "|" + contextPath );
}
else
{
properties.put( Tomcat8Runner.WARS_KEY, warFileName + "|" + contextPath );
}
}
}
}
if ( serverXml != null && serverXml.exists() )
{
os.putArchiveEntry( new JarArchiveEntry( "conf/server.xml" ) );
IOUtils.copy( new FileInputStream( serverXml ), os );
os.closeArchiveEntry();
properties.put( Tomcat8Runner.USE_SERVER_XML_KEY, Boolean.TRUE.toString() );
}
else
{
properties.put( Tomcat8Runner.USE_SERVER_XML_KEY, Boolean.FALSE.toString() );
}
os.putArchiveEntry( new JarArchiveEntry( "conf/web.xml" ) );
IOUtils.copy( getClass().getResourceAsStream( "/conf/web.xml" ), os );
os.closeArchiveEntry();
properties.store( tmpPropertiesFileOutputStream, "created by Apache Tomcat Maven plugin" );
tmpPropertiesFileOutputStream.flush();
tmpPropertiesFileOutputStream.close();
os.putArchiveEntry( new JarArchiveEntry( Tomcat8RunnerCli.STAND_ALONE_PROPERTIES_FILENAME ) );
IOUtils.copy( new FileInputStream( tmpPropertiesFile ), os );
os.closeArchiveEntry();
// add tomcat classes
for ( Artifact pluginArtifact : pluginArtifacts )
{
if ( StringUtils.equals( "org.apache.tomcat", pluginArtifact.getGroupId() ) //
|| StringUtils.equals( "org.apache.tomcat.embed", pluginArtifact.getGroupId() ) //
|| StringUtils.equals( "org.eclipse.jdt.core.compiler", pluginArtifact.getGroupId() ) //
|| StringUtils.equals( "commons-cli", pluginArtifact.getArtifactId() ) //
|| StringUtils.equals( "tomcat8-war-runner", pluginArtifact.getArtifactId() ) )
{
JarFile jarFile = new JarFile( pluginArtifact.getFile() );
extractJarToArchive( jarFile, os, null );
}
}
// add extra dependencies
if ( extraDependencies != null && !extraDependencies.isEmpty() )
{
for ( Dependency dependency : extraDependencies )
{
String version = dependency.getVersion();
if ( StringUtils.isEmpty( version ) )
{
version = findArtifactVersion( dependency );
}
if ( StringUtils.isEmpty( version ) )
{
throw new MojoExecutionException(
"Dependency '" + dependency.getGroupId() + "':'" + dependency.getArtifactId()
+ "' does not have version specified" );
}
// String groupId, String artifactId, String version, String scope, String type
Artifact artifact = artifactFactory.createArtifact( dependency.getGroupId(), //
dependency.getArtifactId(), //
version, //
dependency.getScope(), //
dependency.getType() );
artifactResolver.resolve( artifact, this.remoteRepos, this.local );
JarFile jarFile = new JarFile( artifact.getFile() );
extractJarToArchive( jarFile, os, this.excludes );
}
}
Manifest manifest = new Manifest();
Manifest.Attribute mainClassAtt = new Manifest.Attribute();
mainClassAtt.setName( "Main-Class" );
mainClassAtt.setValue( mainClass );
manifest.addConfiguredAttribute( mainClassAtt );
manifest.write( tmpManifestWriter );
tmpManifestWriter.flush();
tmpManifestWriter.close();
os.putArchiveEntry( new JarArchiveEntry( "META-INF/MANIFEST.MF" ) );
IOUtils.copy( new FileInputStream( tmpManifestFile ), os );
os.closeArchiveEntry();
if ( attachArtifact )
{
//MavenProject project, String artifactType, String artifactClassifier, File artifactFile
projectHelper.attachArtifact( project, attachArtifactClassifierType, attachArtifactClassifier,
execWarJar );
}
if ( extraResources != null )
{
for ( ExtraResource extraResource : extraResources )
{
DirectoryScanner directoryScanner = new DirectoryScanner();
directoryScanner.setBasedir( extraResource.getDirectory() );
directoryScanner.addDefaultExcludes();
directoryScanner.setExcludes( toStringArray( extraResource.getExcludes() ) );
directoryScanner.setIncludes( toStringArray( extraResource.getIncludes() ) );
directoryScanner.scan();
for ( String includeFile : directoryScanner.getIncludedFiles() )
{
getLog().debug( "include file:" + includeFile );
os.putArchiveEntry( new JarArchiveEntry( includeFile ) );
IOUtils.copy( new FileInputStream( new File( extraResource.getDirectory(), includeFile ) ),
os );
os.closeArchiveEntry();
}
}
}
if ( tomcatConfigurationFilesDirectory != null && tomcatConfigurationFilesDirectory.exists() )
{
// Because its the tomcat default dir for configs
String aConfigOutputDir = "conf/";
copyDirectoryContentIntoArchive( tomcatConfigurationFilesDirectory, aConfigOutputDir, os );
}
}
catch ( ManifestException e )
{
throw new MojoExecutionException( e.getMessage(), e );
}
catch ( IOException e )
{
throw new MojoExecutionException( e.getMessage(), e );
}
catch ( ArchiveException e )
{
throw new MojoExecutionException( e.getMessage(), e );
}
catch ( ArtifactNotFoundException e )
{
throw new MojoExecutionException( e.getMessage(), e );
}
catch ( ArtifactResolutionException e )
{
throw new MojoExecutionException( e.getMessage(), e );
}
finally
{
IOUtils.closeQuietly( os );
IOUtils.closeQuietly( tmpManifestWriter );
IOUtils.closeQuietly( execWarJarOutputStream );
IOUtils.closeQuietly( tmpPropertiesFileOutputStream );
}
}
protected String findArtifactVersion( Dependency dependency )
{
// search in project.dependencies
for ( Dependency projectDependency : (List<Dependency>) this.project.getDependencies() )
{
if ( sameDependencyWithoutVersion( dependency, projectDependency ) )
{
return projectDependency.getVersion();
}
}
// search in project.dependencies
for ( Dependency projectDependency : this.project.getDependencyManagement().getDependencies() )
{
if ( sameDependencyWithoutVersion( dependency, projectDependency ) )
{
return projectDependency.getVersion();
}
}
return null;
}
protected boolean sameDependencyWithoutVersion( Dependency that, Dependency dependency )
{
return StringUtils.equals( that.getGroupId(), dependency.getGroupId() ) && StringUtils.equals(
that.getArtifactId(), dependency.getArtifactId() );
}
protected void copyDirectoryContentIntoArchive( File sourceFolder, String destinationPath,
ArchiveOutputStream archiveOutputStream )
throws IOException
{
// Scan the directory
DirectoryScanner directoryScanner = new DirectoryScanner();
directoryScanner.setBasedir( sourceFolder );
directoryScanner.addDefaultExcludes();
directoryScanner.scan();
// Each File
for ( String includeFileName : directoryScanner.getIncludedFiles() )
{
getLog().debug( "include configuration file : " + destinationPath + includeFileName );
File inputFile = new File( sourceFolder, includeFileName );
FileInputStream sourceFileInputStream = null;
try
{
sourceFileInputStream = new FileInputStream( inputFile );
archiveOutputStream.putArchiveEntry( new JarArchiveEntry( destinationPath + includeFileName ) );
IOUtils.copy( sourceFileInputStream, archiveOutputStream );
archiveOutputStream.closeArchiveEntry();
}
finally
{
IOUtils.closeQuietly( sourceFileInputStream );
}
}
}
/**
* Resolves the plugin work dir as a sub directory of {@link #buildDirectory}, creating it if it does not exist.
*
* @return File representing the resolved plugin work dir
* @throws MojoExecutionException if the plugin work dir cannot be created
*/
protected File resolvePluginWorkDir()
throws MojoExecutionException
{
if ( !pluginWorkDirectory.exists() && !pluginWorkDirectory.mkdirs() )
{
throw new MojoExecutionException(
"Could not create plugin work directory at " + pluginWorkDirectory.getAbsolutePath() );
}
return pluginWorkDirectory;
}
protected String[] toStringArray( List<String> list )
{
if ( list == null || list.isEmpty() )
{
return new String[0];
}
List<String> res = new ArrayList<>( list.size() );
for ( String s : list )
{
res.add( s );
}
return res.toArray( new String[res.size()] );
}
/**
* return file can be deleted
*/
protected File addContextXmlToWar( File contextXmlFile, File warFile )
throws IOException, ArchiveException
{
ArchiveOutputStream os = null;
OutputStream warOutputStream = null;
File tmpWar = File.createTempFile( "tomcat", "war-exec" );
tmpWar.deleteOnExit();
try
{
warOutputStream = new FileOutputStream( tmpWar );
os = new ArchiveStreamFactory().createArchiveOutputStream( ArchiveStreamFactory.JAR, warOutputStream );
os.putArchiveEntry( new JarArchiveEntry( "META-INF/context.xml" ) );
IOUtils.copy( new FileInputStream( contextXmlFile ), os );
os.closeArchiveEntry();
JarFile jarFile = new JarFile( warFile );
extractJarToArchive( jarFile, os, null );
os.flush();
}
finally
{
IOUtils.closeQuietly( os );
IOUtils.closeQuietly( warOutputStream );
}
return tmpWar;
}
/**
* Copy the contents of a jar file to another archive
*
* @param file The input jar file
* @param os The output archive
* @throws IOException
*/
protected void extractJarToArchive( JarFile file, ArchiveOutputStream os, String[] excludes )
throws IOException
{
Enumeration<? extends JarEntry> entries = file.entries();
while ( entries.hasMoreElements() )
{
JarEntry j = entries.nextElement();
if ( excludes != null && excludes.length > 0 )
{
for ( String exclude : excludes )
{
if ( SelectorUtils.match( exclude, j.getName() ) )
{
continue;
}
}
}
if ( StringUtils.equalsIgnoreCase( j.getName(), "META-INF/MANIFEST.MF" ) )
{
continue;
}
os.putArchiveEntry( new JarArchiveEntry( j.getName() ) );
IOUtils.copy( file.getInputStream( j ), os );
os.closeArchiveEntry();
}
if ( file != null )
{
file.close();
}
}
}