Chapter 7. Extending Mule

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.

Building a Transformer

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.

Transforms: ESB or Service?

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.

Introducing AbstractTransformer

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.

Building a Filter

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.

Overview

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.

Complex to Simple Filter

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.

Creating a New Filter

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

Building a Router

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.

Overview

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.

MuleEvent Methods

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.

Inbound Routers

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.

Outbound Routers

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 a Router

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.

An Idempotent Router

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.

Summary

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/.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset