Mule ESB: How to Call the Exact Method You Want on a Spring Bean

The Issue

Mule ESB provides a built-in mechanism to call a Spring bean.  Mule also provides an entry point resolver mechanism to choose the method that should be called on the desired bean.  One such method is the property-entry-point-resolver.  This means the incoming message includes a property that specifies the method name.  It looks like this:

        <component doc:name="Complaint DAO">
            <property-entry-point-resolver property="daoMethod"/>
            <spring-object bean="acmComplaintDao"/>
        </component>

This snippet means the incoming message includes a property “daoMethod”; Mule will invoke the acmComplaintDao bean’s method named by this property.

I’ve had three problems with this approach.  First, you can only specify the bean to be called, and hope Mule chooses the right method to invoke.  Second, Mule is in charge of selecting and providing the method arguments.  Suppose the bean has several overloaded methods with the same name? Third, only an incoming message property can be used to specify the method name.  This means either the client code invoking the Mule flow must provide the method name (undesirable since it makes that code harder to read), or the flow design  must be deformed such that the main flow calls a child flow only in order to provide the method name property.

How I Resolved the Issue

Last week I finally noticed Mule provides access to a bean registry which includes all Spring beans.  And I noticed Mule’s expression component allows you to add arbitrary Mule Expression Language to the flow.  Putting these two together results in much simpler code.  I could replace the above example with something like this:

<expression-component>
     app.registry.acmComplaintDao.save(message.payload);
</expression-component>

The “app.registry” is a built-in object provided by the Mule Expression Language.

In my mind this XML snippet is much more clear and easy to read than the previous one.  At a glance the reader can see which method of which bean is being called with which arguments.    And it fits right into the main flow; no need to setup a separate child flow just to specify the method name.

A nice simple resolution to the issues I had with my earlier approach. And the new code is smaller and easier to read!  Good news all around.

 

Mule Integration with Spring: A New Approach

I’ve written about using Mule with Spring on my personal blog.  In this article I described how to integrate Mule into an existing Spring MVC application.

That approach involved configuring the web.xml file to setup a Mule context:

    <listener>
        <listener-class>org.mule.config.builders.MuleXmlBuilderContextListener</listener-class>
    </listener>
    <context-param>
        <param-name>org.mule.config</param-name>
        <param-value>spring-rma.xml</param-value>
    </context-param>

This approach is easy and works well, especially if all your Spring beans are defined in Mule files.  But my application already had a Spring application context with a large library of Spring beans.  The above approach creates a whole separate context!  My Mule flows have no access to my existing Spring beans!  This situation is very depressing in terms of having Mule be able to leverage my existing code.

So I found a better way.  I now create the Mule context as a child of my main Spring application context.  My problem is completely resolved!  It does take a little more work.

First, create a Spring bean to manage the Mule context.  Spring calls methods on this bean when the application context is started, and again when the application context is closing.  These methods start the Mule context when Spring starts, and closes it when Spring is shutting down.  It also has a list of Mule configuration files to include in the Mule context.

package com.armedia.acm.rma;

import org.mule.api.MuleContext;
import org.mule.api.MuleException;
import org.mule.api.context.MuleContextFactory;
import org.mule.config.spring.SpringXmlConfigurationBuilder;
import org.mule.context.DefaultMuleContextFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class MuleContextManager implements ApplicationContextAware
{

    private MuleContext muleContext;
    private transient Logger log = LoggerFactory.getLogger(getClass());
    private ApplicationContext applicationContext;
    private String[] configurationFiles;

    private void startMuleContext(ApplicationContext applicationContext) throws MuleException
    {
        if ( getMuleContext() != null )
        {
            return;
        }
        log.debug("Creating spring config builder.");
        SpringXmlConfigurationBuilder builder = new SpringXmlConfigurationBuilder(getConfigurationFiles());

        builder.setParentContext(applicationContext);
        MuleContextFactory muleContextFactory = new DefaultMuleContextFactory();
        MuleContext muleContext = muleContextFactory.createMuleContext(builder);

        log.debug("Starting mule context");
        muleContext.start();
        setMuleContext(muleContext);
        log.debug("Done.");
    }

    public void shutdownBean()
    {
        try
        {
            if ( getMuleContext() != null )
            {
                log.debug("Stopping Mule context");
                getMuleContext().stop();
            }
        }
        catch (MuleException e)
        {
            log.error("Could not stop Mule context: " + e.getMessage(), e);
        }
    }

    public MuleContext getMuleContext()
    {
        return muleContext;
    }

    public void setMuleContext(MuleContext muleContext)
    {
        this.muleContext = muleContext;
    }

    public ApplicationContext getApplicationContext()
    {
        return applicationContext;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
    {
        this.applicationContext = applicationContext;

        if ( getMuleContext() == null )
        {
            try
            {
                startMuleContext(applicationContext);
            }
            catch (MuleException e)
            {
                log.error("Could not start Mule context: " + e.getMessage(), e);
                throw new IllegalStateException(e);
            }
        }
    }

    public String[] getConfigurationFiles()
    {
        return configurationFiles;
    }

    public void setConfigurationFiles(String[] configurationFiles)
    {
        this.configurationFiles = configurationFiles;
    }
}

The above bean is configured in Spring like so:

    <bean id="muleContextManager" class="com.armedia.acm.rma.MuleContextManager"
            destroy-method="shutdownBean">
        <property name="configurationFiles">
            <array>
                <value type="java.lang.String">spring-rma.xml</value>
            </array>
        </property>
    </bean>

Now must make sure this Spring XML configuration is in your Spring application context, and all is well. Mule still starts and stops when the Web application starts and stops, just like with the original method. And, since we make the Mule context a child of the Spring context (Line 30 in the above Java code), all Mule flows can see all the Spring beans. Life is good!

Don’t forget to remove the Mule configuration elements from the web.xml!!!

Coincidence… or Fate?  A true story postscript.

I wrote the above Java code yesterday.  When I got home, the new book “Mule in Action, Volume 2” was in my mailbox.  I opened the book to a random page: page 209, to be exact.  This page includes a code snippet.  It was the same code I just wrote – the same code you see above!  I was reading the very code I had written earlier that day!  Weird, but true.

Mule and Activiti: Happy Together

Activiti out-of-the-box supports Mule tasks. When Activiti encounters a Mule task, it launches the configured Mule flow. This integration is exactly why my new project uses Mule in the first place. But Activiti also supports execution and task listeners: code that runs in response to an Activiti event. How can I cause Activiti to launch a Mule flow in response to such events?

The short answer is: write a generic Spring bean to launch a Mule flow; then use Activiti’s expression language to call this bean. Example snippet from an Activiti XML configuration file:

<sequenceFlow id='flow1' sourceRef='theStart' targetRef='writeReportTask'>  
    <extensionElements>
        <activiti:executionListener      expression="${cmMuleActivitiListener.sendMessage('vm://activiti-flow', 'Allman Bros. Band',
'bandResults', execution)}"/>
    </extensionElements>
</sequenceFlow>

 

This snippet causes Activiti to launch the vm://activiti-flow Mule flow when the Activiti workflow transitions from theStart to writeReportTask.Allman Bros. Band is the message payload, and the Mule flow results are placed in the bandResults Activiti flow variable. execution is a reference to the currently executing flow, and allows the Spring beancmMuleActivitiListener to give the Mule flow access to the Activiti flow variables.

The Spring configuration file is very simple:

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="https://www.springframework.org/schema/beans"  
       xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.1.xsd">

    <description>
        This Spring configuration file must be loaded into an
        application context which includes a bean named 
        "cm-mule-client".  This cm-mule-client bean must be an
        org.mule.module.client.MuleClient.
    </description>

    <!-- NOTE: bean ids to be used in Activiti expression language must be proper Java identifiers -->
    <bean id="cmMuleActivitiListener" class="com.armedia.acm.activiti.ActivitiMuleListener">
        <property name="realMuleClient" ref="cm-mule-client"/>
    </bean>
</beans>

Please note the description. The project must provide a Spring bean named cm-mule-client.

Finally, the primary method in the Spring Java class is also pretty straightforward:

 private void sendMessage(MuleClient mc, String url, Object payload, String resultVariable, DelegateExecution de)
            throws MuleException
    {

        Map<String, Object> messageProps = new HashMap<String, Object>();
        setPropertyUnlessNull(messageProps, "activityId",
          de.getCurrentActivityId());
        setPropertyUnlessNull(messageProps, "activityName",
          de.getCurrentActivityName());
        setPropertyUnlessNull(messageProps, "eventName",
          de.getEventName());
        setPropertyUnlessNull(messageProps, "id", de.getId());
        setPropertyUnlessNull(messageProps, "parentId",
          de.getParentId());
        setPropertyUnlessNull(messageProps, "processBusinessKey",
          de.getProcessBusinessKey());
        setPropertyUnlessNull(messageProps,
          "processDefinitionId", de.getProcessDefinitionId());
        setPropertyUnlessNull(messageProps,
          "processVariablesMap", de.getVariables());
        setPropertyUnlessNull(messageProps, "processInstanceId",
          de.getProcessInstanceId());

        MuleMessage mm = mc.send(url, payload, messageProps);

        log.debug("Mule sent back: " + mm.getPayload());

        if ( resultVariable != null )
        {
            de.setVariable(resultVariable, mm.getPayload());
        }
    }

    private void setPropertyUnlessNull(
      Map<String, Object> map, String key, Object value)
    {
        if ( value != null )
        {
            map.put(key, value);
        }
    }

 

This code sends the Mule message and waits for Mule to reply (since we call the Mule client send method). To post the message asynchronously we just need a corresponding method to call the Mule client dispatchmethod.

This small module allows me to configure Activiti to invoke Mule flows in response to Activiti events. And Activiti ships with the ability to invoke Mule flows as automated tasks. So now I have pervasive Mule support in Activiti: wherever Activiti supports executable code, it can call Mule. So happy together!

Thinking in Mule: How I Learned to Love the Platform

I just finished my first Mule flow. My flow receives an Alfresco node reference and declares a corresponding record in the Alfresco Records Management Application. I learned about Mule and Alfresco RM along the way… specifically, I learned to think like a Mule, instead of a Java developer.

Take the sub-flow to create the record folder. This folder may or may not already exist. In a concurrent environment, I prefer to try creating the folder first; if that fails, then lookup the folder by path. Either way, the sub-flow has to find the folder node-reference so I can later file the new record into the folder.

The try/catch approach. In a Java class, I might implement this by catching the exception from the create attempt. So in my Mule flow, I tried adding an error strategy. Many things went wrong with this. My custom error strategy had to apply only the record folder lookup flow, so it has to be defined in that flow (otherwise it would handle any other unrelated errors that might occur during the overall records management flow). But Mule only allows custom error strategies in flows… not in subflows. Later I found out calling a private flow changes flow variable visibility: this caused me many problems until I realized what was happening. Also, Mule didn’t always call my error strategy when record folder creation failed; also, it still propagated the error to the outer flow, which tended to stop all processing. In short, this approach failed completely!

The first-successful approach. Mule’s first-successful message processor seemed like just the ticket. And it was a real Mule approach – not Java encoded in Mule! I add a first-successful element with two internal processes: the first one to create the folder, the second one to lookup the folder ID. If the first one failed, Mule will automatically try the second one, and all will be well!

<first-successful>  
   <enricher doc:name="create-record-folder">
   <!-- .... -->
   </enricher>
   <enricher doc:name="get record folder id">
   <!-- .... -->
   </enricher>
</first-successful>

Well, this didn’t always work either, most likely because I didn’t know how to tell Mule how to distinguish success from failure.

The keep trying approach. Since the first-successful approach sometimes worked, I thought to myself, well, let’s just keep retrying it!

<until-successful objectStore-ref="until-successful-object-store" maxRetries="15" secondsBetweenRetries="1">  
   <first-successful>
      <enricher doc:name="create-record-folder">
      <!-- .... -->
      </enricher>
      <enricher doc:name="get record folder id">
      <!-- .... -->
      </enricher>
   </first-successful>
</until-successful>

The less said about this, the better. If it didn’t work the first time, I just saw 14 more failures in the log. Very depressing!

The check-the-result-code approach: it works! Checking the result code is older than Mule obviously; older than Java; older than structured exception handling! But it works very well in this situation.

<http:outbound-endpoint  
    exchange-pattern="request-response"
    method="POST"
    doc:name="create record folder"
    encoding="UTF-8"
    ref="alfresco-create-record-folder"
    contentType="application/json; charset=UTF-8"
    mimeType="application/json">
    <response>
        <json:json-to-object-transformer
            name="getJsonFromRecordFolder"
            returnClass="java.util.Map"/>
    </response>
</http:outbound-endpoint>  
<choice doc:name="Choice">  
    <when expression="message.inboundProperties['http.status'] == '200'">
        <set-variable 
            variableName="record-folder-id" 
            value="#[message.payload.persistedObject]"
            doc:name="Variable"/>
        </when>
    <otherwise>
        <processor-chain doc:name="Folder Lookup">
            <!-- lookup the folder here.... -->
        </processor-chain>
    </otherwise>
</choice>

If we get an HTTP 200 response to the first try, then we did create the folder, so we just record the new folder ID from the response JSON. If we did not get an HTTP 200, we assume the folder already existed (which is a good enough strategy for my purposes at the moment), so we lookup the existing folder’s ID.

Writing this first Mule flow definitely has had the desired educational effect. I feel I have a good basic understanding of the Mule platform now. Also, when I run into roadblocks or undesired / inexplicable behavior, now I will trust the defect is in my understanding, and not in Mule itself. At least for such basic use cases as this one!