Ant, JspC, and Other Horrors

Posted by Uncle Bob on 02/03/2007

I’ve been trying to precompile JSPs today. They reference custom tag libraries. What a joy…

You’d think it would be simple to translate JSP file into Java. Tomcat does it all the time, automatically.

I’d very much like to precompile my JSP files, because I want to write tests for them; and I don’t want to use Cactus, or have the server up for my tests.

I found the Jasper compiler, and the ant task that invokes it, but I thought it might be nice to run it from the command line just to see how it work. You know:

java org.apache.jasper.JspC myFile.jsp

You’d think that would be easy, wouldn’t you?

But NO. You have to include a zillion jars just to get the JspC compiler to run. Jars from apache.common, and appache.server, and commons.logging, etc. You even have to include the ant.jar file.

I consider that last to be completely sick. What the H___ does JspC have to do with Ant? Why in the begrosian devalent is there a dependency on ant, of all things??!!? Haven’t these people heard of Dependency Management?

sigh

Anyway, I gave up on the command line idea because apparently the Jasper compiler wants to see the web.xml file and so the whole web app has to be put together before you can run the compiler. I understand why they did this, but it sure is frustrating for someone who just wants to run the frickin compiler.

So I fell back on the ant task. Now I have to tell you that I hate ant. Ant was born during those sick sick days when people thought that XML was a cool grammar. XML is not a cool grammar. XML is a markup language. It works fine to encode data that machines can read, and humans can barely read, but it is by no means a natural syntax. Ant suffers from terrible keyword-itis, and the nasty inflexibility.

Inflexible you say? Why, can’t you write any ant-task you please?

Sure, so what. You think I want to read a bunch of java code to figure out how to invoke the JspC command line? Hell, all I want to do is compile one frickin JSP file into a JAVA file. If ant let me pass in command lines, like make or rake or some other reasonable build tool, I just might be able to do that.

But NO. The JspC ant task wants to compile the WHOLE web app for me. And it wants there to be a web.xml file that describes that web app.

sigh

So, like a good little ant slave, I put the ant build script for JspC into my build.xml file and fired it up. It took a little fiddling and cajoling. But eventually I got the JspC compiler to run. And what did it say?

/Users/unclebob/projects/Library/Library/build.xml:147: org.apache.jasper.JasperException: file:/Users/unclebob/projects/Library/Library/web/WEB-INF/pages/template.jsp(25,21) Unable to load tag handler class "com.objectmentor.library.web.framework.tags.ActionPathTag" for tag "library:actionPath"

(what is it about error messages nowadays that they have to be 253 characters in length? Why can’t we have nice little error messages?)

The problem is that the JSP file that I am trying to compile is using a custom tag, and apparently there is no way to get the JspC ant task to tell the JspC compiler the classpath of my tag handler. I’ve tried everything I could think of, and searched high and low on the net, but I can’t seem to figure out how to make this work.

So I’m done for the weekend. I was hoping to write a blog on testing JSPs this weekend while attending my son’s wrestling match (he’s likely to make it to state this year), but it’ll have to wait because there’s no internet at the match.

I guess I’ll play with Ruby instead.

UPDATE

While eating dinner, it hit me. If I set the CLASSPATH variable to include the jar with the tag in it, and if I invoke JspC from the command line (or with a java ant task) it might work. So I tried it, and…whaddyano? it worked just fine.

Here is the ant target I eventually used. Note the hideous dependencies required by JspC. You might find this target useful if you ever want to precompile jsps that use custom tags.

 <target name="jsp" depends="jar">

   <delete dir="${basedir}/test/testjsp"/>

   <java classname="org.apache.jasper.JspC" fork="true">

     <arg line="-d ${basedir}/test/testjsp -p com.objectmentor.library.testjsp -webapp ${web.home}"/>

     <classpath>

       <fileset dir="${catalina.home}/common/lib">

         <include name="*.jar"/>

       </fileset>

       <fileset dir="${catalina.home}/server/lib">

         <include name="*.jar"/>

       </fileset>

       <fileset dir="${catalina.home}/bin">

         <include name="*.jar"/>

       </fileset>

       <pathelement location="/Developer/Java/Ant/lib/ant.jar"/>

       <pathelement location="${build.jar.home}/library-framework.jar"/>       

     </classpath>

   </java>

 </target>

Now on to the wrestling match!

Not so fast.

Some of the java files generated from the JSPs don’t compile. There is some mismatch between the tag processing libraries or something. The generated code is hideous to read, and the problem is probably subtle. So I’m going to go to bed.

urg!.

WRESTLING and SUCCESS

So, here I am at the wrestling match. The thing about these matches is that I need to pay attention for about 6 minutes every two hours. That’s how often Justin wrestles. So, of course, I bring my laptop and sit on the bleachers working on code while the whistles are blowing and the parents are screeching and my butt slowly gets numb.

Justin won his first match. It was a slaughter. He’s strong and smart, and has a good chance to get into the state tournament.

I won my first match with JSP too. Apparently the compile environment of my IDE is not exactly the same as the compile environment that Jasper uses. But I was able to resolve that by simply setting the -compile switch on the JspC command line. This compiles the generate java files in place using what seems to be the correct compile environment. At least there are no compiler errors.

I also set the -mapped command line argument. This causes jasper to create a new Out(...) call for each line on the input jsp. This makes the java file a bit easier to read. Apparently this command line is set when tomcat automatically compiles the jsps, and so comparing my generated files and tomcats generated files is easier with this argument set.

I set up a simple unit test to see if I could create an instance of one of the generated servlets. It looks like this:

public class ManageJspTest extends TestCase {

 public void testCreate() throws Exception {

   HttpJspBase manage = new com.objectmentor.library.jsp.WEB_002dINF.pages.books.manage_jsp();

 }

}

At first this didn’t compile because the servlet apparently uses the apache.commons.logging. stuff. So I had to put that in the classpath of the unit tests. Grumble! “Dependency management guys! Dependency Management. Why do my unit tests need to know about logging? sigh

Anyway: I can now compile a unit test that creates a servlet generated by Jasper!!!

This is very good news. I have my doubts about whether it will work however. The code generated by my ant script, and the code generated by tomcat are not exact matches. There is one segment that is markedly different. The following snippet is in the tomcat generated java file, but not in the ant-script generated java file.

 static {

   _jspx_dependants = new java.util.ArrayList(1);

   _jspx_dependants.add("/WEB-INF/tld/LibraryTags.tld");

 }

I don’t understand exactly why, but I think it must have something to do with a difference in the way the two environments view the tag libraries.

Anyway, I imagine that when I try to execute the servlets generated by the ant script, they will fail because this code is missing.

Anyway, I think I’ll go watch Justin wrestle some more.

Justin won his second match at Regionals. That put’s him in the finals for the tournament, and ensures that he has a spot in the Sectionals next week.

Justin doesn’t wrestle again until 4pm, so Ann Marie and I went home for a couple of hours. While there, I was able to set up a quick test to invoke the generated servlet. It looks like this:

public class ManageJspTest extends TestCase {

 private MockPageContext pageContext;

 private MockJspWriter jspWriter;

 private JspFactory mockFactory;

 public void testCreate() throws Exception {

   jspWriter = new MockJspWriter(10000, false);

   pageContext = new MockPageContext(jspWriter);

   mockFactory = new JspFactory() {

     public PageContext getPageContext(Servlet servlet, ServletRequest servletRequest, ServletResponse servletResponse, String string, boolean b, int i, boolean b1) {

       return pageContext;

     }

     public void releasePageContext(PageContext pageContext) {

     }

     public JspEngineInfo getEngineInfo() {

       return null;

     }

   };

   JspFactory.setDefaultFactory(mockFactory);

   HttpJspBase manage = new com.objectmentor.library.jsp.WEB_002dINF.pages.books.manage_jsp();

   MockHttpServletRequest request = new MockHttpServletRequest();

   MockHttpServletResponse response = new MockHttpServletResponse();

   manage._jspService(request, response);

 }

}

Note all the mocks! Here is where the tomcat guys did a nice bit of work. The JspFactory is used within the generated servlet to gain access to all the objects it uses. It turns out that you can override the default implementation of the JspFactory by simply calling setDefaultFactory. Nice! That’s Dependency Management!

I did a quick breakpoint at the very end of my test and, sure enough, there is output stored in the MockJspWriter! Unfortunately, it’s nowhere near enough. The output stops as soon as the first tag was invoked. Indeed, there is a wonderfully silent return right in the generated code. No exception, no error message, just silent failure. sigh. Here’s the generated code:

     out.write("<form action=\"");

     if (_jspx_meth_library_actionPath_0(_jspx_page_context))

       return;

It was generated from this portion of the jsp.

<form action="<library:actionPath actionName="books/manage"/>" method="post" id="list_form">

The actionPath tag is one of our custom tags. All it does is much a controller path into an appropriate url format. (If you don’t understand that, don’t worry about it.) Clearly our custom tag is not being found. This is probably because the jasper compiler is not setting the _jspx_dependants variable properly. So now I need to figure out why…

For some reason, Jasper is not paying attention to the following line in my jsp file:

<%@ taglib uri="/WEB-INF/tld/LibraryTags.tld" prefix="library" %>

It’s not clear why, though I have to say that pattern of silent errors is really starting to bother me. An error message would certainly be nice.

Why is Jasper ignoring the taglib directive? The LibraryTags.tld file is there, and in the right place. The ActionPathTag.class file is in the classpath used to invoke jasper. I’ve played with the -uribase argument of the jasper compiler (for fun, read the description of this argument and see if YOU understand what it’s saying.) It’s a mystery…

Comments

Leave a response