/*
* ========================================================================
*
* Copyright 2003 The Apache Software Foundation.
*
* Licensed 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.integration.ant;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.cactus.integration.ant.util.ResourceUtils;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.War;
import org.apache.tools.ant.types.EnumeratedAttribute;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.XMLCatalog;
import org.apache.tools.ant.types.ZipFileSet;
import org.apache.tools.ant.util.FileUtils;
import org.codehaus.cargo.module.webapp.DefaultWarArchive;
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.WebXmlMerger;
import org.codehaus.cargo.module.webapp.WebXmlVersion;
import org.codehaus.cargo.util.monitor.AntMonitor;
import org.xml.sax.SAXException;
/**
* An Ant task that injects elements necessary to run Cactus tests into an
* existing WAR file.
*
* @version $Id: CactifyWarTask.java 239172 2005-05-17 09:14:26Z grimsell $
*/
public class CactifyWarTask extends War
{
// Constants ---------------------------------------------------------------
/**
* The name of the Cactus filter redirector class.
*/
private static final String FILTER_REDIRECTOR_CLASS =
"org.apache.cactus.server.FilterTestRedirector";
/**
* The default mapping of the Cactus filter redirector.
*/
private static final String DEFAULT_FILTER_REDIRECTOR_MAPPING =
"/FilterRedirector";
/**
* The default mapping of the Cactus JSP redirector.
*/
private static final String DEFAULT_JSP_REDIRECTOR_MAPPING =
"/JspRedirector";
/**
* The name of the Cactus servlet redirector class.
*/
private static final String SERVLET_REDIRECTOR_CLASS =
"org.apache.cactus.server.ServletTestRedirector";
/**
* The default mapping of the Cactus servlet redirector.
*/
private static final String DEFAULT_SERVLET_REDIRECTOR_MAPPING =
"/ServletRedirector";
// Inner Classes -----------------------------------------------------------
/**
* Abstract base class for nested redirector elements.
*/
public abstract static class Redirector
{
// Instance Variables --------------------------------------------------
/**
* The name of the redirector.
*/
protected String name;
/**
* The URL pattern that the redirector will be mapped to.
*/
protected String mapping;
/**
* Comma-separated list of role names that should be granted access to
* the redirector.
*/
protected String roles;
// Abstract Methods ----------------------------------------------------
/**
* Merges the definition of the redirector into the provided deployment
* descriptor.
*
* @param theWebXml The deployment descriptor into which the redirector
* definition should be merged
*/
public abstract void mergeInto(WebXml theWebXml);
// Public Methods ------------------------------------------------------
/**
* Sets the name of the redirector.
*
* @param theName The name to set
*/
public final void setName(String theName)
{
this.name = theName;
}
/**
* Sets the URL pattern that the redirector should be mapped to.
*
* @param theMapping The URL pattern to set
*/
public final void setMapping(String theMapping)
{
this.mapping = theMapping;
}
/**
* Sets the comma-separated list of role names that should be granted
* access to the redirector.
*
* @param theRoles The roles to set
*/
public final void setRoles(String theRoles)
{
this.roles = theRoles;
}
// Protected Methods ---------------------------------------------------
/**
* Adds the comma-separated list of security roles to a deployment
* descriptor.
*
* @param theWebXml The deployment descriptor
*/
protected final void addSecurity(WebXml theWebXml)
{
StringTokenizer tokenizer = new StringTokenizer(this.roles, ",");
List roles = new ArrayList();
while (tokenizer.hasMoreTokens())
{
String role = tokenizer.nextToken().trim();
if (!theWebXml.hasSecurityRole(role))
{
theWebXml.addSecurityRole(role);
}
roles.add(role);
}
if (!roles.isEmpty())
{
if (!theWebXml.hasLoginConfig())
{
theWebXml.setLoginConfig("BASIC", "myrealm");
}
if (!theWebXml.hasSecurityConstraint(this.mapping))
{
theWebXml.addSecurityConstraint("Cactus Test Redirector",
this.mapping, roles);
}
}
}
}
/**
* Implementation of <code>Redirector</code> for filter test redirectors.
*/
public static final class FilterRedirector extends Redirector
{
/**
* Default constructor.
*/
public FilterRedirector()
{
this.name = "FilterRedirector";
this.mapping = DEFAULT_FILTER_REDIRECTOR_MAPPING;
}
/**
* @see CactifyWarTask.Redirector#mergeInto
*/
public void mergeInto(WebXml theWebXml)
{
if (WebXmlVersion.V2_3.compareTo(theWebXml.getVersion()) <= 0)
{
theWebXml.addFilter(this.name, FILTER_REDIRECTOR_CLASS);
theWebXml.addFilterMapping(this.name, this.mapping);
if (this.roles != null)
{
addSecurity(theWebXml);
}
}
}
}
/**
* Implementation of <code>Redirector</code> for JSP test redirectors.
*/
public static final class JspRedirector extends Redirector
{
/**
* Default constructor.
*/
public JspRedirector()
{
this.name = "JspRedirector";
this.mapping = DEFAULT_JSP_REDIRECTOR_MAPPING;
}
/**
* @see CactifyWarTask.Redirector#mergeInto
*/
public void mergeInto(WebXml theWebXml)
{
theWebXml.addJspFile(this.name, "/jspRedirector.jsp");
theWebXml.addServletMapping(this.name, this.mapping);
if (this.roles != null)
{
addSecurity(theWebXml);
}
}
}
/**
* Implementation of <code>Redirector</code> for servlet test redirectors.
*/
public static final class ServletRedirector extends Redirector
{
/**
* Default constructor.
*/
public ServletRedirector()
{
this.name = "ServletRedirector";
this.mapping = DEFAULT_SERVLET_REDIRECTOR_MAPPING;
}
/**
* @see CactifyWarTask.Redirector#mergeInto
*/
public void mergeInto(WebXml theWebXml)
{
theWebXml.addServlet(this.name, SERVLET_REDIRECTOR_CLASS);
theWebXml.addServletMapping(this.name, this.mapping);
if (this.roles != null)
{
addSecurity(theWebXml);
}
}
}
/**
* Enumeration for the <em>version</em> attribute.
*/
public static final class Version extends EnumeratedAttribute
{
/**
* @see org.apache.tools.ant.types.EnumeratedAttribute#getValues()
*/
public String[] getValues()
{
return new String[] {"2.2", "2.3"};
}
}
/**
* Implements the nested element ejbref
*/
public static final class EjbRef
{
/**
* The name
*/
private String name;
/**
* The local interface
*/
private String localInterface;
/**
* The local home interface
*/
private String localHomeInterface;
/**
* The jndi name
*/
private String jndiName;
/**
* The type
*/
private String type;
/**
* Returns the jndi name
*
* @return Returns the jndiName.
*/
public String getJndiName()
{
return jndiName;
}
/**
* Sets the jndiName
*
* @param theJndiName The jndiName to set.
*/
public void setJndiName(String theJndiName)
{
this.jndiName = theJndiName;
}
/**
* Returns the local home interface
*
* @return Returns the localHomeInterface.
*/
public String getLocalHomeInterface()
{
return localHomeInterface;
}
/**
* Sets the local home interface
*
* @param theLocalHomeInterface The localHomeInterface to set.
*/
public void setLocalHomeInterface(String theLocalHomeInterface)
{
this.localHomeInterface = theLocalHomeInterface;
}
/**
* Return the local interface
*
* @return Returns the localInterface.
*/
public String getLocalInterface()
{
return localInterface;
}
/**
* Sets the local interface
*
* @param theLocalInterface The localInterface to set.
*/
public void setLocalInterface(String theLocalInterface)
{
this.localInterface = theLocalInterface;
}
/**
* Returns the name
*
* @return Returns the name.
*/
public String getName()
{
return name;
}
/**
* Sets the name
*
* @param theName The name to set.
*/
public void setName(String theName)
{
this.name = theName;
}
/**
* Returns the type
*
* @return Returns the type.
*/
public String getType()
{
return type;
}
/**
* Sets the type
*
* @param theType The type to set.
*/
public void setType(String theType)
{
this.type = theType;
}
}
// Instance Variables ------------------------------------------------------
/**
* The archive that contains the web-app that should be cactified.
*/
private File srcFile;
/**
* Location of the descriptor of which the content should be merged into
* the descriptor of the cactified archive.
*/
private File mergeWebXml;
/**
* The Cactus test redirectors.
*/
private List redirectors = new ArrayList();
/**
* For resolving entities such as DTDs.
*/
private XMLCatalog xmlCatalog = null;
/**
* The web-app version to use when creating a WAR from scratch.
*/
private String version = null;
/**
* List of ejb-refs to add to the deployment descriptor of the cactified war
*/
private List ejbRefs = new ArrayList();
// Public Methods ----------------------------------------------------------
/**
* @see org.apache.tools.ant.Task#execute()
*/
public void execute() throws BuildException
{
WebXml webXml = null;
if (this.srcFile != null)
{
log("Analyzing war: " + this.srcFile.getAbsolutePath(),
Project.MSG_INFO);
// Add everything that's in the source WAR to the destination WAR
ZipFileSet currentFiles = new ZipFileSet();
currentFiles.setSrc(this.srcFile);
currentFiles.createExclude().setName("WEB-INF/web.xml");
currentFiles.createExclude().setName("WEB-INF/weblogic.xml");
currentFiles.createExclude().setName("WEB-INF/orion-web.xml");
currentFiles.createExclude().setName("WEB-INF/ibm-web-bnd.xmi");
addZipfileset(currentFiles);
// Parse the original deployment descriptor
webXml = getOriginalWebXml();
}
if (this.srcFile == null || webXml == null)
{
if (this.version == null)
{
throw new BuildException("You need to specify either the "
+ "[srcfile] or the [version] attribute");
}
WebXmlVersion webXmlVersion = null;
if (this.version.equals("2.2"))
{
webXmlVersion = WebXmlVersion.V2_2;
}
else
{
webXmlVersion = WebXmlVersion.V2_3;
}
try
{
webXml = WebXmlIo.newWebXml(webXmlVersion);
}
catch (ParserConfigurationException pce)
{
throw new BuildException(
"Could not create deployment descriptor", pce);
}
}
File tmpWebXml = cactifyWebXml(webXml);
setWebxml(tmpWebXml);
addCactusJars();
try
{
super.execute();
}
finally
{
// Even though the temporary descriptor will get deleted
// automatically when the VM exits, delete it explicitly here just
// to be a better citizen
tmpWebXml.delete();
}
}
/**
* Adds a Cactus filter test redirector.
*
* @param theFilterRedirector The redirector to add
*/
public final void addFilterRedirector(FilterRedirector theFilterRedirector)
{
this.redirectors.add(theFilterRedirector);
}
/**
* Adds a Cactus JSP test redirector.
*
* @param theJspRedirector The redirector to add
*/
public final void addJspRedirector(JspRedirector theJspRedirector)
{
this.redirectors.add(theJspRedirector);
}
/**
* Adds a Cactus servlet test redirector.
*
* @param theServletRedirector The redirector to add
*/
public final void addServletRedirector(
ServletRedirector theServletRedirector)
{
this.redirectors.add(theServletRedirector);
}
/**
* Adds an XML catalog to the internal catalog.
*
* @param theXmlCatalog the XMLCatalog instance to use to look up DTDs
*/
public final void addConfiguredXMLCatalog(XMLCatalog theXmlCatalog)
{
if (this.xmlCatalog == null)
{
this.xmlCatalog = new XMLCatalog();
this.xmlCatalog.setProject(getProject());
}
this.xmlCatalog.addConfiguredXMLCatalog(theXmlCatalog);
}
/**
* Adds a configured EjbRef instance. Called by Ant.
*
* @param theEjbRef the EjbRef to add
*/
public final void addConfiguredEjbref(EjbRef theEjbRef)
{
ejbRefs.add(theEjbRef);
}
/**
* The descriptor to merge into the original file.
*
* @param theMergeFile the <code>web.xml</code> to merge
*/
public final void setMergeWebXml(File theMergeFile)
{
this.mergeWebXml = theMergeFile;
}
/**
* Sets the web application archive that should be cactified.
*
* @param theSrcFile The WAR file to set
*/
public final void setSrcFile(File theSrcFile)
{
this.srcFile = theSrcFile;
}
/**
* 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();
}
// Private Methods ---------------------------------------------------------
/**
* Adds the libraries required by Cactus on the server side.
*/
private void addCactusJars()
{
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");
}
/**
* 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
*/
private void addJarWithClass(String theClassName, String theDescription)
{
String resourceName = "/" + theClassName.replace('.', '/') + ".class";
if (this.srcFile != null)
{
try
{
WarArchive srcWar = new DefaultWarArchive(srcFile);
if (srcWar.containsClass(theClassName))
{
log("The " + theDescription + " JAR is already present in "
+ "the WAR", Project.MSG_VERBOSE);
return;
}
}
catch (IOException ioe)
{
log("Problem reading source WAR to when trying to detect "
+ "already present JAR files (" + ioe + ")",
Project.MSG_WARN);
}
}
ZipFileSet jar = new ZipFileSet();
File file = ResourceUtils.getResourceLocation(resourceName);
if (file != null)
{
jar.setFile(file);
addLib(jar);
}
else
{
log("Could not find the " + theDescription + " JAR",
Project.MSG_WARN);
log("You need to add the JAR to the classpath of the task",
Project.MSG_INFO);
log("(Searched for class " + theClassName + ")", Project.MSG_DEBUG);
}
}
/**
* Adds the Cactus JSP redirector file to the web application.
*/
private void addJspRedirector()
{
// 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
{
ResourceUtils.copyResource(getProject(),
"/org/apache/cactus/server/jspRedirector.jsp",
jspRedirectorFile);
}
catch (IOException e)
{
log("Could not copy the JSP redirector (" + e.getMessage() + ")",
Project.MSG_WARN);
}
FileSet fileSet = new FileSet();
fileSet.setFile(jspRedirectorFile);
addFileset(fileSet);
}
/**
* Adds the definitions corresponding to the nested redirector elements to
* the provided deployment descriptor.
*
* @param theWebXml The deployment descriptor
*/
private void addRedirectorDefinitions(WebXml theWebXml)
{
boolean filterRedirectorDefined = false;
boolean jspRedirectorDefined = false;
boolean servletRedirectorDefined = false;
// add the user defined redirectors
for (Iterator i = this.redirectors.iterator(); i.hasNext();)
{
Redirector redirector = (Redirector) i.next();
if (redirector instanceof FilterRedirector)
{
filterRedirectorDefined = true;
}
else if (redirector instanceof JspRedirector)
{
jspRedirectorDefined = true;
}
else if (redirector instanceof ServletRedirector)
{
servletRedirectorDefined = true;
}
redirector.mergeInto(theWebXml);
}
// now add the default redirectors if they haven't been provided by
// the user
if (!filterRedirectorDefined)
{
new FilterRedirector().mergeInto(theWebXml);
}
if (!servletRedirectorDefined)
{
new ServletRedirector().mergeInto(theWebXml);
}
if (!jspRedirectorDefined)
{
new JspRedirector().mergeInto(theWebXml);
}
}
/**
* 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
*/
private File cactifyWebXml(WebXml theWebXml)
{
addRedirectorDefinitions(theWebXml);
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.setMonitor(new AntMonitor(this));
merger.merge(parsedMergeWebXml);
}
catch (IOException e)
{
throw new BuildException(
"Could not merge deployment descriptors", e);
}
catch (SAXException e)
{
throw new BuildException("Parsing of merge file failed", e);
}
catch (ParserConfigurationException e)
{
throw new BuildException("XML parser configuration error", 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 = fileUtils.createTempFile("cactus", "tmp.dir",
getProject().getBaseDir());
tmpDir.mkdirs();
tmpDir.deleteOnExit();
File webXmlFile = null;
try
{
ZipFileSet fileSet = new ZipFileSet();
fileSet.setDir(tmpDir);
File[] files = WebXmlIo.writeAll(theWebXml, tmpDir);
for (int i = 0; i < files.length; i++)
{
File f = files[i];
f.deleteOnExit();
if (f.getName().equals("web.xml"))
{
webXmlFile = f;
}
else
{
fileSet.createInclude().setName(f.getName());
}
}
addWebinf(fileSet);
}
catch (IOException ioe)
{
throw new BuildException(
"Could not write temporary deployment descriptor", ioe);
}
return webXmlFile;
}
/**
* Extracts and parses the original web deployment descriptor from the
* web-app.
*
* @return The parsed descriptor or null if not found
* @throws BuildException If the descriptor could not be
* parsed
*/
private WebXml getOriginalWebXml() throws BuildException
{
// Open the archive as JAR file and extract the deployment descriptor
WarArchive war = null;
try
{
war = new DefaultWarArchive(this.srcFile);
WebXml webXml = war.getWebXml();
return webXml;
}
catch (SAXException e)
{
throw new BuildException(
"Parsing of web.xml deployment descriptor failed", e);
}
catch (IOException e)
{
throw new BuildException("Failed to open WAR", e);
}
catch (ParserConfigurationException e)
{
throw new BuildException("XML parser configuration error", 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();
if ("Session".equals(ref.getType()))
{
theWebXml.addLocalSessionEjbRef(ref.getName(),
ref.getLocalInterface(),
ref.getLocalHomeInterface(),
ref.getJndiName());
}
else if ("Entity".equals(ref.getType()))
{
theWebXml.addLocalEntityEjbRef(ref.getName(),
ref.getLocalInterface(),
ref.getLocalHomeInterface(),
ref.getJndiName());
}
}
}
}