2
votes

Consider the following mule configuration and having mule embedded in a Web (Java EE) Application:

<jms:connector
        name="jmsConnector"
        connectionFactory-ref="jmsConnectionFactory"> <!-- From spring -->
        <!-- JNDI Name Resover here? -->
</jms:connector>
<flow name="mainTestFlow">
    <jms:inbound-endpoint connector-ref="jmsConnector" 
        queue="jms/MessageQueue" />
    <logger level="INFO" category="mule.message.logging" message="Message arrived." />
</flow>

jmsConnectionFactory refers to a JMS Connection Factory defined in Spring, from:

<bean id="jmsConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="jms/QueueConnectionFactory" />
</bean>

The Queue Connection Factory was tested and is working.

The jms/MessageQueue queue name refers to a resource-ref defined in the web application web.xml file. This JNDI reference is bound at the container level to a javax.jms.Queue managed by the application server and connected to a proper messaging server (ActiveMQ, in this case).

However, Mule doesn't treat the queue="" attribute as a JNDI destination, but as the queue name itself. So, when the above code is initialized, it actually creates a new queue in ActiveMQ named "jms/MessageQueue". What I really wanted was that it correctly retrieved the queue from the JNDI reference in the Web Application descriptor.

Ok, you could say, all I had to do was to configure a JNDI Name Resolver at the JMS Connector and also add the jndiDestinations="true" and forceJndiDestinations="true" attributes to it.

This is acceptable:

<jms:default-jndi-name-resolver 
    jndiProviderUrl="tcp://localhost:1099"
    jndiInitialFactory="???"/>

The real problem is that I don't want to place the real Initial Context Factory class name in the jndiInitialFactory, because it would fall into a container-specific definition. However, my application is sometimes deployed into JBoss 4.2.3, and sometimes, into WebSphere 7. Having 2 configurations and 2 EAR packages is not an option, due to our development process.

Anyway, is it anyhow possible to either tell Mule-ESB to assume the current container (as it is in embedded mode) as the default JNDI Initial Factory for lookups or provide a "generic" JNDI Initial Factory that would recognize the container's JNDI environment? That shouldn't be a problem, because a web application can refer to it's container JNDI environment without additional (or even visible) configuration.

If not possible, can I have my jms:inbound-endpoint refer to a javax.jms.Queue defined in Spring, just as the jms:connector does with the JMS Connection Factory? That would actually be rather elegant and clean, as Mule is Spring-friendly.

Thank you all in advance!


Solution

After much thought and consideration, I finally solved my problem by creating a custom JndiNameResolver wired up to spring JNDI facilities (JndiTemplate, for instance). This is far from the best solution, but I found that to be the one that would least interfere and tamper with Mule's and Spring's inner intricacies.

That said, here is the class: package com.filcobra.mule;

import javax.naming.NamingException;

import org.mule.transport.jms.jndi.AbstractJndiNameResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jndi.JndiObjectLocator;
import org.springframework.jndi.JndiTemplate;

public class SpringJndiNameResolver extends AbstractJndiNameResolver implements InitializingBean {
    private static Logger logger = LoggerFactory.getLogger(SpringJndiNameResolver.class);
    private JndiTemplate jndiTemplate;

    @Override
    public void afterPropertiesSet() throws Exception {
        if (jndiTemplate == null) {
            jndiTemplate = new JndiTemplate();
        }
    }

    @Override
    public Object lookup(String name) throws NamingException {
        Object object = null;
        if (name != null) {
            logger.debug("Looking up name "+name);
            object = jndiTemplate.lookup(name);
            logger.debug("Object "+object+" found for name "+name);
        }
        return object;
    }

    public JndiTemplate getJndiTemplate() {
        return jndiTemplate;
    }

    public void setJndiTemplate(JndiTemplate jndiTemplate) {
        this.jndiTemplate = jndiTemplate;
    }
}

With that, the configuration falls back into the usual:

<spring:bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate" />

<jms:connector
        name="jmsConnector"
        connectionFactoryJndiName="java:comp/env/jms/MyConnectionFactory" <!-- from Resource-Ref -->
        jndiDestinations="true"
        forceJndiDestinations="true"
        specification="1.1" >

    <jms:custom-jndi-name-resolver class="com.filcobra.mule.SpringJndiNameResolver">
        <spring:property name="jndiTemplate" ref="jndiTemplate"/>
    </jms:custom-jndi-name-resolver>    
</jms:connector>

With that, I was finally able to not have my Mule ESB installation tied up to a specific JMS vendor/implementation. In fact, the JMS (queues and factories) configuration is all left under the application server responsibility.

Nevertheless, one thing remained odd. I expected that the JMS endpoints also used my Jndi Name Resolver in order to lookup the queue from a resource-reference, or its JNDI Name, the same way it did with the Connection Factory. That wouldn't work whatsoever. I finally worked around that by placing the queue name itself, as created in the JMS server:

<flow name="mainTestFlow">
    <jms:inbound-endpoint connector-ref="jmsConnector" queue="queue/myQueue"/> <!-- Queue Name, not JNDI Name -->

That worked. So, I'm assuming the JMS Connector doesn't try and look up the queue, but simply uses the connection factory (looked up or not) to directly access the JMS Server.

Regards!

1

1 Answers

1
votes

I see the problem in the source code: basically if you provide an externally created connection factory, jndiDestinations and forceJndiDestinations are forcefully set to false.

I haven't messed with JNDI enough recently to provide a generic solution to your problem, which indeed would be the best.

What I would try would be to sub-class org.mule.transport.jms.Jms11Support, inject my Spring looked-up queues in it, rewire it internally to use these queues and, lastly, inject it in the Mule JMS connector itself.