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)
   {
   }
}

Thursday, 11 February 2010

Using RichFaces to give versioned URL's

RichFaces has some nice components that can load scripts and styles when placed anywhere in the xhtml page and automatically inserts them into the header at render time. For example a footer could say it requires stylesheet, after you've already defined a header.

The tags are pretty simple there is the style loader, loadStyle:
<afj:loadstyle src="/css/test.css"></afj:loadstyle>
And the script loader, loadScript:
<afj:loadscript src="/javascript/test.js">

The src can be a path be:
  1. Relative to the current page.
  2. Path relative to the context if starting with a slash.
  3. Or loaded by RichFaces resource framework
    Using "resource:///path/to/file"
Now, the problem most web developers always face is that of caching. If you disable caching then the site is slow for the user and your bandwidth is used up. If you enable caching and release a new version, how do you clear the user's and ISP's cache so they can see your changes?

RichFaces already deals with this by creating virtual URL's for all their resources served (the static javascript files and the generated css files and images). It prepends "afj/g/[version]" to the url's. For example:
<script src="/myapp/jsf/a4j/g/3_3_2.SR1jquery.js" type="text/javascript">
Where myapp is the name of the context and I have a mapping to the faces servlet for /jsf/*. This gets prepended to all resources served through the framework, even ones from your war.

This neat little feature can be used to version your own deployments, so that after each build a new URL is generated.

If your using maven its simple.
First add this to your web.xml:
<!-- 
  not used, but needs to be set
-->
<context-param>
  <param-name>org.ajax4jsf.RESOURCE_URI_PREFIX</param-name>
  <param-value>a4j/${project.version}-r${buildNumber}</param-value>
</context-param>
<!-- 
  the prefix used by richfaces for global cached resources
-->
<context-param>
  <param-name>org.ajax4jsf.GLOBAL_RESOURCE_URI_PREFIX</param-name>
  <param-value>a4j/g/${project.version}-r${buildNumber}</param-value>
</context-param>
<!-- 
  the prefix used by richfaces for session cached resources
-->
<context-param>
  <param-name>org.ajax4jsf.SESSION_RESOURCE_URI_PREFIX</param-name>
  <param-value>a4j/s/${project.version}-r${buildNumber}</param-value>
</context-param>
Next you'll need to add the buildnumber plugin:
<build>
   <plugins>
      <plugin>
         <groupId>org.codehaus.mojo</groupId>
         <artifactId>buildnumber-maven-plugin</artifactId>
         <executions>
            <execution>
               <phase>validate</phase>
               <goals>
                  <goal>create</goal>
               </goals>
            </execution>
         </executions>
         <configuration>
            <doCheck>false</doCheck>
            <doUpdate>false</doUpdate>
         </configuration>
      </plugin>
      <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-war-plugin</artifactId>
         <configuration>
            <warSourceDirectory>WebContent</warSourceDirectory>
            <webXml>src/PROD/web.xml</webXml>
            <filteringDeploymentDescriptors>true</filteringDeploymentDescriptors>
            <archive>
               <manifest>
                  <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
               </manifest>
               <manifestEntries>
                  <Implementation-Build>${buildNumber}</Implementation-Build>
               </manifestEntries>
            </archive>
         </configuration>
      </plugin>
If your using eclipse to do development then you'll probably want to have 2 web.xml files. One that eclipse can read with the filtering (property subsititution turned off) and one for production builds.
In

Thursday, 4 February 2010

Safari removed javascript API

In the latest version of Safari (WebKit) they have removed some deprecated javascript API & tags which can cause problems with older code. This is my attempt to keep track of them so I can search through the code and change it to use a newer method.

Here is a list of what works in Firefox 3 or Internet Explorer but not Safari:

  • document.[form name] where [form name] matches the element <form name='[form name]'>
    Replacement:Use document.getElementById() instead
  • document.embeds
    Replacement:
    Use document.getElementById() instead
  • Form element's id defaulted to their name
    Replacement:
    Manually specify the id
  • <applet> tag
    Replacement:
    <object> tag
That's it for the moment but I'll keep updating the list as I find them.

Tuesday, 2 February 2010

Building your own jbossall-client.jar

The newer versions of JBoss (I'm using 5.1.0 GA) very annoyingly have all the client jars split into at least 47 separate jars! Which is a bit rude when you consider the total number of jars they expect you to copy into your tomcat/lib dir is a whopping 93.

JBoss are declined to make the client jars into a single file again as its meant to make it easier to upgrade... That's arguable but still leaves us with all these files to copy around. After searching the net I've found a rather nice solution which I have adapted below.
The way I use it is as follows. I combine all jboss*.jar files into a single jar and then copy the additional dependencies.

Create a directory an empty directory and put the build.xml in it and create a /jboss dir. Copy all jboss jboss*.jar from jboss-5.1.0.GA/client.

Create a /build.xml:
<project default="combine">
   <property name="temp.dir" value="unpacked" />
   <property name="lib.dir" value="jboss" />
   <property name="jar.filename" value="jbossall-client.jar" />

   <target name="clean">
      <delete dir="${temp.dir}" quiet="true" />
      <delete file="${base.dir}/${jar.filename}" quiet="true" />
   </target>
   
   <target name="unjar.jar">
      <unjar dest="${temp.dir}">
         <patternset>
            <exclude name="META-INF/MANIFEST.MF" />
         </patternset>
         <fileset dir="${lib.dir}">
            <include name="**/*.jar" />
         </fileset>
      </unjar>
   </target>


   <target name="combine" depends="unjar.jar">
      <jar jarfile="${basedir}/${jar.filename}"
         basedir="${temp.dir}" update="true"
         compress="false">
      </jar>
   </target>
</project>
It will extract and recombine the jar files for you.

The dependencies I also copy across are:
concurrent.jar
ejb3-persistence.jar
hibernate-annotations.jar
javassist.jar
jmx-client.jar
jmx-invoker-adaptor-client.jar
jnp-client.jar
trove.jar
xmlsec.jar

Monday, 1 February 2010

Automake unable to detect vendor on RedHat EL5

After chasing down some build problems with subversion 1.6.9 on EL5 I was noticing that it was detecting the system as x86_65-unknown-linux-gnu. Worried that it would cause me later problems I started digging through the configure script. It lead me to the build/config.guess script.
After spending too long searching the net and trying to resolve the issue, I just did this:
subversion-1.6.9>cp /usr/lib/rpm/config.guess ./build/config.guess
Basically overriding the one that came with the distro with redhat's hack.