You would like to capture commands in an XML document, and create a framework to execute these commands.
Write a custom implementation of Rule
, and create a rule set that instructs
Commons Digester to invoke these rules when specific elements are
parsed. Consider the example of a system that sends an encrypted email.
The following XML document contains instructions for the primitive
encryption of an email:
<?xml version="1.0"?> <operations xmlns="http://discursive.com/textmanip"> <email to="tobrien@discursive.com" from="root@discursive.com"> <replace search="o" replace="q"/> <replace search="d" replace="z"/> <lower/> <reverse/> </email> </operations>
The email
tag surrounds
three elements—replace
, lower
, and reverse
. The system that executes these
commands receives a message as a String
and runs this String
through three stages before sending an
email to tobrien@discursive.com
.
When the parser encounters the replace
element, it replaces all occurrences
of the contents of the search
attribute with the contents of the replace
attribute. When the parser encounters
a lower
element, it translates all
characters to lowercase; and when the parser encounters a reverse
element, it reverses the String
. When the parser encounters the end of
the email
element, the result of
these four operations is sent to the recipient specified in the to
attribute of the email
element.
import org.apache.commons.digester.Digester; // Message object that holds text to manipulate Message message = new Message( ); message.setText( "Hello World!" ); System.out.println( "Initial Message: " + message.getText( ) ); // XML Document with set of commands InputStream encrypt = getClass( ).getResourceAsStream("./encrypt.xml"); // Create Custom Rules (or Commands) Digester digester = new Digester( ); digester.addRule( "*/email", new EmailRule( ) ); digester.addRule( "*/lower", new LowerRule( ) ); digester.addRule( "*/reverse", new ReverseRule( ) ); digester.addRule( "*/replace", new ReplaceRule( ) ); digester.push( message ); // Parse the XML document - execute commands digester.parse( encrypt ); System.out.println("Resulting Message: " + message.getText( ) );
The Message
object is a bean
with one String
property: text
. This Message
object is pushed onto the Digester
's Stack
and is acted upon by each of the
commands in the XML document encrypt.xml
, shown previously. This code is
executed, and the following output is produced showing that the original
message has been passed through two replace commands, a lowercase
command, and a reverse command:
Intial Message: Hello World! Resulting Message: !zlrqw qlleh
This example defines three new extensions of Rule
: EmailRule
, LowerRule
, ReverseRule
, and ReplaceRule
. Each of these rules will retrieve
and operate upon the root object from the Digester
; this "root" object is the bottom of
the Stack
, and, in this case, the
Message
object pushed onto the
Digester
before parsing. These rules
are assigned to patterns; for example, the previous code associates the
EmailRule
with the */email
pattern and the LowerRule
with the */lower
pattern. The Rule
object defines a series of callback
methods to handle different stages of parsing an element—begin( )
, body(
)
, end( )
, and finish( )
. The LowerRule
from the previous example overrides
one method, and manipulates the Message
that which is on the top of the
Digester
Stack
:
package com.discursive.jccook.xml.bean; import org.apache.commons.digester.Rule; import org.apache.commons.lang.StringUtils; public class LowerRule extends Rule { public LowerRule( ) { super( ); } public void body(String namespace, String name, String text) throws Exception { Message message = (Message) digester.getRoot( ); String lower = StringUtils.lowerCase( message.getText( ) ); message.setText( lower ); } }
LowerRule
uses StringUtils
from Commons Lang to translate the
text
property of the Message
object to lowercase. If you need to
write a Rule
that can access
attributes, you would override the begin(
)
method. The following class, ReplaceRule
, extends Rule
and overrides the begin( )
method:
package com.discursive.jccook.xml.bean; import org.apache.commons.digester.Rule; import org.apache.commons.lang.StringUtils; import org.xml.sax.Attributes; public class ReplaceRule extends Rule { public ReplaceRule( ) { super( ); } public void begin(Attributes attributes) throws Exception { Message message = (Message) digester.getRoot( ); String repl = attributes.getValue("search"); String with = attributes.getValue("replace"); String text = message.getText( ); String translated = StringUtils.replace( text, repl, with ); message.setText( translated ); } }
ReplaceRule
reads the search
and replace
attributes, using StringUtils
to replace all occurrences of the
search String
in the text
property of Message
with the replace String
. The EmailRule
demonstrates a more complex
extension of the Rule
object by
overriding begin( )
and end( )
:
import org.apache.commons.digester.Rule; import org.apache.commons.net.smtp.SMTPClient; import org.xml.sax.Attributes; public class EmailRule extends Rule { private String to; private String from; public EmailRule( ) { super( ); } public void begin(Attributes attributes) throws Exception { to = attributes.getValue( "to" ); from = attributes.getValue( "from" ); } public void end( ) throws Exception { Message message = (Message) digester.getRoot( ); SMTPClient client = new SMTPClient( ); client.connect("www.discursive.com"); client.sendSimpleMessage(from, to, message.getText( ) ); } }
The email
element encloses the
four elements that control the primitive message encryption, and the end
of this element tells this rule to send an email to the address
specified in the to
attribute
recorded in begin()
. EmailRule
uses the SMTPClient
from Commons Net to send a simple
email in end( )
.
The Rule
class defines four
methods that you can override to execute code when XML is
parsed—begin( )
, body( )
, end(
)
, and finish( )
. begin( )
provides access to an element's
attributes; body( )
provides access
to the element's namespace, local name, and body text; end( )
is called when the end of an element is
encountered; and finish( )
is called
after end( )
and can be used to clean
up data or release resources, such as open network connections or files.
When using the Digester in this manner, you are using a technique much
closer to writing a SAX parser; instead of dealing with a single
startElement
, Digester
registers itself as a content handler
with an XMLReader
and delegates to
Rule
objects associated with a given
pattern. If you are simply dealing with attributes, elements, and text
nodes, Commons Digester can be a very straightforward alternative to
writing a SAX parser.
This recipe uses StringUtils
to
manipulate text. For more information about
StringUtils
, see Chapter 2. An email message is sent
from the EmailRule
using the SMTPClient
from Commons Net. For more
information about Commons Net, see Chapter
10.