John D'Emic's blog about programming, integration, system administration, etc...

Wednesday, September 29, 2010

Annotations in Mule 3

The official Mule 3 release was a couple of weeks ago, Ross covers a lot of the new features here. I've been using the release candidates for about two months in a few contexts. The MongoDB transport and SAAJ module have SNAPSHOT support for 3.x. David and I are working on upgrading the book examples over to Mule 3. I'm additionally using Mule 3 on a new project. We started on RC2 and are readying for an alpha release next week.

So, to sum it up, I've spent a lot of time recently with Mule 3. In doing so, I came to appreciate one of the nice "themes" of Mule 3.0: the reduction of configuration noise. Annotation support is a feature that particularly reinforces this. I refactored a couple of services that were using a Quartz inbound-endpoint to instead use the new @Schedule annotation. Here's what I did, hopefully it will illustrate how the XML configuration is cut down by some of the new annotations.

The service in question was responsible for executing a configurable command on the system and sending the output to an FTP server. An additional requirement is that the filename on the FTP server needs to have the timestamp of the command execution embedded in it.

Here's how I refactored the implementation to use Mule 3 annotations. First off, here's the service class:


/**
* Simple facility to run a system command and return the output as a String.
*/
public class CommandRunner {

String command;

@Schedule(interval = 5000)
public String runCommand(@OutboundHeaders Map<String, Object> outHeaders) throws Exception {

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream, System.out);

CommandLine commandLine = CommandLine.parse(command);
DefaultExecutor executor = new DefaultExecutor();
executor.setStreamHandler(streamHandler);
int exitValue = executor.execute(commandLine);

if (exitValue < 0) {
throw new RuntimeException("Error running: " + command);
}

outputStream.close();
outHeaders.put("COMMAND_TIME_STAMP",new Date().getTime());

return new String(outputStream.toByteArray());
}

public void setCommand(String command) {
this.command = command;
}

}



The @Schedule annotation invokes the "runCommand" method every 5 seconds. Note that there is a Map being passed as the argument to runCommand(). This is annotated with @OutboundHeaders, indicating the contents of this map will be available as message properties on the outbound endpoint. We'll use this to set the COMMAND_TIME_STAMP header to contain the timestamp the command was run. That's it from the Java side. Let's see how this is wired up in the XML config:


<model name="Integration Services">
<service name="CLI to FTP Service">
<component>
<spring-object bean="commandRunner"/>
</component>
<outbound>

<pass-through-router>
<ftp:outbound-endpoint user="${ftp.user}"
password="${ftp.password}"
host="${ftp.host}"
port="${ftp.port}"
path="${ftp.path}"
outputPattern="#[header:COMMAND_TIME_STAMP].txt">
</ftp:outbound-endpoint>
</pass-through-router>
</outbound>
</service>
</model>




Using the @Schedule annotation eliminates the need for explicitly configuring an inbound-endpoint, which is missing from the above. We also don't need to jump through hoops with the MuleMessage in either the service class or with message-properties-transformers to use the COMMAND_TIME_STAMP to name the FTP file.

As I mentioned in my diatribe about implementing component classes, I prefer to avoid implementing Callable on my components. I find doing so couples the code too tightly to the Mule runtime. Using the annotations allows you to implement functionality previously required by getting at the MuleContext without extending or implementing a Mule class. This is a best-of-both-worlds scenario. Your component code can still stay decoupled from Mule, to ease unit testing, while indirectly giving you access to common pieces of the MuleContext (ie, the message payload, inbound and outbound headers, etc.)

So the annotation support, for me, is a welcome addition to the available configuration options. In addition to reducing XML noise it also streamlines integrating POJO's with the message flow.

The improved jBPM support on Mule 3 is another feature I'm excited about. For one reason or another, jBPM has been a constant in practically every Mule project I've worked on in the last year. I'll cover that in the next post.



4 comments:

Unknown said...

Great post, thanks for sharing :)

You could have even less XML by using a flow construct, a configuration element newly introduced in Mule 3.

Anonymous said...
This comment has been removed by the author.
Anonymous said...

Trying again...
John thanks fos sharing. If you did use flow as David suggests you can almost half the amount of XML needed - http://pastebin.com/XQc3nh1D

johndemic said...

Nice, thanks for the tip guys. I haven't had a chance to try out the flow stuff yet - looks very promising...