Chapter 14. Extending Smooks
14.1. APIs in Smooks
All existing Smooks functionality (Java Binding, EDI processing etc) is built through the extension of a number of well defined APIs.
- Reader APIs
- Those for processing Source/Input data (Readers) so as to make it consumable by other Smooks components as a series of well defined hierarchical events (based on the SAX event model) for all of the message fragments and sub-fragments.
- Visitor APIs
- Those for consuming the message fragment SAX Events produced by a Source/Input Reader.
14.2. Configuring Smooks Components
resources
which are configured using a SmooksResourceConfiguration
instance.
14.3. Namespace-specific Configurations
SmooksResourceConfiguration
class) is the basic <resource-config> XML configuration from the base configuration namespace (http://www.milyn.org/xsd/smooks-1.1.xsd).
14.4. Namespace-specific Configuration Example
<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd"> <resource-config selector=""> <resource></resource> <param name=""></param> </resource-config> </smooks-resource-list>
- The
selector
attribute is the mechanism by which the resource is "selected" (for example, it can be an XPath for a Visitor implementation). - The
resource
element is the actual resource. This can be a Java Class name or some other form of resource such as a template. The resource is assumed to be a Java class name for the remainder for this section. - The
param
elements are configuration parameters for the resource defined in the resource element.
14.5. Runtime Representation
14.6. Configuration Annotations
<param>
element details. This is done using the @ConfigParam
and @Config
annotations.
14.7. The @ConfigParam Annotation
@ConfigParam
annotation reflectively injects the named parameter from the <param>
elements that have the same name as the annotated property itself. The name can be different but the default behavior matches against the name of the component property.
14.8. @ConficParam Benefits
- Handles decoding of the <param> value before setting it on the annotated component property. Smooks provides DataDecoders for all of the main types (int, Double, File, Enums etc), but you can implement and use a custom DataDecoder where the out of the box decoders don't cover specific decoding requirements (for example,
@ConfigParam(decoder = MyQuirkyDataDecoder.class)
). Smooks will automatically use your custom decoder (that is, you won't need to define the decoder property on this annotation) if it is registered. See the DataDecoder Javadocs for details on registering a DataDecoder implementation such that Smooks will automatically locate it for decoding a specific data type. - Supports a
choice
constraint for theconfig
property, generating a configuration exception where the configured value is not one of the defined choice values. For example, you may have a property which has a constrained value set ofON
andOFF
. You can use the choice property on this annotation to constrain the config, raise exceptions, and so on. (For example,@ConfigParam(choice = {"ON", "OFF"})
.) - Can specify default config values e.g.
@ConfigParam(defaultVal = "true")
. - Can specify whether or not the property config value is required or optional e.g.
@ConfigParam(use = Use.OPTIONAL)
. By default, all properties areREQUIRED
, but setting adefaultVal
implicitly marks the property as beingOPTIONAL
.
14.9. Using the @ConfigParam Annotation
DataSeeder
and its corresponding Smooks configuration:
public class DataSeeder { @ConfigParam private File seedDataFile; public File getSeedDataFile() { return seedDataFile; } // etc... }
<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd"> <resource-config selector="dataSeeder"> <resource>com.acme.DataSeeder</resource> <param name="seedDataFile">./seedData.xml</param> </resource-config> </smooks-resource-list>
14.10. The @Config Annotation
@Config
annotation reflectively injects the full SmooksResourceConfiguration
instance, associated with the component resource, onto the annotated component property. An error will result if this annotation is added to a component property that is not of type SmooksResourceConfiguration
.
14.11. Using the @Config Annotation
public class MySmooksComponent { @Config private SmooksResourceConfiguration config; // etc...
14.12. @Initialize and @Uninitialize
@Initialize
annotation.
@Uninitialize
annotation.
14.13. A Basic Initialization/Un-initialization Sequence
smooks = new Smooks(..); // Initialize all annotated components @Initialize // Use the smooks instance through a series of filterSource invocations... smooks.filterSource(...); smooks.filterSource(...); smooks.filterSource(...); ... etc ... smooks.close(); // Uninitialize all annotated components @Uninitialize
14.14. Using @Initialize and @Uninitialize
In this example, assume we have a component that opens multiple connections to a database on initialization and then needs to release all those database resources when we close the Smooks instance.
public class MultiDataSourceAccessor { @ConfigParam private File dataSourceConfig; Map<String, Datasource> datasources = new HashMap<String, Datasource>(); @Initialize public void createDataSources() { // Add DS creation code here.... // Read the dataSourceConfig property to read the DS configs... } @Uninitialize public void releaseDataSources() { // Add DS release code here.... } // etc... }
@Initialize
and @Uninitialize
annotations above, the following should be noted:
- The
@Initialize
and@Uninitialize
methods must be public, zero-arg methods. - The
@ConfigParam
properties are all initialized before the first@Initialize
method is called. Therefore, you can use the@ConfigParam
component properties as input to the initialization process. - The
@Uninitialize
methods are all called in response to a call to theSmooks.close
method.
14.15. Defining Custom Configuration Namespaces
<resource-config>
base configuration.
14.16. Using Custom Configuration Namespaces
- Writing an configuration XSD for your component that extends the base http://www.milyn.org/xsd/smooks-1.1.xsd configuration namespace. This XSD must be supplied on the classpath with your component. It must be located in the
/META-INF/
folder and have the same path as the namespace URI. For example, if your extended namespace URI is http://www.acme.com/schemas/smooks/acme-core-1.0.xsd, then the physical XSD file must be supplied on the class-path in/META-INF/schemas/smooks/acme-core-1.0.xsd
. - Writing a Smooks configuration namespace mapping configuration file that maps the custom name-space configuration into a
SmooksResourceConfiguration
instance. This file must be named (by convention) based on the name of the name-space it is mapping and must be physically located on the class-path in the same folder as the XSD. Extending the above example, the Smooks mapping file would be/META-INF/schemas/smooks/acme-core-1.0.xsd-smooks.xml
. Note the-smooks.xml
postfix.
Note
14.17. Implementing a Source Reader
org.xml.sax.XMLReader
interface from the Java JDK. However, if you want to be able to configure the Reader implementation, it needs to implement the org.milyn.xml.SmooksXMLReader
interface. org.milyn.xml.SmooksXMLReader
is an extension of org.xml.sax.XMLReader
. You can easily use an existing org.xml.sax.XMLReader
implementation, or implement a new one.
14.18. Implementing a Source Reader for use with Smooks
- You should first implement a basic reader class as shown below:
public class MyCSVReader implements SmooksXMLReader { // Implement all of the XMLReader methods... }
Two methods from theorg.xml.sax.XMLReader
interface are of particular interest:setContentHandler(ContentHandler)
is called by Smooks Core. It sets theorg.xml.sax.ContentHandler
instance for the reader. Theorg.xml.sax.ContentHandler
instance methods are called from inside theparse(InputSource)
method.parse(InputSource)
: This is the method that receives the Source data input stream, parses it (i.e. in the case of this example, the CSV stream) and generates the SAX event stream through calls to theorg.xml.sax.ContentHandler
instance supplied in thesetContentHandler(ContentHandler)
method.
Refer to http://download.oracle.com/javase/6/docs/api/org/xml/sax/ContentHandler.html for more details. - Configure your CSV reader with the names of the fields associated with the CSV records. Configuring a custom reader implementation is the same for any Smooks component. See the example below:
public class MyCSVReader implements SmooksXMLReader { private ContentHandler contentHandler; @ConfigParam private String[] fields; // Auto decoded and injected from the "fields" <param> on the reader config. public void setContentHandler(ContentHandler contentHandler) { this.contentHandler = contentHandler; } public void parse(InputSource csvInputSource) throws IOException, SAXException { // TODO: Implement parsing of CSV Stream... } // Other XMLReader methods... }
- Now that you have the basic Reader implementation stub, you can start writing unit tests to test the new reader implementation. To do this you will need something with CSV input. Observe the example below featuring a simple list of names in a file with the name
names.csv
:Tom,Jones Mike,Jones Mark,Jones
- Use a test Smooks configuration to configure Smooks with your MyCSVReader. As stated before, everything in Smooks is a resource and can be configured with the basic
<resource-config>
configuration. While this works fine, it's a little noisy, so Smooks provides a basic<reader>
configuration element specifically for the purpose of configuring a reader. The configuration for the test looks like the following, in themycsvread-config.xml
:<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd"> <reader class="com.acme.MyCSVReader"> <params> <param name="fields">firstname,lastname</param> </params> </reader> </smooks-resource-list>
- Implement the JUnit test class:
public class MyCSVReaderTest extends TestCase { public void test() { Smooks smooks = new Smooks(getClass().getResourceAsStream("mycsvread-config.xml")); StringResult serializedCSVEvents = new StringResult(); smooks.filterSource(new StreamSource(getClass().getResourceAsStream("names.csv")), serializedCSVEvents); System.out.println(serializedCSVEvents); // TODO: add assertions etc } }
- Implement the
parse
method:public class MyCSVReader implements SmooksXMLReader { private ContentHandler contentHandler; @ConfigParam private String[] fields; // Auto decoded and injected from the "fields" <param> on the reader config. public void setContentHandler(ContentHandler contentHandler) { this.contentHandler = contentHandler; } public void parse(InputSource csvInputSource) throws IOException, SAXException { BufferedReader csvRecordReader = new BufferedReader(csvInputSource.getCharacterStream()); String csvRecord; // Send the start of message events to the handler... contentHandler.startDocument(); contentHandler.startElement(XMLConstants.NULL_NS_URI, "message-root", "", new AttributesImpl()); csvRecord = csvRecordReader.readLine(); while(csvRecord != null) { String[] fieldValues = csvRecord.split(","); // perform checks... // Send the events for this record... contentHandler.startElement(XMLConstants.NULL_NS_URI, "record", "", new AttributesImpl()); for(int i = 0; i < fields.length; i++) { contentHandler.startElement(XMLConstants.NULL_NS_URI, fields[i], "", new AttributesImpl()); contentHandler.characters(fieldValues[i].toCharArray(), 0, fieldValues[i].length()); contentHandler.endElement(XMLConstants.NULL_NS_URI, fields[i], ""); } contentHandler.endElement(XMLConstants.NULL_NS_URI, "record", ""); csvRecord = csvRecordReader.readLine(); } // Send the end of message events to the handler... contentHandler.endElement(XMLConstants.NULL_NS_URI, "message-root", ""); contentHandler.endDocument(); } // Other XMLReader methods... }
- Run the unit test class to see the following output on the console (formatted):
<message-root> <record> <firstname>Tom</firstname> <lastname>Jones</lastname> </record> <record> <firstname>Mike</firstname> <lastname>Jones</lastname> </record> <record> <firstname>Mark</firstname> <lastname>Jones</lastname> </record> </message-root>
After this, it is a case of expanding the tests, hardening the reader implementation code, and so on. Then you can use your reader to perform all sorts of operations supported by Smooks.
14.19. Configuring the Reader with java-binding-config.xml Example
java-binding-config.xml
) can be used to bind the names into a List
of PersonName
objects:
<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"> <reader class="com.acme.MyCSVReader"> <params> <param name="fields">firstname,lastname</param> </params> </reader> <jb:bean beanId="peopleNames" class="java.util.ArrayList" createOnElement="message-root"> <jb:wiring beanIdRef="personName" /> </jb:bean> <jb:bean beanId="personName" class="com.acme.PersonName" createOnElement="message-root/record"> <jb:value property="first" data="record/firstname" /> <jb:value property="last" data="record/lastname" /> </jb:bean> </smooks-resource-list>
public class MyCSVReaderTest extends TestCase { public void test_java_binding() { Smooks smooks = new Smooks(getClass().getResourceAsStream("java-binding-config.xml")); JavaResult javaResult = new JavaResult(); smooks.filterSource(new StreamSource(getClass().getResourceAsStream("names.csv")), javaResult); List<PersonName> peopleNames = (List<PersonName>) javaResult.getBean("peopleNames"); // TODO: add assertions etc } }
14.20. Tips for Using a Reader
- Reader instances are never used concurrently. Smooks Core will create a new instance for every message, or, will pool and reuse instances as per the
readerPoolSize
FilterSettings
property. - If your Reader requires access to the Smooks
ExecutionContext
for the current filtering context, your Reader needs to implement theorg.milyn.xml.SmooksXMLReader
interface. - If your Source data is a binary data stream your Reader must implement the
org.milyn.delivery.StreamReader
interface. - You can configure your reader within your source code (e.g. in your unit tests) using a
GenericReaderConfigurator
instance, which you then set on theSmooks
instance. - While the basic <reader> configuration is fine, it is possible to define a custom configuration namespace (XSD) for your custom CSV Reader implementation. This topic is not covered here. Review the source code to see the extended configuration namespace for the Reader implementations supplied with Smooks, e.g. the
EDIReader
,CSVReader
,JSONReader
etc. From this, you should be able to work out how to do this for your own custom Reader.
14.21. Binary Source Readers
org.milyn.delivery.StreamReader
interface. This is just a marker interface that tells the Smooks runtime to ensure that an InputStream
is supplied.
parse
method should use the InputStream
from the InputSource
(i.e. call InputSource..getByteStream()
instead of InputSource.getCharacterStream()
) and generate the XML events from the decoded binary data.
14.22. Implementing a Binary Source Reader
- To implement a binary source reader, observe the following
parse
method implementation:public static class BinaryFormatXXReader implements SmooksXMLReader, StreamReader { @ConfigParam private String xProtocolVersion; @ConfigParam private int someOtherXProtocolConfig; // etc... public void parse(InputSource inputSource) throws IOException, SAXException { // Use the InputStream (binary) on the InputSource... InputStream binStream = inputSource.getByteStream(); // Create and configure the data decoder... BinaryFormatXDecoder xDecoder = new BinaryFormatXDecoder(); xDecoder.setProtocolVersion(xProtocolVersion); xDecoder.setSomeOtherXProtocolConfig(someOtherXProtocolConfig); xDecoder.setXSource(binStream); // Generate the XML Events on the contentHandler... contentHandler.startDocument(); // Use xDecoder to fire startElement, endElement etc events on the contentHandler (see previous section)... contentHandler.endDocument(); } // etc.... }
- Configure the
BinaryFormatXXReader
reader in your Smooks configuration as you would any other reader:<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd"> <reader class="com.acme.BinaryFormatXXReader"> <params> <param name="xProtocolVersion">2.5.7</param> <param name="someOtherXProtocolConfig">1</param> ... etc... </params> </reader> ... Other Smooks configurations e.g. <jb:bean> configs for binding the binary data into Java objects... </smooks-resource-list>
- Run the Smooks execution code (note the
InputStream
supplied to theStreamSource
). In this case, two results are generated: XML and Java objects.StreamResult xmlResult = new StreamResult(xmlOutWriter); JavaResult javaResult = new JavaResult(); InputStream xBinaryInputStream = getXByteStream(); smooks.filterSource(new StreamSource(xBinaryInputStream), xmlResult, javaResult); // etc... Use the beans in the javaResult...
14.23. Visitor Implementations
ExecutionContext
and ApplicationContext
context objects, accomplishing a common goal by working together.
14.24. Supported Visitor Implementations
- SAX-based implementations based on the
org.milyn.delivery.sax.SAXVisitor
sub-interfaces. - DOM-based implementations based on the
org.milyn.delivery.dom.DOMVisitor
sub-interfaces.
14.25. SAX and DOM Visitor Implementations
Important
Smooks.filterSource
method. All state associated with the current Smooks.filterSource
execution must be stored in the ExecutionContext
.
14.26. The SAX Visitor API
org.xml.sax.ContentHandler
SAX events that a SAXVisitor
implementation can capture and processes. Depending on the use case being solved with the SAXVisitor
implementation, you may need to implement one or all of these interfaces.
14.27. SAX Visitor API Interfaces
org.milyn.delivery.sax.SAXVisitBefore
- Captures the
startElement
SAX event for the targeted fragment element:public interface SAXVisitBefore extends SAXVisitor { void visitBefore(SAXElement element, ExecutionContext executionContext) throws SmooksException, IOException; }
org.milyn.delivery.sax.SAXVisitChildren
- Captures the character based SAX events for the targeted fragment element, as well as Smooks generated (pseudo) events corresponding to the
startElement
events of child fragment elements:public interface SAXVisitChildren extends SAXVisitor { void onChildText(SAXElement element, SAXText childText, ExecutionContext executionContext) throws SmooksException, IOException; void onChildElement(SAXElement element, SAXElement childElement, ExecutionContext executionContext) throws SmooksException, IOException; }
org.milyn.delivery.sax.SAXVisitAfter
- Captures the
endElement
SAX event for the targeted fragment element:public interface SAXVisitAfter extends SAXVisitor { void visitAfter(SAXElement element, ExecutionContext executionContext) throws SmooksException, IOException; }
14.28. SAX Visitor API Example
This pulls together three interfaces into a single interface in the org.milyn.delivery.sax.SAXElementVisitor
interface:
<message> <target-fragment> <!-- SAXVisitBefore.visitBefore --> Text!! <!-- SAXVisitChildren.onChildText --> <child> <!-- SAXVisitChildren.onChildElement --> </child> </target-fragment> <!-- SAXVisitAfter.visitAfter --> </message>
org.milyn.delivery.sax.SAXElement
type is passed in all method calls. This object contains details about the targeted fragment element, including attributes and their values. It also contains methods for managing text accumulation, as well as accessing the Writer
associated with any StreamResult
instance that may have been passed in the Smooks.filterSource(Source, Result)
method call.
14.29. Text Accumulation with SAX
14.30. org.milyn.delivery.sax.SAXElement
org.milyn.delivery.sax.SAXElement
will always contain attribute data associated with the targeted element, but will not contain the fragment child text data, whose SAX events ( SAXVisitChildren.onChildText
) occur between the SAXVisitBefore.visitBefore
and SAXVisitAfter.visitAfter
events. The text events are not accumulated on the SAXElement
because that could result in a significant performance drain. The downside to this is that if the SAXVisitor
implementation needs access to the text content of a fragment, you need to explicitly tell Smooks to accumulate text for the targeted fragment. This is done by calling the SAXElement.accumulateText
method from inside the SAXVisitBefore.visitBefore
method implementation of your SAXVisitor
.
14.31. Text Accumulation Example
public class MyVisitor implements SAXVisitBefore, SAXVisitAfter { public void visitBefore(SAXElement element, ExecutionContext executionContext) throws SmooksException, IOException { element.accumulateText(); } public void visitAfter(SAXElement element, ExecutionContext executionContext) throws SmooksException, IOException { String fragmentText = element.getTextContent(); // ... etc ... } }
14.32. The @TextConsumer Annotation
@TextConsumer
annotation can be used to annotate your SAXVisitor
implementation instead of using the SAXVisitBefore.visitBefore
method.
14.33. @TextConsumer Example
@TextConsumer public class MyVisitor implements SAXVisitAfter { public void visitAfter(SAXElement element, ExecutionContext executionContext) throws SmooksException, IOException { String fragmentText = element.getTextContent(); // ... etc ... } }
Note
SAXVisitAfter.visitAfter
event.
14.34. StreamResult Writing/Serialization
The Smooks.filterSource(Source, Result)
method can take one or more of a number of different Result
type implementations, one of which is the javax.xml.transform.stream.StreamResult
class. Smooks streams the Source in and back out again through the StreamResult
instance.
StreamResult
instance provided to the Smooks.filterSource(Source, Result)
method. If the Source provided to the Smooks.filterSource(Source, Result)
method is an XML stream and a StreamResult
instance is provided as one of the Result
instances, the Source XML will be written out to the StreamResult
unmodified, unless the Smooks instance is configured with one or more SAXVisitor
implementations that modify one or more fragments.
14.35. Configuring StreamResult Writing/Serialization
- To turn the default serialization behavior on or off, access the filter settings and configure them to do so.
- To modify the serialized form of one of the message fragments, implement a
SAXVisitor
to perform the transformation and target it at the message fragment using an XPath-like expression. - To modify the serialized form of a message fragment, use one of the provided templating components. These components are also
SAXVisitor
implementations.
14.36. Implementing the SAXVisitor
- To implement a
SAXVisitor
geared towards transforming the serialized form of a fragment, program Smooks so theSAXVisitor
implementation will be writing to theStreamResult
. This is because Smooks supports targeting of multipleSAXVisitor
implementations at a single fragment, but only oneSAXVisitor
is allowed to write to theStreamResult
, per fragment. - If a second
SAXVisitor
attempts to write to theStreamResult
, aSAXWriterAccessException
will result and you will need to modify your Smooks configuration. - To specify the
StreamResult
to write, theSAXVisitor
needs to "acquire ownership" of theWriter
to theStreamResult
. It does this by making a call to theSAXElement.getWriter(SAXVisitor)
method from inside theSAXVisitBefore.visitBefore
methods implementation, passingthis
as theSAXVisitor
parameter.
14.37. SAXVisitor Implementation Example
public class MyVisitor implements SAXElementVisitor { public void visitBefore(SAXElement element, ExecutionContext executionContext) throws SmooksException, IOException { Writer writer = element.getWriter(this); // ... write the start of the fragment... } public void onChildText(SAXElement element, SAXText childText, ExecutionContext executionContext) throws SmooksException, IOException { Writer writer = element.getWriter(this); // ... write the child text... } public void onChildElement(SAXElement element, SAXElement childElement, ExecutionContext executionContext) throws SmooksException, IOException { } public void visitAfter(SAXElement element, ExecutionContext executionContext) throws SmooksException, IOException { Writer writer = element.getWriter(this); // ... close the fragment... } }
14.38. The SAXElement.setWriter
Writer
instance so it diverts serialization of the sub-fragments.
SAXVisitBefore.visitBefore
method just to make a call to the SAXElement.getWriter
method to acquire ownership of the Writer
. For this reason, we have the @StreamResultWriter
annotation. Used in combination with the @TextConsumer
annotation, it is only necessary to implement the SAXVisitAfter.visitAfter
method.
14.39. StreamResultWriter Example
@StreamResultWriter public class MyVisitor implements SAXVisitAfter { public void visitAfter(SAXElement element, ExecutionContext executionContext) throws SmooksException, IOException { Writer writer = element.getWriter(this); // ... serialize to the writer ... } }
14.40. SAXToXMLWriter
SAXToXMLWriter
class. It simplifies the process of serializing SAXElement
data as XML. This class allows you to write SAXVisitor
implementations.
14.41. SAXToXMLWriter Example
@StreamResultWriter public class MyVisitor implements SAXElementVisitor { private SAXToXMLWriter xmlWriter = new SAXToXMLWriter(this, true); public void visitBefore(SAXElement element, ExecutionContext executionContext) throws SmooksException, IOException { xmlWriter.writeStartElement(element); } public void onChildText(SAXElement element, SAXText childText, ExecutionContext executionContext) throws SmooksException, IOException { xmlWriter.writeText(childText, element); } public void onChildElement(SAXElement element, SAXElement childElement, ExecutionContext executionContext) throws SmooksException, IOException { } public void visitAfter(SAXElement element, ExecutionContext executionContext) throws SmooksException, IOException { xmlWriter.writeEndElement(element); } }
14.42. Configuring the SAXToXMLWriter
- When writing a
SAXVisitor
implementation with theSAXToXMLWriter
, set theSAXToXMLWriter
constructor to a boolean. This is theencodeSpecialChars
arg and it should be set based on therewriteEntities
filter setting. - Move the
@StreamResultWriter
annotation from the class and onto theSAXToXMLWriter
instance declaration. This results in Smooks creating theSAXToXMLWriter
instance which is then initialized with therewriteEntities
filter setting for the associated Smooks instance:@TextConsumer public class MyVisitor implements SAXVisitAfter { @StreamResultWriter private SAXToXMLWriter xmlWriter; public void visitAfter(SAXElement element, ExecutionContext executionContext) throws SmooksException, IOException { xmlWriter.writeStartElement(element); xmlWriter.writeText(element); xmlWriter.writeEndElement(element); } }
14.43. Visitor Configuration
SAXVisitor
configuration is useful for testing purposes and works in exactly the same way as any other Smooks component. When configuring Smooks Visitor instances, the configuration selector
is interpreted in a similar manner as an XPath expression. Visitor instances can be configured within program code on a Smooks instance.
14.44. Example Visitor Configuration
SAXVisitor
implementation as follows:
@TextConsumer public class ChangeItemState implements SAXVisitAfter { @StreamResultWriter private SAXToXMLWriter xmlWriter; @ConfigParam private String newState; public void visitAfter(SAXElement element, ExecutionContext executionContext) throws SmooksException, IOException { element.setAttribute("state", newState); xmlWriter.writeStartElement(element); xmlWriter.writeText(element); xmlWriter.writeEndElement(element); } }
ChangeItemState
to fire on <order-item> fragments having a status of OK
is shown below:
<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd"> <resource-config selector="order-items/order-item[@status = 'OK']"> <resource>com.acme.ChangeItemState </resource> <param name="newState">COMPLETED</param> </resource-config> </smooks-resource-list>
ChangeItemState
component. A custom configuration namespace component is configured as follows:
<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd" xmlns:order="http://www.acme.com/schemas/smooks/order.xsd"> <order:changeItemState itemElement="order-items/order-item[@status = 'OK']" newState="COMPLETED" /> </smooks-resource-list>
Smooks smooks = new Smooks(); smooks.addVisitor(new ChangeItemState().setNewState("COMPLETED"), "order-items/order-item[@status = 'OK']"); smooks.filterSource(new StreamSource(inReader), new StreamResult(outWriter));
14.45. The ExecutionLifecycleCleanable
ExecutionLifecycleCleanable
life-cycle interface will be able to perform post Smooks.filterSource
life-cycle operations. See the example below:
public interface ExecutionLifecycleCleanable extends Visitor { public abstract void executeExecutionLifecycleCleanup( ExecutionContext executionContext); }
executeExecutionLifecycleCleanup
calls):
smooks = new Smooks(..); smooks.filterSource(...); // ** VisitorXX.executeExecutionLifecycleCleanup ** smooks.filterSource(...); // ** VisitorXX.executeExecutionLifecycleCleanup ** smooks.filterSource(...); // ** VisitorXX.executeExecutionLifecycleCleanup ** ... etc ...
Smooks.filterSource
execution life-cycle can be cleaned up for the associated ExecutionContext
.
14.46. The VisitLifecycleCleanable
VisitLifecycleCleanable
life-cycle interface will be able to perform post SAXVisitAfter.visitAfter
life-cycle operations.
public interface VisitLifecycleCleanable extends Visitor { public abstract void executeVisitLifecycleCleanup(ExecutionContext executionContext); }
executeVisitLifecycleCleanup
calls):
smooks.filterSource(...); <message> <target-fragment> < --- VisitorXX.visitBefore Text!! < --- VisitorXX.onChildText <child> < --- VisitorXX.onChildElement </child> </target-fragment> < --- VisitorXX.visitAfter ** VisitorXX.executeVisitLifecycleCleanup ** <target-fragment> < --- VisitorXX.visitBefore Text!! < --- VisitorXX.onChildText <child> < --- VisitorXX.onChildElement </child> </target-fragment> < --- VisitorXX.visitAfter ** VisitorXX.executeVisitLifecycleCleanup ** </message> VisitorXX.executeExecutionLifecycleCleanup smooks.filterSource(...); <message> <target-fragment> < --- VisitorXX.visitBefore Text!! < --- VisitorXX.onChildText <child> < --- VisitorXX.onChildElement </child> </target-fragment> < --- VisitorXX.visitAfter ** VisitorXX.executeVisitLifecycleCleanup ** <target-fragment> < --- VisitorXX.visitBefore Text!! < --- VisitorXX.onChildText <child> < --- VisitorXX.onChildElement </child> </target-fragment> < --- VisitorXX.visitAfter ** VisitorXX.executeVisitLifecycleCleanup ** </message> VisitorXX.executeExecutionLifecycleCleanup
SAXVisitor
implementation can be cleaned up for the associated ExecutionContext
.
14.47. The ExecutionContext
ExecutionContext
is a context object for the storing of state information. It is scoped specifically around a single execution of a Smooks.filterSource
method. All Smooks Visitor implementations must be stateless within the context of a single Smooks.filterSource
execution, allowing the Visitor implementation to be used across multiple concurrent executions of the Smooks.filterSource
method. All data stored in an ExecutionContext
instance will be lost on completion of the Smooks.filterSource
execution. The ExecutionContext
is supplied in all Visitor API message event calls.
14.48. The ApplicationContext
ApplicationContext
is a context object for storing of state information. It is scoped around the associated Smooks
instance, that is, only one ApplicationContext
instance exists per Smooks
instance. This context object can be used to store data that needs to be maintained and accessible across multiple Smooks.filterSource
executions. Components (including SAXVisitor
components) can gain access to their associated ApplicationContext
instance by declaring an ApplicationContext
class property and annotating it with the @AppContext
annotation. See the example below:
public class MySmooksComponent { @AppContext private ApplicationContext appContext; // etc... }