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
selectorattribute is the mechanism by which the resource is "selected" (for example, it can be an XPath for a Visitor implementation). - The
resourceelement 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
paramelements 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
choiceconstraint for theconfigproperty, 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 ofONandOFF. 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 adefaultValimplicitly 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
@Initializeand@Uninitializemethods must be public, zero-arg methods. - The
@ConfigParamproperties are all initialized before the first@Initializemethod is called. Therefore, you can use the@ConfigParamcomponent properties as input to the initialization process. - The
@Uninitializemethods are all called in response to a call to theSmooks.closemethod.
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
SmooksResourceConfigurationinstance. 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.xmlpostfix.
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.XMLReaderinterface are of particular interest:setContentHandler(ContentHandler)is called by Smooks Core. It sets theorg.xml.sax.ContentHandlerinstance for the reader. Theorg.xml.sax.ContentHandlerinstance 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.ContentHandlerinstance 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
parsemethod: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
readerPoolSizeFilterSettingsproperty. - If your Reader requires access to the Smooks
ExecutionContextfor the current filtering context, your Reader needs to implement theorg.milyn.xml.SmooksXMLReaderinterface. - If your Source data is a binary data stream your Reader must implement the
org.milyn.delivery.StreamReaderinterface. - You can configure your reader within your source code (e.g. in your unit tests) using a
GenericReaderConfiguratorinstance, which you then set on theSmooksinstance. - 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,JSONReaderetc. 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
parsemethod 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
BinaryFormatXXReaderreader 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
InputStreamsupplied 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.SAXVisitorsub-interfaces. - DOM-based implementations based on the
org.milyn.delivery.dom.DOMVisitorsub-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
startElementSAX 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
startElementevents 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
endElementSAX 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
SAXVisitorto 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
SAXVisitorimplementations.
14.36. Implementing the SAXVisitor
- To implement a
SAXVisitorgeared towards transforming the serialized form of a fragment, program Smooks so theSAXVisitorimplementation will be writing to theStreamResult. This is because Smooks supports targeting of multipleSAXVisitorimplementations at a single fragment, but only oneSAXVisitoris allowed to write to theStreamResult, per fragment. - If a second
SAXVisitorattempts to write to theStreamResult, aSAXWriterAccessExceptionwill result and you will need to modify your Smooks configuration. - To specify the
StreamResultto write, theSAXVisitorneeds to "acquire ownership" of theWriterto theStreamResult. It does this by making a call to theSAXElement.getWriter(SAXVisitor)method from inside theSAXVisitBefore.visitBeforemethods implementation, passingthisas theSAXVisitorparameter.
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
SAXVisitorimplementation with theSAXToXMLWriter, set theSAXToXMLWriterconstructor to a boolean. This is theencodeSpecialCharsarg and it should be set based on therewriteEntitiesfilter setting. - Move the
@StreamResultWriterannotation from the class and onto theSAXToXMLWriterinstance declaration. This results in Smooks creating theSAXToXMLWriterinstance which is then initialized with therewriteEntitiesfilter 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...
}

Where did the comment section go?
Red Hat's documentation publication system recently went through an upgrade to enable speedier, more mobile-friendly content. We decided to re-evaluate our commenting platform to ensure that it meets your expectations and serves as an optimal feedback mechanism. During this redesign, we invite your input on providing feedback on Red Hat documentation via the discussion platform.