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="[email protected]" from="[email protected]"> <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 [email protected]
. 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.
3.133.156.251