/* * JBoss, Home of Professional Open Source. * Copyright 2009, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.jsfunit.ant; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.zip.ZipFile; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.apache.cactus.integration.ant.util.ResourceUtils; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; import org.apache.tools.ant.types.FileSet; import org.codehaus.cargo.module.webapp.WebXml; import org.w3c.dom.Document; import org.xml.sax.SAXException; import com.sun.org.apache.xml.internal.serialize.OutputFormat; import com.sun.org.apache.xml.internal.serialize.XMLSerializer; /** * This class provides for an Ant task that will "JSFUnify" a WAR. * * @author Matt Wringe * @author Stan Silvert * @since 1.0 */ public class JSFUnitWarTask extends Task { private String containerId = ""; private File srcfile; private File destfile; private List<JSFUnitFilter> jsfFilters = new ArrayList<JSFUnitFilter>(); private List<FileSet> libs = new ArrayList<FileSet>(); private List<FileSet> classes = new ArrayList<FileSet>(); private Boolean autoAddJars = true; private List<JSFUnitTestRunnerFilter> testRunnerFilters = new ArrayList<JSFUnitTestRunnerFilter>(); public JSFUnitWarTask() { super(); } /** * Set the container Id for the target WAR. This is sometimes needed * to auto-add jars correctly. Currently, the container value that has * an affect is "jboss5x". * * @param containerId "jboss5x" or "" */ public void setContainer(String containerId) { this.containerId = containerId; if (containerId.equals("jboss6x")) this.containerId = "jboss5x"; } /** * Sets the original archive that should have JSFUnit specifics added * * @param srcfile A WAR file or a directory containing an exploded WAR. */ public void setSrcfile (File srcfile) { this.srcfile = srcfile; } /** * Sets the destination for the newly created archive with JSFUnit specifics * * @param destfile The destination WAR or directory for an exploded WAR. */ public void setDestfile (File destfile) { this.destfile = destfile; } /** * Sets the fileset that should be added to the WEB-INF/lib directory of the * created archive. * * @param libFileSet The fileset to add */ public void addLib (FileSet libFileSet) { libs.add(libFileSet); } /** * Sets the fileset that should be added to the WEB-INF/classes directory of * the created archive. This is typically used to specify the JSFUnit test * classes. * * @param classesFileSet Files to be added to WEB-INF/classes */ public void addClasses (FileSet classesFileSet) { classes.add(classesFileSet); } /** * Sets whether or not to automatically add needed jars into the * WEB-INF/lib directory of the crearted archive. * * @param autoAddJars True to automatically add jars, false otherwise */ public void setAutoAddJars (Boolean autoAddJars) { this.autoAddJars = autoAddJars; } /** * This class represents a filter declaration and mapping that will be added to web.xml. */ public static class Filter { protected String name; protected String mapping; protected String servletClass; protected String filterClass; protected String servletName; public Filter(){} public void setName (String name) { this.name = name; } public void setMapping (String mapping) { this.mapping = mapping; } public void setServletClass (String servletClass) { this.servletClass = servletClass; } public void addFilter(WebXml webXml) { if (!webXml.hasServlet(this.servletName)) { webXml.addServlet(this.servletName, this.servletClass); } // only add the filter if it doesn't already exist if (!webXml.hasFilter(this.name)){ webXml.addFilter(this.name, this.filterClass); } webXml.addServletMapping(this.servletName, this.mapping); webXml.addFilterMapping(this.name, this.mapping); } } /** * This class represents the JSFUnitFilter that will be added to web.xml. */ public static class JSFUnitFilter extends Filter { private final String DEFAULT_NAME = "JSFUnitFilter"; private final String DEFAULT_MAPPING = "/ServletRedirector"; private final String DEFAULT_SERVLET_CLASS = "org.jboss.jsfunit.framework.JSFUnitServletRedirector"; private final String DEFAULT_FILTER_CLASS = "org.jboss.jsfunit.framework.JSFUnitFilter"; public JSFUnitFilter() { name = DEFAULT_NAME; mapping = DEFAULT_MAPPING; servletClass = DEFAULT_SERVLET_CLASS; filterClass = DEFAULT_FILTER_CLASS; servletName = "ServletRedirector"; } } /** * This class represents the ServletTestFilter that may be added to web.xml. */ public static class JSFUnitTestRunnerFilter extends Filter { private final String DEFAULT_NAME = "ServletTestFilter"; private final String DEFAULT_MAPPING = "/ServletTestRunner"; private final String DEFAULT_SERVLET_CLASS = "org.apache.cactus.server.runner.ServletTestRunner"; private final String DEFAULT_FILTER_CLASS = "org.jboss.jsfunit.framework.JSFUnitFilter"; public JSFUnitTestRunnerFilter() { name = DEFAULT_NAME; mapping = DEFAULT_MAPPING; servletClass = DEFAULT_SERVLET_CLASS; filterClass = DEFAULT_FILTER_CLASS; servletName="ServletTestRunner"; } } /** * Adds a JSFFilter. * Note: this doesn't actually set the once JSFFilter but rather * adds it to the list of already added filters. * @param jsfFilter The JSFFilter */ public void addJSFUnitFilter(JSFUnitFilter jsfFilter) { jsfFilters.add(jsfFilter); } /** * Add the ServletTestRunner filter to web.xml. * * @param testRunnerFilter An instance of JSFUnitTestRunnerFilter */ public void addTestRunner(JSFUnitTestRunnerFilter testRunnerFilter){ testRunnerFilters.add(testRunnerFilter); } /** * Execute the ant task. */ public void execute() { if (srcfile == null) { throw new BuildException ("A srcfile must be specified"); } else if (destfile == null) { throw new BuildException ("A destfile must be specified"); } else if (destfile.equals(srcfile)) { throw new BuildException ("The destfile and srcfile must not be the same"); } else { log("using srcfile :" + srcfile, Project.MSG_DEBUG); if (srcfile.isDirectory()) { log("srcfile is a directory", Project.MSG_DEBUG); JSFUnitExplodedWar(); } else if (srcfile.isFile()) { log("srcfile is a file", Project.MSG_DEBUG); JSFUnitWar(); } else { throw new BuildException ("Cannot find specifiec srcfile : " + srcfile ); } } } /** * This method is called when we are dealing with an exploded * War archive. */ private void JSFUnitExplodedWar () { try { // make a copy of the original war that will be edited Utils.copy(srcfile, destfile); } catch (Exception e) { e.printStackTrace(); log(e.getMessage(), Project.MSG_ERR); } try { // get the web.xml WebXml webxml = getWebXml(destfile); // edit the web.xml webxml = editWebXml(webxml); // write the web.xml back writeWebXml(destfile, webxml); addLibs(destfile); autoAddLibs(destfile); addClasses(destfile); } catch (Exception e) { e.printStackTrace(); log(e.getMessage(), Project.MSG_ERR); } } /** * Returns a WebXml object from the specified archive that represents the web.xml file * @param archiveRoot The archive to retrieve the web.xml from * @return The WebXml object * @throws SAXException If an exception occurs when trying to read the archive * @throws IOException If an exception occurs when trying to read the archive * @throws ParserConfigurationException If an exception occurs when trying to read the archive */ private WebXml getWebXml (File archiveRoot) throws SAXException, IOException, ParserConfigurationException { // the location within the archive where a web.xml file should exist String webXMLLocation = archiveRoot.getPath() + File.separator + "WEB-INF" + File.separator + "web.xml"; File webXMLFile = new File (webXMLLocation); if (!webXMLFile.exists()) { // if there is no web.xml file, then we can't do anything throw new BuildException ("No web xml descriptor : " + webXMLLocation); } else { DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); Document webxmlDoc = docBuilder.parse(webXMLFile); WebXml webxml = new WebXml(webxmlDoc); return webxml; } } /** * Writes the web.xml file back into the archive * @param archiveRoot the archive to contain the web.xml * @param webxml the web.xml file to be written * @throws IOException if an exception occurs during file writing */ private void writeWebXml (File archiveRoot, WebXml webxml) throws IOException { String webXMLLocation = archiveRoot.getPath() + File.separator + "WEB-INF" + File.separator + "web.xml"; OutputFormat format = new OutputFormat(); format.setIndenting(true); XMLSerializer xmlSerializer = new XMLSerializer(format); xmlSerializer.setOutputCharStream(new java.io.FileWriter(webXMLLocation)); xmlSerializer.serialize(webxml.getDocument()); } /** * This method gets called if we are dealing with an actual archive and * not an exploded archive. * This archive is exploded to a temporary directory so that it can be altered * in the same manner as if we were dealing with an expldoded archive. */ private void JSFUnitWar () { try{ File tmpDir = File.createTempFile("jsfunitwartask", ""); // File.createTempFile creates the actual file, so if we want a // directory instead of a regular file we need to delete the file // and tell it to make a directory instead. tmpDir.delete(); tmpDir.mkdir(); // get and explode the zip file to the temp directory ZipFile zip = new ZipFile(srcfile); File explodedArchive = Utils.explodeArchive(zip, tmpDir); // get the web.xml WebXml webXml = getWebXml(explodedArchive); // edit the web.xml editWebXml(webXml); // write the back the web.xml writeWebXml(explodedArchive, webXml); // add any specified jars to WEB-INF/lib addLibs(explodedArchive); // automatically add any required jars to WEB-INF/lib autoAddLibs(explodedArchive); // add any classes to WEB-INF/classes addClasses(explodedArchive); // implode the archive to the specified destfile Utils.archive(explodedArchive, destfile); } catch (Exception e) { log(e.getMessage(), Project.MSG_ERR); e.printStackTrace(); } } /** * Edit the web.xml by adding the specified JSFUnit filters * @param webXml webXml object to manipulate * @return the edited webXML object */ private WebXml editWebXml (WebXml webXml) { // if there is no filter specified, then create the default JSFUnit filter if (jsfFilters.isEmpty()) { jsfFilters.add(new JSFUnitFilter()); } // add each filter to the web.xml for (java.util.Iterator<JSFUnitFilter> i = jsfFilters.iterator(); i.hasNext();) { i.next().addFilter(webXml); } for (java.util.Iterator<JSFUnitTestRunnerFilter> i = testRunnerFilters.iterator(); i.hasNext();) { i.next().addFilter(webXml); } return webXml; } /** * Adds a list of files to the archive * @param archiveRoot the archive * @param directoryName the name of the directory to add the files * @param fileSets the files to add * @throws Exception if an exception occurs during the file transfers */ private void addFilesToArchive (File archiveRoot, String directoryName, List<FileSet> fileSets) throws Exception{ // if there are no requested to be added, then do nothing if (!fileSets.isEmpty()) { // file object to represent the directory File directory = new File (archiveRoot.getPath() + File.separator + directoryName); if (!directory.exists()) { //only create the directory if it doesn't already exist directory.mkdir(); } else if (!directory.isDirectory()) { throw new Exception ("The " + directoryName + " directory for the archive is not a directory"); } for (java.util.Iterator<FileSet> i = fileSets.iterator(); i.hasNext();) { FileSet fileSet = i.next(); DirectoryScanner directoryScanner = fileSet.getDirectoryScanner(getProject()); String[] files = directoryScanner.getIncludedFiles(); for (int j=0; j<files.length;j++) { File file = new File(fileSet.getDir(getProject()) + File.separator + files[j]); File dest = new File(directory + File.separator + files[j]); if (dest.getParentFile() != null) { dest.getParentFile().mkdirs(); } Utils.copy(file, dest); } } } } /** * Add the specified jars to the WEB-INF/lib directory of the archive * @param archiveRoot the archive * @throws Exception if an exception occured during the file transfer */ private void addLibs (File archiveRoot) throws Exception { String libsDirectory = "WEB-INF" + File.separator + "lib"; addFilesToArchive(archiveRoot, libsDirectory, libs); } /** * Add the specified classes to the WEB-INF/classes directory of the archive * @param archiveRoot the archive * @throws Exception if an exception occured during the file transfer */ private void addClasses (File archiveRoot) throws Exception { String classesDirectory = "WEB-INF" + File.separator + "classes"; addFilesToArchive(archiveRoot, classesDirectory, classes); } /** * Automatically add the jars that are needed for JSFUnit into the WEB-INF/lib directory * @param archiveRoot The archive * @throws Exception If an exception occurs during file transfer */ private void autoAddLibs (File archiveRoot) throws Exception { if (!autoAddJars) return; log("Automatically adding JSFunit required jars to the new war", Project.MSG_INFO); // TODO: pick out actual classes used, these ones were picked at random from the jars String[][] classNames = new String[][] { {"/org/jboss/jsfunit/init/AllJSFUnitTests.class", "JSFUnit Catus 2.0 or higher"}, {"/org/jboss/jsfunit/context/NoNewEntryMap.class", "JSFUnit Core 2.0 or higher"}, {"/com/gargoylesoftware/htmlunit/AjaxController.class", "HTMLUnit 2.8 or higher"}, {"/org/apache/commons/codec/BinaryDecoder.class", "Commons Codec 1.4 or higher"}, {"/net/sourceforge/htmlunit/corejs/javascript/ScriptableObject.class", "HTMLUnitJavascript 2.8 or higher"}, {"/org/apache/commons/logging/Log.class", "Commons Logging 1.0.4 or higher"}, {"/org/apache/http/client/CredentialsProvider.class", "Apache HTTP Client 4.0.1 or higher"}, {"/org/apache/http/entity/mime/MIME.class", "Apache HTTP MIME 4.0.1 or higher"}, {"/org/apache/james/mime4j/message/Body.class", "Apache James mime4j 0.6 or higher"}, {"/org/apache/http/params/HttpParams.class", "Apache HTTP Core 4.0.1 or higher"}, {"/org/apache/commons/lang/ArrayUtils.class", "Commons Lang 2.4 or higher"}, {"/org/apache/commons/collections/ArrayStack.class", "Commons Collections 3.2.1 or higher"}, {"/org/apache/commons/io/FileUtils.class", "CommonsIO 1.4 or higher"}, {"/com/steadystate/css/parser/HandlerBase.class", "CSSParser 0.9.5 or higher"}, {"/org/w3c/css/sac/SACMediaList.class", "W3C SAC 1.3 or higher"}, {"/org/cyberneko/html/ObjectFactory.class", "NekoHTML 1.9.14 or higher"}, {"/org/apache/html/dom/CollectionIndex.class", "XercesImpl 2.8.1 or higher"}, {"/org/apache/xalan/extensions/ExpressionContext.class", "Xalan 2.7.0 or higher"}, // should be in JDK {"/org/w3c/dom/Attr.class", "XML APIs 1.0.b2 or higher"}, {"/org/apache/cactus/Request.class", "Cactus 1.7.1 or higher"}, {"/org/apache/cactus/integration/ant/CactifyEarTask.class", "Cactus Ant 1.7.1 or higher"}, {"/junit/framework/Assert.class", "JUnit 3.8.1 or higher"}, {"/org/aspectj/runtime/CFlow.class", "AspectJ 1.2.1 or higher"}, {"/org/codehaus/cargo/ant/CargoTask.class", "Cargo 0.5 or higher"}, {"/org/apache/tools/ant/AntClassLoader.class", "Ant 1.5.4 or higher"} }; for (int i=0; i<classNames.length; i++) { if (skipForJBoss5(classNames[i][1])) continue; try{ File jar = ResourceUtils.getResourceLocation(classNames[i][0]); if (jar == null) { log("Could not find the " + classNames[i][1] + " jar in the classpath. Cannot automatically add this jar to the war"); } else { log("Auto-adding " + jar.getName()); addLib (archiveRoot, jar); } } catch (NoClassDefFoundError ncdfe) { log("Could not find " + classNames[i][0] + " class in the " + classNames[i][1] + " jar. Cannot automatically add this jar to the war"); } } } private boolean skipForJBoss5(String libraryName) { if (!"jboss5x".equalsIgnoreCase(this.containerId)) return false; if (libraryName.startsWith("XercesImpl")) return true; if (libraryName.startsWith("Xalan")) return true; return false; } /** * Add a specified File into the WEB-INF/lib directory of the archive * @param archiveRoot the archive * @param libJar the file to add * @throws Exception if an exception occured during file transfer */ private void addLib (File archiveRoot, File libJar) throws Exception { if (!libJar.exists()){ throw new Exception ("Can not find " + libJar.getName()); } String libDirectoryName = archiveRoot.getPath() + File.separator + "WEB-INF" + File.separator + "lib"; File libDir = new File (libDirectoryName); if (!libDir.exists()) { libDir.mkdir(); } String libFileName = libDirectoryName + File.separator + libJar.getName(); File destFile = new File(libFileName); Utils.copy(libJar, destFile); } }