6.4. Creating a Simple XML Command Language

Problem

You would like to capture commands in an XML document, and create a framework to execute these commands.

Solution

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( ).

Discussion

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.

See Also

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.156.251