Thursday 11 April 2013

Using an embedded Mule to lookup a JMS connection factory with Spring on JBoss 4.2.3


My problem

I recently had the pleasure of working with Mule.  In my use case I wanted to lookup a JMS queue, post to that queue and receive a reply – a synchronous JMS bridge pattern.  This would be packaged into a web application that uses Spring, and deployed on JBoss 4.2.3.  With such a “simple” solution I can’t see why I ran into problems…


My error in the JBoss console was as follows:
Caused by: org.mule.retry.RetryPolicyExhaustedException: Unsupported ConnectionFactory type: $Proxy286
                at org.mule.retry.policies.AbstractPolicyTemplate.execute(AbstractPolicyTemplate.java:105)
                at org.mule.transport.AbstractConnector.connect(AbstractConnector.java:1616)
                at org.mule.transport.jms.JmsConnector.connect(JmsConnector.java:460)
                at org.mule.transport.AbstractConnector.start(AbstractConnector.java:428)
                at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
                at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
                at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
                at java.lang.reflect.Method.invoke(Method.java:601)
                at org.mule.lifecycle.phases.DefaultLifecyclePhase.applyLifecycle(DefaultLifecyclePhase.java:225)
                at org.mule.lifecycle.RegistryLifecycleManager$RegistryLifecycleCallback.onTransition(RegistryLifecycleManager.java:276)
                at org.mule.lifecycle.RegistryLifecycleManager.invokePhase(RegistryLifecycleManager.java:155)
                at org.mule.lifecycle.RegistryLifecycleManager.fireLifecycle(RegistryLifecycleManager.java:126)
                at org.mule.registry.AbstractRegistryBroker.fireLifecycle(AbstractRegistryBroker.java:80)
                at org.mule.registry.MuleRegistryHelper.fireLifecycle(MuleRegistryHelper.java:120)
                at org.mule.lifecycle.MuleContextLifecycleManager$MuleContextLifecycleCallback.onTransitio



Searching for a solution (ask Google approach)

As usual I ended up trawling through loads of forums, Stackoverflow, MuleSoft, etc and of course the Mule documentation.  The main stumbling block seemed to be my desire to use the local JNDI lookup.  Mule is predominantly used for integration and as such will usually need to run standalone.  However, my use case called for it to run embedded in my web application and I didn’t want to specify the JNDI configuration of each Web container I choose to deploy on.

Mule docs:
Possible issues:





My configuration

My configuration was pretty straight forward.

web.xml

<!-- Mule Bridge Settings -->
<resource-ref id="ResourceRef_jmsConnectionFactory">
       <description>Used to get connections to JMS queue, using JMS as a bridge to Mule</description>
       <res-ref-name>jms/jmsConnectionFactory</res-ref-name>
       <res-type>javax.jms.ConnectionFactory</res-type>
       <res-auth>Container</res-auth>
</resource-ref>

jboss-web.xml

    <resource-ref>
        <res-ref-name>jms/jmsConnectionFactory</res-ref-name>
        <res-type>javax.jms.ConnectionFactory</res-type>
        <jndi-name>java:/ConnectionFactory</jndi-name>
    </resource-ref>

uil2-service.xml

   <mbean code="org.jboss.naming.LinkRefPairService"
          name="jboss.jms:alias=QueueConnectionFactory">
      <attribute name="JndiName">QueueConnectionFactory</attribute>
      <attribute name="RemoteJndiName">ConnectionFactory</attribute>
      <attribute name="LocalJndiName">java:/JmsXA</attribute>
      <depends>jboss:service=Naming</depends>
   </mbean>




Debugging the problem

Debugging the problem was a little tedious as I mostly received exceptions in the JBoss log and not much else.  In hindsight, perhaps attaching a debugger and stepping through the Mule source might have been quicker. The main points were to confirm that I could connect to the queue, I could lookup the queue in my web application, and I could  



Now for the solution

Unfortunately we need one new class in our web application to help lookup the queue and this class depends on Spring.

package my.package;

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.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;
    }
}

mule-config.xml (client or local side)

<?xml version="1.0" encoding="UTF-8"?>

<mule xmlns:jms="http://www.mulesoft.org/schema/mule/jms"
     xmlns:file="http://www.mulesoft.org/schema/mule/file"
     xmlns:vm="http://www.mulesoft.org/schema/mule/vm"
     xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:doc="http://www.mulesoft.org/schema/mule/documentation" xmlns:spring="http://www.springframework.org/schema/beans" version="EE-3.3.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="
http://www.mulesoft.org/schema/mule/vm http://www.mulesoft.org/schema/mule/vm/current/mule-vm.xsd
http://www.mulesoft.org/schema/mule/file http://www.mulesoft.org/schema/mule/file/current/mule-file.xsd
http://www.mulesoft.org/schema/mule/jms http://www.mulesoft.org/schema/mule/jms/current/mule-jms.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-current.xsd
http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd ">
    <spring:beans>
        <spring:bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate"/>
    </spring:beans>

     <!--
     The following suggestion works:
     http://stackoverflow.com/questions/12461714/mule-embedded-use-the-containers-own-jndiinitialfactory
     -->
     <jms:connector
           name="local-jms-connector"
           connectionFactoryJndiName="java:comp/env/jms/jmsConnectionFactory"
           jndiDestinations="true"
           forceJndiDestinations="true"
           specification="1.1" doc:name="JMS">
    <jms:custom-jndi-name-resolver class="com.temenos.hothouse.mule.SpringJndiNameResolver">
           <spring:property name="jndiTemplate" ref="jndiTemplate"/>
    </jms:custom-jndi-name-resolver>   
     </jms:connector>

<!--
throws exception with "Unsupported ConnectionFactory type: $Proxy338"
     <spring:bean id="jmsConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
           <spring:property name="jndiName" value="java:env/comp/jms/jmsConnectionFactory"/>
           <spring:property name="lookupOnStartup" value="false"/>
           <spring:property name="cache" value="true"/>
           <spring:property name="proxyInterface" value="javax.jms.ConnectionFactory"/>
     </spring:bean>

    <jms:connector name="local-jms-connector" connectionFactory-ref="jmsConnectionFactory"
        jndiDestinations="true" forceJndiDestinations="true" disableTemporaryReplyToDestinations="true" doc:name="JMS Local">
    </jms:connector>
 -->   
   
    <!-- TODO Can't seem to get the local jms connection factory lookup to work
    (even this remote way barfs with the same exception as above) -->
<!--
    <jms:connector name="remote-jms-connector" jndiInitialFactory="org.jnp.interfaces.NamingContextFactory"
        jndiProviderUrl="jnp://127.0.0.1:1099"
        maxRedelivery="1"
        connectionFactoryJndiName="QueueConnectionFactory" jndiDestinations="true"
        forceJndiDestinations="true" disableTemporaryReplyToDestinations="true" doc:name="JMS">
    </jms:connector>
 -->

    <flow name="vm://Forecast-view-command" doc:name="vm://Forecast-view-command">
        <vm:inbound-endpoint exchange-pattern="request-response" path="Forecast-view-command" doc:name="VM"/>
        <logger message="VM Request:  #[payload]" level="INFO" doc:name="Logger"/>
        <set-payload value="#[mule:message.payload(java.lang.String)]" doc:name="Set Payload"/>
        <file:outbound-endpoint path="c:\logs\" outputPattern="forecast-request-#[function:datestamp].xml" responseTimeout="10000" doc:name="File"/>
        <jms:outbound-endpoint exchange-pattern="request-response" queue="queue/MuleRequest" doc:name="MuleRequest" connector-ref="local-jms-connector" />
        <file:outbound-endpoint path="c:\logs\" outputPattern="forecast-response-#[function:datestamp].xml" responseTimeout="10000" doc:name="File"/>
        <jms:jmsmessage-to-object-transformer doc:name="JmsMessage to Object"/>
        <object-to-string-transformer doc:name="Object to String"/>
        <logger message="Response:  #[payload]" level="INFO" doc:name="Logger"/>
    </flow>

</mule>


mule-config.xml (server or remote side)

<?xml version="1.0" encoding="UTF-8"?>

<mule xmlns:mulexml="http://www.mulesoft.org/schema/mule/xml" xmlns:stdio="http://www.mulesoft.org/schema/mule/stdio"
      xmlns:jms="http://www.mulesoft.org/schema/mule/jms" xmlns:data-mapper="http://www.mulesoft.org/schema/mule/ee/data-mapper" xmlns:file="http://www.mulesoft.org/schema/mule/file" xmlns:tracking="http://www.mulesoft.org/schema/mule/ee/tracking" xmlns:vm="http://www.mulesoft.org/schema/mule/vm" xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:doc="http://www.mulesoft.org/schema/mule/documentation" xmlns:spring="http://www.springframework.org/schema/beans" version="EE-3.3.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="
http://www.mulesoft.org/schema/mule/xml http://www.mulesoft.org/schema/mule/xml/current/mule-xml.xsd
http://www.mulesoft.org/schema/mule/file http://www.mulesoft.org/schema/mule/file/current/mule-file.xsd
http://www.mulesoft.org/schema/mule/stdio http://www.mulesoft.org/schema/mule/stdio/current/mule-stdio.xsd
http://www.mulesoft.org/schema/mule/jms http://www.mulesoft.org/schema/mule/jms/current/mule-jms.xsd
http://www.mulesoft.org/schema/mule/ee/data-mapper http://www.mulesoft.org/schema/mule/ee/data-mapper/current/mule-data-mapper.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-current.xsd
http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/ee/tracking http://www.mulesoft.org/schema/mule/ee/tracking/current/mule-tracking-ee.xsd
http://www.mulesoft.org/schema/mule/vm http://www.mulesoft.org/schema/mule/vm/current/mule-vm.xsd ">
    <custom-transformer class="org.mule.transformer.codec.XmlEntityDecoder" name="XmlEntityDecoder" doc:name="Java"/>

    <jms:connector name="remote-jms-connector" jndiInitialFactory="org.jnp.interfaces.NamingContextFactory"
        jndiProviderUrl="jnp://127.0.0.1:1099"
        maxRedelivery="1"
        connectionFactoryJndiName="java:/QueueConnectionFactory" jndiDestinations="true"
        forceJndiDestinations="true" disableTemporaryReplyToDestinations="true" doc:name="JMS">
        <!--retry:forever-policy frequency="2000"/-->
    </jms:connector>
    <data-mapper:config name="wsdlresponsetoentityresponse" transformationGraphPath="wsdlresponsetoentityresponse.grf" doc:name="DataMapper"/>
    <data-mapper:config name="reqtores" transformationGraphPath="reqtores.grf" doc:name="reqtores"/>

    <flow name="ProcessFromQueue" doc:name="ProcessFromQueue">
        <jms:inbound-endpoint queue="queue/MuleRequest" connector-ref="remote-jms-connector" doc:name="Request" />
        <set-payload value="#[mule:message.payload(java.lang.String)]" doc:name="Set Payload"/>
        <logger message="Before WSDL #[payload]" level="INFO" doc:name="Logger"/>
        <set-payload value="#[xpath('//viewcommand/pathparameters/postcode').text]" doc:name="Extract PostCode"/>
        <set-variable variableName="Requested_PostCode" value="#[payload]" doc:name="Set Requested_PostCode"/>
        <logger message="Requested_PostCode:  #[Requested_PostCode]" level="INFO" doc:name="Logger"/>
        <flow-ref name="ForecastWSDL" doc:name="ForecastWSDL"/>
        <logger message="Latest Forecast:  #[xpath('//forecastResult/forecasts/com.cdyne.ws.weatherws.Forecast[1]/desciption').text]" level="INFO" doc:name="Logger"/>
        <data-mapper:transform config-ref="wsdlresponsetoentityresponse" doc:name="DataMapper">
            <data-mapper:input-arguments>
                <data-mapper:input-argument key="Requested_PostCode">#[Requested_PostCode]</data-mapper:input-argument>
            </data-mapper:input-arguments>
        </data-mapper:transform>
        <object-to-string-transformer doc:name="Object to String"/>
        <jms:outbound-endpoint queue="queue/MuleResponse" connector-ref="remote-jms-connector" doc:name="Response"/>
    </flow>
    <sub-flow name="ForecastWSDL" doc:name="ForecastWSDL">
        <outbound-endpoint exchange-pattern="request-response" address="wsdl-cxf:http://wsf.cdyne.com/WeatherWS/weather.asmx?WSDL&amp;method=GetCityForecastByZIP" doc:name="WSDL"/>
        <mulexml:object-to-xml-transformer doc:name="Object to XML"/>
        <stdio:outbound-endpoint system="OUT" doc:name="STDIO"/>
        <file:outbound-endpoint path="c:\logs\" outputPattern="wsdl-response-#[function:datestamp].xml" responseTimeout="10000" doc:name="File"/>
<!--         <file:outbound-endpoint path="c:\logs" outputPattern="jms-response-#[function:datestamp].xml" responseTimeout="10000" doc:name="File"/>
 -->
     </sub-flow>
    <flow name="mule-configFlow1" doc:name="mule-configFlow1">
        <data-mapper:transform config-ref="reqtores" doc:name="DataMapper">
            <data-mapper:input-arguments>
                <data-mapper:input-argument key="Requested_PostCode">#[Requested_PostCode]</data-mapper:input-argument>
            </data-mapper:input-arguments>
        </data-mapper:transform>
    </flow>
</mule>

4 comments: