Building Mule elements is a fundamental task when putting Mule applications together. The first section in this chapter covers the abstract and base classes and interfaces to use to build your own Mule transformer. In the sections that follow we will examine how you can create a Mule filter and a Mule router.
A transformer in Mule is nothing more complex than a simple Java class that converts data from one type of object (or structure) to another. It is meant to facilitate the conversion from one sort of object (or structure) to another, and will work on in-flight data before the service component receives the Mule message or after the component has dispatched a Mule message, depending on the direction of the message (inbound or outbound respectively).
Some transformers are available out of the box; these are typically transport-specific transformers such as JMSMessageToObject
. On the other hand, transformations that work on the data in your application are highly specialized and aren't normally available inside Mule.
You can implement a new transformer by implementing the Mule Transformer interface inside your class, but there is a fair amount of code that you'd need to re-do, so using the AbstractTransformer
class would be the more practical route to take in most cases. This class implements a number of common methods and implements the Transformer interface so all you need to do is implement one single method to get your transformer up and running.
The Enterprise Service Bus (ESB) industry is awash with opinions about transformations, but two schools of thought are prominent:
Transformation occurs within the bus. This is what Mule does and is the recommended approach, as it allows you to code reusable transformers that are distinct from the business logic.
Transformation should be nothing more than a service in its own right. While this is overkill, you can achieve some performance improvement in Mule if you have a processor-intensive transformation. You can also let an external item plugged into the bus perform any transformations it needs to.
Mule is flexible enough to allow you to use either approach, depending on which makes the most sense to you and does not impose any design choice on you. We are going to focus on the first, traditional approach in this section.
AbstractTransformer
is the class to use when building your own transformer. You can use the Transformer interface of course, but you will need to implement some, if not all, of the methods in the interface, so the class is a better starting place in most cases. Let's run through an example before looking at the methods and properties of this class.
Taking an airline seat booking as an example, the process to successfully book a seat results in a confirmation of the booking distributed via e-mail to the customer.
In Mule we could configure an outbound router collection to use a multicasting router to send the message to a database (say along a JDBC endpoint) and to the customer (along an SMTP endpoint). For this latter route we would need a transformer that converts a bookedSeat
class into an e-mail.
The transformer will need to create the body of the e-mail using details from the message itself. All transformation will occur within the doTransform()
method if we inherit from the AbstractTransformer
class as follows:
protected Object doTransform(Object src, String encoding) throws TransformerException { Booking aBooking = (Booking) src; return "Dear "+aBooking.passengerName +"We would like to inform you that " +"your seat on Flight" +aBooking.FlightNumber+" leaving "v +aBooking.OriginAirport+" on " +aBooking.DepartureDate+" at " +aBooking.DepartureTime+" is confirmed." +" Please print out this e-mail and " +" present it to the check-in desk when" +" you arrive at the airport. Reference +" number: "+aBooking.PNR"; }
In this code sample we have the transformation logic for the BookingToConfirmation
transformer. The first thing to do is confirm that the object received really is what we're expecting, and then we can craft the full e-mail body by using the fields inside the Booking
class received.
Mule allows transformers to register which classes they can handle. This is done in the constructor—essentially a transformer will tell Mule on initialization which items it can transform. In our case, the transformer can handle Booking
classes, so our constructor looks like this:
public
BookingToConfirmation() { registerSourceType(Booking.class
); }
A transformer may be able to handle multiple source types, in which case, multiple items may be registered in the constructor.
Next we need to configure our transformer on the outbound endpoint.
<custom-transformer name="BookingToEmail" class="com.ricstonairways.ticketing.transformers .BookingToConfirmation"/> <service name="PaymentService"> <inbound> <vm:inbound-endpoint address="confirmedBookings"/> </inbound> <component class="com.ricstonairways.payment"/> <outbound> <outbound-pass-through-router> <smtp:outbound-endpoint [email protected] transformer-refs="BookingToEmail"/> </outbound-pass-through-router> </outbound> </service>
At the beginning of this example, a custom transformer is declared and refers to the BookingToConfirmation
class that we created previously. It is named BookingToEmail
, and this name is then used on endpoints.
The service shown reads messages off a VM queue and returns its results to an e-mail address. This e-mail will contain the details of the booking as the e-mail body.
To round out our description of the AbstractTransformer
class we will list the methods and properties it contains. Here are the methods:
checkReturnClass
is a protected method that confirms the object matches the value of the returnClass
property, if set.
doTransform
is an abstract method that must be implemented in classes that inherit from this class. It should contain the actual transformation logic.
generateTransformerName
is used to automatically generate a name if it is needed but is not set.
isAcceptNull
is a public method that returns false
. It can be overridden to let a transformer accept null values.
isSourceTypeSupported
checks the list of sourceTypes
to validate whether a specific class type is supposed to be supported by the transformer.
registerSourceTypes
lets you add a source type to the list of accepted source types. This can be reversed using the unregisterSourceType
method.
The class has the following properties:
endpoint
refers to the endpoint that this transformer is configured on.
ignoreBadInput
lets the transformer ignore any input that is not supported.
logger
refers to the Log4J class to use.
name
is a unique name that can be configured or automatically generated by the generateTransformerName
method.
returnClass
lets a transformer be configured to return a specific type.
sourceTypes
is a list that contains all the source types the transformer can handle.
After transformations, the second most common Mule element to be extended or coded is the filter. Filtering is a very common pattern, but the available filters may not be sufficient for application-specific needs. This section shows how a filter needs to be designed and built and then used within Mule.
Filtering is the ability to choose which messages to route, whether within an inbound or outbound message flow. The inbound router required for filtering is the SelectiveConsumer
router, while the FilteringOutboundRouter
is its outbound counterpart. A number of the other inbound and outbound routers in Mule inherit from these two respectively; also many routers in Mule implement commonly-used filtering patterns.
Each filter is expected to return a boolean value that indicates whether the message should be accepted or not. The router does not need to know any further specifics and will operate according to the boolean value that the filter returns. The filter needs to check a specific expression against the current message, which typically would be encoded as a property inside the configuration file. This expression can operate on a Mule message, that is, it can look at any value in the payload or the properties of the MuleMessage
.
The Filter interface has a single method, accept()
, that needs to be implemented. This method accepts a single, non-null MuleMessage
and should return a boolean value that indicates whether this message passes the filter or not. The accept
method will only be invoked by routers, and therefore it is guaranteed that the accept
method will always receive non-null messages.
One reason to implement a filter class is to condense multiple filters into one single class. An example of this is the ExceptionTypeFilter
, which is meant to filter for messages whose payload is an exception. This is the equivalent of having a PayloadTypeFilter
for exception messages.
Using an airline ticketing example, if a booking is being made by a frequent flyer member who has enough miles on this trip alone to qualify for a free upgrade, we would want to point that out to him. This means we need to filter for messages that have not been finalized yet (are unpaid and therefore still booking requests), which are for passengers who are frequent flyer members and who have enough miles on this trip to qualify for free upgrades.
public class AutoUpgradeFilter implements Filter private int threshold; public boolean accept (MuleMessage message) { if (message.getPayload() instanceOf (BookingRequest)) { BookingRequest aBooking = message.getPayload(); } return (aBooking.FrequentFlyer != null) || (aBooking.FlyerMiles >= threshold); }
In the preceding code, you can see a new filter class characterized by the fact that it implements the Filter interface. A private integer property called threshold
, which will be configured at design time, allows different thresholds to be set. (The getter and setter methods are not shown here.)
The accept
method extracts the payload of the message and returns true
only if the FrequentFlyer
field of the booking request is not null and if the FlyerMiles
field of the booking request is larger than or equal to the threshold specified. The filter can then be reused for situations where we need to filter for different FrequentFlyer
mile amounts.
Now let's look at the Mule configuration for our filter:
<service name="UpgradeService"> <inbound> <vm:inbound-endpoint address="Bookings"/> <selective-consumer-router> <custom-filter class= "com.ricstonairways.filters.AutoUpgradeFilter"> <spring:property name="threshold" value="1500"/> </spring:property> </custom-filter> </selective-consumer-router> </inbound> <component class="com.ricstonairways.Upgrade"/> </service>
In the preceding configuration, the inbound selective consumer router uses a custom filter that refers to our previous example, and which is set to have a threshold of 1500. All bookings read off the VM endpoint called Bookings
will therefore be passed to the upgrade service provided that the passenger is a frequent flyer member and also has chosen a ticket that generates enough miles for a free upgrade.
The properties for the class are set using the Spring namespace. The custom filter is defined in the Mule schema and does not have a threshold
attribute, so we cannot just add the item as an attribute in XML.
Another example of a filter would be one that does something that the existing filters do not do, such as filter messages that contain attachments. This filter will only make sense if you read messages from SOAP or one of the e-mail transports (SMTP or POP3). However, you should avoid checking the transport directly as perhaps some new or improved transport in the future will support attachments; your filter should only look for the existence of attachments.
The filter criteria could use the getAttachmentNames
method to see if an empty set is returned and return true
or false
based upon that. No additional parameters are required as there is nothing else that can be configured.
The filter class in this case is very simple; all it needs to do is match the result of the getAttachmentNames()
method with an empty set.
public class HasAttachmentsFilter implements Filter public boolean accept (MuleMessage message) { return message.getAttachmentNames() == null; }
All the routing patterns available in the Enterprise Integration Patterns[8] book are included inside Mule, and a wide range of message routers are there for you to use within your Mule applications. There are cases when you may need to tweak a pattern slightly so that it fits your architecture. This section shows how you can customize or combine routing patterns to achieve these aims.
Routers control how messages are sent or received by service components. Inbound routers control how messages are received from an endpoint and passed to a service component, while outbound routers control how messages dispatched by the service component are sent via one or more endpoints. The routers don't work with the messages directly; they operate on a MuleEvent
that contains the MuleMessage
together with other information (such as the endpoint) relevant to the context of the current message. Some additional methods are available to manipulate the current message.
Inbound routers can return an array of one or more Mule events to the service component. This would happen if a single message needs to be split into multiple ones or if the router is maintaining a list of messages (for instance if it is a resequencer router). It can also return a null message to show that the service has nothing to process. An aggregator router, for example, would return null when it receives individual messages, but will return a single message when it has the complete item.
Outbound routers can return the resulting message if the message flow is synchronous or will return null if the flow is asynchronous. The message may be processed in any number of ways depending on the actual routing logic encoded into the router.
The MuleEvent
represents any data event occurring within the current Mule environment. It also contains the following methods to let you further manipulate the event and inspect the current message:
getEndpoint
will return the endpoint associated with this event.
isSynchronous
will indicate whether the event flow is synchronous.
isStopFurtherProcessing
checks to see if this message flow is meant to be continued or if it is being disposed of. You can set this manually by using the setStopFurtherProcessing
method.
getMessage
will return the current message. This is a MuleMessage
object.
getMessageAsBytes
will return the message as a byte array.
getMessageAsString
will return the message as a string. It will use the default encoding to perform the conversion.
transformMessage
will return the message in its transformed state. This will use the transformers configured on the endpoint.
transformMessageToBytes
will first transform the message and then convert it into a byte array.
transformMessageToString
will first transform the message and then convert it into a string. It will use the default encoding to do this.
The Inbound Router interface defines these basic functions for an inbound router:
isMatch
determines whether the current event should be handled by this router, usually by using filters. This method returns a boolean value that indicates whether the message should be accepted.
process
returns a null value or an array of Mule events.
All core inbound routers inherit from the SelectiveConsumer
router, which implements the InboundRouter
interface and adds the following properties:
filter
is a private property that contains the filter condition, if any. The filter can be set and retrieved using the getter and setter commands.
transformFirst
is another private property that defaults to true
and indicates if the router should operate on the transformed message or not.
There are two further abstract inbound router classes that derive from the SelectiveConsumer
router:
AbstractEventAggregator
knows how to aggregate a series of messages into a single message.
AbstractEventResequencer
knows how to receive a series of messages, resequence them, and forward them on.
The Outbound Router interface defines these basic functions for an outbound router:
setEndpoints
allows a number of endpoints to be set for this router. There are similar helper methods called getEndpoints (), addEndpoints()
, and removeEndpoints()
.
setReplyTo
is an endpoint that will receive all responses. Other Mule routers will then use this property to send replies back.
setTransactionConfig
sets the configured transaction values.
isDynamicEndpoints
indicates whether this router expects configured endpoints or if it will build them up dynamically from the message payload or properties.
isMatch
determines whether the current event should be handled by this router, usually by using filters. This method returns a boolean value that indicates whether the message should be accepted.
route
is the method that routes the message. It will return null if the message flow is asynchronous and will return the MuleMessage
if not.
All core outbound routers inherit from the FilteringOutboundRouter
, which implements the OutboundRouter
interface and adds the filter
property—a property that contains the filter condition, if any. The filter can be set and retrieved using the getter and setter commands.
There are two additional abstract outbound router classes that derive from the FilteringOutboundRouter
class:
AbstractMessageSplitter
knows how to split a message into multiple parts.
AbstractRecipientList
knows how to dispatch a single message to multiple recipients over the same transport.
Extending an existing router means that we need to extend the routing mechanism. Note that this is different from extending the selection mechanism, since we can merely write a new filter for that. In this case, we need to extend or tweak one of the routing patterns to match our architecture.
Taking the Idempotent pattern as an example, the default mechanism uses the unique ID that a transport encodes within a MuleMessage
to determine if the message has been processed or not. In our airline case, we want to use this pattern to make sure that a passenger does not try to make multiple bookings for himself. We could use a combination of the passenger name and the credit card number to enforce this pattern.
We can start off by extending the IdempotentReceiver
class that rejects messages if they have been processed. The ancestor class takes care of the necessary behavior for us—including maintaining and persisting a list of group IDs—but we need to override the mechanism to determine the ID for a given message. This is achieved using the getIdForEvent()
method, which needs to be overridden to retrieve the PassengerDetails
class from our MuleMessage
and then create a new ID based on the passenger name and credit card number.
The getIdForEvent()
method needs to return the group identifier for the current message as shown here:
{ MuleMessage obj = event.getMessage(); if (obj.getPayload() == null) { return null; } if (obj.getPayload() instanceof PassengerFlightQuery) { PassengerFlightQuery details = (PassengerFlightQuery) obj.getPayload(); return details.getPassengerName() +";" +details.getCreditCardNumber(); } else { return super.getIdForEvent (event); } }
The method receives the MuleEvent
that contains information about the entire message flow including the MuleMessage
. If there is no payload, then we can return null as the ID and let the ancestor's rules for handling null values take over. If not null, we need to ensure that the payload really is a PassengerFlightQuery
message. If it is, we can typecast the payload and extract the relevant information from it to return a valid ID. If it is not, then we can delegate responsibility to the ancestor for generating an ID.
Finally, here is the configuration for our router:
<inbound> <vm:inbound-endpoint path="FlightQuery"/> <custom-inbound-router class="com.ricstonairways. routers.SingleBookingPerPassengerRouter"> <!- Custom filter? --> </custom-inbound-router> </inbound>
For simplicity's sake, we have only displayed the inbound router collection here and not the details for the full service. As you can see, all details are neatly contained within code and nothing is exposed through configuration.
Don't forget that since all inbound routers inherit from the SelectiveConsumer
router, we can also add a filter to the configuration should we want to.
Transformations may be application specific, and these must usually be created as they are not available inside Mule. While you can choose to use transformation on the bus or include transformation as a service in its own right, Mule does not impose a restriction on you and you can use either one.
The Transformer interface is what you should implement if you want to create a raw transformer, but it makes sense to use the AbstractTransformer
class in the first place.
Filtering, on the other hand, offers the ability in Mule to choose whether to send a message to a service component or not (or to an endpoint or not). The Filter interface needs to be implemented; it allows for a single method that will return true
if the message matches the filter, or false
if the message does not. This operation will normally work on the complete MuleMessage
, that is, you can filter on any MuleMessage
property or on any property in the payload.
You will need to build your own filters either to combine multiple filters into one simple class or to provide filtering support if none already exists. If filtering is not the sort of routing pattern you want to extend or enhance, the next section will explain how you can create new routers of your own.
In the last section, we saw how a routing pattern can be extended and how this is different from extending filtering. Routers operate by manipulating MuleEvent
objects, which contain the MuleMessage
and a set of methods to manipulate the MuleMessage
.
All core inbound and outbound routers inherit from the Filtering routing pattern—the SelectiveConsumer
or the FilteringOutboundRouter
. In either case, an in-depth knowledge of the routing patterns and how they're implemented inside Mule lets you pick the right one for your situation.
[8] "Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions" by Gregory Hohpe, Bobby Woolf http://www.enterpriseintegrationpatterns.com/
.