The Smooks JavaBean Cartridge allows you to create and populate Java objects from your message data (i.e. bind data to). You need to add the milyn-smooks-javabean-1.1.jar to your classpath. If you are using Maven then add the org.milyn:milyn-smooks-javabean:1.1 dependency to the POM.
Note: As you know, Smooks supports a range of source data formats (XML, EDI, CSV, Java etc), but for the purposes of this topic, we will always refer to the message data in terms of an XML format.
In the examples we will be referring a lot to the following XML message data:
In some examples we will use different XML message data. Where this happens, the data is explicitly defined there then.
The JavaBean Cartridge is used via the http://www.milyn.org/xsd/smooks/javabean-1.1.xsd configuration namespace. Install the schema in your IDE and avail of autocompletion.
An example configuration:
This configuration simply creates an instance of the example.model.Order class and binds it into the bean context under the beanId "order". The instance is created at the very start of the message on the $document element (i.e. the start of the root <order> element).
The createOnElement attribute controls when the bean instance is created. Population of the bean properties is controlled through the binding configurations (child elements of the <jb:bindings> element).
The namespace of the createOnElement can be specified via the createOnElementNS attribute.
The bean context (also known as "bean map") is a very important part of the JavaBean Cartridge. One bean context is created per execution context (i.e. per Smooks.filter operation). Every bean, created by the cartridge, is put into this context under its beanId. If you want the contents of the bean context to be returned at the end of the Smooks.filter process, supply a org.milyn.delivery.java.JavaResult object in the call to Smooks.filter method. The following example illustrates this principal:
If you need to access the bean context beans at runtime (e.g. from a customer Visitor implementation), you do so via the BeanRepository class.
The Javabean cartridge has the following conditions for javabeans:
- A public no-argument constructor
- Public property setter methods. The don't need to follow any specific name formats, but it would be better if they do follow the standard property setter method names.
- Setting javabean properties directly is not supported.
The configuration shown above simply created the example.model.Order bean instance and bound it into the bean context. This section will describe how to bind data into that bean instance.
The Javabean Cartridge provides support for 3 types of data bindings, which are added as child elements of the <jb:bindings> element:
Value binding, via the <jb:value> binding configuration. This is used to bind data values from the Source message event stream into the target bean.
Wiring binding, via the <jb:wiring> binding configuration. This is used to "wire" another bean instance from the bean context into a bean property on the target bean. This is the configuration that allows you to construct an object graph (Vs just a loose bag of Java object instances).
Expression based binding, via the <jb:expression> configuration. As it's name suggests, this configuration is used to bind in a value calculated from an expression, a simple example being the binding of an order item total value into an OrderItem bean based on the result of an expression that calculates the value from the items price and quantity (e.g. "price * quantity").
Taking the Order XML message (previous section), lets see what the full XML to Java binding configuration might be. We've seen the order XML (above). Now lets look at the Java Objects that we want to populate from that XML message (getters and setters not shown):
The Smooks config required to bind the data from the order XML and into this object model is as follows:
Configuration (1) defines the creation rules for the com.acme.Order bean instance (top level bean). We create this bean instance at the very start of the message i.e. on the <order> element (createOnElement="order"). In fact, we create the each of the beans instances ((1), (2), (3) - all accepts the (4)) at the very start of the message (on the <order> element). We 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 List<OrderItem> 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.
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 Date Decoder (decoder="Date").
Configuration (3) creates the List<OrderItem> bean instance for holding the OrderItem instances. Configuration (3.a) wires in the orderItem bean ((4)) instances 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).
Configuration (4) creates the OrderItem bean instances. Note how the createOnElement is set to the <order-item> element. This is because we want a new instance of this bean to be created for every <order-item> element (and wired into the List<OrderItem> (3.a)). If the createOnElement attribute for this configuration was not set to the <order-item> element (e.g. if 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 i.e. you would be left with a List<OrderItem> with just a single OrderItem instance containing the <order-item> data from the last <order-item> encountered in the source message.
Extended Lifecycle Bindings
Binding Key Value Pairs into Maps
If the <jb:value property> attribute of a binding is not defined (or is empty), then the name of the selected node will be used as the map entry key (where the beanClass is a Map).
There is one other way to define the map key. The value of the <jb:value property> attribute can start with the @ character. The rest of the value then defines the attribute name of the selected node, from which the map key is selected. The following example demonstrates this:
An the config:
This would create a HashMap with three entries with the keys set [key1, key2, key3].
Off course 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.
Virtual Object Models (Maps & Lists)
It is possible to create a complete object model without writing your own Bean classes. This virtual model is created using only maps and lists . This is very convenient if you use the javabean cartridge between two processing steps. For example from xml -> java -> edi.
The following example demonstrates the principle:
Take a look at the milyn/smooks-examples/xml-to-java-virtual for another example.
Merging Multiple Data Entities Into a Single Binding
This can be achieved using Expression Based Bindings (<jb:expression>).
Generating the Smooks Binding Configuration
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.
From the commandline:
- 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 aditional binding parameters.
The optional "-p" properties file parameter allows specification of additional config parameters:
- packages.included: Semi-colon seperated list of packages scoping classes to be included in the binding generation.
- packages.excluded: Semi-colon seperated list of packages scoping classes to be excluded in the binding generation.
After running this utility against the target class, you typically need to perform the following follow-up tasks in order to make the binding configuration work for your Source data model.
- For each <jb:bindings> element, set the createOnElement attribute to the event element that should be used to create the bean instance.
- Update the <jb:value data> attributes to select the event element/attribute supplying the binding data for that bean property.
- Check the <jb:value decoder> attributes. Not all will be set, depending on the actual property type. These must be configured by hand e.g. you may need to configure <jb:decodeParam> sub-elements for the decoder on some of the bindings. E.g. for a date field.
- Double-check the binding config elements (<jb:value> and <jb:wiring>), making sure all Java properties have been covered in the generated configuration.
Determining the selector values can sometimes be difficult, especially for non XML Sources (Java etc). The Html Reporting tool can be a great help here because it helps you visualise the input message model (against which the selectors will be applied) as seen by Smooks. So, first off, 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.
The following is an example of a generated configuration. Note the "$TODO$" tokens.