Wednesday, 17 February 2010

ClassNotFoundException destroying a session in Tomcat

When a session was being expired (either through a timeout, or via using the Tomcat Manager to manually destroy them) that contained an EJB I was getting a ClassNotFoundException. The stack trace when killing from the manager was:
java.lang.RuntimeException: Specified calling class, net.devgrok.MyEjbRemote could not be found for WebappClassLoader
  delegate: false
  repositories:
----------> Parent Classloader:
org.apache.catalina.loader.StandardClassLoader@70cb6009

 at org.jboss.ejb3.common.lang.SerializableMethod.getClassFromName(SerializableMethod.java:311)
 at org.jboss.ejb3.common.lang.SerializableMethod.getClassType(SerializableMethod.java:282)
 at org.jboss.ejb3.common.lang.SerializableMethod.toMethod(SerializableMethod.java:233)
 at org.jboss.ejb3.common.lang.SerializableMethod.toMethod(SerializableMethod.java:220)
 at org.jboss.ejb3.proxy.impl.handler.session.SessionProxyInvocationHandlerBase.invoke(SessionProxyInvocationHandlerBase.java:182)
 at org.jboss.ejb3.proxy.impl.handler.session.SessionProxyInvocationHandlerBase.invoke(SessionProxyInvocationHandlerBase.java:164)
 at net.devgrok.JndiRemoteProxyInvocationHandler.invoke(JndiRemoteProxyInvocationHandler.java:55)
 at $Proxy15.logout(Unknown Source)
 at org.apache.catalina.session.StandardSession.expire(StandardSession.java:702)
 at org.apache.catalina.session.StandardSession.expire(StandardSession.java:660)
 at org.apache.catalina.session.StandardSession.invalidate(StandardSession.java:1113)
 at org.apache.catalina.session.StandardSessionFacade.invalidate(StandardSessionFacade.java:150)
 at org.apache.catalina.manager.HTMLManagerServlet.invalidateSessions(HTMLManagerServlet.java:822)
 at org.apache.catalina.manager.HTMLManagerServlet.doSessions(HTMLManagerServlet.java:690)
 at org.apache.catalina.manager.HTMLManagerServlet.doGet(HTMLManagerServlet.java:128)
 at org.apache.catalina.manager.HTMLManagerServlet.doPost(HTMLManagerServlet.java:164)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:637)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
 ...
 at java.lang.Thread.run(Thread.java:619)
Caused by: java.lang.ClassNotFoundException: net.devgrok.MyEjbRemote
 at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1387)
 at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1233)
 at java.lang.Class.forName0(Native Method)
 at java.lang.Class.forName(Class.java:247)
 at org.jboss.ejb3.common.classloader.PrimitiveAwareClassLoader.findClass(PrimitiveAwareClassLoader.java:105)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
 at org.jboss.ejb3.common.lang.SerializableMethod.getClassFromName(SerializableMethod.java:307)
 ... 32 more
After some investigating I discovered the current thread's ClassLoader was incorrect. So I created a HttpSessionListener to swap the ClassLoader, unload the session and put the ClassLoader back.
public class SessionLifecycleEventListener implements HttpSessionListener
{
   private static final Logger log = LoggerFactory.getLogger(SessionLifecycleEventListener.class);
   public void sessionDestroyed(HttpSessionEvent evt)
   {
      HttpSession session = evt.getSession();
      //remember class loader
      ClassLoader currentLoad = Thread.currentThread().getContextClassLoader();
      try
      {
         //set classloader to be the webapp's
         ClassLoader webApp = this.getClass().getClassLoader();
         Thread.currentThread().setContextClassLoader(webApp);
         
         //clear all the session objects now
         Enumeration<String> names = session.getAttributeNames();
         while (names.hasMoreElements())
         {
            String name = names.nextElement();
            session.removeAttribute(name);
         }
      }
      catch (RuntimeException e)
      {
         log.error("closeEjbSession caught exception", e);
      }
      finally
      {
         //restore classloader
         Thread.currentThread().setContextClassLoader(currentLoad);
      }
      
   public void sessionCreated(HttpSessionEvent evt)
   {
   }
}

No comments:

Post a Comment