Guía para la migración mental de OX2 a OX3

Este guía es para ayudar a un desarrollador OpenXava 2 a empezar rápidamente con OpenXava 3.
OX3 será completamente compatible con OX2 y podremos ejecutar nuestras aplicaciones OX2 usando OX3, además podemos continuar desarrollando con XML en OX3.

Definición de componentes

La principal diferencia entre OX2 y OX3 es el formato para definir componentes.
En OX2 usamos XML, como sigue:
<componente nombre="Profesor">
 
    <entidad>
        <propiedad nombre="codigo" tipo="String"
            clave="true" longitud="5" requerido="true"/>
        <propiedad nombre="nombre" tipo="String"
            longitud="40" requerido="true"/>
        <coleccion nombre="alumnos">
            <referencia modelo="Alumno"/>
        </coleccion>
        <metodo nombre="anadirAlumno">
            <calculador clase="org.openxava.escuela.calculadores.CalculadorAnadirAlumno"/>
        </metodo>
    </entidad>
 
    <mapeo-entidad tabla="MYSCHOOL@separator@TEACHER">
        <mapeo-propiedad
            propiedad-modelo="codigo" columna-tabla="ID"/>
        <mapeo-propiedad
            propiedad-modelo="nombre" columna-tabla="NAME"/>
    </mapeo-entidad>
 
</componente>

En OX3 usamos Java con anotaciones:
package org.openxava.escuela.modelo;
 
import javax.persistence.*;
import org.openxava.annotations.*;
 
/**
 *
 * @author Javier Paniza
 */
 
@Entity
public class Profesor {
 
    @Id @Column(length=5) @Required
    private String codigo;
 
    @Column(length=40) @Required
    private String nombre;
 
    public String getCodigo() {
        return codigo;
    }
 
    public void setCodigo(String codigo) {
        this.codigo = codigo;
    }
 
    public String getNombre() {
        return nombre;
    }
 
    public void setNombre(String nombre) {
        this.nombre = nombre;
    }
 
}
 
Ahora podemos escribir la lógica directamente en nuestras clases Java, por tanto no es necesario usar calculadores para propiedades calculadas o métodos.
El mapeo objeto-relacional se hace mediante las anotaciones JPA. Y el resto (tab y vista) es una traducción literal del XML de OpenXava a anotaciones Java.

Agregados

Los agregados ya no existen en OX3. Pero podemos simular los agregados usando objetos embebidos para las referencias simples, y colecciones de entidades con borrar en cascada para las colecciones.
Es decir, en OX escribiamos:
<entidad>
    <referencia nombre="direccion" modelo="Direccion"/>
</entidad>
 
<agregado nombre="Direccion">
...
</agregado>
Y en OX3 escribimos:
// Cliente.java
@Entity
public class Cliente {
 
    @Embedded
    private Direccion direccion;
 
}
 
// Direccion.java
@Embeddable
public class Direccion {
 
}
Como podemos ver, usamos objetos embebidos JPA que funcionan exactamente igual que los agregados de OpenXava para el caso de una referencia simple.
Para colecciones de agregados con OX2 escribimos:
<componente nombre="Factura">
 
    <entidad>
        <coleccion nombre="lineas">
            <referencia modelo="LineaFactura"/>
        </coleccion>
    </entidad>
 
    <agregado nombre="LineaFactura">
    ...
    </aggregate>
 
</component>
Pero en OX3 escribimos:
// Factura.java
@Entity
public class Factura {
 
    @OneToMany (mappedBy="factura", cascade=CascadeType.REMOVE)
    private Collection<LineaFactura> details;
 
}
 
// LineaFactura.java
@Entity
public class LineaFactura {
 
}
Es decir, usamos una coleccion de entidades con borrar en cascada, y esto se comporta como una colección de agregados de OX2.

Valores posibles

Los <valores-posibles/> de OX2 se implementan en OX3 usando enums de Java 5.
Es decir, en OX2 escribimos:
<propiedad nombre="distancia">
    <valores-posibles>
        <valor-posible valor="local"/>
        <valor-posible valor="nacional"/>
        <valor-posible valor="internacional"/>
    </valores-posibles>
</propiedad>
Su equivalente en OX3 es:
private Distancia distancia;
public enum Distancia { LOCAL, NACIONAL, INTERNACIONAL };
 
Tiene el mismo efecto, con la diferencia de que:
  • El tipo de dato no es int, sino Distancia.
  • Al grabar en la base de datos los valores posibles graban por defecto 0 para vacío, 1 para local, 2 para naciona y 3 para internacional, mientras que el enum graba nulo para vacío, 0 para LOCAL, 1 para NACIONAL y 2 para INTERNACIONAL.
Es decir, si queremos usar una database de OX2 con OX3 necesitamos usar un Hibernate Type, como sigue:
@org.hibernate.annotations.Type(type="org.openxava.types.Base1EnumType",
parameters={
    @Parameter(name="enumType", value="org.openxava.test.modelo.LineaFactura$TipoServicio")
}
)
private TipoServicio tipoServicio;
public enum TipoServicio { ESPECIAL, URGENTE }
 
Base1EnumType es un Hibernate Type incluido en OpenXava para grabar un enum de Java 5 usando índice de base 1. De esta manera podemos ir contra la base de datos de nuestra aplicación OX3 usando OX2.

Calculadores valor defecto al crear

El calculador-valor-defecto de OX2 esta disponible en OX3 mediante la anotación @DefaultValueCalculator. Pero @DefaultValueCalculator no soporta al-crear="true" de su homólogo en XML. Hemos de simular el efecto de este tipo de calculador usando su equivalente en JPA.
Para un id autogenerado podemos usar la anotacion JPA @GeneratedValue. Por ejemplo, en OX2 escribimos:
<propidad nombre="codigo" tipo="Integer" clave="true" longitud="5" requerido="false">
    <calculador-valor-defecto clase="org.openxava.calculators.IdentityCalculator" al-crear="true"/>
</propiedad>
En OX3 escribimos:
@Id @Column(length=5)
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer codigo;
Es decir, código JPA puro y duro.
Pero <calculador-valor-defecto ... al-crear="true"/> es más flexible que @GeneratedValue porque admite cualquier lógica, y puede ser usada por propiedades no clave. En este caso podemos usar un método @PrePersist de JPA. Veamos un ejemplo. Cuando en OX2 escribimos:
<propiedad nombre="oid" tipo="String" clave="true" oculta="true">
    <calculador-valor-defecto
        clase="org.openxava.test.calculadores.CalculadorLineaFacturaOid"
        al-crear="true"/>
</propiedad>
 
Donde InvoiceDetailOidCalculator tiene una lógica personalizada para generar el valor. Esto puede ser fácilmente traducido a OX3 añadiendo el siguiente método a nuestro POJO:
@PrePersist
private void calcularOID() {
    oid = factura.getAño() + ":" + factura.getNumero() + ":" + System.currentTimeMillis();
}
 
En este caso la lógica del método calcularOID is la misma que la de InvoiceDetailOIDCalculator. Y una vez más usamos código JPA puro y duro.

Calculadores de retrollamada: poscrear, postmodificar, poscargar, preborrar and posborrar

En lugar de <calculador-poscrear/>, <calculador-posmodificar/>, <postload-calculator/>, <preremove-calculator/> y <postremove-calculator/> en OX3 usamos los métodos de retrollamada de JPA, es decir, métodos en nuestras entidades anotados con @PrePersist, @PostPersist, @PreRemove, @PostRemove, @PreUpdate, @PostUpdate y @PostLoad.
Por ejemplo, si tenemos una colección como esta en un componente Factura:
<coleccion nombre="lineas">
    <referencia modelo="LineaFactura"/>
    <calculador-posborrar
        clase="org.openxava.test.calculadores.CalculadorPosborrarLineaFactura"/>
</coleccion>
Con esta clase calculador:
package org.openxava.test.calculadores;
 
import java.rmi.*;
 
import org.openxava.calculators.*;
import org.openxava.test.ejb.*;
 
/**
 * @author Javier Paniza
 */
public class CalculadorPosborrarLineaFactura implements IModelCalculator {
 
    private IFactura factura;
 
    public Object calculate() throws Exception {
        factura.setComentario(factura.getComentario() + "DETALLE BORRADO");
        return null;
    }
 
    public void setModel(Object modelo) throws RemoteException {
        this.factura = (IFactura) modelo;
    }
 
}
En OX3 solo necesitamos tener un método como este en la clase LineaFactura:
@PostRemove
private void despuesDeBorrar() {
    factura.setComentario(factura.getComentario() + "DETALLE BORRADO");
}
 
En este caso, más fácil que en OX2.

Conversores

Los utilísimos conversores de OpenXava no están disponibles en OX3.Pero no nos debemos preocupar demasiado, porque podemos usar Type de Hibernate, que proporciona casi toda la funcionalidad de los conversores de OpenXava.
Mira la Referencia de Hibernate sobre Type,
Type de Hibernate tiene algunos inconvenientes sobre los clásicos conversores de OpenXava:
  • Los conversores por defecto (del archivo defaults-converter.xml y conversores.xml) no están soportados.
  • Conversores para referencias no se soportan.

persistence.xml en vez de configuration

En el build.xml la propiedad 'configuration' ya no se usa. Ahora en vez de los archivos de propiedades podemos comentar y descomentar código en persistence.xml para poder trabajar con varias configuraciones, de esta manera:
    <!-- Tomcat + Hypersonic -->
    <persistence-unit name="default">
        <non-jta-data-source>java:comp/env/jdbc/MySchoolDS</non-jta-data-source>
        <class>org.openxava.session.GalleryImage</class>
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
        </properties>
    </persistence-unit>
 
    <!-- JBoss + Hypersonic
    <persistence-unit name="default">
        <non-jta-data-source>java:/MiEscuelaDS</non-jta-data-source>
        <class>org.openxava.session.GalleryImage</class>
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
        </properties>
    </persistence-unit>
    -->
 
    <!-- WebSphere + AS/400
    <persistence-unit name="default">
        <non-jta-data-source>jdbc/MiEscuelaDS</non-jta-data-source>
        <class>org.openxava.session.GalleryImage</class>
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.DB2400Dialect"/>
            <property name="hibernate.show_sql" value="false"/>
        </properties>
    </persistence-unit>
    -->
 
Aquí tenemos 3 configuraciones y podemos activar una de ellas fácilmente con solo comentar o descomentar.

Pruebas JUnit: Poniendo valor a un combo para una referencia con clave compuesta

En OX3 ésta es la forma de poner valor a un combo con clave compuesta:
Envio envio = (Envio) Envio.findAll().iterator().next();
setValue("envio.KEY", toKeyString(envio));
 
Como se ve, hemos de usar el método toKeyString() (incluido en ModuleTestBase), y no el método toString() del POJO.
Esta técnica también funciona con OX2.

Propiedad de vista

Las propiedades de vista no existen en OX3. Pero es fácil simularlos usando propiedades transitorias en nuestro modelo.
Es decir, en OX2 escribimos:
<vista>
    <propiedad nombre="entregadoPor">
        <valores-posibles>
            <valor-posible valor="empleado"/>
            <valor-posible valor="transportista"/>
        </valores-posibles>
        <calculador-valor-defecto
             clase="org.openxava.calculators.IntegerCalculator">
            <poner propiedad="value" valor="0"/>
        </calculador-valor-defecto>
    </propiedad>
 
    <vista-propiedad propiedad="entregadoPor">
        <al-cambiar clase="org.openxava.test.actiones.AlCambiarEntregadoPor"/>
    </vista-propiedad>
    ...
</vista>
Y ahora con OX3 escribimos:
@Transient
@DefaultValueCalculator(value=EnumCalculator.class,
    properties={
        @PropertyValue(name="enumType", value="org.openxava.test.modelo.Albaran$EntregadoPor")
        @PropertyValue(name="value", value="TRANSPORTISTA")
    }
)
@OnChange(AlCambiarEntradoPor.class)
private EntragadoPor entregadoPor;
public enum EntregadoPor { TRABAJADOR, TRANSPORTISTA }
Con el mismo efecto.