/*
* ========================================================================
*
* 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.
*
* ========================================================================
*/
package org.apache.cactus.maven2.mojos;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import org.apache.cactus.integration.api.cactify.CactifyUtils;
import org.apache.cactus.integration.api.version.Version;
import org.apache.maven.archiver.MavenArchiveConfiguration;
import org.apache.maven.archiver.MavenArchiver;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.installer.ArtifactInstaller;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.assembly.archive.ArchiveExpansionException;
import org.apache.maven.plugin.assembly.utils.AssemblyFileUtils;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.model.fileset.FileSet;
import org.apache.tools.ant.types.XMLCatalog;
import org.codehaus.cargo.container.internal.util.ResourceUtils;
import org.codehaus.cargo.maven2.log.MavenLogger;
import org.codehaus.cargo.module.webapp.DefaultWarArchive;
import org.codehaus.cargo.module.webapp.EjbRef;
import org.codehaus.cargo.module.webapp.WarArchive;
import org.codehaus.cargo.module.webapp.WebXml;
import org.codehaus.cargo.module.webapp.WebXmlIo;
import org.codehaus.cargo.module.webapp.WebXmlUtils;
import org.codehaus.cargo.module.webapp.WebXmlVersion;
import org.codehaus.cargo.module.webapp.merge.WebXmlMerger;
import org.codehaus.cargo.util.log.Logger;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.jar.ManifestException;
import org.codehaus.plexus.archiver.manager.ArchiverManager;
import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
import org.codehaus.plexus.archiver.war.WarArchiver;
import org.codehaus.plexus.util.FileUtils;
import org.jdom.JDOMException;
/**
* A maven2 mojo that injects elements necessary to run Cactus tests into an
* existing WAR file.
*
* @version $Id: CactifyWarMojo.java 394252 2008-04-29 04:20:17Z ptahchiev $
* @goal cactifywar
* @requiresDependencyResolution compile
*/
public class CactifyWarMojo extends AbstractMojo
{
/**
* Name of the generated web app file.
*/
private String FILE_NAME = "cactus.war";
/**
* Context of the cactus web application.
* @parameter
*/
private String context;
/**
* Get some non-crypto-grade randomness from various places.
*/
private static Random rand = new Random(System.currentTimeMillis()
+ Runtime.getRuntime().freeMemory());
/**
* Used to create artifacts.
*
* @component
*/
private ArtifactFactory artifactFactory;
/**
* For resolving entities such as DTDs.
*/
private XMLCatalog xmlCatalog = null;
/**
* The archive that contains the web-app that should be cactified.
* @parameter
*/
private File srcFile;
/**
* Test classes.
* @parameter
*/
private FileSet testClasses;
/**
* The War archiver.
*
* @parameter expression="${component.org.codehaus.plexus.archiver.Archiver#war}"
* @required
*/
private WarArchiver warArchiver;
/**
* The maven project.
*
* @parameter expression="${project}"
* @required
* @readonly
*/
private MavenProject project;
/**
* The maven archive configuration to use.
*
* @parameter
*/
private MavenArchiveConfiguration archive = new MavenArchiveConfiguration();
/**
* Location of the descriptor of which the content should be merged into
* the descriptor of the cactified archive.
* @parameter
*/
private File mergeWebXml;
/**
* The Cactus test redirectors.
* @parameter
*/
private final List redirectors = new ArrayList();
/**
* List of ejb-refs to add to the deployment descriptor
* of the cactified war.
*/
private final List ejbRefs = new ArrayList();
/**
* The cargo ResourceUtils.
*/
private ResourceUtils utils = new ResourceUtils();
/**
* The archive manager.
* @component
*/
private ArchiverManager archiverManager;
/**
* Dependencies to be included in the WEB-INF/lib folder.
* @parameter
*/
private final List libDependencies = new ArrayList();
/**
* @plexus.requirement
*/
private ArtifactFactory factory;
/**
* @parameter expression="${component.org.apache.maven.artifact.installer.ArtifactInstaller}"
* @required
* @readonly
*/
protected ArtifactInstaller installer;
/**
* The file that we want to produce.
* @parameter
* @required
*/
private File destFile;
/**
* @parameter expression="${localRepository}"
* @required
* @readonly
*/
protected ArtifactRepository localRepository;
/**
* GroupId of the artifact to be installed. Retrieved from POM file
* if specified.
*
* @parameter expression="${project.groupId}"
*/
protected String groupId;
/**
* ArtifactId of the artifact to be installed. Retrieved from POM file
* if specified.
*
* @parameter expression="${project.artifactId}"
*/
protected String artifactId;
/**
* Version of the artifact to be installed. Retrieved from POM file
* if specified
*
* @parameter expression="${project.version}"
*/
protected String projectVersion;
/**
* Version of the web.xml to create.
*
* @parameter
*/
protected String version = null;
/**
* Packaging type of the artifact to be installed. Retrieved from POM file
* if specified
*
* @parameter expression="${project.packaging}"
*/
protected String packaging;
/**
* Classifier type of the artifact to be installed. For example, "sources"
* or "javadoc".
* Defaults to none which means this is the project's main jar.
*
* @parameter expression="${project.classifier}"
*/
protected String classifier;
/**
* The "main" method of the mojo.
* @throws MojoExecutionException in case an error occurs.
* @throws MojoFailureException in case a failure occurs.
*/
public void execute() throws MojoExecutionException, MojoFailureException
{
if (this.srcFile != null)
{
getLog().info("Analyzing war: " + this.srcFile.getAbsolutePath());
}
WebXml webXml = null;
MavenArchiver archiver = new MavenArchiver();
archiver.setArchiver(warArchiver);
archiver.setOutputFile(destFile);
File tmpWebXml = null;
File tempLocation = null;
try
{
if (srcFile != null)
{
webXml = getOriginalWebXml();
if (webXml == null)
{
if (this.version == null)
{
throw new MojoExecutionException("Your source file "
+ "does not contain a web.xml. Please provide a "
+ "war with a web.xml or specify the [version] "
+ "attribute.");
}
WebXmlVersion webXmlVersion = null;
if (this.version.equals("2.2"))
{
webXmlVersion = WebXmlVersion.V2_2;
}
else if (this.version.equals("2.3"))
{
webXmlVersion = WebXmlVersion.V2_3;
}
else
{
webXmlVersion = WebXmlVersion.V2_4;
}
webXml = WebXmlIo.newWebXml(webXmlVersion);
}
}
else
{
if (this.version == null)
{
throw new MojoExecutionException("You need to specify "
+ "either the [srcFile] or the [version] attribute");
}
else
{
WebXmlVersion webXmlVersion = null;
if (this.version.equals("2.2"))
{
webXmlVersion = WebXmlVersion.V2_2;
}
else if (this.version.equals("2.3"))
{
webXmlVersion = WebXmlVersion.V2_3;
}
else
{
webXmlVersion = WebXmlVersion.V2_4;
}
webXml = WebXmlIo.newWebXml(webXmlVersion);
}
}
tmpWebXml = cactifyWebXml(webXml);
//Add the required libs for Cactus.
addJarWithClass("org.aspectj.lang.JoinPoint",
"AspectJ Runtime");
addJarWithClass("org.apache.cactus."
+ "ServletTestCase", "Cactus Framework");
addJarWithClass("org.apache.commons.logging.Log",
"Commons-Logging");
addJarWithClass("org.apache.commons."
+ "httpclient.HttpClient", "Commons-HttpClient");
addJarWithClass("junit.framework."
+ "TestCase", "JUnit");
tempLocation = createTempFile("cactus", "explode.tmp.dir",
getProject().getBasedir(), true);
tempLocation.mkdirs();
tempLocation.deleteOnExit();
if (testClasses != null)
{
//Add the classes.
warArchiver.addClasses(new File(testClasses.getDirectory()),
testClasses.getIncludesArray(),
testClasses.getExcludesArray());
}
//Now add all of the additional lib files.
for (Iterator iter = libDependencies.iterator(); iter.hasNext();)
{
org.apache.cactus.maven2.mojos.Dependency dependency =
(org.apache.cactus.maven2.mojos.Dependency) iter.next();
warArchiver.addLib(new File(dependency.getDependencyPath(
project, getLog())));
}
try
{
if(this.srcFile != null)
{
AssemblyFileUtils.unpack(this.srcFile, tempLocation,
archiverManager);
}
}
catch (ArchiveExpansionException e)
{
throw new MojoExecutionException("Error extracting the"
+ " archive.", e);
}
catch (NoSuchArchiverException e)
{
throw new MojoExecutionException("Problem reading the "
+ "source archive.", e);
}
warArchiver.addDirectory(tempLocation);
warArchiver.setWebxml(tmpWebXml);
archiver.createArchive(getProject(), getArchive());
}
catch (ArchiverException e)
{
throw new MojoExecutionException("Problem reading the "
+ "source archive.", e);
}
catch (JDOMException e)
{
throw new MojoExecutionException("Unable to cactify "
+ "your web.xml.", e);
}
catch (ManifestException e)
{
throw new MojoExecutionException("Problem reading the "
+ "source archive.", e);
}
catch (IOException e)
{
throw new MojoExecutionException("Input/output error reading the"
+ "source archive.", e);
}
catch (DependencyResolutionRequiredException e)
{
throw new MojoExecutionException("Error resolving your "
+ "dependencies", e);
}
finally
{
try
{
if (tempLocation != null)
{
FileUtils.deleteDirectory(tempLocation);
}
}
catch (IOException e)
{
throw new MojoExecutionException("Error deleting temporary "
+ "folder", e);
}
}
}
/**
* Enhances the provided web deployment descriptor with the definitions
* required for testing with Cactus.
*
* @param theWebXml The original deployment descriptor
* @return A temporary file containing the cactified descriptor
* @throws JDOMException in case a JDOM exception is thrown.
* @throws MojoExecutionException in case any other error occurs.
*/
private File cactifyWebXml(WebXml theWebXml) throws JDOMException,
MojoExecutionException
{
CactifyUtils utils = new CactifyUtils();
utils.setLogger(createLogger());
utils.addRedirectorDefinitions(theWebXml, redirectors);
addJspRedirector();
addEjbRefs(theWebXml);
// If the user has specified a deployment descriptor to merge into the
// cactified descriptor, perform the merge
if (this.mergeWebXml != null)
{
try
{
WebXml parsedMergeWebXml = WebXmlIo.parseWebXmlFromFile(
this.mergeWebXml, this.xmlCatalog);
WebXmlMerger merger = new WebXmlMerger(theWebXml);
merger.setLogger(utils.getLogger());
merger.merge(parsedMergeWebXml);
}
catch (IOException e)
{
throw new MojoExecutionException(
"Could not merge deployment descriptors", e);
}
}
// Serialize the cactified deployment descriptor into a temporary file,
// so that it can get picked up by the War task
//FileUtils fileUtils = FileUtils.newFileUtils();
File tmpDir = createTempFile("cactus", "tmp.dir",
new File("."), true);
tmpDir.mkdirs();
tmpDir.deleteOnExit();
File webXmlFile = null;
try
{
tmpDir.mkdir();
File[] files = WebXmlIo.writeAll(theWebXml,
tmpDir.getAbsolutePath());
List includes = new ArrayList();
for (int i = 0; i < files.length; i++)
{
File f = files[i];
f.deleteOnExit();
if (f.getName().equals("web.xml"))
{
webXmlFile = f;
}
else
{
includes.add(f.getName());
}
}
String[] strIncludes = new String[includes.size()];
int i = 0;
for (Iterator iter = includes.iterator(); iter.hasNext();)
{
strIncludes[i] = iter.next().toString();
i++;
}
try
{
warArchiver.addWebinf(tmpDir, strIncludes, null);
}
catch (ArchiverException e)
{
throw new MojoExecutionException(
"Error reading the source archive.", e);
}
}
catch (IOException ioe)
{
throw new MojoExecutionException(
"Could not write temporary deployment descriptor", ioe);
}
return webXmlFile;
}
/**
* A method to create the temporary files.
*
* @param thePrefix the prefix of the filename.
* @param theSuffix the suffix of the filename
* @param theParentDir the parent directory
* @param isDeleteOnExit should we delete the directories on exit?
* @return the temporary file
*/
public File createTempFile(String thePrefix, String theSuffix,
File theParentDir, boolean isDeleteOnExit)
{
File result = null;
String parent = (theParentDir == null)
? System.getProperty("java.io.tmpdir")
: theParentDir.getPath();
DecimalFormat fmt = new DecimalFormat("#####");
synchronized (rand)
{
do
{
result = new File(parent,
thePrefix + fmt.format(Math.abs(rand.nextInt()))
+ theSuffix);
}
while (result.exists());
}
if (isDeleteOnExit)
{
result.deleteOnExit();
}
return result;
}
/**
* Adds the Cactus JSP redirector file to the web application.
* @throws MojoExecutionException in case an error occurs
*/
private void addJspRedirector() throws MojoExecutionException
{
// Now copy the actual JSP redirector file into the web application
File jspRedirectorFile = new File(
new File(System.getProperty("java.io.tmpdir")),
"jspRedirector.jsp");
jspRedirectorFile.deleteOnExit();
try
{
utils.copyResource("/org/apache/cactus/server/jspRedirector.jsp",
jspRedirectorFile);
}
catch (IOException e)
{
getLog().warn("Could not copy the JSP redirector ("
+ e.getMessage() + ")");
}
try
{
warArchiver.addFile(jspRedirectorFile, jspRedirectorFile.getName());
}
catch (ArchiverException e)
{
throw new MojoExecutionException("Failed to add jsp redirector", e);
}
}
/**
* Adds the JAR file containing the specified resource to the WEB-INF/lib
* folder of a web-application archive.
*
* @param theClassName The name of the class that the JAR contains
* @param theDescription A description of the JAR that should be displayed
* to the user in log messages
* @return File the jar file
* @throws ArchiverException in case an error occurs.
*/
private File addJarWithClass(String theClassName, String theDescription)
throws ArchiverException
{
String resourceName = "/" + theClassName.replace('.', '/') + ".class";
if (srcFile != null)
{
try
{
WarArchive srcWar = new DefaultWarArchive(
new FileInputStream(srcFile));
getLog().info("Inspecting..");
if (srcWar.containsClass(theClassName))
{
getLog().info("The " + theDescription + " JAR is "
+ "already present in the WAR. Will skip.");
return null;
}
}
catch (IOException ioe)
{
getLog().warn("Problem reading source WAR to when "
+ "trying to detect already present JAR files (" + ioe + ")");
}
}
File file = utils.getResourceLocation(resourceName);
if (file != null)
{
getLog().info("Adding: " + file.getName());
warArchiver.addLib(file);
}
return file;
}
/**
* Extracts and parses the original web deployment descriptor from the
* web-app.
*
* @return The parsed descriptor or null if not found
* @throws MojoExecutionException If the descriptor could not be
* parsed
* @throws JDOMException in case is JDOM exception is thrown.
*/
private WebXml getOriginalWebXml() throws MojoExecutionException,
JDOMException
{
// Open the archive as JAR file and extract the deployment descriptor
WarArchive war = null;
try
{
war = new DefaultWarArchive(new FileInputStream(this.srcFile));
WebXml webXml = war.getWebXml();
return webXml;
}
catch (IOException e)
{
throw new MojoExecutionException("Failed to get the original "
+ "web.xml", e);
}
}
/**
* Add ejb references to a web.xml.
*
* @param theWebXml the web.xml to modify
*/
private void addEjbRefs(WebXml theWebXml)
{
Iterator i = ejbRefs.iterator();
while (i.hasNext())
{
EjbRef ref = (EjbRef) i.next();
WebXmlUtils.addEjbRef(theWebXml, ref);
}
}
/**
* Getter method for the MavenProject.
* @return the MavenProject
*/
public MavenProject getProject()
{
return project;
}
/**
* Setter method for the MavenProject.
* @param project
*/
public void setProject(MavenProject theProject)
{
this.project = theProject;
}
/**
* Getter method for the MavenArchiveConfiguration.
* @return the MavenArchiveConfiguration
*/
public MavenArchiveConfiguration getArchive()
{
return archive;
}
/**
* Create a logger. If a <code><log></code> configuration element
* has been specified by the user then use it. If none is specified then
* log to the Maven 2 logging subsystem.
*
* @return the logger to use for logging this plugin's activity
*/
protected Logger createLogger()
{
Logger logger;
logger = new MavenLogger(getLog());
return logger;
}
/**
* Returns the context.
*
* @return <code>java.lang.String</code>
*/
public String getContext()
{
return context;
}
/**
* Returns the source file for cactification.
*
* @return <code>java.io.File</code>
*/
public File getSrcFile()
{
return this.srcFile;
}
/**
* Sets the context.
*
* @param context
*/
public void setContext(String theContext)
{
this.context = theContext;
}
/**
* Sets the web-app version to use when creating a WAR file from scratch.
*
* @param theVersion The version
*/
public final void setVersion(Version theVersion)
{
this.version = theVersion.getValue();
}
/**
* Sets the source file for cactification.
*
* @param theSrcFile The source file
*/
public final void setSrcFile(File theSrcFile)
{
this.srcFile = theSrcFile;
}
/**
* Gets the file name.
*
* @return the name of the web app file
*/
public String getFileName()
{
return FILE_NAME;
}
/**
* Setter method for the destFile.
*
* @param destFile
*/
public void setDestFile(File theDestFile)
{
this.destFile = theDestFile;
}
/**
* Adds a configured EjbRef instance. Called by Ant.
*
* @param theEjbRef the EjbRef to add
*/
public final void addConfiguredEjbref(EjbRef theEjbRef)
{
ejbRefs.add(theEjbRef);
}
/**
* Setter for the warArchiver so that the cactifyearmojo
* can set it.
*
* @param warArchiver
*/
public void setWarArchiver(WarArchiver theWarArchiver)
{
this.warArchiver = theWarArchiver;
}
}