Saturday, June 7, 2008

GlassFish and Cactus Unit Tests

I started creating Cactus unit tests for a number of servlet filters in openEPRS.  I didn't get far before encountering a number of strange problems.  For the life of me I could not get the cactifywar ant task to add a FilterRedirector to my project's web.xml.  I posted a number of questions to the Cactus user's mailing list and although I received excellent support from Petar Tahchiev, we could not determine what was causing the problem.  I decided to RTFM and to make a long story short...

The following is from the Cactus 1.8.0 documentation:

The Cactus test redirectors are automatically injected into the deployment descriptor of the destination archive. By the way, the task will examine the version of the *web-app DTD used in the source file, which determines the servlet API version in use (if no |DOCTYPE| declaration is found, it assumes servlet API 2.2)*. The filter test redirector will only be inserted if the servlet API version is 2.3 (or later).

The Glassfish deployment descriptors (Servlet 2.5) use schemas, not DOCTYPEs.  This is what was causing the Cactus ant tasks to believe my 2.5 application was really a 2.2 application and therefore did not add the FilterRedirector to the deployment descriptor. I finally got my filter tests to work after forcing a FilterRedirector into the original web.xml. I should also mention that other Cactus options such as mergewebxml exhibited the same behavior.  At this point Petar was able to confirm that the problem was with Cargo which is used by Cactus to manipulate application servers and their deployment descriptors.  Cargo does not support Servlet 2.5 deployment descriptors and Petar reported the problem to the Cargo team.

Now that I knew why it wasn't working, I needed to figure out how to make it work. The following is a bit of FrankenAnt that allows me to build and run my Cactus tests until Cargo supports Servlet 2.5 deployment descriptors. I am basically stuffing a 2.3 DOCTYPE into my deployment descriptor, cactifying the WAR and removing the DOCTYPE afterward. This solution allows me to use the cactifywar task as well as not leaving any invalid junk behind in my deployment descriptors.

The solution (this is as ugly as it gets):
<target depends="-copy-cactus-tests,dist" name="cactify-war">
    <unwar dest="${dist.dir}/${cactus.tmp.dir}" src="${dist.war}">    
        
    <replaceregexp <file="${dist.dir}/${cactus.tmp.dir}/WEB-INF/web.xml"
        match="<\?xml version="1.0" encoding="UTF-8"\?>"
        replace="<\?xml version="1.0" encoding="UTF-8"\?><!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">"/>

    <war basedir="${dist.dir}/${cactus.tmp.dir}" includes="**/*" jarfile="${dist.dir}/${cactus.tmp.war}">
        
    <delete dir="${dist.dir}/${cactus.tmp.dir}">

    <grep group="3" in="${javac.test.classpath}" property="cactus.shared.api.jar" regex="(.*)(:)(/(.*)/cactus.integration.shared.api-1.8.0.jar){1}">

        
    <cactifywar destfile="${dist.dir}/${cactus.war}" srcfile="${dist.dir}/${cactus.tmp.war}">
        <servletredirector>
        <servletredirector mapping="/${cactus.secure.servlet.redirector}" name="${cactus.secure.servlet.redirector}" roles="admin,test">
        <filterredirector mapping="/${cactus.secure.filter.redirector}" name="${cactus.secure.filter.redirector}" roles="admin,test">
        <jspredirector mapping="/${cactus.secure.jsp.redirector}" name="${cactus.secure.jsp.redirector}" roles="admin,test">
        <lib file="${cactus.shared.api.jar}">
    </cactifywar>
        
         
    <delete file="${dist.dir}/${cactus.tmp.war}">
           
    <unwar dest="${dist.dir}/${cactus.tmp.dir}" src="${dist.dir}/${cactus.war}">

    <delete file="${dist.dir}/${cactus.war}">
        
    <replaceregexp <file="${dist.dir}/${cactus.tmp.dir}/WEB-INF/web.xml"
        match="<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"(.*)"http://java.sun.com/dtd/web-app_2_3.dtd">"
        replace=""/>

    <war basedir="${dist.dir}/${cactus.tmp.dir}" includes="**/*" jarfile="${dist.dir}/${cactus.war}">
            
    <delete dir="${dist.dir}/${cactus.tmp.dir}">
            
    <delete failonerror="false" includeemptydirs="true">
        <fileset dir="${basedir}" includes="cactus*">
    </delete>
</target>