
Second Milestone Release of Smooks 2
27 Nov 2020 - Claude
This post was reproduced on the On Code & Design blog.

We’re delighted to announce the second milestone release of Smooks 2. The second milestone is geared towards simplifying Smook’s API along with a few goodies. Here’s a rundown of the new notable features:
SAX NG
Earlier versions of Smooks could have visitors applying operations on either DOM nodes or SAX events. To ensure interoperability, one would implement a visitor supporting both DOM node and SAX event processing which meant implementing two different application APIs:
...
import org.milyn.SmooksException;
import org.milyn.container.ExecutionContext;
import org.milyn.delivery.dom.DOMElementVisitor;
import org.milyn.delivery.sax.SAXElement;
import org.milyn.delivery.sax.SAXElementVisitor;
import org.milyn.delivery.sax.SAXText;
import org.w3c.dom.Element;
public class MyDomAndSaxVisitor implements SAXElementVisitor, DOMElementVisitor {
@Override
public void visitBefore(Element element, ExecutionContext executionContext) throws SmooksException {
System.out.println("Applying operation at the beginning of a DOM element...");
...
}
@Override
public void visitAfter(Element element, ExecutionContext executionContext) throws SmooksException {
System.out.println("Applying operation at the end of a DOM element...");
...
}
@Override
public void visitBefore(SAXElement element, ExecutionContext executionContext) throws SmooksException {
System.out.println("Applying operation at the beginning of a SAX element event...");
...
}
@Override
public void onChildText(SAXElement element, SAXText childText, ExecutionContext executionContext) throws SmooksException {
System.out.println("Applying operation on a SAX child text event...");
...
}
@Override
public void onChildElement(SAXElement element, SAXElement childElement, ExecutionContext executionContext) throws SmooksException {
System.out.println("Applying operation on a SAX child element event...");
...
}
@Override
public void visitAfter(SAXElement element, ExecutionContext executionContext) throws SmooksException {
System.out.println("Applying operation at the end of a SAX element event...");
...
}
}
The above broadly translated into two different execution paths, with all the baggage it entailed. Smooks 2.0.0-M2 unifies the DOM and SAX visitor APIs without sacrificing convenience or performance. The new SAX NG filter drops the API distinction between DOM and SAX. Instead, it streams SAX events as partial DOM elements to SAX NG visitors:
...
import org.smooks.container.ExecutionContext;
import org.smooks.delivery.sax.ng.ElementVisitor;
import org.w3c.dom.Element;
public class MySaxNgVisitor implements ElementVisitor {
@Override
public void visitBefore(Element element, ExecutionContext executionContext) {
System.out.println("Applying operation at the beginning of a fragment...");
...
}
@Override
public void visitChildText(Element element, ExecutionContext executionContext) {
System.out.println("Applying operation on a child text fragment...");
...
}
@Override
public void visitChildElement(Element childElement, ExecutionContext executionContext) {
System.out.println("Applying operation on a child fragment...");
...
}
@Override
public void visitAfter(Element element, ExecutionContext executionContext) {
System.out.println("Applying operation at the end of a fragment...");
...
}
}
Traditional DOM trees are still supported with the help of the max.node.depth global config parameter. The max.node.depth knob instructs the SAX NG filter to build DOM trees for the targeted elements such that the tree’s maximum depth is equal to the max.node.depth value.
<smooks-resource-list xmlns="https://www.smooks.org/xsd/smooks-2.0.xsd">
<params>
<!-- 0 denotes infinite depth -->
<param name="max.node.depth">0</param>
</params>
...
</smooks-resource-list>
It’s all transparent from a visitor’s perspective. A visitor only sees an element but with the caveat that an element may have child nodes when max.node.depth is greater than 1.
The latest cartridge releases have been migrated to SAX NG. We recommend future visitor implementations extend the org.smooks.delivery.sax.ng.SaxNgVisitor interface given that it supersedes the org.smooks.delivery.dom.DOMVisitor and org.smooks.delivery.sax.SAXVisitor interfaces.
JSR Annotations
Smooks-specific annotations were dropped in favour of JSR annotations. Smooks 1 resources would have their dependencies injected like so:
...
import org.milyn.cdr.SmooksResourceConfiguration;
import org.milyn.cdr.annotation.AppContext;
import org.milyn.cdr.annotation.Config;
import org.milyn.cdr.annotation.ConfigParam;
import org.milyn.container.ApplicationContext;
public class MySmooks1Resource {
@ConfigParam(name = "MyProperty", defaultVal = "hello world")
private String propertyOne;
@ConfigParam
private String propertyTwo;
@ConfigParam(use = ConfigParam.Use.OPTIONAL)
private String propertyThree;
@AppContext
private ApplicationContext appContext;
...
}
Smooks 2.0.0-M2 does away with all these adhoc annotations and provides a single @Inject for this purpose:
...
import org.smooks.container.ApplicationContext;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.Optional;
public class MySmooks2Resource {
@Inject
@Named("MyProperty")
private String propertyOne = "hello world";
@Inject
private String propertyTwo;
@Inject
private Optional<String> propertyThree;
@Inject
private ApplicationContext appContext;
...
}
Additionally, the @Initialize and @Uninitialize lifecycle annotations were replaced with the standard @PostConstruct and @PreDestroy annotations, respectively.
EDIFACT Java Bindings
By popular demand, we’re generating and distributing the Java bindings for the EDIFACT schemas starting from the M2 release of the EDIFACT cartridge. The bindings are available from Maven Central at the coordinates: org.smooks.cartridges.edi:[message version/release]-edifact-binding:2.0.0-M2
. For instance, the D03B EDIFACT binding dependency can be declared in your POM with:
...
<dependency>
<groupId>org.smooks.cartridges.edi</groupId>
<artifactId>d03b-edifact-binding</artifactId>
<version>2.0.0-M2</version>
</dependency>
...
Visit the java-to-edifact project in the examples catalogue to see how Smooks turns an interchange Java bean instance into EDIFACT.
Selector Namespace Prefixes
In Smooks 1, you would bind selector namespace prefixes as follows:
<smooks-resource-list xmlns="https://www.smooks.org/xsd/smooks-1.2.xsd"
xmlns:core="https://www.smooks.org/xsd/smooks/smooks-core-1.5.xsd">
<core:namespaces>
<core:namespace prefix="a" uri="http://a"/>
<core:namespace prefix="b" uri="http://b"/>
<core:namespace prefix="c" uri="http://c"/>
<core:namespace prefix="d" uri="http://d"/>
</core:namespaces>
<resource-config selector="/a:ord[@num = 3122 and @state = 'finished']/a:items/c:item[@c:code = '8655']/d:units[text() = 1]">
<resource>org.acme.MyVisitor</resource>
</resource-config>
</smooks-resource-list>
This is no longer required. As of 2.0.0-M2, you can now bind a selector namespace prefix just like any other XML namespace prefix:
<smooks-resource-list xmlns="https://www.smooks.org/xsd/smooks-2.0.xsd"
xmlns:a="http://a" xmlns:b="http://b" xmlns:c="http://c" xmlns:d="http://d">
<resource-config selector="/a:ord[@num = 3122 and @state = 'finished']/a:items/c:item[@c:code = '8655']/d:units[text() = 1]">
<resource>org.acme.MyVisitor</resource>
</resource-config>
</smooks-resource-list>
Furthermore, the new smooks-2.0.xsd removes the default-selector-namespace and selector-namespace XML attributes in favour of declaring namespaces within the standard xmlns attribute from the smooks-resource-list element. smooks-2.0.xsd also removes the default-selector attribute from the smooks-resource-list element: some may not like this change but we think a config is easier to comprehend when selectors are explicit.
Visitor Memento
Visitor mementos are a convenient way to store, track, and retrieve a visitor’s state from the execution context. In the past, given that visitors are not thread-safe, the general approach to holding onto state between visits was to stash the state inside the execution context and retrieve it later on. This led to boilerplate code because oftentimes the state to be retrieved depended on the fragment being processed and the visitor instance. Visitor mementos were introduced in order to eliminate this boilerplate code.
A great example showcasing mementos is accumulating character data in a visitor while SAX events are streamed:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
...
import org.smooks.container.ExecutionContext;
import org.smooks.delivery.memento.NodeVisitable;
import org.smooks.delivery.memento.TextAccumulatorMemento;
import org.smooks.delivery.sax.ng.ElementVisitor;
import org.w3c.dom.Element;
import java.util.function.Consumer;
public class MyTextAccumulatorVisitor implements ElementVisitor {
@Override
public void visitBefore(Element element, ExecutionContext executionContext) {
}
@Override
public void visitChildText(Element element, ExecutionContext executionContext) {
executionContext.getMementoCaretaker().stash(new TextAccumulatorMemento(new NodeVisitable(element), this),
textAccumulatorMemento -> textAccumulatorMemento.accumulateText(element.getTextContent()));
}
@Override
public void visitChildElement(Element childElement, ExecutionContext executionContext) {
}
@Override
public void visitAfter(Element element, ExecutionContext executionContext) {
TextAccumulatorMemento textAccumulatorMemento = new TextAccumulatorMemento(new NodeVisitable(element), this);
executionContext.getMementoCaretaker().restore(textAccumulatorMemento);
System.out.println("Accumulated character data: " + textAccumulatorMemento.getText());
}
}
getMementoCareTaker() is a new addition to the ExecutionContext interface. A MementoCareTaker manages mementos on behalf of visitors. In line 19, the MementoCareTaker stashes each chunk of read character data. It then goes on to restore the collected character data in the visitor’s visitAfter(Element, ExecutionContext) method as shown in lines 29-30. Observe that the element and visitor objects serve as keys for saving as well as restoring the memento’s state. Consult the Javadocs to take a deep dive into mementos.