Java y XML con JAXB2 (V)
En el post que toca veremos más configuraciones posibles al compilar el Schema. Hoy veremos como configurar las priopiedades/elementos y como determinar el tipo de datos con el que las clases Java almacenan los valores de los elementos y atributos del XML.
Configuración de propiedades
Si las configuraciones de clase
estaban relacionadas principalmente con los tipos complejos, las
configuraciones de propiedades lo hacen con los elementos más simples del
Schema, element y attribute, que son traducidos a datos miembros en las
clases Java generadas. Estas configuraciones se aplican con la etiqueta
<xjb:property> y podemos destacar las siguiente:
- Nombre de la propiedad, por defecto es el mismo en el schema y la clase Java, pero a través del atributo name podemos designar otro nombre.
- generateIsSetMethod, collectionType, fixedAttributeAsConstantProperty sobreescriben el posible valor configurado en los globalBindings para la propiedad seleccionada.
Además de estos atributos, existe la etiqueta <xjs:baseType> que a su vez contiene la etiqueta <xjs:javaType> con la que podemos configurar que tipo de datos va a contener la propiedad.
Propiedades de Tipo de Datos Java
Estas propiedades de configuración permiten definir el tipo de datos java que almacenará la propiedad. Estos tipos de datos suelen ser los definidos por Java o una clase propia que debe tener un constructor vació. En ambas situaciones, JAXB crea una clase Adapter, para que podamos realizar ciertas transformaciones o realizar llamadas a operaciones de transformación al realizar la lectura y escritura del XML. Los atributos de configuración de la etiqueta <xjs:javaType> son:
-
name, nombre del tipo de datos que va a recoger el valor del XML, puede
ser una clase propia de Java o una creada para al efecto.
- xmlType, es obligatoria y solo se usa cuando la configuración javaType se aplica dentro de globalBindings, y a través de ella decidimos en que tipos de datos XML se aplicará la configuración.
- parseMethod y
printMethod son las propiedades que designan a que métodos llamará
JAXB2. El valor de estas propiedades debe ser la ruta completa a la
operación (paquete.clase.operación) y las operaciones serán desde el
Adapter cuando cuando se realiza el marshall y unmarshall
respectivamente. Las operaciones deben, en el caso del
unmarshall/lectura recibir un String y retornar un instancia de la
propia clase y en el caso del marshall/escritura, la inversa, recibir
una instancia de la clase y retornar un String. Por supuesto estas
operaciones no tienen porque estar en la clase designada en el atributo
name (pensar que si se selecciona un tipo de datos java no podríais
modificar la Clase)
Para ello, debemos realizar la siguiente configuración (modo inline) en nuestro Schema:
<xs:element name="campo-uno-uno" type="xs:int">
<xs:annotation>
<xs:documentation>
campo-uno-uno sera trasladado al tipo
com.empresa.EnteroDecimal
</xs:documentation>
<xs:appinfo>
<jxb:property>
<jxb:baseType>
<jxb:javaType name="com.empresa.EnteroDecimal"
parseMethod="com.empresa.EnteroDecimal.crearEnteroDecimal"
printMethod="com.empresa.EnteroDecimal.escribirEntero"/>
</jxb:baseType>
</jxb:property>
</xs:appinfo>
</xs:annotation>
</xs:element>
<xs:element name="campo-uno-dos" type="xs:double" minOccurs="1">
<xs:annotation>
<xs:documentation>
Denominamos a campo-uno-dos campoDecimal y sin
operaciones is set
</xs:documentation>
<xs:appinfo>
<jxb:property name="campoDecimal"
generateIsSetMethod="false"
/>
</xs:appinfo>
</xs:annotation>
</xs:element>
La clase EnteroDecimal debe poseer los métodos indicados en la configuración y responder a las exigencia de JAXB, por tanto la clase debe tener un constructor vacío, el método crearEnteroDecimal debe ser estático, recibir un String y retornar una instancia de EnteroDecimal y el método escribirEntero la inversa. Veamoslo:
public class EnteroDecimal {
private Double decimal = new Double(0.0);
public EnteroDecimal(){}
public static EnteroDecimal crearEnteroDecimal(String valorEntero){
EnteroDecimal traducido = new EnteroDecimal();
traducido.setDecimal(new Double(valorEntero));
return traducido;
}
public static String escribirEntero(EnteroDecimal dec){
return ""+ dec.getDecimal().intValue();
}
public void setDecimal(Double decimal) {
this.decimal = decimal;
}
public Double getDecimal() {
return decimal;
}
}
Al ejecutar XJC mediante nuestra tarea de ANT, vemos los resultados:
- Se ha creado una clase Adapter1 dentro del paquete configurado en nuestro schemaBinding.
package org.lasterra.jaxb2;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import com.empresa.EnteroDecimal;
public class Adapter1
extends XmlAdapter<String, EnteroDecimal>
{
public EnteroDecimal unmarshal(String value) {
return (com.empresa.EnteroDecimal.crearEnteroDecimal(value));
}
public String marshal(EnteroDecimal value) {
return (com.empresa.EnteroDecimal.escribirEntero(value));
}
} - La
clase campoSecundario relacionada con el tipo complejo campo-uno
muestra los cambios de las configuraciones del ejemplo con varias
anotaciones nuevas y la inclusión del nuevo tipo de datos EnteroDecimal
para campoUnoUno y el nuevo nombre de campo-uno-dos. El método
isSetCampoDecimal ya no es generado por XJC.
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "campo-uno", propOrder = {
"campoUnoUno",
"campoDecimal"
})
public class ElementoSecundario {
@XmlElement(name = "campo-uno-uno", required = true, type = String.class)
@XmlJavaTypeAdapter(Adapter1 .class)
@XmlSchemaType(name = "int")
protected EnteroDecimal campoUnoUno;
@XmlElement(name = "campo-uno-dos")
protected double campoDecimal;
@XmlAttribute
protected String attr;Hay que destacar como en la etiqueta XmlElement del campoUnoUno se utiliza como tipo String para que JAXB2 haga las lecturas y escrituras del XML usando este tipo, eso sí, pasando por el Adapter1. Otra nota, no he conseguido encontrar forma de renombrar la clase Adapter1 con la configuración.
La configuración externa sería la siguiente
....
<jxb:bindings node="//xs:element[@name='campo-uno-uno']">
<jxb:property>
<jxb:baseType>
<jxb:javaType name="com.empresa.EnteroDecimal"
parseMethod="com.empresa.EnteroDecimal.crearEnteroDecimal"
printMethod="com.empresa.EnteroDecimal.escribirEntero"/>
</jxb:baseType>
</jxb:property>
</jxb:bindings>
<jxb:bindings node="//xs:element[@name='campo-uno-dos']">
<jxb:property name="campoDecimal"
generateIsSetMethod="false"
/>
</jxb:bindings>
...
Hemos visto como realizar configuraciones sobre los elementos básicos del Schema, y como manejar los tipos de datos haciendo posible transformaciones entre el Schema y nuestras clases Java.





