Creación de reglas personalizadas con Apache Commons Digester

Este documento contiene una breve introducción sobre que es Apache Commons Digester y posee un ejemplo de cómo crear reglas personalizadas para utilizar desde archivos XML.

¿Qué es Apache Commons Digester?

Es una librería de clases que se utiliza para procesar archivos XML a través de la búsqueda de reglas.

Parseo de archivos XML

Existen dos métodos básicos para parsear documentos XML. Uno es utilizar el método DOM (Document Object Model). Cuando se parsean documentos con DOM, el parser lee el documento XML entero y crea una representación en forma de árbol del mismo.

El segundo método es utilizar SAX y parsear el documento XML a traves de eventos. Un parser SAX genera eventos cada vez que encuentra un tag XML (tags de comienzo, tags de fin, atributos, etc), lo cual permite procesar el documento.

El método DOM es más fácil de implementar, aunque es más lento ya que utiliza más recursos que SAX. Digester simplifica el parseo SAX brindando una interfaz de alto nivel a los eventos que genera SAX. Esta interfaz, oculta toda la complejidad que trae la navegación de documentos XML permitiendo al programador concentrarse en el procesamiento de los datos en vez de preocuparse por el parseo del documento.

Conceptos básicos

Digester introduce tres conceptos importantes: patrones de busqueda de elementos, reglas de procesamiento y pila de objetos.

Patrones de busqueda de elementos: asocia elementos XML con reglas de procesamiento. El siguiente ejemplo muestra los patrones de busqueda de elementos para una jerarquia XML.

<datasources>       --> 'datasources'
<datasource>      --> 'datasources/datasource'
<name/>       --> 'datasources/datasource/name'
<driver/>     --> 'datasources/datasource/driver'
</datasource>
<datasource>      --> 'datasources/datasource'
<name/>       --> 'datasources/datasource/name'
<driver/>     --> 'datasources/datasource/driver'
</datasource>
</datasources>

Cada vez que un patrón es encontrado, su regla asociada es ejecutada. En el ejemplo anterior el la regla asociada al patron ‘datasources/datasource’ se ejecuta dos veces.

Reglas de procesamiento: definen que sucede cuando Digester encuentra un patrón. Digester incluye reglas de procesamiento predefinidas. Pueden crearse reglas personalizadas. Una regla de procesamiento es una clase java que extiende org.apache.commons.digester.Rule. Cada regla puede implementar uno o más de los siguientes metodos (eventos) que son llamados cuando Digester encuentra un patrón asociado a dicha regla.

begin(): Este método es llamado apenas se encuentre el tag de apertura de un elemento XML.

Ej: <tag-apertura>

 

Este método recibe como parametro un objeto Attributes que contiene todos los atributos correspondientes a dicho elemento.

body(): Es llamado cuando es encontrado el contenido de un elemento. Se realiza un trim() al contenido como proceso del parseo.

Ej: <tag-apertura>Contenido del elemento tag-apertura</tag-apertura>

end(): Este método es llamado al encontrar el tag de cierre del elemento XML. Los tag de cierre son aquellos que contienen una "/" antes del nombre del tag.

Ej: </tag-apertura>

 

finish(): Este método es llamado cuando el parseo del documento es completado. Este permite limpiar todos los datos temporales que hayamos utilizado durante el precesamiento del documento.

Pila de objetos: es una pila que deja disponibles a los objetos para su manipulación a medida que se procesan las reglas. Los objetos pueden agregarse o eliminarse de la pila de objetos en forma manual o a traves de reglas de procesamiento.

Creacion de reglas personalizadas desde doumentos XML

FromXmlRuleSet es una implementacion de la clase RuleSet que inicializa Digester tomando las reglas definidas en un archivo XML. Para que Digester tome las reglas definidas en este archivo es necesario pasarle el path del archivo a su constructor

De manera alternativa, la clase DigesterLoader define un método estatico, (Digester.createDigester(String rulesXml) throws DigesterLoaderException). Cuando le pasamos el nombre del archivo que contiene las reglas de precesamiento, este método devuelve una instacia de Digester con las reglas iniciializadas.

Para crear nuestras reglas personalizadas necesitamos realizar lo siguiente:

  • Actualizar el DTD de digester para que acepte nuestras reglas. Se debe agregar un tipo de elemento para cada regla. El elemento debera tener un atributo correspondiente a cada parametro de inicializacion de la regla.
  • Extender DigesterRuleParser y definir un objectCreationFactory.
  • Crear nuestra regla.

 

DigesterRuleParser es una clase del tipo RuleSet para parsear reglas desde archivos XML. Se debera extender esta clase y sobreescribir el método addRuleInstances() para agregar las reglas para parsear tus nuevos elementos. A continuación se muestra un ejemplo de como crear una regla que evalúe una condición.

ConditionalRule

La idea es crear una regla que tenga tres atributos name, condition y property; Cada vez que Digester encuentre esta regla, primero buscara dentro del elemento que se esta parseando un atributo con el valor del nombre name, luego evaluara por verdadero el valor del atributo name con el valor del atributo condition, es decir que si ambos valors de atributos son iguales, seteará en el bean la propiedad especificada en property, con el valor del elemento que se este parseando.

Ejemplo:

Clase VO:

public class PublicacionVO {
String id = null;
String nombre = null;
String contenido = null;
Date fecha = null;
// Importante: el Bean al cual voy a setearle propiedades
// debera tener un contructor vacio. // Ya que digester lo utiliza para obtener una instancia. public PublicacionVO() { } public PublicacionVO(String id, String name,
String contenido, Date fecha) { this.id = id; this.name = name; this.contenido = contenido; this.fecha = fecha; } ... // Metodos getters; ... // Metodos setters; }

XML a parsear:

<?xml encoding="ISO-8859-1"?>
<publicaciones>
<assistkb>
<publicacion id="001" nombre="Titulo publicación 1"
fecha="12-08-1981"> Contenido de la publicación con ID-001. </publicacion> <publicacion id="002" nombre=" Titulo publicación 2"
fecha="13-07-1966"/> Contenido de la publicación con ID-002. </publicacion> </assistkb> </publicaciones>

Archivo de reglas XML:

<digester-rules>
<pattern value="publicaciones">
<object-create-rule pattern="assistkb/publicacion"
classname="com.assistsa.assistkb.publicacion.PublicacionVO"/>
<set-conditional-rule pattern="assistkb/publicacion" name="id"
condition="002" property="contenido"/>
</pattern>
</digester-rules>

La regla object-create-rule es una de las reglas predefinidas que viene con Digester, y crea un objeto del tipo "classname" cada vez que enecuentra el ‘pattern’ especificado y lo deja disponible en la pila de objetos para su manipulacion.

Aqui cada vez que Digester encuentre el patrón (atributo pattern) ‘publicaciones/assistkb/publicacion‘ buscará dentro del elemento publicación un atributo de nombre ‘id‘, obtendrá su valor y luego lo comparará con el valor del attributo condition del archivo de reglas, asi si ambos valores coinciden seteará en el objeto instanciado por la regla object-creation-rule la propiedad especificada en el atributo propery. Una vez que corramos Digester con este archivo de reglas, nuestro objeto deberia contener los siguientes datos:

PublicacionVO

id: null
nombre: null
contenido: 'Contenido de la publicación con ID-002.'
fecha: null

Los valores null en id, nombre y fecha se deben a que solamente hemos especificado una sola regla.

Ahora que ya tenemos la idea, vemos como implementar esta regla.

Actualizacion del DTD

Actualizamos el DTD de digester agregando los siguientes elementos:

<!-- Agregamos nuestro elemento a la lista
de elementos ya existentes. --> <!ENTITY % rule-elements "bean-property-setter-rule | call-method-rule |
call-param-rule | object-param-rule |
factory-create-rule | object-create-rule | set-properties-rule | set-property-rule
| set-conditional-rule | set-top-rule | set-next-rule"> <!ELEMENT digester-rules (pattern | include | bean-property-setter-rule
| call-method-rule | call-param-rule |
object-param-rule | factory-create-rule | object-create-rule | set-properties-rule |
set-property-rule | set-conditional-rule | set-top-rule | set-next-rule )*> <!ELEMENT pattern (pattern | include | bean-property-setter-rule |
call-method-rule | call-param-rule |
factory-create-rule | object-create-rule | set-conditional-rule | set-properties-rule |
set-property-rule | set-top-rule | set-next-rule )*> <!-- Definimos nuestro elemento. --> <!-- SetConditionalRule --> <!ELEMENT set-conditional-rule EMPTY> <!ATTLIST set-conditional-rule pattern CDATA #IMPLIED name CDATA #REQUIRED condition CDATA #REQUIRED property CDATA #REQUIRED>

Eso es todo lo que tenemos que modificar en el DTD, ahora debemos definir un ObjectCreationFactory.

Definicion de ObjectCreationFactory

Para definir un ObjectCreationFactory creamos una clase que extienda DigesterRuleParser la cual llamaremos CustomRuleParser.java y hacemos lo siguiente:

Primero creamos una fábrica de nuestros objetos SetConditionalRule. Esta clase la llamamos SetConditionalRuleFactory y es una inner class, que debera extender AbstractObjectCreationFactory. Esta estará definida en la clase CustomRuleParser.java.

La definicion de la inner class es la siguiente:

  protected class SetConditionalRuleFactory 
extends AbstractObjectCreationFactory { public Object createObject(Attributes attributes)
throws Exception { String name = attributes.getValue("name"); String condition = attributes.getValue("condition"); String property = attributes.getValue("property"); return new SetConditionalRule(name, condition, property); } }

Una vez que tenemos definida nuestra fábrica de objetos sobreescribimos el método "public void addRuleInstances(Digester digester)" y en su cuerpo agregamos lo siguente:

  // Inicializamos las reglas predefinidas de digester.
super.addRuleInstances(digester);
// Obtenemos el nombre de la clase de la regla que creamos.
final String ruleClassName = Rule.class.getName();
// Cada vez que encuentre el patrón '*/set-conditional-rule'
// en el archivo de reglas // agregara al objeto digester // una factory de nuestra regla. (SetConditionalRuleFactory()
// en una inner class que // definiremos luego.)
digester.addFactoryCreate("*/set-conditional-rule",
new SetConditionalRuleFactory()); // Agregamos la reglas pattern a nuestra regla. // Esto se hace para poder utilizar nested patterns.
// digester.addRule("*/set-conditional-rule",
new PatternRule("pattern")); // Ahora si agregamos nuestra regla al digester. digester.addSetNext("*/set-conditional-rule",
"add", ruleClassName);

Ahora sólo resta crear la clase Regla cuyo código es el siguiente:

public class SetConditionalRule extends Rule {
// El nombre del atributo a evaluar.
protected String attrName = null;
// Valor de la condicion.
protected String condition = null;
// Propiedad del Bean a setear.
protected String propertyBean = null;
// Valor del atributo especificado por attrName.
private String attrValue = null;
// String que posee el elemento que se esta parseando.
private String tagText = null;
// ---- Constructores ---- //
public SetConditionalRule(String attrName, 
String condition, String propertyBean) { this.attrName = attrName; this.condition = condition; this.propertyBean = propertyBean; } public SetConditionalRule(Digester digester) { this(); } public SetConditionalRule() { } // ---- Métodos ---- // public void begin(String namespace,
String localName, Attributes attributes) throws Exception { // Itero la cantidad de atributos que tiene el tag. for (int i = 0; i < attributes.getLength(); i++) { // tmpName tiene el valor del attributo
// leido por el parser. String tmpName = attributes.getLocalName(i); if ("".equals(tmpName)){ tmpName = attributes.getQName(i); } // Si el nombre leido por el parser es igual al
// que se le pasa por parametro // seteo la variable attrValue, para luego
// compararla con la condicion. if (tmpName.equals(attrName)){ attrValue=attributes.getValue(i); return; } attrValue = null; } } public void body(String namespace,
String localName, String bodyText) throws Exception { // Seteo el texto del tag. tagText = bodyText.trim(); } public void end(String namespace, String localName)
throws Exception { // Obtengo el objeto que voy a setear. // peek() me devuelve el objeto creado por la
// regla object-creation-rule. Object object = digester.peek(); // Compruebo que las variables sean validas. if (attrValue == null) throw new Exception("El valor del atributo debe ser
distinto de null."); if (condition == null) throw new Exception("El valor de la condición debe ser
distinto de null."); if (propertyBean == null) throw new Exception("La propiedad del bean debe
ser un nombre valido."); // Realizo la comparación del valor del atributo con la condicion. // Si es true seteo la propiedad del bean con el valor del tag. if (attrValue.equals(condition)) {
BeanUtils.setProperty(object, propertyBean, tagText);
}
}
}

Eso es todo, ahora para utilizar nuestra regla debemos hacer lo siguiente:

// Obtengo el archivo de reglas de digester.
File rules = new File("archivo-reglas.xml");
// Obtengo un objeto Set de reglas desde XML, pasandole 
// mi archivo de reglas y mi parser. FromXmlRuleSet xmlRuleSet = new FromXmlRuleSet(rules.toURL(),
// new CustomRuleParser()); // Obtengo una instancia de digester. Digester digester = new Digester(); // Al set de reglas le agrego el objeto digester. xmlRuleSet.addRuleInstances(digester); // Relleno el VO. PublicacionVO objPublicacion = (PublicacionVO) digester.parse(new File("publicaciones-assistkb.xml"));

Para más informacion sobre el uso de reglas predefinidas y uso general se recomienda la lectura de la API de Apache Digester, y su guia de usuario.

Java Users Group Argentina
http://cricava.com/java/detalleArticulos.php?id=453

Leave A Comment?