martes, 1 de febrero de 2011

Anotaciones en XStream


En un post anterior hablé sobre XStream. En este veremos como se pueden usar anotaciones en dicha librería.

XStream soporta anotaciones que hacen las cosas un poco más simples, pueden encontrar más información aquí.

Vamos a serguir trabajando con los POJOs del post anterior Person y PhoneNumber, pero ahora supongamos que el xml que debemos escribir tiene ciertas reglas, a saber:
  1. las etiquetas del xml para clase Person, cuando el nombre es una combinación de varias palabras, deben llevar separadas las palabras el caracter "_" sin las comillas.
  2. cuando un campo de tipo PhoneNumber sea serializado deberá llevar el atributo type con el valor "phone_number"

Vamos a usar la anotación XStreamAlias que se puede aplicar tanto a clases como a campos dentro de una clase y elimina la necesidad de crear los alias individualmente para cada clase y cada campo dentro de ellas.

ejemplo 1 - pojos usando anotaciones
public class PhoneNumber {
  private int code;
  private String number;
  // ... constructores y métodos
}

@XStreamAlias("person")
public class Person {
  @XStreamAlias("first_name") private String firstname;
  @XStreamAlias("last_name") private String lastname;
  private PhoneNumber phone;
  private PhoneNumber fax;
  // ... constructores y métodos
}

Por alguna razón que aun no termino de entender XStream reemplaza todos los "_" por "__" por lo cual debemos crear un nuevo XmlFriendlyReplacer.

ejemplo 2 - creando un nuevo XmlFriendlyReplacer
new XmlFriendlyReplacer("__", "_")

La mejor forma que encontré para agregar el atributo type de acuerdo a las reglas dadas es crear una implementación de XppDriver.

IMPORTANTE: debemos usar XStream con la librería XPP3.

ejemplo 3 - creando una implementación de XppDriver
new XppDriver(new XmlFriendlyReplacer("__", "_"))
{
  @Override
  public HierarchicalStreamWriter createWriter(Writer out) 
  {
    return new PrettyPrintWriter(out, this.xmlFriendlyReplacer())
    {
      
      @Override
      public void startNode(String name, Class type) 
      {
        super.startNode(name, type);
        if (PhoneNumber.class.isAssignableFrom(type)) {
          addAttribute("type", "phone_number");
        }
      }
    };
  }
}

Ahora ponemos todo junto y obtenemos lo siguiente.

ejemplo 4 - usando XStream con anotaciones y un nuevo XppDriver
XStream xstream = new XStream(new XppDriver(new XmlFriendlyReplacer("__", "_"))
{
  @Override
  public HierarchicalStreamWriter createWriter(Writer out) 
  {
    return new PrettyPrintWriter(out, this.xmlFriendlyReplacer())
    {
      
      @Override
      public void startNode(String name, Class type) 
      {
        super.startNode(name, type);
        if (PhoneNumber.class.isAssignableFrom(type)) {
          addAttribute("type", "phone_number");
        }
      }
    };
  }
});
xstream.processAnnotations(new Class[]{Person.class});//[1] procesa todas las anotaciones
xstream.autodetectAnnotations(true);//[2] habilita autodeteción de clases anotadas.

Person joe = new Person("Joe", "Walnes");
joe.setPhone(new PhoneNumber(123, "1234-456"));
joe.setFax(new PhoneNumber(123, "9999-999"));
String xml = xstream.toXML(joe);

Si revisamos el contenido de la variable xml veremos que se ha generado un xml con las condiciones indicadas al inicio de este post.

ejemplo 5 - contenido de la variable xml

  Joe
  Walnes
  
    123
    1234-456
  
  
    123
    9999-999
  



Hasta la próxima. :D

[Notas]
1 Las anotaciones tienen la desventaja de que es necesario procesarlas antes de poder leer un archivo XML; Con processAnnotations nos aseguramos las clases pasadas como parámetro están listas para usarse tanto para leer como para escribir xml.

2 autodetectAnnotations le permite a XStream autodetectar clases anotadas y automatizar su procesamiento pero debemos recordar que llamar al método processAnnotations desactiva esta funcionalidad.