Chapter 6. Producing Output Data

6.1. The Smooks Javabean Cartridge

You can use the Smooks Javabean Cartridge to create and populate Java objects from your message data. It can be used purely as a Java binding framework for XML, EDI, CSV and so forth. However, Smooks' Java binding capabilities are also the cornerstone of many other capabilities. This is because Smooks makes the Java objects it creates (and to which it binds data) available through the BeanContext class. This class is essentially a Java bean context that is made available to any Smooks visitor implementation via the Smooks ExecutionContext.

6.2. Javabean Cartridge Features

  • Templating: This usually involves applying a template to the objects in the BeanContext.
  • Validation: Business rules validation normally involves applying a rule to the objects in the BeanContext.
  • Message splitting and routing: This works by generating split messages from the objects in the BeanContext, either by using the objects themselves and routing them, or by applying a template to them and routing the result of that operation to a new file.
  • Persistence: These features depend on the Java binding functions for creating and populating the Java objects (such as entities) that are to be committed to the database. Data read from a database will normally be bound to the BeanContext.
  • Message enrichment: Enrichment data (read, for example from a database) is normally bound to the BeanContext, from where it is available to all of Smooks' other features, including the Java binding functionality itself (making it available for expression-based bindings.) This allows you to enrich messages generated by Smooks.

6.3. Javabean Cartridge Example

The following example is based on this XML:
<order>
    <header>
        <date>Wed Nov 15 13:45:28 EST 2006</date>
        <customer number="123123">Joe</customer>
    </header>
    <order-items>
        <order-item>
            <product>111</product>
            <quantity>2</quantity>
            <price>8.90</price>
        </order-item>
        <order-item>
            <product>222</product>
            <quantity>7</quantity>
            <price>5.20</price>
        </order-item>
    </order-items>
</order>
The Javabean Cartridge is used via the http://www.milyn.org/xsd/smooks/javabean-1.4.xsd configuration namespace. (Install the schema in your IDE to avail yourself of the latter's auto-complete functionality.)
Here is an example configuration:
<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd" xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.4.xsd">
 
    <jb:bean BeanId="order" class="example.model.Order" createOnElement="#document" />
 
</smooks-resource-list>
This configuration creates an instance of the example.model.Order class and binds it to the bean context under the BeanId called order. The instance is created at the very start of the message on the #document element (in other words, at the start of the root order element).
The configuration shown above creates the example.model.Order bean instance and binds it to the bean context.

6.4. Javabean Elements

  • beanId: this is the bean's identifier.
  • class: this is the bean's fully-qualified class name.
  • createOnElement: use this attribute to control when the bean instance is to be created. You can control the population of the bean properties through the binding configurations (which are child elements of the jb:bean element).
  • createOnElementNS: you can specify the namespace of the createOnElement via this attribute.

6.5. Javabean Conditions

The Javabean Cartridge sets the following conditions to Java beans:
  • There is a public no-argument constructor.
  • There are public property setter methods. These do not need to follow any specific name formats, but it would be better if they do follow those for the standard property setter method names.
  • You cannot set Java bean properties directly.

6.6. Javabean Cartridge Data Bindings

These are the three types of data bindings the Javabean Cartridge allows for:
  • jb:value: use this to bind data values from the source message event stream to the target bean.
  • jb:wiring: use this to "plug" another bean instance from the bean context into a bean property on the target bean. You can use this configuration to construct an object graph (as opposed to a loose collection of Java object instances). You can plug beans in based on their beanId, their Java class type or their annotation.
  • jb:expression: use this configuration to bind a value calculated from an expression (in the MVEL language). A simple example is the ability to bind an order item total value to an OrderItem bean (based on the result of an expression that calculates price * quantity). Use the execOnElement attribute expression to define the element on which the expression is to be evaluated and to which the result will be bound. (If you do not define it, the expression is executed based on the value of the parent jb:bean createOnElement.) The value of the targeted element is available in the expression as a string variable under the name _VALUE (note the underscore).

6.7. Binding Data

  1. Using the Order XML message, look at the full XML-to-Java binding configuration. Here are the Java objects that you must populate from that XML message (the "getters" and "setters" are not shown):
    public class Order {
        private Header header;
        private List<OrderItem> orderItems;
    }
     
    public class Header {
        private Date date;
        private Long customerNumber;
        private String customerName;
        private double total;
    }
     
    public class OrderItem {
        private long productId;
        private Integer quantity;
        private double price;
    }
    
  2. Use this configuration to bind the data from the order XML to the object model:
    <?xml version="1.0"?>
    <smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd" xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.4.xsd">
     
    (1)   <jb:bean beanId="order" class="com.acme.Order" createOnElement="order">
    (1.a)     <jb:wiring property="header" beanIdRef="header" />
    (1.b)     <jb:wiring property="orderItems" beanIdRef="orderItems" />
          </jb:bean>
     
    (2)   <jb:bean beanId="header" class="com.acme.Header" createOnElement="order">
    (2.a)     <jb:value property="date" decoder="Date" data="header/date">
                  <jb:decodeParam name="format">EEE MMM dd HH:mm:ss z yyyy</jb:decodeParam>
              </jb:value>
    (2.b)     <jb:value property="customerNumber" data="header/customer/@number" />
    (2.c)     <jb:value property="customerName" data="header/customer" />
    (2.d)     <jb:expression property="total" execOnElement="order-item" >
                  += (orderItem.price * orderItem.quantity);
              </jb:expression>
          </jb:bean>
     
    (3)   <jb:bean beanId="orderItems" class="java.util.ArrayList" createOnElement="order">
    (3.a)     <jb:wiring beanType="com.acme.OrderItem" /> <!-- Could also wire using beanIdRef="orderItem" -->
          </jb:bean>
     
    (4)   <jb:bean beanId="orderItem" class="com.acme.OrderItem" createOnElement="order-item">
    (4.a)     <jb:value property="productId" data="order-item/product" />
    (4.b)     <jb:value property="quantity" data="order-item/quantity" />
    (4.c)     <jb:value property="price" data="order-item/price" />
          </jb:bean>
     
    </smooks-resource-list>
    

6.8. Binding Data Configurations

Configuration (1) defines the creation rules for the com.acme.Order bean instance (the top level bean). See the following configurations for details:
  • You should create each of the beans instances ((1), (2), (3) but not (4)) at the very start of the message (on the order element). Do this because there will only ever be a single instance of these beans in the populated model.
  • Configurations (1.a) and (1.b) define the wiring configuration for wiring the Header and ListOrderItem bean instances ((2) and (3)) into the order bean instance (see the beanIdRef attribute values and how the reference the beanId values defined on (2) and (3)). The property attributes on (1.a) and (1.b) define the Order bean properties on which the wirings are to be made.
    Note also that beans can also be wired into an object based on their Java class type (beanType), or by being annotated with a specific Annotation (beanAnnotation).
    Configuration (2) creates the com.acme.Header bean instance.
  • Configuration (2.a) defines a value binding onto the Header.date property. Note that the data attribute defines where the binding value is selected from the source message; in this case it is coming from the header/date element. Also note how it defines a decodeParam sub-element. This configures the DateDecoder.
  • Configuration (2.b) defines a value binding configuration onto the Header.customerNumber property. Note how to configure the data attribute to select a binding value from an element attribute on the source message.
    Configuration (2.b) also defines an expression binding where the order total is calculated and set on the Header.total property. The execOnElement attribute tells Smooks that this expression needs to be evaluated (and bound/rebound) on the order-item element. So, if there are multiple order-item elements in the source message, this expression will be executed for each order-item and the new total value rebound into the Header.total property. Note how the expression adds the current orderItem total to the current order total (Header.total).
  • Configuration (2.d) defines an expression binding, where a running total is calculated by adding the total for each order item (quantity * price) to the current total. Configuration (3) creates the ListOrderItem bean instance for holding the OrderItem instances.
  • Configuration (3.a) wires all beans of type com.acme.OrderItem ( i.e. (4)) into the list. Note how this wiring does not define a property attribute. This is because it wires into a Collection (same applies if wiring into an array). You can also perform this wiring using the beanIdRef attribute instead of the beanType attribute.
  • Configuration (4) creates the OrderItem bean instances. Note how the createOnElement is set to the order-item element. This allows for a new instance of this bean to be created for every order-item element (and wired into the ListOrderItem (3.a)).
If the createOnElement attribute for this configuration was not set to the order-item element (if, for example, it was set to one of the order, header or order-items elements), then only a single OrderItem bean instance would be created and the binding configurations ((4.a) etc) would overwrite the bean instance property bindings for every order-item element in the source message, that is, you would be left with a ListOrderItem with just a single OrderItem instance containing the order-item data from the last order-item encountered in the source message.

6.9. Binding Tips

Here are some binding tips:
  • set jb:bean createOnElement to the root element (or #document) for bean instances where only a single instance will exist in the model.
    Set it to the recurring element for collection bean instances.

    Warning

    If you do not specify the correct element in this case, you could lose data.
  • jb:value decoder: in most cases, Smooks will automatically detect the data-type decoder to be used for a jb:value binding. However, some decoders require configuration (one example being that the DateDecoder [decoder="Date"]). In these cases, you must define the decoder attribute (and the jb:decodeParam child elements for specifying the decode parameters for that decoder) on the binding.
  • jb:wiring property is not required when binding to collections.
  • To set the required collection type, define the jb:bean class and wire in the collection entries. For arrays, just postfix the jb:bean class attribute value with square brackets (for example, class=&"com.acme.OrderItem[]").

6.10. DataDecoder/DataEncoder Implementations

The a DataEncoder implements methods for encoding and formatting an object value to a string. These DataDecoder/DataEncoder implementations are available:
  • Date: decodes/encodes a string to a java.util.Date instance.
  • Calendar: decodes/encodes a string to a java.util.Calendar instance.
  • SqlDate: decodes/encodes a string to a java.sql.Date instance.
  • SqlTime: decodes/encodes a string to a java.sql.Time instance.
  • SqlTimestamp: decodes/encodes a string to a java.sql.Timestamp instance.
You configure all of these implementations in the same way.

6.11. DataDecoder/DataEncoder Date Example

Here is a date example:
<jb:value property="date" decoder="Date" data="order/@date">
    <jb:decodeParam name="format">EEE MMM dd HH:mm:ss z yyyy</jb:decodeParam>
    <jb:decodeParam name="locale">sv_SE</jb:decodeParam>
</jb:value>

6.12. DataDecoder/DataEncoder SqlTimestamp Example

Here is an SqlTimestamp example:
<jb:value property="date" decoder="SqlTimestamp" data="order/@date">
    <jb:decodeParam name="format">EEE MMM dd HH:mm:ss z yyyy</jb:decodeParam>
    <jb:decodeParam name="locale">sv</jb:decodeParam>
</jb:value>

6.13. DataDecoder/DataEncoder decodeParam Example

The decodeParam format is based on the ISO 8601 standard for date formatting. The locale decodeParam value is an underscore-separated string, with the first token being the ISO language code for the locale and the second token being the ISO country code. This decodeParam can also be specified as two separate parameters for language and country:
<jb:value property="date" decoder="Date" data="order/@date">
    <jb:decodeParam name="format">EEE MMM dd HH:mm:ss z yyyy</jb:decodeParam>
    <jb:decodeParam name="locale-language">sv</jb:decodeParam>
    <jb:decodeParam name="locale-country">SE</jb:decodeParam>
</jb:value>

6.14. Number-Based DataDecoder/DataEncoder Implementations

Several number-based DataDecoder/DataEncoder implementations are available:
  • BigDecimalDecoder: use this to decode/encode a string to a java.math. BigDecimal instance.
  • BigIntegerDecoder: use this to decode/encode a string to a java.math. BigInteger instance.
  • DoubleDecoder: use this to decode/encode a string to a java.lang.Double instance (including primitive).
  • FloatDecoder: use this to decode/encode a string to a java.lang.Float instance (including primitive).
  • IntegerDecoder: use this to decode/encode a string to a java.lang.Integer instance (including primitive).
  • LongDecoder: use this to decode/encode a string to a java.lang.Long instance (including primitive).
  • ShortDecoder: use this to decode/encode a string to a java.lang.Short instance (including primitive).
You configure all of these implementations in the same way.

6.15. Number-Based DataDecoder/DataEncoder Example

Here is a BigDecimal example:
<jb:value property="price" decoder="BigDecimal" data="orderItem/price">
    <jb:decodeParam name="format">#,###.##</jb:decodeParam>
    <jb:decodeParam name="locale">en_IE</jb:decodeParam>
</jb:value>

6.16. Number-Based DataDecoder/DataEncoder Integer Example

<jb:value property="percentage" decoder="Integer" data="vote/percentage">
    <jb:decodeParam name="format">#%</jb:decodeParam>
</jb:value>

6.17. Number-Based DataDecoder/DataEncoder decodeParam Example

The format decodeParam is based on the NumberFormat pattern's syntax. The locale decodeParam value is an underscore-separated string, with the first token being the ISO Language Code for the locale and the second token being the ISO country code. You can also specify this decodeParam as two separate parameters for language and country. See the example:
  
<jb:value property="price" decoder="Double" data="orderItem/price">
    <jb:decodeParam name="format">#,###.##</jb:decodeParam>
    <jb:decodeParam name="locale-language">sv</jb:decodeParam>
    <jb:decodeParam name="locale-country">SE</jb:decodeParam>
</jb:value>

6.18. Using the Mapping Decoder to Bind

  1. Configure the Mapping Decoder as shown below to bind a different value to your object model, based on the data in your input message:
    <jb:value property="name" decoder="Mapping" data="history/@warehouse">
        <jb:decodeParam name="1">Dublin</jb:decodeParam>
        <jb:decodeParam name="2">Belfast</jb:decodeParam>
        <jb:decodeParam name="3">Cork</jb:decodeParam>
    </jb:value>
    
  2. An input data value of "1" is mapped to the name property as a value of "Dublin". Likewise for values "2" and "3".

6.19. The Enum Decoder

The Enum Decoder is a specialized version of the Mapping Decoder. Normally, enumerations are decoded automatically (without any specific configuration needed) if the data input values map exactly to the enum values/names. However when this is not the case, you need to define mappings from the input data value to the enum value/name.

6.20. Enum Decoder Example

  1. In the following example, the header/priority field in the input message contains values of LOW, MEDIUM and HIGH. You should map these to the LineOrderPriority enum values of NOT_IMPORTANT, IMPORTANTand VERY_IMPORTANT respectively:
    <jb:value property="priority" data="header/priority" decoder="Enum">
        <jb:decodeParam name="enumType">example.trgmodel.LineOrderPriority</jb:decodeParam>
        <jb:decodeParam name="LOW">NOT_IMPORTANT</jb:decodeParam>
        <jb:decodeParam name="MEDIUM">IMPORTANT</jb:decodeParam>
        <jb:decodeParam name="HIGH">VERY_IMPORTANT</jb:decodeParam>
    </jb:value>
    
  2. If mappings are required, specify the enumeration type using the enumType decodeParam.

6.21. BeanContext Configuration

By default, every bean in the Smooks configuration except the first one is removed from the BeanContext after the fragment that created the bean (createOnElement) is processed. (In other words, the bean is added to the BeanContext on the start/visitBefore of the createOnElement fragment, and is removed from the BeanContext at the end/visitAfter.)
By default, this rule applies to all but the first bean configured in the Smooks configuration. (The first bean is the only bean that is retained in the BeanContext, and so can be accessed after the message has been processed.)
To change this behaviour, use the jb:bean element's retain configuration attribute. This attribute allows you to manually control bean retention within the Smooks BeanContext.

6.22. Javabean Cartridge Actions

The Java Bean cartridge:
  • extracts string values from the source/input message stream.
  • decodes the string value based on the decoder and decodeParam configurations (if these are not defined, an attempt is made to reflectively resolve the decoder).
  • sets the decoded value on the target bean.

6.23. Pre-processing String Data

Before decoding, you may need to pre-process the string data value. An example of this is when the source data has some characters not supported by the locale configuration on Numeric Decoding, such as the numeric value 876592.00 being represented as "876_592!00"jb:bean element. To decode this value as (for instance) a double value, delete the underscore and replace the exclamation mark with a full-stop. You can specify a valuePreprocess decodeParam, which is a simple expression that you can applied to the String value before decoding it.

6.24. Pre-processing Example

This example provides a solution to a numeric decoding issue:
<!-- A bean property binding example: -->
<jb:bean beanId="orderItem" class="org.milyn.javabean.OrderItem" createOnElement="price">
    <jb:value property="price" data="price" decoder="Double">
        <jb:decodeParam name="valuePreprocess">value.replace("_", "").replace("!", ".")</jb:decodeParam>
    </jb:value>
</jb:bean>
<!-- A direct value binding example: -->
<jb:value beanId="price" data="price" decoder="BigDecimal">
    <jb:decodeParam name="valuePreprocess">value.replace("_", "").replace("!", ".")</jb:decodeParam>
</jb:value>
The String data value is referenced in the expression via the value variable name. (The expression can be any valid MVEL expression that operates on the value String and returns a String.)

6.25. The Javabean Cartridge and Factories

The Javabean Cartridge allows you to use factories to create beans. In these cases you do not need to use a public parameterless constructor. You do not need to have defined the actual class name in the class attribute. Any of the object's interfaces suffice. However you can only bind to that interface's methods. (Even if you define a factory, you must always set the class attribute in the bean definition.)
The factory definition is set in the bean element's factory attribute. The default factory definition language looks like this:
some.package.FactoryClass#staticMethod{.instanceMethod}
Use this basic definition language to define a static public parameterless method that Smooks will call to create the bean. (The instanceMethod part is optional. If you set it, it defines the method that will be called on the object that is returned from static method, which should create the bean. The { } characters are only there to illustrate that the part is optional and should be left out of the actual definition.)

6.26. Instantiate an ArrayList Object Using a Static Factory Method

  1. Follow this example to instantiate an ArrayList object using a static factory method:
    <jb:bean
       beanId="orders"
       class="java.util.List"
       factory="some.package.ListFactory#newList"
       createOnElement="orders"
    >
         <!-- ... bindings -->
    </jb:bean>
    
    The some.package.ListFactory#newList factory definition establishes that the newList method must be called on the some.package.ListFactory class in order to create the bean. The class attributes define the bean as a List object. The specific kind of List object that it is (be it an ArrayList or a LinkedList), is decided by the ListFactory itself.
  2. Observe this additional example:
    <jb:bean
       beanId="orders"
       class="java.util.List"
       factory="some.package.ListFactory#getInstance.newList"
       createOnElement="orders"
    >
         <!-- ... bindings -->
    </jb:bean>
    
    This defines that an instance of the ListFactory needs to be retrieved using the static method getInstance and then the newList method needs to be called on the ListFactory object to create the List object. This construct lets you use singleton factories.

6.27. Declaring Definition Language

These are the ways you can declare which definition language you want to use:
  • Each definition language can have an alias. For instance MVEL has the alias mvel. To define that you want to use MVEL for a specific factory definition you put mvel: in front of the definition. For example. mvel:some.package.ListFactory.getInstance().newList(). The alias of the default basic language is basic.
  • To set a language as a global default you need to set the ‘factory.definition.parser.class’ global parameter to the full class path of the class that implements the FactoryDefinitionParser interface for the language that you want to use. If you have a definition with your default language that includes a : you should prefix that definition with default: to avoid an exception.
  • You can set the full classpath of the class that implements the FactoryDefinitionParser interface for the language that you want to use. (For example, org.milyn.javabean.factory.MVELFactoryDefinitionParser:some.package.ListFactory.getInstance().newList().) You should generally use this for test purposes only. It is much better to define an alias for your language.

6.28. Using Your Own Definition Language

  1. To define your own language, implement the org.milyn.javabean.factory.FactoryDefinitionParser interface. Observe the org.milyn.javabean.factory.MVELFactoryDefinitionParser or org.milyn.javabean.factory.BasicFactoryDefinitionParser for examples.
  2. To define the alias for a definition language, add the org.milyn.javabean.factory.Alias annotation with the alias name to your FactoryDefinitionParser class.
  3. For Smooks to find your alias you need create the file META-INF/smooks-javabean-factory-definition-parsers.inf on the root of your classpath. T

6.29. The MVEL Language

MVEL has some advantages over the basic default definition language. It lets you use objects from the bean context as the factory object and allows you to call factory methods with parameters. These parameters can be defined within the definition or they can be objects from the bean context. To use MVEL, use the alias mvel or set the factory.definition.parser.class global parameter to org.milyn.javabean.factory.MVELFactoryDefinitionParser.

6.30. MVEL Example

<smooks-resource-list
   xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd"
   xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.4.xsd">
 
    <jb:bean
        beanId="orders"
        class="java.util.List"
        factory="mvel:some.package.ListFactory.getInstance().newList()"
        createOnElement="orders"
    >
        <!-- ... bindings -->
    </jb:bean>
 
</smooks-resource-list>

6.31. Extracting a List Object with MVEL

To use MVEL to extract a List object from an existing bean in the bean context, see the example below. (The Order object in this example has a method that returns a list which we must use to add order lines.)
<smooks-resource-list
   xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd"
   xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.4.xsd">
 
    <jb:bean
        beanId="order"
        class="some.package.Order"
        createOnElement="order"
    >
        <!-- ... bindings -->
    </jb:bean>
 
    <!-- 
           The factory attribute uses MVEL to access the order 
           object in the bean context and calls its getOrderLines() 
           method to get the List. This list is then added to the bean 
           context under the beanId 'orderLines' 
    -->
    <jb:bean
        BeanId="orderLines"
        class="java.util.List"
        factory="mvel:order.getOrderLines()" 
        createOnElement="order"
    >
        <jb:wiring BeanIdRef="orderLine" />
    </jb:bean>
 
    <jb:bean
        BeanId="orderLine"
        class="java.util.List"
        createOnElement="order-line"
    >
        <!-- ... bindings -->
    </jb:bean>
 
</smooks-resource-list>

Note

Array objects are not supported. If a factory returns an array then Smooks will throw an exception.

6.32. The jb:value Property

If the jb:value property attribute of a binding is not defined when binding key value pairs into maps, the name of the selected node will be used as the map entry key (where the beanClass is a Map). You can also define the jb:value property attribute by putting the "@" character in front of it. The rest of the value then defines the attribute name of the selected node, from which the map key is selected.

6.33. jb:value Property Example

<root>
    <property name="key1">value1</property>
    <property name="key2">value2</property>
    <property name="key3">value3</property>
</root>
On the config:
<jb:bean BeanId="keyValuePairs" class="java.util.HashMap" createOnElement="root">
    <jb:value property="@name" data="root/property" />
</jb:bean>
This would create a HashMap with three entries with the keys set [key1, key2, key3].

Note

The "@" character notation doesn't work for bean wiring. The cartridge will simply use the value of the property attribute, including the "@" character, as the map entry key.

6.34. Virtual Object Models

You can create a virtual object model without writing your own Bean classes. This virtual model is created using only maps and lists. This is convenient if you use the Javabean Cartridge between two processing steps. For example, as part of a model driven transform - xml to java to xml or xml to java to edi.

6.35. Virtual Object Model Example

<?xml version="1.0"?>
<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd" 
                      xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.4.xsd"
                      xmlns:ftl="http://www.milyn.org/xsd/smooks/freemarker-1.1.xsd">
 
    <!--
    Bind data from the message into a Virtual Object model in the bean context....
    -->    
    <jb:bean beanId="order" class="java.util.HashMap" createOnElement="order">
        <jb:wiring property="header" beanIdRef="header" />
        <jb:wiring property="orderItems" beanIdRef="orderItems" />
    </jb:bean>    
    <jb:bean beanId="header" class="java.util.HashMap" createOnElement="order">
        <jb:value property="date" decoder="Date" data="header/date">
            <jb:decodeParam name="format">EEE MMM dd HH:mm:ss z yyyy</jb:decodeParam>
        </jb:value>
        <jb:value property="customerNumber" decoder="Long" data="header/customer/@number" />
        <jb:value property="customerName" data="header/customer" />
        <jb:expression property="total" execOnElement="order-item" >
            header.total + (orderItem.price * orderItem.quantity);
        </jb:expression>
    </jb:bean>    
    <jb:bean beanId="orderItems" class="java.util.ArrayList" createOnElement="order">
        <jb:wiring beanIdRef="orderItem" />
    </jb:bean>    
    <jb:bean beanId="orderItem" class="java.util.HashMap" createOnElement="order-item">
        <jb:value property="productId" decoder="Long" data="order-item/product" />
        <jb:value property="quantity" decoder="Integer" data="order-item/quantity" />
        <jb:value property="price" decoder="Double" data="order-item/price" />
    </jb:bean>
 
    <!--
    Use a FreeMarker template to perform the model driven transformation on the Virtual Object Model...
    -->
    <ftl:freemarker applyOnElement="order">
        <ftl:template>/templates/orderA-to-orderB.ftl</ftl:template>
    </ftl:freemarker>
 
</smooks-resource-list>
Note above how the decoder attribute for a Virtual Model (Map) is always defined. This is because Smooks has no way of auto-detecting the decode type for data binding to a Map. If you need typed values bound into your Virtual Model, you need to specify an appropriate decoder. If the decoder is not specified in this case, Smooks will simply bind the data into the Virtual Model as a String.

6.36. Merging Multiple Data Entities Into a Single Binding

You can merge multiple data entities into a single binding using Expression Based Bindings (jb:expression). The Javabean cartridge uses the Smooks DataDecoder to create an Object from a selected data element/attribute. It then adds it directly to the bean context.

6.37. Value Binding

The ValueBinder class is the visitor that does the value binding. The value binding XML configuration is part of the JavaBean schema from Smooks 1.3 on http://www.milyn.org/xsd/smooks/javabean-1.4.xsd. The element for the value binding is "value".

6.38. Value Binding Attributes

The ValueBinder class has the following attributes:
  • beanId: The ID under which the created object is to be bound in the bean context.
  • data: The data selector for the data value to be bound. (For example, order/orderid or order/header/@date
  • dataNS: The namespace for the data selector
  • decoder: The DataDecoder name for converting the value from a String into a different type. The DataDecoder can be configured with the decodeParam elements.
  • default: The default value for if the selected data is null or an empty string.

6.39. Value Binding Example

The Order message will be used as an example. It will be configured for getting the order number, name and date as Value Objects in the form of an Integer and String.
The message input:
<order xmlns="http://x">
     <header>
         <y:date xmlns:y="http://y">Wed Nov 15 13:45:28 EST 2006</y:date>
         <customer number="123123">Joe</customer>
         <privatePerson></privatePerson>
     </header>
     <order-items>
         <!-- .... -->
     </order-items>
 </order>
The configuration:
<?xml version="1.0"?>
 <smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd" xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.4.xsd">
 
    <jb:value
       beanId="customerName"
       data="customer"
       default="unknown"
    />
 
    <jb:value
       beanId="customerNumber"
       data="customer/@number"
       decoder="Integer"
    />
 
   <jb:value
       beanId="orderDate"
       data="date"
       dateNS="http://y"
       decoder="Date"
    >
         <jb:decodeParam name="format">EEE MMM dd HH:mm:ss z yyyy</jb:decodeParam>
         <jb:decodeParam name="locale-language">en</jb:decodeParam>
         <jb:decodeParam name="locale-country">IE</jb:decodeParam>
   </jb:value>
 
 </smooks-resource-list>

6.40. Programmatic Value Binding Example

The value binder can be programmatically configured using the org.milyn.javabean.Value object:
//Create Smooks. normally done globally!
Smooks smooks = new Smooks();
 
//Create the Value visitors
Value customerNumberValue = new Value( "customerNumber", "customer/@number").setDecoder("Integer");
Value customerNameValue = new Value( "customerName", "customer").setDefault("Unknown");
 
//Add the Value visitors
smooks.addVisitors(customerNumberValue);
smooks.addVisitors(customerNameValue);
 
//And the execution code: 
JavaResult result = new JavaResult();
 
smooks.filterSource(new StreamSource(orderMessageStream), result);
Integer customerNumber = (Integer) result.getBean("customerNumber");
String customerName = (String) result.getBean("customerName");

6.41. Java Binding in Smooks

Java Binding Configurations can be programmatically added to a Smooks using the Bean configuration class. This class can be used to programmatically configure a Smooks instance for performing a Java Bindings on a specific class. To populate a graph, you simply create a graph of bean instances by binding beans onto beans. The bean class uses a Fluent API (all methods return the bean instance), making it easy to string configurations together to build up a graph of bean configuration.

6.42. Java Binding Example

This Order message example shows how to bind it into a corresponding Java Object model.
The message input:
<order xmlns="http://x">
    <header>
        <y:date xmlns:y="http://y">Wed Nov 15 13:45:28 EST 2006</y:date>
        <customer number="123123">Joe</customer>
        <privatePerson></privatePerson>
    </header>
    <order-items>
        <order-item>
            <product>111</product>
            <quantity>2</quantity>
            <price>8.90</price>
        </order-item>
        <order-item>
            <product>222</product>
            <quantity>7</quantity>
            <price>5.20</price>
        </order-item>
    </order-items>
</order>
The Java Model (not including getters/setters):
public class Order {
    private Header header;
    private List<OrderItem> orderItems;
}
 
public class Header {
    private Long customerNumber;
    private String customerName;
}
 
public class OrderItem {
    private long productId;
    private Integer quantity;
    private double price;
}
The configuration code:
  
Smooks smooks = new Smooks();
 
Bean orderBean = new Bean(Order.class, "order", "/order");
 
orderBean.bindTo("header",
    orderBean.newBean(Header.class, "/order")
        .bindTo("customerNumber", "header/customer/@number")
        .bindTo("customerName", "header/customer")
    ).bindTo("orderItems",
    orderBean.newBean(ArrayList.class, "/order")
        .bindTo(orderBean.newBean(OrderItem.class, "order-item")
            .bindTo("productId", "order-item/product")
            .bindTo("quantity", "order-item/quantity")
            .bindTo("price", "order-item/price"))
    );
 
smooks.addVisitors(orderBean);
The execution code:
 JavaResult result = new JavaResult();
 
smooks.filterSource(new StreamSource(orderMessageStream), result);
Order order = (Order) result.getBean("order");
Here is an example where an anonymous Factory class is defined and used:
Bean orderBean = new Bean(Order.class, "order", "/order", new Factory<Order>() {
 
 public Order create(ExecutionContext executionContext) {
  return new Order();
 }
 
});

6.43. The org.milyn.javabean.gen.ConfigGenerator Utility Class

The Javabean Cartridge contains the org.milyn.javabean.gen.ConfigGenerator utility class that can be used to generate a binding configuration template. This template can then be used as the basis for defining a binding.

6.44. org.milyn.javabean.gen.ConfigGenerator Example

From the commandline:
$JAVA_HOME/bin/java -classpath <classpath> org.milyn.javabean.gen.ConfigGenerator -c <rootBeanClass> -o <outputFilePath> [-p <propertiesFilePath>]
  • The-c commandline arg specifies the root class of the model whose binding config is to be generated.
  • The -o commandline arg specifies the path and filename for the generated config output.
  • The -p commandline arg specifies the path and filename optional binding configuration file that specifies additional binding parameters.
  • The optional -p properties file parameter allows specification of additional config parameters.
  • packages.included: Semi-colon separated list of packages. Any fields in the class matching these packages will be included in the binding configuration generated.
  • packages.excluded: Semi-colon separated list of packages. Any fields in the class matching these packages will be excluded from the binding configuration generated.

6.45. Programming the Binding Configuration

After running the the org.milyn.javabean.gen.ConfigGenerator utility class against the target class, you should perform these tasks to make the binding configuration work for your Source data model.
  1. For each jb:bean element, set the createOnElement attribute to the event element that should be used to create the bean instance.
  2. Update the jb:value data attributes to select the event element/attribute supplying the binding data for that BFean property.
  3. Check the jb:value decoder attributes. Not all will be set, depending on the actual property type. These must be configured by hand. You may need to configure jb:decodeParam sub-elements for the decoder on some of the bindings, for example, on a date field.
  4. Double-check the binding configuration elements (jb:value and jb:wiring), making sure all Java properties have been covered in the generated configuration.

6.46. Configuring Transformations

  1. Access the HTML Reporting Tool when determining selector values. It helps you visualise the input message model (against which the selectors will be applied) as seen by Smooks.
  2. Generate a report using your Source data, but with an empty transformation configuration. In the report, you can see the model against which you need to add your configurations. Add the configurations one at a time, rerunning the report to check they are being applied.
  3. Add the configurations one at a time, rerunning the report to check they are being applied.
  4. As a result, a configuration that looks like this will be generated (note the $TODO$ tokens):
    <?xml version="1.0"?>
    <smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd" xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.4.xsd">
     
        <jb:bean beanId="order" class="org.milyn.javabean.Order" createOnElement="$TODO$">
            <jb:wiring property="header" beanIdRef="header" />
            <jb:wiring property="orderItems" beanIdRef="orderItems" />
            <jb:wiring property="orderItemsArray" beanIdRef="orderItemsArray" />
        </jb:bean>
     
        <jb:bean beanId="header" class="org.milyn.javabean.Header" createOnElement="$TODO$">
            <jb:value property="date" decoder="$TODO$" data="$TODO$" />
            <jb:value property="customerNumber" decoder="Long" data="$TODO$" />
            <jb:value property="customerName" decoder="String" data="$TODO$" />
            <jb:value property="privatePerson" decoder="Boolean" data="$TODO$" />
            <jb:wiring property="order" beanIdRef="order" />
        </jb:bean>
     
        <jb:bean beanId="orderItems" class="java.util.ArrayList" createOnElement="$TODO$">
            <jb:wiring beanIdRef="orderItems_entry" />
        </jb:bean>
     
        <jb:bean beanId="orderItems_entry" class="org.milyn.javabean.OrderItem" createOnElement="$TODO$">
            <jb:value property="productId" decoder="Long" data="$TODO$" />
            <jb:value property="quantity" decoder="Integer" data="$TODO$" />
            <jb:value property="price" decoder="Double" data="$TODO$" />
            <jb:wiring property="order" beanIdRef="order" />
        </jb:bean>
     
        <jb:bean beanId="orderItemsArray" class="org.milyn.javabean.OrderItem[]" createOnElement="$TODO$">
            <jb:wiring beanIdRef="orderItemsArray_entry" />
        </jb:bean>
     
        <jb:bean beanId="orderItemsArray_entry" class="org.milyn.javabean.OrderItem" createOnElement="$TODO$">
            <jb:value property="productId" decoder="Long" data="$TODO$" />
            <jb:value property="quantity" decoder="Integer" data="$TODO$" />
            <jb:value property="price" decoder="Double" data="$TODO$" />
            <jb:wiring property="order" beanIdRef="order" />
        </jb:bean>
     
    </smooks-resource-list>
    

    Note

    There is no guarantee as to the exact contents of a JavaResult instance after calling the Smooks.filterSource method. After calling this method, the JavaResult instance will contain the final contents of the bean context, which can be added to by any Visitor implementation.

6.47. jb:result Configuration Example

You can restrict the Bean set returned in a JavaResult by using a jb:result configuration in the Smooks configuration. In the following example configuration, we tell Smooks to only retain the order bean in the ResultSet:
<?xml version="1.0"?>
<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd"
                      xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.4.xsd">
 
    <!-- Capture some data from the message into the bean context... -->
    <jb:bean beanId="order" class="com.acme.Order" createOnElement="order">
        <jb:value property="orderId" data="order/@id"/>
        <jb:value property="customerNumber" data="header/customer/@number"/>
        <jb:value property="customerName" data="header/customer"/>
        <jb:wiring property="orderItems" beanIdRef="orderItems"/>
    </jb:bean>
    <jb:bean beanId="orderItems" class="java.util.ArrayList" createOnElement="order">
        <jb:wiring beanIdRef="orderItem"/>
    </jb:bean>
    <jb:bean beanId="orderItem" class="com.acme.OrderItem" createOnElement="order-item">
        <jb:value property="itemId" data="order-item/@id"/>
        <jb:value property="productId" data="order-item/product"/>
        <jb:value property="quantity" data="order-item/quantity"/>
        <jb:value property="price" data="order-item/price"/>
    </jb:bean>
 
    <!-- Only retain the "order" bean in the root of any final JavaResult. -->
    <jb:result retainBeans="order"/>
 
</smooks-resource-list>
After applying this configuration, any calls to the JavaResult.getBean(String) method for non-order Bean results will return null. This will work in cases such as the above example, because the other bean instances are wired into the order graph.

Note

Note that as of Smooks v1.2, if a JavaSource instance is supplied to the Smooks.filterSource method (as the filter Source instance), Smooks will use the JavaSource to construct the bean context associated with the ExecutionContect for that Smooks.filterSource invocation. This will mean that some of the JavaSource bean instances may be visible in the JavaResult.
Red Hat logoGithubRedditYoutubeTwitter

Learn

Try, buy, & sell

Communities

About Red Hat Documentation

We help Red Hat users innovate and achieve their goals with our products and services with content they can trust.

Making open source more inclusive

Red Hat is committed to replacing problematic language in our code, documentation, and web properties. For more details, see the Red Hat Blog.

About Red Hat

We deliver hardened solutions that make it easier for enterprises to work across platforms and environments, from the core datacenter to the network edge.

© 2024 Red Hat, Inc.