All existing Smooks functionality (Java Binding, EDI processing etc) is built through the extension of a number of well defined APIs.
The main extension points/APIs in Smooks are Reader and Visitor 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.
All Smooks components are configured in exactly the same way. When using the Smooks Core code, all Smooks components are resources which are configured using a SmooksResourceConfiguration instance.
Smooks provides mechanisms for constructing namespace (XSD) specific XML configurations for components. The most basic configuration (and the one that maps directly to the SmooksResourceConfiguration class) is the basic <resource-config> XML configuration from the base configuration namespace (http://www.milyn.org/xsd/smooks-1.1.xsd).
Copy to ClipboardCopied!Toggle word wrapToggle overflow
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.
Smooks takes care of all the details of creating the runtime representation of the resource (for example, constructing the class named in the the resource element) and injects all the configuration parameters. It also works out what the resource type is, and from that, how to interpret things like the selector. (For example, if the resource is a Visitor instance, it knows the selector is an XPath, selecting a Source message fragment.)
After your component has been created, you need to configure it with the <param> element details. This is done using the @ConfigParam and @Config annotations.
The @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.
This annotation eliminates excess code from your component because it:
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 the config 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 of ON and OFF. 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 are REQUIRED, but setting a defaultVal implicitly marks the property as being OPTIONAL.
The @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.
Sometimes your component needs more involved configuration for which we need to write some "initialization" code. For this, Smooks provides the @Initialize annotation.
Likewise, there are times when you need to undo work performed during initialization when the associated Smooks instance is being discarded (garbage collected) e.g. to release some resources acquired during initialization. For this, Smooks provides the @Uninitialize annotation.
This is 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
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
Copy to ClipboardCopied!Toggle word wrapToggle overflow
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...
}
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...
}
Copy to ClipboardCopied!Toggle word wrapToggle overflow
When using the @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 the Smooks.close method.
Smooks supports a mechanism for defining custom configuration namespaces for components. This allows you to support custom, XSD-based, configurations for your components that can be validated instead of treating them all as generic Smooks resources using the <resource-config> base configuration.
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
The easiest way to get familiar with this mechanism is by looking at existing extended namespace configurations within the Smooks code itself. All Smooks components (including the Java Binding functionality) use this mechanism for defining their configurations. Smooks Core itself defines a number of extended configuration namespaces.
You can implement a source reader for your custom data format which immediately opens all Smooks capabilities to that data format such as Java Binding, Templating, Persistence, Validation, Splitting, Routing etc. The only Smooks requirement is that the Reader implements the standard 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.
You should first implement a basic reader class as shown below:
public class MyCSVReader implements SmooksXMLReader
{
// Implement all of the XMLReader methods...
}
public class MyCSVReader implements SmooksXMLReader
{
// Implement all of the XMLReader methods...
}
Copy to ClipboardCopied!Toggle word wrapToggle overflow
Two methods from the org.xml.sax.XMLReader interface are of particular interest:
setContentHandler(ContentHandler) is called by Smooks Core. It sets the org.xml.sax.ContentHandler instance for the reader. The org.xml.sax.ContentHandler instance methods are called from inside the parse(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 the org.xml.sax.ContentHandler instance supplied in the setContentHandler(ContentHandler) method.
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...
}
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...
}
Copy to ClipboardCopied!Toggle word wrapToggle overflow
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
Tom,Jones
Mike,Jones
Mark,Jones
Copy to ClipboardCopied!Toggle word wrapToggle overflow
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 the mycsvread-config.xml:
Copy to ClipboardCopied!Toggle word wrapToggle overflow
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
}
}
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
}
}
Copy to ClipboardCopied!Toggle word wrapToggle overflow
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...
}
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...
}
Copy to ClipboardCopied!Toggle word wrapToggle overflow
Run the unit test class to see the following output on the console (formatted):
Copy to ClipboardCopied!Toggle word wrapToggle overflow
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.
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 readerPoolSizeFilterSettings property.
If your Reader requires access to the Smooks ExecutionContext for the current filtering context, your Reader needs to implement the org.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 the Smooks 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.
A binary source reader is a reader for a binary data source. Your reader should implement the org.milyn.delivery.StreamReader interface. This is just a marker interface that tells the Smooks runtime to ensure that an InputStream is supplied.
The binary Reader implementation is essentially the same as a non-binary Reader implementation (see above), except that the implementation of the the 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.
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....
}
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....
}
Copy to ClipboardCopied!Toggle word wrapToggle overflow
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>
<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>
Copy to ClipboardCopied!Toggle word wrapToggle overflow
Run the Smooks execution code (note the InputStream supplied to the StreamSource). 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...
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...
Copy to ClipboardCopied!Toggle word wrapToggle overflow
Visitor implementations are the workhorses of Smooks. Most of the out-of-the-box functionality in Smooks (Java Binding, Templating, Persistence, and so on) was created by using one or more Visitor implementations. Visitor implementations often collaborate through the ExecutionContext and ApplicationContext context objects, accomplishing a common goal by working together.
Your implementation can support both SAX and DOM, but Red Hat recommends implementing a SAX only Visitor. SAX-based implementations are usually easier to create and perform faster.
Important
All Visitor implementations are treated as stateless objects. A single Visitor instance must be usable concurrently across multiple messages, that is, across multiple concurrent calls to the Smooks.filterSource method. All state associated with the current Smooks.filterSource execution must be stored in the ExecutionContext.
The SAX Visitor API is made up of a number of interfaces. These interfaces are based on the 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.
Copy to ClipboardCopied!Toggle word wrapToggle overflow
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:
Copy to ClipboardCopied!Toggle word wrapToggle overflow
The above is an illustration of a Source message event stream as XML. It could be EDI, CSV, JSON, or some other format. Consider it to be a Source message event stream, serialized as XML for easy reading.
As can be seen from the above SAX interfaces, the 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.
SAX is a stream based processing model. It doesn't create a Document Object Model (DOM) or "accumulate" event data in any way. This is why it is a suitable processing model for processing huge message streams.
The 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.
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.
By default, Smooks will always serialize the full Source event stream as XML to any 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.
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.
To implement a SAXVisitor geared towards transforming the serialized form of a fragment, program Smooks so the SAXVisitor implementation will be writing to the StreamResult. This is because Smooks supports targeting of multiple SAXVisitor implementations at a single fragment, but only one SAXVisitor is allowed to write to the StreamResult, per fragment.
If a second SAXVisitor attempts to write to the StreamResult, a SAXWriterAccessException will result and you will need to modify your Smooks configuration.
To specify the StreamResult to write, the SAXVisitor needs to "acquire ownership" of the Writer to the StreamResult. It does this by making a call to the SAXElement.getWriter(SAXVisitor) method from inside the SAXVisitBefore.visitBefore methods implementation, passing this as the SAXVisitor parameter.
The SAXElement.setWriter lets you control the serialization of sub-fragments you need to reset the Writer instance so it diverts serialization of the sub-fragments.
Sometimes you know that the target fragment you are serializing/transforming will never have sub-fragments. In this situation, it is inefficient implement the 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.
Smooks provides the SAXToXMLWriter class. It simplifies the process of serializing SAXElement data as XML. This class allows you to write SAXVisitor implementations.
When writing a SAXVisitor implementation with the SAXToXMLWriter, set the SAXToXMLWriter constructor to a boolean. This is the encodeSpecialChars arg and it should be set based on the rewriteEntities filter setting.
Move the @StreamResultWriter annotation from the class and onto the SAXToXMLWriter instance declaration. This results in Smooks creating the SAXToXMLWriter instance which is then initialized with the rewriteEntities 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);
}
}
@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);
}
}
Copy to ClipboardCopied!Toggle word wrapToggle overflow
The 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.
Copy to ClipboardCopied!Toggle word wrapToggle overflow
Custom configuration namespaces can be used to define a cleaner and more strongly typed configuration for the ChangeItemState component. A custom configuration namespace component is configured as follows:
Visitor components implementing the 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);
}
public interface ExecutionLifecycleCleanable extends Visitor
{
public abstract void executeExecutionLifecycleCleanup(
ExecutionContext executionContext);
}
Copy to ClipboardCopied!Toggle word wrapToggle overflow
The basic call sequence can be described as follows (note the executeExecutionLifecycleCleanup calls):
Copy to ClipboardCopied!Toggle word wrapToggle overflow
This life-cycle method allows you to ensure that resources scoped around the Smooks.filterSource execution life-cycle can be cleaned up for the associated ExecutionContext.
Visitor components implementing the 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);
}
public interface VisitLifecycleCleanable extends Visitor
{
public abstract void executeVisitLifecycleCleanup(ExecutionContext executionContext);
}
Copy to ClipboardCopied!Toggle word wrapToggle overflow
The basic call sequence can be described as follows (note the executeVisitLifecycleCleanup calls):
Copy to ClipboardCopied!Toggle word wrapToggle overflow
This life-cycle method allows you to ensure that resources scoped around a single fragment execution of a SAXVisitor implementation can be cleaned up for the associated ExecutionContext.
The 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.
The 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...
}
public class MySmooksComponent
{
@AppContext
private ApplicationContext appContext;
// etc...
}
Copy to ClipboardCopied!Toggle word wrapToggle overflow
We help Red Hat users innovate and achieve their goals with our products and services with content they can trust. Explore our recent updates.
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.