Friday, January 19, 2007

Tomcat class loader problems

Yesterday I had to fight several hours to find a problem with Tomcat (see stacktrace below).
The reason was a class file conflict, in my case it was Struts. Unfortunately the stacktrace was total unusable. Even as I debugged into the class loading the execption occured where no Struts resources where loaded?!
I installed a package from www.jpackage.org: tomcat5-admin-webapps which installed Struts following its dependencies. This clashed with my web application which was build on top of Struts as well and had its own libraries.

Conclusion: Class conflicts can result in real unpredictable behavior and should be avoided very carefully. I have hacked a little perl script to search all jar files and compare them for some given paths to help me to track this:

#!/usr/bin/perl -w

# script to find and remove duplicated class files
# $Id: dupfind-jar,v 1.4 2005/04/21 16:05:06 th Exp $
#
# TODO:
# - testing binary equality of classes with same name
# - detect simple class file equality (not in jars)
# - query the VM lib classpath

use strict;

my @DIR = grep {!/^\-/} @ARGV;
my @SET = grep {/^\-/} @ARGV;
my $ref = {};
my $back = {};
my $base = `pwd`;
my $debug = opt("-d");

push(@DIR, split(/[:;]/, $ENV{"CLASSPATH"})) if exists $ENV{"CLASSPATH"} && ! opt("-x");

map { die("No such file or directory: $_") if ! -d $_ || $#DIR < 0; } @DIR if opt("-s");

&exitus() if $#DIR < 0 || opt("-h");

map { dive($_); } @DIR;

my %already = ();
foreach(sort bycount keys(%{$ref})) {
my $class = "";
my $packages = "";
if( opt("-l") ) {
print $_, "\n";
} elsif( opt("-1") || $#{$ref->{$_}} > 0) {
$class = $#{$ref->{$_}} + 1 . ": $_: ";
my $counter = 0;
foreach my $f (sort(@{$ref->{$_}})) {
$packages .= $f . " ";
}
print $class, $packages, "\n" if ! exists $already{$packages};
$already{$packages} = 1 if ! opt("-f");
}
}

warn("Showed only matching packages if one class file matched, omitting possible other class files.\n") if ! opt("-f");

###############################################################################
### <subroutines> #############################################################

sub exitus {
print @_, "\n" if @_;
print qq§USAGE: dupfind [OPTIONS]
OPTIONS:
-d debug
-x exclude looking in CLASSPATH
-s strict checking if files really exists
-f full report
-h show this help and exit
-l locate a class in all files
§;
die;
}

sub opt {
return grep {/^\Q$_\E$/} @SET;
}

sub bycount {
return $#{$ref->{$a}} <=> $#{$ref->{$b}};
}

sub dive {
my $directory = shift;
warn("diving into: $directory\n") if $debug;
do { warn "No such directory: $directory\n"; return; } if ! -d $directory && $debug;
opendir(DIR, $directory);
my @entries = grep {!/^\.+$/ } readdir(DIR);
closedir(DIR);
foreach my $entry (@entries) {
# warn("directory: $entry\n") if $debug && -d $entry;
my $name = $entry;
$entry = $directory . "/" . $entry;
do { dive($entry); next; } if -d $entry && ! -l $entry;
if(-f $entry && $entry =~ /.jar$/i ) {
foreach my $file (split(/[\r\n]/, `jar tf "$entry"`)) {
if($file =~ /\.class/i) {
if (! exists $back->{$file . "<=>" . $name}) {
push(@{$ref->{$file}}, $entry);
$back->{$file . "<=>" . $name} = 1;
}
}
}
}
}
}

### </subroutines> #############################################################
###############################################################################


The stack trace of the error:

07-01-18 12:02:05 ERROR - Servlet.service() for servlet action threw exception
sun.misc.InvalidJarIndexException: Invalid index
at sun.misc.URLClassPath$JarLoader.getResource(URLClassPath.java:854)
at sun.misc.URLClassPath$JarLoader.getResource(URLClassPath.java:762)
at sun.misc.URLClassPath$JarLoader.findResource(URLClassPath.java:732)
at sun.misc.URLClassPath.findResource(URLClassPath.java:145)
at java.net.URLClassLoader$2.run(URLClassLoader.java:362)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findResource(URLClassLoader.java:359)
at java.lang.ClassLoader.getResource(ClassLoader.java:977)
at java.lang.ClassLoader.getResource(ClassLoader.java:972)
at java.lang.ClassLoader.getResourceAsStream(ClassLoader.java:1159)
at org.apache.catalina.loader.WebappClassLoader.getResourceAsStream(WebappClassLoader.java:1170)
at org.apache.jasper.servlet.JasperLoader.getResourceAsStream(JasperLoader.java:143)
at org.apache.jasper.compiler.JDTCompiler$1.findType(JDTCompiler.java:184)
at org.apache.jasper.compiler.JDTCompiler$1.findType(JDTCompiler.java:169)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.askForType(LookupEnvironment.java:122)
at org.eclipse.jdt.internal.compiler.lookup.PackageBinding.getTypeOrPackage(PackageBinding.java:178)
at org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope.findImport(CompilationUnitScope.java:438)
at org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope.checkAndSetImports(CompilationUnitScope.java:181)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.completeTypeBindings(LookupEnvironment.java:193)
at org.eclipse.jdt.internal.compiler.Compiler.beginToCompile(Compiler.java:383)
at org.eclipse.jdt.internal.compiler.Compiler.compile(Compiler.java:397)
at org.apache.jasper.compiler.JDTCompiler.generateClass(JDTCompiler.java:404)
at org.apache.jasper.compiler.Compiler.compile(Compiler.java:297)
at org.apache.jasper.compiler.Compiler.compile(Compiler.java:276)
at org.apache.jasper.compiler.Compiler.compile(Compiler.java:264)
at org.apache.jasper.JspCompilationContext.compile(JspCompilationContext.java:563)
at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:303)
at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:314)
at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:264)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:252)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:173)
at com.opensymphony.module.sitemesh.filter.PageFilter.doFilter(PageFilter.java:39)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:202)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:173)
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:672)
at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:463)
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:398)
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:301)
at org.apache.struts.action.RequestProcessor.doForward(RequestProcessor.java:1063)
at org.apache.struts.action.RequestProcessor.processForwardConfig(RequestProcessor.java:386)
at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:229)
at org.apache.struts.action.ActionServlet.process(ActionServlet.java:1194)
at org.apache.struts.action.ActionServlet.doGet(ActionServlet.java:414)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:689)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:252)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:173)
at com.itth.commons.j2ee.filter.ReplaceTextFilter.doFilter(ReplaceTextFilter.java:285)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:202)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:173)
at com.opensymphony.module.sitemesh.filter.PageFilter.parsePage(PageFilter.java:118)
at com.opensymphony.module.sitemesh.filter.PageFilter.doFilter(PageFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:202)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:173)
at com.itth.commons.hibernate.HibernateSessionFilter.doFilter(HibernateSessionFilter.java:32)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:202)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:173)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:213)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:178)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:126)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:105)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:107)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:148)
at org.apache.jk.server.JkCoyoteHandler.invoke(JkCoyoteHandler.java:199)
at org.apache.jk.common.HandlerRequest.invoke(HandlerRequest.java:282)
at org.apache.jk.common.ChannelSocket.invoke(ChannelSocket.java:754)
at org.apache.jk.common.ChannelSocket.processConnection(ChannelSocket.java:684)
at org.apache.jk.common.ChannelSocket$SocketConnection.runIt(ChannelSocket.java:876)
at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:684)
at java.lang.Thread.run(Thread.java:619)