''Why Xalan fails with JDK 1.4, and the solutions''

The problem

The JDK 1.4 introduced the new XML-related java.xml.* APIs, and is shipped with an XML parser (Crimson and now Xerces) and an XSLT Processor (Xalan). Although this helped making XML technologies more widespread and easy to use, this led to numerous problems in Cocoon.

Why are there some problems? Well, the version of Xalan shipped with the JDK is old (and gets older as time goes by), and Cocoon requires more recent versions (bug fixes, enhancements, XSLTC, etc). And there are some conflicts between these versions.

The Servlet specification (section 9.7.2) states that webapp classes, located in WEB-INF/lib and WEB-INF/classes, should be loaded preferably to the same classes in the container-wide libraries. But it also states that a webapp should not override J2SE classes, of which the XML apis and Xalan are now part.

And this is what happens in Tomcat (see the excellent classloader how-to): the "old" Xalan classes present in the JDK are loaded instead of the new ones shipped with Cocoon.

So you think Cocoon then runs with the old Xalan? This is unfortunately not so simple: Tomcat (at least some versions of it) doesn't use the same priority algorithm for classes and resources. And Xalan has a lot of configurations written in resources included in its jar file. So it happens that classes from the JDK's "old" Xalan load a resource, which is fetched from the "new" jar, and this resource lists class names that do not exist in the JDK's old Xalan.

Result? Bing, Bang, Crash! Welcome to the "jar hell". You got a blank page and some cryptic error messages in the servlet engine's log file (and not always in Cocoon's logs):

Fixing the problem with the "endorsed" directory

Fortunately, the JDK provides a mechanism to solve this problem: libraries shipped with the JDK can be upgraded by putting the updated libraries either in the jre/lib/endorsed libraries, or in a path given using the java.endorsed.dirs JVM argument.

So you can put new versions of Xalan and Xerces there. But changing the configuration of the JDK is not something that you may want to do when you deploy a web application (also your sysadmin and/or hosting provider may not allow it).

Fortunately (again), Tomcat startup scripts automatically sets java.endorsed.dirs to ''tomcat-home''/common/endorsed. This means you just have to put the new jars there and not in the JDK.

However (this story never ends), your sysadmin may not let you modify the serlvet engine configuration, or you may, as I did, encounter the following problem: two different webapps running on the same Tomcat requiring each a different version of Xalan! What's the solution, then? Installing each application in a different Tomcat could be the solution, but requires yet another JVM to run on the server (not to mention the additional system maintainance for startup and shutdown).

Fixing the problem with the !ParanoidCocoonServlet

[This is valid if the Cocoon that you're using (either 2.0 or 2.1) is built from the CVS of June 6th, 2003 or later]

The Upside

To finally solve this annoying problem, Cocoon provides an alternate servlet named ParanoidCocoonServlet. What this servlet does is create a special classloader, then use it to load and run the standard CocoonServlet in a sandbox that shields it from other libraries lying around in the system.

This special classloader (the ParanoidClassLoader) has a stricter priority algorithm than the one defined by the servlet specification: classes and resources are always searched first in WEB-INF/lib and WEB-INF/classes, ignoring any classes with the same name that exist in the parent classloader. Xalan, Xerces, etc. shipped with Cocoon are now really immune to whatever version of the same library can exist either in the JDK or in the servlet engine.

This time, you no more have to copy your libraries somewhere outside your web application.

The Downside

Such a strong shielding can have some minor inconveniences, however: if a class is given by the servlet engine (e.g. a JNDI context) and the same class exists in the webapp libs (e.g. in WEB-INF/lib/jndi.jar), then you're very likely to get a ClassCastException. This is likely to happen mostly with standard APIs, and the solution is then to delete the offending library from your WEB-INF/lib.

Why this exception? Because a class is defined by its name and its classloader. This means that if you get an object from the servlet engine whose class is defined by the engine's classloader and try to cast it to a class with the same class name, but loaded by the ParanoidClassLoader, the cast will fail because the classes are different.

-- SylvainWallez

To Use ParanoidCocoonServlet

To make Cocoon use the ParanoidCocoonServlet edit web.xml (found in the WEB-INF directory). Look for:

{{{... <web-app>

Change the contents of the <servlet-class> element from org.apache.cocoon.servlet.CocoonServlet to org.apache.cocoon.servlet.ParanoidCocoonServlet

You can also specify a servlet parameter pointing to a file containing explicit classpath information: {{{... <web-app>

</web-app>}}}

The file user.classpath may look like this (before 2.1.4 or the 2004/01/20 context:/ and # are not supported):

#lib-dir: all JAR and ZIP files within this directory are added to
#         the classpath
lib-dir:context:/WEB-INF/lib
#class-dir: all Java classes in this directory (or any subdirectory) are added to the classpath
class-dir:context:/WEB-INF/classes
#this entry is considered as URL
file:/C:\\lib\core\javacApi.jar
#this entry is an URL too but uses the context pseudo protocol which is resolved as the root 
#directory of the servlet context
context:/WEB-INF/lib/javacApi.jar


See also

EndorsedLibsProblem (last edited 2009-09-20 23:39:51 by localhost)