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)

Thursday, November 23, 2006

Moving components from one container to an other container

I had some trouble moving a component from a JPanel with CardLayout to another container (e.g. JSplitPane) to view two panels at the same time. The moved panels were not shown in the new container.
The trick was that the CardLayout made the components invisible. So if you want to get shown them in the new container you have to make sure they are visbile by setVisible(true).

search hints: moving, removing, adding, load, unload, CardLayout, JPanel, JSplitPane

Friday, November 25, 2005

Java RMI connection problems

The standard RMI implementation is simple and easy to use. But it has a design flaw which make many developers confusing:

The server insists on setting a host address in the communication protocol when a client opens a connection. This address is used then for all following calls of the client to the server.

This result in following problem:

The server does not know how the client reached him. The RMI registry just waits on its ports on the network interface and handles the connection then. In detail it handshakes a new port with the client. Unfortunately it overrides the host where the client did come from as well. This is a contrast behavior to standard socket connections where the port is changed as well, but the host address keeps to what the client specified. The server chooses straightly one of its local ip addresses or the address/name specified by the "java.rmi.server.hostname" property.

This leads a lot of coders in a trap. They are wondering why the connection can be established to the remote server, but every consecutive remote call fails. In 99,9% the server has more than one NIC or the server assigns the localhost device to the connection.

The only way to get around this is to specify the same name of the server in each client and set this as the servers hostname in the "java.rmi.server.hostname" property. This name can be resolved to even different IP addresses in each client depending in how the server will be reached.