Spring, ESBs, and File Suckers - No Fluff Just Stuff

Spring, ESBs, and File Suckers

Posted by: Craig Walls on August 5, 2005

In the enterprise, it's unusual that any application stands alone. Quite often, two or more applications must collaborate, sharing data between each other. A workforce scheduling application may need to share data with the payroll system. Likewise, the payroll system may need to share information with the financials system. And they all may need to interact with the HR application.

There are several tricks, schemes, and hacks to share data between systems. Most of these tricks are one-off integration solutions that are only useful to tie two specific systems together. When you need to throw another system into the mix, you'll probably need to start over and come up with another integration scheme.

A primitive, yet very common integration solution is file-sharing (known as the File Transfer pattern as described in Enterprise Integration Patterns). Using file-sharing a payroll system may write information to a file to be picked up and processed later by the financials system. Often, file-sharing involves some form of scheduling (e.g., cron) to trigger the receiving application to pick up the data file.

The problem with scheduled file-sharing is that the sending application has a finite window of opportunity to write the file before the receiving application comes along and picks it up. In the best case, the sending application finishes writing the file well in advance of the schedule (resulting in wasted time that the receiving application could use to process the data). In the worst case, the receiving application wakes up before the sending application has completed its work (resulting in missing or incomplete data).

In a perfect world, the financials system would pick up the file at precisely the moment that the payroll system has finished writing it. No sooner...no later. The file will always be complete and never have to sit idle waiting for financials to pick it up.

This sounds like a job for a File Sucker.

ESBs as File Suckers

"File sucker" is the name that I use to describe any mechanism that watches a directory for new files. Upon seeing a new file, it immediately sucks the file in and passes it on to any other applications that need it.

File sucking is hardly a new idea. In fact, it is a common feature in most Enterprise Service Bus (ESB) implementations. Although ESBs provide a rich array of integration options that are more robust than file sucking, most ESBs still provide support for monitoring directories and reading files into the bus. Support for file sucking in ESBs enable incremental adoption of the ESB into environments where file-sharing is already a common integration mechanism.

One such ESB that supports file sucking is Mule ESB. In Mule, creating a file sucker involves configuring a component whose inbound endpoint is a file directory. When a file appears in the watched directory it triggers an event in Mule, prompting Mule to read the file into the bus.

There are several ways to setup a file sucker in Mule. But since I'm a Spring fanatic and this is a Spring-centric blog, let me show you a very easy way to use Mule and Spring together to create a file sucker.

What you'll need

Here are the JAR files that I'm using to write the file-sucker example:

  • spring.jar (version 1.2.3)
  • commons-logging.jar
  • mule-1.1-rc1.jar
  • mule-extras-spring-1.1-rc1.jar
  • geronimo-spec-j2ee-connector-1.5-rc4.jar
  • jug-1.0.3.jar
  • concurrent-1.3.4.jar
  • commons-discovery.jar
  • commons-beanutils.jar
  • commons-collections.jar
  • xstream-1.0.2.jar
  • commons-pool.jar

Most of these files (or their equivalents) come with the Mule download. But you'll need to provide your own spring.jar file. For your convenience, I've collected all of the required JAR files and have uploaded them along with my example code for you to download here.

Configuring a multicaster

Mule comes with (in Mule extras) MuleEventMulticaster. This clever class subscribes to one or more service endpoints and, when an event arrives on that endpoint, it multicasts it out to any bean who is interested.

For the purposes of our file sucker example, we'll configure the MuleEventMulticaster as follows:

<beans>
  <bean id="applicationEventMulticaster"
      class="org.mule.extras.spring.events.MuleEventMulticaster">
    <property name="subscriptions">
      <list>
        <value>file:///inbox</value>
      </list> 
    </property>
  </bean>
</beans>

As you may have guessed, this particular MuleEventMulticaster keeps an eye on the "/inbox" directory, pulling in any files it finds there. But what does it do with the files that it finds?

To try this out, let's write a simple class with a main() method to load this bean into a Spring container. Perhaps something like this:

public class FileSucker {
  public static void main(String[] args) {
    new FileSystemXmlApplicationContext("sucker.xml");
  }
}

After running this code, copy some files into the inbox directory. If you take a look at the directory, you'll notice the files mysteriously disappearing almost as soon as they arrive. This proves that Mule is pulling them in. But where do they go?

At this point, the MuleEventMulticaster is sucking in the files, but since there aren't any event listeners, there's nobody to receive them. They're all sucked up with nowhere to go. Let's give them some place to go by adding an event listener.

Building an event listener

Mule's documentation implies that any implementation of Spring's ApplicationListener interface will receive Mule events. But as I've found in my own experiments (and as evidenced in the source code for MuleEventMulticaster) your listener classes must implement MuleEventListener. The good news is that MuleEventListener extends Spring's ApplicationListener and has no additional methods beyond onApplicationEvent()...so creating a Mule listener is not much different than creating any other Spring ApplicationListener implementation.

Here's a simple implementation of MuleEventListener:

package com.habuma.sucker.mule;

import org.mule.extras.spring.events.MuleEventListener;
import org.springframework.context.ApplicationEvent;

public class EventListener implements MuleEventListener {
  public void onApplicationEvent(ApplicationEvent event) {
      System.out.println("GOT AN EVENT: ");
      System.out.println(event.getSource());
  }
}

In the "real world", this event listener would probably be more exciting, reacting to the event by performing some sort of business logic. For simplicity's sake, this event listener simply sends the contents of its event to stdout.

Aside from implementing MuleEventListener, this class is no different than any other Spring ApplicationListener. But by implementing MuleEventListener you're indicating that you want this class to receive Mule events.

Now let's configure EventListener as a bean in the Spring context...

<beans>
...
  <bean id="myListener"
      class="com.habuma.sucker.mule.EventListener" />
...
</beans>

To see the event listener in action, startup the application and copy some files into inbox. You should see the EventListener react to every file that is copied into inbox.

There's only one gotcha: MuleEventMulticaster will send the file events to all MuleEventListeners. What if your application has several event listeners and you want each to list for specific events. For example, what if you have two instances of EventListener, one to receive files from "inbox" and another to receive files from "otherbox"? As configured above, both instances of EventListener will receive all events from MuleEventMulticaster regardless of where they came from. This means that both EventListener instances would receive files from both directories.

To remedy that situation, you'll need to implement MuleSubscriptionEventListener instead of MuleEventListener:

package com.habuma.sucker.mule;

import org.mule.extras.spring.events.MuleSubscriptionEventListener;
import org.springframework.context.ApplicationEvent;

public class SubscriptionListener implements MuleSubscriptionEventListener {
 
  public void onApplicationEvent(ApplicationEvent event) {
      System.out.println("GOT AN EVENT ("+this.hashCode()+") : ");
      System.out.println(event.getSource());
  }
  
  private String[] subs;
  
  public void setSubscriptions(String[] arg0) {
    this.subs = arg0;
    
  }

  public String[] getSubscriptions() {
    return subs;
  }
}

MuleSubscriptionEventListener requires that you implement a getSubscriptions() method in addition to onApplicationEvent(). getSubscriptions() should return an array of String that contains all of the endpoints that the listener is interested in. Although it's not required by MuleSubscriptionEventListener, I've also added a setSubscriptions() method so that I can use Spring's IoC to wire in my endpoint definitions.

One more thing you'll need to do is reconfigure your application context such that each SubscriptionListener specifies their own list of endpoints:

<beans>
  <bean id="applicationEventMulticaster"
      class="org.mule.extras.spring.events.MuleEventMulticaster"/>

  <bean id="myListener"
      class="com.habuma.sucker.mule.SubscriptionListener">
    <property name="subscriptions">
      <list>
        <value>file:///inbox</value>
      </list> 
    </property>
  </bean>

  <bean id="myOtherListener"
      class="com.habuma.sucker.mule.SubscriptionListener">
    <property name="subscriptions">
      <list>
        <value>file:///otherbox</value>
      </list> 
    </property>
  </bean>
</beans>

Here we have two instances of SubscriptionListener, each with their own selection of endpoint subscriptions. Also, notice that MuleEventMulticaster no longer keeps its own list of subscriptions.

Now if you fire up the application, you'll be able to copy files into either "inbox" or "otherbox"...each instance of SubscriptionListener receiving its own set of files.

So what?

As I've said, a real world application would likely do something more interesting than simply print out the contents of the files that are sucked in. It may simply write it to a database or process it and then fire the results out as another event in the ESB. As an exercise for the reader, try to imagine the possibilities of how MuleEventMulticaster can be used in your real world applications.

Before you get too carried away, you should know that there's really more here than meets the eye. Although I've been talking about creating a file sucker, the only thing that makes this a file sucker is that the listeners are subscribed to a "file://" endpoint. The event listener classes themselves have no idea that their content is coming from a file. What if instead of reacting to files being written to a directory, you'd like to respond to an e-mail, a JMS message, or perhaps a message sent via TCP?

No problem...just swap out the "file://" endpoint with any one of Mule's other endpoints. For example, to listen on TCP, port 9999:

  <bean id="myListener"
      class="com.habuma.sucker.mule.SubscriptionListener">
    <property name="subscriptions">
      <list>
        <value>tcp://localhost:9999</value>
      </list> 
    </property>
  </bean>

Or maybe you want to receive messages from a JMS topic or queue...

  <bean id="myListener"
      class="com.habuma.sucker.mule.SubscriptionListener">
    <property name="subscriptions">
      <list>
        <value>jms://my.queue</value>
      </list> 
    </property>
  </bean>

  <!--
     To use JMS you need some additional setup
  -->
  <bean id="jmsConnector" class="org.mule.providers.jms.JmsConnector">
    <property name="specification">
        <value>1.1</value>
    </property>
    <property name="connectionFactory" ref="connectionFactory" />
  </bean>

  <bean id="connectionFactory"
      class="org.activemq.ActiveMQConnectionFactory">
    <property name="brokerURL" value="tcp://localhost:61616" />
  </bean>    

But why choose? Notice that the "subscriptions" property is an array. If you want, you can use listen on several endpoints at the same time:

  <bean id="myListener"
      class="com.habuma.sucker.mule.SubscriptionListener">
    <property name="subscriptions">
      <list>
        <value>file:///inbox</value>
        <value>tcp://localhost:9999</value>
        <value>jms://my.queue</value>
      </list> 
    </property>
  </bean>

Files, TCP, and JMS are just the beginning. Mule has several providers for a variety of different endpoints, including:

  • AS400 DataQueue
  • E-mail
  • File
  • FTP
  • HTTP
  • JDBC
  • JMS
  • Quartz (for scheduled events)
  • RMI
  • Servlets
  • SOAP (via Axis or Glue)
  • SSL
  • TCP
  • UDP
  • VM (in-process or persistent queues)
  • XMPP (Jabber)

Use your imagination...Instead of a file sucker, you can use this same technique to have a bean listen for e-mails to arrive at a certain POP3 address. Or maybe you'd like a bean that processes XML messages that arrive via HTTP.

This article has been very Mule-specific. ServiceMix is another up-and-coming ESB in CodeHaus that I've been tinkering with. In some future installment, I'll show you how to do have some similar fun using Spring and ServiceMix. Stay tuned.

There's much more to ESBs and Mule than simply sucking in files and multicasting them to interested listeners. But that's another discussion for another day. In the meantime, if you want to learn more about ESBs, take a look at both Mule and ServiceMix and start tinkering. I also recommend that you check out David Chappell's Enterprise Service Bus book. This book cleared up a lot of ESB concepts for me.

Another book I've enjoyed on application integration in general is Enterprise Integration Patterns by Gregor Hohpe and Bobby Woolf. Mule's architecture is based heavily on the patterns described in this book.

I'll be talking more about using Spring with Mule and ServiceMix next weekend at the LoneStar Software Symposium in Austin (and again later this year in Dallas) and at The Spring Experience in December. Hope to see you there!

Craig Walls

About Craig Walls

Craig Walls is a Principal Engineer, Java Champion, Alexa Champion, and the author of Spring AI in Action, Spring in Action, and Build Talking Apps. He's a zealous promoter of the Spring Framework, speaking frequently at local user groups and conferences and writing about Spring. When he's not slinging code, Craig is planning his next trip to Disney World or Disneyland and spending as much time as he can with his wife, two daughters, 1 bird and 2 dogs.

Why Attend the NFJS Tour?

  • » Cutting-Edge Technologies
  • » Agile Practices
  • » Peer Exchange

Current Topics:

  • Languages on the JVM: Scala, Groovy, Clojure
  • Enterprise Java
  • Core Java, Java 8
  • Agility
  • Testing: Geb, Spock, Easyb
  • REST
  • NoSQL: MongoDB, Cassandra
  • Hadoop
  • Spring 4
  • Cloud
  • Automation Tools: Gradle, Git, Jenkins, Sonar
  • HTML5, CSS3, AngularJS, jQuery, Usability
  • Mobile Apps - iPhone and Android
  • More...
Learn More »