Friday, January 14, 2011

Teach Spring to use per-listener JMS destination queue names, not hard-coded ones

This took some experimenting.

My problem: I am using Spring Framework 3.0 JMS support to wire up a JMS listener container to listener beans. The XML syntax looks like:

<jms:listener-container>
    <jms:listener
            destination="hard-coded-queue-name"
            ref="listener"/>
</jms:listener-container>

This XML is distributed with each program instance. Not a single single server or client instance but a server cluster or client farm: each one of these needs a unique destination so JMS routes correctly.

My first try fixed the uniqueness problem but was less than fully usable:

<jms:listener-container>
    <jms:listener
            destination="#{T(java.util.UUID).randomUUID().toString()}"
            ref="listener"/>
</jms:listener-container>

This suffers excess cleverness. Each listener gets a random UUID for its destination. But, how does the listener refer to this queue name when filling in a JMS reply-to field, or logging or other purposes?

The real answer is to ask the listener for a destination, no produce one externally:

<jms:listener-container>
    <jms:listener
            destination="#{listener.inbox}"
            ref="listener"/>
</jms:listener-container>

And in the listener:

private final String inbox = UUID.randomUUID().toString();

public String getInbox() {
    return inbox;
}

Wednesday, January 12, 2011

JacORB in Jetty

There is a nasty JVM bug with class verification when you use JacORB in a webapp in Jetty. The issue only occurs when you use a custom classloader (as Jetty does) and load the CORBA in JacORB which replace the default implementation in the JDK. The characteristic message is:

java.lang.VerifyError: (class: org/jacorb/orb/Delegate, method: getReference signature: (Lorg/jacorb/poa/POA;)Lorg/omg/CORBA/portable/ObjectImpl;) Incompatible object argument for function call

The problem has been ongoing since at least 2004 that I can tell from googling without much progress. It happens to other containers than Jetty.

The solution is to ensure the JacORB classes are loaded with the system classloader, not the custom classloader in the webapp container. The only way this can happen is

  1. The jacorb jar and its dependencies are not in WEB-INF/lib so Jetty cannot find them there with its classloader, forcing delegation to the system classloader.
  2. The system classloader has the jacorb jar and its dependencies prepended to the boot classpath so they can take precedence over the default CORBA implementation in the JDK.

As I use maven, this means my POM for building the war includes:

<dependency>
    <groupId>jacorb</groupId>
    <artifactId>jacorb</artifactId>
    <optional>true</optional>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <optional>true</optional>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <optional>true</optional>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>${ch.qos.logback.version}</version>
    <optional>true</optional>
    <scope>provided</scope>
</dependency>

(I am relying on a parent pom to define versions for these)

And my command line to run Jetty from maven with the excellent jetty-maven-plugin (I am on Windows for this project):

$ MAVEN_OPTS='-Xbootclasspath/p:jacorb-2.3.1jboss.patch01-brew.jar\;slf4j-api-1.6.1.jar\;logback-classic-0.9.26.jar\;logback-core-0.9.26.jar -Dorg.omg.CORBA.ORBClass=org.jacorb.orb.ORB -Dorg.omg.CORBA.ORBSingletonClass=org.jacorb.orb.ORBSingleton' mvn jetty:run-war

I am glad this is finally behind me; a day and a half I could have better spent playing elsewhere.