Transformers

on waitingforcode.com

Transformers

In one from first articles about Spring Integration we discovered the idea of conversion service. However, it's not only one element which can change message from one format to another one. The second element is message transformer, in integration world known also as message translator.

At the beginning we'll focus on definition of message translator in entreprise integration patterns world. After that we'll see how it's implemented in Spring Integration (SI) project. The last part'll be dedicated to test case illustrating message transformer in SI.

Message transformer in entreprise integration patterns

Entreprise integration is a world of network where different applications exchange data. Sometimes exchanged data can semantically represent the same think (for example: a customer), but technically it can be represented differently in two of systems making the exchange. For example, the first system can consider that a customer is somebody identified by e-mail address while the second one can ignore e-mail address and identify customer by auto-generated customer's reference. In this case the second application won't be able to work on such received Customer object. To make these to systems work together, we could use an entreprise design pattern called message transformed (known also as message translator, we'll use the first name).

Message transformer is based on the same principles as adaptor design pattern. It tries to adapt one element to be understood by all other classes working with it. Transformer makes the same thing by taking incoming message and transforming it into message understood by output channel.

Message transformer in Spring Integration

Message transformers in Spring Integration are represented by the implementations of org.springframework.integration.transformer.Transformer interface. This interface contains only one method, Message<?> transform(Message<?> message);, responsible for generating new message from already existent one.

Base class for the majority of Spring's transformers is AbstractTransformer. It defines an additionnal protected abstract Object doTransform(Message<?> message) throws Exception, needed to be implemented by subclasses to achieve message's transformation. This method can return both Message as new message's payload instance, as you can observe in following snippet of AbstractTransformer's transform method:

@Override
public final Message<?> transform(Message<?> message) {
    try {
      Object result = this.doTransform(message);
      if (result == null) {
        return null;
      }
      return (result instanceof Message) ? (Message<?>) result
        : this.getMessageBuilderFactory().withPayload(result).copyHeaders(message.getHeaders()).build();
    }
    catch (MessageTransformationException e) {
      throw e;
    }
    catch (Exception e) {
      throw new MessageTransformationException(message, "failed to transform message", e);
    }
}

Exactly as in the case of another components, as routers, Spring Integration allows to define customized transformers or to use already existent. If we want to define our own transformer, we need to configure bean and provide method attribute for <transformer /> configuration tag. We can also define this method with @Transformer annotation. In the other hand, for already existent transformers, we must only specify corresponding configuration tag. Spring provides transformers for exchaning: JSON message (JsonToObjectTransformer and ObjectToJsonTransformer), map transformers (ObjectToMapTransformer, MapToObjectTransformer), String (ObjectToStringTransformer) or even for mails (MailToString). All of them extend directly AbstractTransformer and implement doTransform in appropriated way.

Note also that if you're looking at org.springframework.intergration.transformer package, you'll see there some of elements described previously: content and header enrichers. They're also considered as message transformers because they generate new message with supplementary stuff (headers, content) directly from incoming message.

Example of message transformer in Spring Integration

In our test case we'll focus on transforming message containing Order payload to message containing ShoppingCart payload with customized transformed. Two another examples will show how to implement Object-to-String and Object-to-JSON transformation. This is configuration of application's context:

<context:annotation-config />
<context:component-scan base-package="com.waitingforcode"/>

<int:channel id="sender" />
<int:channel id="senderString" />
<int:channel id="senderJson" />
<int:channel id="receiver">
  <int:queue />
</int:channel>
<int:channel id="receiverString">
  <int:queue />
</int:channel>
<int:channel id="receiverJson">
  <int:queue />
</int:channel>

<!-- custom transformer which translates com.waitingforcode.model.Order instance to
com.waitingforcode.model.ShoppingCart instance -->
<int:transformer id="ordToShopCartTrans" ref="shoppingCartTransformer" method="fromOrder" input-channel="sender"
  output-channel="receiver" />

<!-- sample configuration for ObjectToStringTransformer -->
<int:object-to-string-transformer input-channel="senderString" output-channel="receiverString"/>

<!-- configuration for ObjectToJsonTransformer. Note that before use it, you need to add one marshaller library in your
dependency file. An example of marshaller can look like that in Maven's pom.xml :
    <dependency>
        <groupId>org.codehaus.jackson</groupId>
        <artifactId>jackson-mapper-asl</artifactId>
        <version>1.9.13</version>
    </dependency>
-->
<int:object-to-json-transformer input-channel="senderJson" output-channel="receiverJson"/>

Nothing complicated. We can now take a look on ShoppingCartTransformed, defined in the first <transformer /> element:

/**
 * Sample message transformer's implementation that constructs the payload for new message directly from incoming message's
 * payload.
 *
 * @author Bartosz Konieczny
 */
@Component
public class ShoppingCartTransformer {

  public ShoppingCart fromOrder(Order order) {
    ShoppingCart shoppingCart = new ShoppingCart();
    shoppingCart.setCreationDate(new Date());
    shoppingCart.setOrder(order);
    return shoppingCart;
  }

}

Test cases illustrating transformers way of work look like:

/**
 * Test cases for message transformers.
 *
 * @author Bartosz Konieczny
 */
@ContextConfiguration(locations = "classpath:META-INF/transformer-sample.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class TransformerTest {

  @Autowired
  @Qualifier("sender")
  private DirectChannel sender;

  @Autowired
  @Qualifier("senderString")
  private DirectChannel senderString;

  @Autowired
  @Qualifier("senderJson")
  private DirectChannel senderJson;

  @Autowired
  @Qualifier("receiver")
  private QueueChannel receiver;

  @Autowired
  @Qualifier("receiverString")
  private QueueChannel receiverString;

  @Autowired
  @Qualifier("receiverJson")
  private QueueChannel receiverJson;

  @Test
  public void testOrderToShoppingCartTransform() {
    Message<Order> msg = constructSampleOrderMessage();
    Order order = msg.getPayload();

    sender.send(msg, 2000);

    Message<?> receivedMsg = receiver.receive(2000);
    ShoppingCart shoppingCart = (ShoppingCart) receivedMsg.getPayload();
    assertEquals("ShoppingCart should be transformed from Order instance created here", order.getId(),
      shoppingCart.getOrder().getId());
  }

  @Test
  public void testStringTransformer() {
    Message<Order> msg = constructSampleOrderMessage();
    senderString.send(msg, 2000);
    Message<?> receivedMsg = receiverString.receive(2000);
    assertEquals("Object to String transform failed", "Order {products: [Product {name: milk, priority level: 0, " +
      "price: 0.0}], final price: null}", receivedMsg.getPayload());
  }

  @Test
  public void testJsonTransformer() {
    Message<Order> msg = constructSampleOrderMessage();
    senderJson.send(msg, 2000);
    Message<?> receivedMsg = receiverJson.receive(2000);
    assertEquals("Object to JSON transform falied", "{\"id\":300,\"products\":[{\"name\":\"milk\",\"priorityLevel\":0," +
      "\"price\":0.0}],\"finalPrice\":0.0}", receivedMsg.getPayload());
  }

  private Message<Order> constructSampleOrderMessage() {
    Order order = new Order();
    order.setId(300);
    Product milk = new Product();
    milk.setName("milk");
    order.addProduct(milk);
    Message<Order> msg = MessageBuilder.<Order>withPayload(order).build();
    return msg;
  }

}

We learned here what to do when messages are interpreted a little bit differently for incoming and outcoming message channels. This situation can be regulated simply with message transformer adapting incoming message to format expected by output channel. Spring Integration provides default abstract class, AbstractTransformer, implemented by all Spring default transformers. We saw also that it was possible to define custom transformer by providing transformation method through @Transformer annotation or method attribute in XML configuration file.

Share on: