openxava / documentación / Capítulo 2: Modelo XML (clásico)

1.Introducción XML | 2.Modelo XML | 3.Vista XML | 4.Tab XML | 5.Mapeo XML | 6.Aspectos XML

Tabla de contenidos

Capítulo 2: Modelo XML (clásico)
Implementación en Java
Componente de negocio
Entidad y agregados
Entidad
Bean (1)
EJB (2)
Implementa (3)
Propiedad (4)
Estereotipo
Estereotipo GALERIA_IMAGENES (nuevo en v2.0)
Concurrencia y propiedad versión (nuevo en v2.2.3)
Valores posibles
Calculador
Calculador valor defecto
Validador
Validador por defecto (nuevo en v2.0.3)
Cálculo (nuevo en v5.7)
Referencia (5)
Calculador valor por defecto para una referencia
Colección (6)
Método (7)
Buscador (8)
Calculador poscrear (9)
Calculador posmodificar (11)
Calculadores poscargar y preborrar (10, 12)
Validador (13)
Validador borrar (14)
Agregado
Referencia a agregado
Colección de agregados
Relaciones de muchos-a-muchos
La capa del modelo en una aplicación orientada a objetos es la que contiene la lógica de negocio, esto es la estructura de los datos con los que se trabaja y todos los cálculos, validaciones y procesos asociados a esos datos.
OpenXava es un marco orientado al modelo, en donde el modelo es lo más importante, y todo lo demás (p. ej. la interfaz gráfica) depende de él.
La forma de definir el modelo en OpenXava es mediante XML y un poquito de Java. OpenXava a partir de nuestra definición genera el código Java que implementa ese modelo.

Implementación en Java

Actualmente OpenXava genera código para las siguiente 4 alternativas:
  1. Clases de Java convencionales (los famosos POJOs) para el modelo usando Hibernate para la persistencia (nuevo en v2.1).
  2. Clases de Java convencionales para el modelo usando EJB3 JPA (Java Persistence API) para la persistencia.
  3. Los clásicos EntityBeans de EJB2 para el modelo y la persistencia.
  4. POJOs + Hibernate dentro de un contenedor EJB.
La opción 2 es la opción por defecto (nuevo en v3.0) y la más adecuada para la mayoría de los casos. La opción 1 es también buena, especialmente si necesitamos usar Java 1.4. La opción 3 es para soportar todas las aplicaciones OpenXava escritas con EJB2 (EJB2 era la única opción en OpenXava 1.x). La opción 4 puede ser útil en ciertas circunstancias. Podemos ver como configurar esto en OpenXavaTest/properties/xava.properties.

Componente de negocio

Como bien hemos visto la unidad básica para crear una aplicación OpenXava es el componente de negocio. Un componente de negocio se define en un archivo XML. La estructura de un componente de negocio en OpenXava es:
<?xml version="1.0" encoding="ISO-8859-1"?>
 
<!DOCTYPE component SYSTEM "dtds/componente.dtd">
 
<componente nombre="NombreComponente">
 
    <!-- Modelo -->
    <entidad>...</entidad>
    <agregado nombre="...">...</agregado>
    <agregado nombre="...">...</agregado>
    ...
 
    <!-- Vista -->
    <vista>...</vista>
    <vista nombre="...">...</vista>
    <vista nombre="...">...</vista>
    ...
 
    <!-- Datos tabulares -->
    <tab>...</tab>
    <tab nombre="...">...</tab>
    <tab nombre="...">...</tab>
    ...
 
    <!-- Mapeo objeto relacional -->
    <mapeo-entidad tabla="...">...</mapeo-entidad>
    <mapeo-agregado agregado="..." tabla="...">...</mapeo-agregado>
    <mapeo-agregado agregado="..." tabla="...">...</mapeo-agregado>
    ...
 
</component>
La primera parte del componente, la parte de entidad y de los agregados, es la que se usa para definir el modelo. En este capítulo vamos a ver la sintaxis completa de esta parte.

Entidad y agregados

La definición de la entidad y de los agregados es prácticamente idéntica. La entidad es el objeto principal que representa al concepto de negocio, mientras que los agregados son objetos adicionales necesarios para definir el concepto de negocio pero que por sí solos no pueden tener vida propia. Por ejemplo, al definir un componente Factura, los datos de cabecera de la factura estarían en la entidad, mientras que para las líneas podríamos poner un agregado LineaFactura; podemos notar como el ciclo de vida de un línea de factura está ligado a la factura, esto es una línea de factura sin factura no tiene sentido, y compartir una línea entre varias facturas no es posible, por eso lo modelamos como un agregado.
Formalmente, la relación entre A y B es agregación, y B puede ser modelada como un agregado cuando:
A veces un mismo concepto puede ser modelado como agregado o entidad en otro componente. Por ejemplo, el concepto dirección. Si la dirección es compartida por varias personas se debería modelar como una referencia a otra entidad, mientras que sí cada persona tiene su propia dirección puede que lo más práctico sea un agregado.

Entidad

La sintaxis de la entidad es como sigue:
<entidad>
    <bean ... />                    <!--  1 -->
    <ejb ... />                     <!--  2 -->
    <implementa .../>               <!--  3 -->
    <propiedad .../>                <!--  4 -->
    <referencia .../>               <!--  5 -->
    <coleccion .../>                <!--  6 -->
    <metodo .../>                   <!--  7 -->
    <buscador .../>                 <!--  8 -->
    <calculador-poscrear .../>      <!--  9 -->
    <calculador-poscargar .../>     <!-- 10 -->
    <calculador-posmodificar .../>  <!-- 11 -->
    <calculador-preborrar .../>     <!-- 12 -->
    <validador .../>                <!-- 13 -->
    <validador-borrar .../>         <!-- 14 -->
</entidad>
  1. bean (uno, opcional): Permite usar un JavaBean existente (una simple clase Java, uno de esos famosos POJOs). Esto aplica si usamos JPA o Hibernate como gestor de persistencia. En este caso la generación de código para POJO y mapeo de Hibernate para este componente no se produce.
  2. ejb (uno, opcional): Permite usar un EJB existente. Esto solo aplica si usamos EJB CMP2 para gestionar la persistencia. En este caso la generación de código EJB para este componente no se produce. No aplica a EJB3.
  3. implementa (varios, opcional): Para que el código generado implemente una o varias interfaces Java cualquiera.
  4. propiedad (varias, opcional): Las propiedades representan propiedades Java (con su setter y getter) en el código generado.
  5. referencia (varias, opcional): Referencias a otros modelos, podemos referenciar a la entidad de otro componente o a un agregado del nuestro.
  6. coleccion (varias, opcional): Colecciones de referencias, en el código generado se convierten en una propiedad que devuelve un java.util.Collection.
  7. metodo (varios, opcional): Para crear un método en el código generado, en este caso la lógica del método estará contenida en un calculador (ICalculator).
  8. buscador (varios, opcional): Usado para crear métodos de búsqueda. Los buscadores son métodos estáticos localizados en las clase POJO. En el caso de generación EJB2 se generan finders de EJB2.
  9. calculador-poscrear (varios, opcional): Lógica a ejecutar después de hacer el objeto persistente. En Hibernate en el evento PreInsertEvent, en EJB2 en el método ejbPostCreate.
  10. calculador-poscargar (varios, opcional): Lógica a ejecutar justo después de cargar el estado del objeto desde el almacenamiento persistente. En Hibernate en el evento PostLoadEvent, en EJB2 en el método ejbLoad.
  11. calculador-posmodificar (varios, opcional): Lógica a ejecutar después de modificar el objeto persistente y antes de grabarlo en el almacenamiento persistente. En Hibernate en el evento PreUpdateEvent, en EJB2 en el método ejbStore.
  12. calculador-preborrar (varios, opcional): Lógica a ejecutar justo antes de borrar el objeto persistente. En Hibernate en el evento PreDeleteEvent, en EJB2 en el método ejbRemove.
  13. validador (varios, opcional): Ejecuta una validación a nivel de modelo. Este validador puede recibir el valor de varias propiedades del modelo. Para validar una sola propiedad es preferible poner el validador a nivel de propiedad.
  14. validador-borrar (varios, opcional): Se ejecuta antes de borrar, y tiene la posibilidad de vetar el borrado del objeto.

Bean (1)

Con <bean/> podemos especificar que deseamos usar nuestra propia clase Java.
Por ejemplo:
<entidad>
    <bean clase="org.openxava.test.modelo.Familia"/>
    ...
De esta forma tan simple podemos escribir nuestro propio código Java en vez de dejar que OpenXava lo genere.
Por ejemplo podemos escribir la clase Familia como sigue:
package org.openxava.test.modelo;
 
import java.io.*;
 
/**
 * @author Javier Paniza
 */
public class Familia implements Serializable {
 
    private String oid;
    private int codigo;
    private String descripcion;
 
    public String getOid() {
        return oid;
    }
    public void setOid(String oid) {
        this.oid = oid;
    }
 
    public int getCodigo() {
        return codigo;
    }
    public void setCodigo(int codigo) {
        this.codigo = codigo;
    }
 
    public String getDescripcion() {
        return descripcion;
    }
    public void setDescripcion(String descripcion) {
        this.descripcion = descripcion;
    }
 
}
Si queremos referenciar desde el código generado por OpenXava a nuestro propio código necesitamos que nuestra clase Java implemente una interfaz (IFamilia en este caso) que extienda de IModel (ver org.openxava.test.Family en OpenXavaTest/src).
Adicionalmente tenemos que definir el mapeo usando Hibernate:
<?xml version="1.0"?>
 
<!DOCTYPE hibernate-mapping
    SYSTEM "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
 
<hibernate-mapping package="org.openxava.test.modelo">
 
  <class
      name="Familia"
      table="XAVATEST@separator@FAMILIA">
 
    <id name="oid" column="OID" access="field">
        <generator class="uuid"/>
    </id>
 
    <property name="codigo" column="CODIGO"/>
    <property name="descripcion" column="DESCRIPCION"/>
 
  </class>
 
</hibernate-mapping>
Podemos poner este archivo en la carpetea hibernate de nuestro proyecto. Además en esta carpeta tenemos el archivo hibernate.cfg.xml que hemos de editar de esta forma:
    ...
    <session-factory>
        ...
        <mapping resource="Familia.hbm.xml"/>
        ...
    </session-factory>
    ...
De esta forma tan sencilla podemos envolver nuestras clases y mapeos de hibernate existentes usando OpenXava. Por supuesto, si estamos creando un sistema nuevo es mucho mejor dejar la generación del código en manos de OpenXava.

EJB (2)

Con <ejb/> podemos especificar que queremos usar un EJB propio.
Por ejemplo:
<entidad>
    <ejb remote="org.openxava.test.ejb.Family"
        home="org.openxava.test.ejb.FamilyHome"
        primaryKey="org.openxava.test.ejb.FamilyKey"
        jndi="ejb/openxava.test/Family"/>
    ...
De esta forma tan sencilla podemos escribir nuestro código EJB a mano, en vez de usar el código que OpenXava genera.
El código EJB lo podemos escribir manualmente de forma íntegra (pero eso solo si somos hombre de verdad), si somos programadores al uso(quiero decir vagos) preferiremos usar los asistentes de un IDE, o mejor aún XDoclet. Si optamos por usar XDoclet, podemos poner nuestras clases XDoclet en el paquete modelo (o cualquier otro paquete, depende del valor que demos a la variable model.package en nuestro build.xml) de la carpeta src de nuestro proyecto; y nuestro código XDoclet se generará junto con el resto de código OpenXava.
Para nuestro ejemplo podriamos escribir una clase FamiliaBean como sigue:
package org.openxava.test.ejb.xejb;
 
import java.util.*;
import javax.ejb.*;
 
import org.openxava.calculators.*;
import org.openxava.test.ejb.*;
 
/**
 * @ejb:bean name="Familia" type="CMP" view-type="remote"
 *     jndi-name="OpenXavaTest/ejb/openxava.test/Familia"
 * @ejb:interface extends="org.openxava.ejbx.EJBReplicable"
 * @ejb:data-object extends="java.lang.Object"
 * @ejb:home extends="javax.ejb.EJBHome"
 * @ejb:pk extends="java.lang.Object"
 *
 * @jboss:table-name "XAVATEST@separator@FAMILIA"
 *
 * @author Javier Paniza
 */
abstract public class FamiliaBean
    extends org.openxava.ejbx.EJBReplicableBase // 1
    implements javax.ejb.EntityBean {
 
    private UUIDCalculator calculadorOid = new UUIDCalculator();
 
    /**
      * @ejb:interface-method
      * @ejb:pk-field
      * @ejb:persistent-field
      *
      * @jboss:column-name "OID"
      */
    public abstract String getOid();
    public abstract void setOid(String nuevoOid);
 
    /**
     * @ejb:interface-method
     * @ejb:persistent-field
     *
     * @jboss:column-name "CODIGO"
     */
    public abstract int getCodigo();
    /**
     * @ejb:interface-method
     */
    public abstract void setCodigo(int newCodigo);
 
    /**
     * @ejb:interface-method
     * @ejb:persistent-field
     *
     * @jboss:column-name "DESCRIPCION"
     */
    public abstract String getDescripcion();
    /**
     * @ejb:interface-method
     */
    public abstract void setDescripcion(String newDescripcion);
 
    /**
     * @ejb:create-method
     */
    public FamilyKey ejbCreate(Map propiedades) // 2
        throws
            javax.ejb.CreateException,
            org.openxava.validators.ValidationException,
            java.rmi.RemoteException {
        executeSets(propiedades);
        try {
            setOid((String)calculadorOid.calculate());
        }
        catch (Exception ex) {
            ex.printStackTrace();
            throw new EJBException(
                "Imposible crear Familia por:\n" +
                ex.getLocalizedMessage()
            );
        }
        return null;
    }
 
    public void ejbPostCreate(Map propiedades) throws javax.ejb.CreateException {
    }
}
Al escribir nuestro EJB tenemos que observar dos pequeñas restricciones:
  1. La clase ha de descender de org.openxava.ejbx.EJBReplicableBase
  2. Ha de haber al menos un ejbCreate (con su ejbPostCreate) que reciba como argumento un mapa y asigne sus valores al bean como en este ejemplo.
Sí, sí, es un poco intrusivo, pero ¿no son los EJB la culminación de la intrusión?

Implementa (3)

Con <implementa/> especificamos una interfaz Java que será implementada por el código generado. Como sigue:
<entidad>
    <implementa interfaz="org.openxava.test.modelo.IConNombre"/>
    ...
    <propiedad nombre="nombre" tipo="String" requerido="true"/>
    ...
Y podemos hacer nuestra interfaz Java:
package org.openxava.test.modelo;
 
import java.rmi.*;
 
/**
 * @author Javier Paniza
 */
public interface IConNombre {
 
    String getNombre() throws RemoteException;
 
}
Hemos de tener cuidado para que el código generado implemente nuestra interfaz. En este caso tenemos una propiedad nombre que generará un método getNombre() y por ende se implementará la interfaz.
En nuestro código generado nos encontramos una interfaz ICliente como sigue:
public interface ICliente extends org.openxava.test.modelo.IConNombre {
    ...
}
In the POJO generated code you can see:
public class Cliente implements Serializable, org.openxava.test.modelo.ICliente {
    ...
}
En el código EJB generado (si es que generamos EJB) podremos observar en la interfaz remota
public interface ClienteRemote extends
    org.openxava.ejbx.EJBReplicable,
    org.openxava.test.modelo.ICliente
y la clase del bean también se ve afectada
abstract public class ClienteBean extends EJBReplicableBase
    implements
        org.openxava.test.modelo.ICliente,
        EntityBean
Esta jugosa característica hace del polimorfismo un invitado privilegiado de OpenXava.
Se puede ver como OpenXava genera una interfaz por cada componente. Es bueno usar estas interfaces en lugar de los POJOs o las interfaces remotas de EJB2. Todo el código generado de esta forma puede usarse en una versión POJO y EJB2 al mismo tiempo. Esto también facilitará una posible migración del código de EJB2 a POJO. Aunque, si trabajamos usando POJOs exclusivamente podemos escoger trabajar directamente con las clases POJO ignorando las interfaces, al gusto.

Propiedad (4)

Una propiedad OpenXava corresponde exactamente a una propiedad Java. Representa parte del estado de un objeto que se puede consultar y en algunos casos cambiar. El objeto no tiene la obligación de guardar físicamente la información de la propiedad, solo de devolverla cuando se le pregunte.
La sintaxis para definir una propiedad es:
<propiedad
    nombre="nombrePropiedad"         <!--  1 -->
    etiqueta="etiqueta"              <!--  2 -->
    tipo="tipo"                      <!--  3 -->
    estereotipo="ESTEREOTIPO"        <!--  4 -->
    longitud="longitud"              <!--  5 -->
    escala="longitud"                <!--  6  nuevo en v2.0.4 -->
    requerido="true|false"           <!--  7 -->
    clave="true|false"               <!--  8 -->
    oculta="true|false"              <!--  9 -->
    clave-busqueda="true|false"      <!-- 10  nuevo en v2.2.4 -->
    version="true|false"             <!-- 11  nuevo en v2.2.3 -->
>
    <valores-posibles .../>          <!-- 12 -->
    <calculador .../>                <!-- 13 -->
    <calculador-valor-defecto .../>  <!-- 14 -->
    <calculo .../>                   <!-- 15  nuevo en v5.7 -->
    <validador .../>                 <!-- 16 -->
</propiedad>
  1. nombre (obligado): Es el nombre que tendrá la propiedad en Java, por lo tanto ha de seguir la normativa para nombres de propiedad Java, entre ellas empezar por minúscula. Se desaconseja el uso de subrayados (_).
  2. etiqueta (opcional): Etiqueta que se mostrará al usuario final. Es mucho mejor usar los archivos i18n.
  3. tipo (opcional): Corresponde a un tipo Java. Todos los tipos válidos para una propiedad Java son válidos aquí, esto incluye clases definidas por nosotros mismos. Solo hemos de proveer un conversor para grabar en la base de datos y un editor para visualizar en HTML; así que cosas como una java.sql.Connection o así puede que sean complicadas de tratar, pero no imposible. Es opcional, pero solo si hemos especificado <bean/> o <ejb/> antes o asignamos un estereotipo con un tipo asociado.
  4. estereotipo (opcional): Permite especificar un comportamiento especial para cierta propiedades.
  5. longitud (opcional): Longitud en caracteres de la propiedad. Especialmente útil a la hora de generar interfaces gráficas. Si no especificamos longitud asume un valor por defecto asociado al tipo o estereotipo que se obtiene de default-size.xml o longitud-defecto.xml.
  6. escala (opcional): (nuevo en v2.0.4) Escala (tamaño de la parte decimal) de la propiedad. Solo aplica a propiedades numéricas. Si no especificamos escala asume un valor por defecto asociado al tipo o estereotipo que se obtiene de default-size.xml o longitud-defecto.xml.
  7. requerido (opcional): Indica si esa propiedad es requerida. Por defecto es true para las propiedades clave ocultas (nuevo en v2.1.3) o sin calculador valor por defecto al crear y false en todos los demás casos. Al grabar OpenXava comprobará si las propiedades requeridas están presentes, si no lo están no se producirá la grabación y se devolverá una lista de errores de validación. La lógica para determinar si una propiedad está presente o no se puede configurar creando un archivo validators.xml o validadores.xml en nuestro proyecto. Podemos ver la sintaxis en OpenXava/xava/validators.xml.
  8. clave (opcional): Para indicar si una propiedad forma parte de la clave. Al menos una propiedad (o referencia) ha de ser clave. La combinación de propiedades (y referencias) clave se debe mapear a un conjunto de campos en la base de datos que no tengan valores repetidos, típicamente con la clave primaria.
  9. oculta (opcional): Una propiedad oculta es aquella que tiene sentido para el desarrollador pero no para el usuario. Las propiedades ocultas se excluyen cuando se generan interfaces gráficas automáticas, sin embargo a nivel de código generado están presentes y son totalmente funcionales, incluso si se les hace alusión explicita podrían aparecer en una interfaz gráfica.
  10. clave-busqueda (opcional): (nuevo en v2.2.4) Las propiedades clave de búsqueda se usan por los usuarios para buscar los objetos. Son editables en la interfaz de usuario de las referencias permitiendo al usuario teclear su valor para buscar. OpenXava usa las propiedades clave (clave="true") para buscar por defecto, y si la propiedades clave (clave="true") están ocultas usa la primera propiedad en la vista. Con clave-busqueda podemos elegir las propiedades para buscar explicitamente.
  11. version (opcional): (nuevo en v2.2.3) Una propiedad versión se usa para el control de concurrencia optimista. Si queremos control de concurrencia solo necesitamos tener una propiedad marcada como version="true" en nuestro componente. Solo podemos especificar una propiedad de versión por componente. Los siguientes tipos son soportados para propiedades versión: int, Integer, short, Short, long, Long, Timestamp. Las propiedades de versión también se consideran ocultas.
  12. valores-posibles (uno, opcional): Para indicar que la propiedad en cuestión solo puede tener un conjunto de valore fijos.
  13. calculador (uno, opcional): Para implementar la lógica de una propiedad calculada. Una propiedad calculada solo tiene getter y no se almacena en la base de datos.
  14. calculador-valor-defecto (uno, opcional): Para implementar la lógica para calcular el valor inicial de la propiedad. Una propiedad con calculador-valor-defecto sí tiene setter y es persistente.
  15. calculo (uno, opcional): (nuevo en v5.7) Expresión aritmética para calcular el valor de la propiedad. El cálculo se hace en la interfaz de usuario cuando alguno de los operandos cambia.
  16. validador (varios, opcional): Indica la lógica de validación a ejecutar sobre el valor a asignar a esta propiedad antes de crear o modificar.

Estereotipo

Un estereotipo es la forma de determinar un comportamiento especifico dentro de un tipo. Por ejemplo, un nombre, un comentario, una descripción, etc. todos corresponden al tipo Java java.lang.String pero si queremos que los validadores, logitud por defecto, editores visuales, etc. sean diferente en cada caso y necesitamos afinar más; lo podemos hacer asignando un esterotipo a cada uno de estos casos. Es decir, podemos tener los estereotipos NOMBRE, TEXTO_GRANDE o DESCRIPCION y asignarlos a nuestras propiedades.
El OpenXava viene configurado con los siguientes estereotipos:
Vamos a ver como definiríamos un estereotipo propio. Crearemos uno llamado NOMBRE_PERSONA para representar nombres de persona.
Editamos (o creamos) el archivo editors.xml o editores.xml en nuestra carpeta xava. Y añadimos
<editor url="editorNombrePersona.jsp">
    <para-estereotipo estereotipo="NOMBRE_PERSONA"/>
</editor>
De esta forma indicamos que editor se ha de ejecutar para editar y visualizar propiedades con el estereotipo NOMBRE_PERSONA.
También podemos editar stereotype-type-default.xml o tipo-estereotipo-defecto.xml y añadir la línea:
<para estereotipo="NOMBRE_PERSONA" tipo="String"/>
Además es útil indicar la longitud por defecto, eso se hace editando default-size.xml o longitud-defecto.xml:
<para-estereotipo nombre="NOMBRE_PERSONA" longitud="40"/>
Y así si no ponemos longitud asumirá 40 por defecto.
Menos común es querer cambiar el validador para requerido, pero si queremos cambiarlo lo podemos hacer añadiendo a validators.xml o validadores.xml de nuestro proyecto lo siguiente:
<validador-requerido>
    <clase-validador clase="org.openxava.validators.NotBlankCharacterValidator"/>
    <para-estereotipo estereotipo="NOMBRE_PERSONA"/>
</validador-requerido>
Ahora podemos definir propiedades con estereotipo NOMBRE_PERSONA:
<propiedad nombre="nombre" estereotipo="NOMBRE_PERSONA" requerido="true"/>
En este caso asume 40 longitud y tipo String, así como ejecutar el validador NotBlankCharacterValidator para comprobar que es requerido.

Estereotipo GALERIA_IMAGENES (nuevo en v2.0)

Si queremos que una propiedad de nuestro componente almacene una galería de imágenes. Solo necesitamos declarar que nuestra propiedad sea del estereotipo GALERIA_IMAGENES. De esta manera:
<propiedad nombre="fotos" estereotipo="GALERIA_IMAGENES"/>
Además, en el mapeo tenemos que mapear la propiedad contra una columna adecuada para almacenar una cadena (String) con 32 caracteres de longitud (VARCHAR(32)).
Y ya está todo.
Pero, para que nuestra aplicación soporte este estereotipo necesitamos configurar nuestro sistema.
Lo primero es crear a tabla en la base de datos para almacenar las imágenes:
CREATE TABLE IMAGENES (
    ID VARCHAR(32) NOT NULL PRIMARY KEY,
    GALLERY VARCHAR(32) NOT NULL,
    IMAGE BLOB);
 
CREATE INDEX IMAGENES01
    ON IMAGENES (GALLERY);
El tipo de la columna IMAGE puede ser un tipo más adecuado para almacenar byte [] en el caso de nuestra base de datos (por ejemplo LONGVARBINARY) .
El nombre de la tabla puede ser cualquiera que queramos. Especificamos el nombre de la tabla y nombre de esquema (nuevo en v2.2.4) en el archivo de configuración (un archivo .properties en la raiz de nuestro proyecto OpenXava). De esta forma:
images.schema=MIESQUEMA
images.table=IMAGENES
Si queremos trabajar sin esquema (nuevo en v3.0.2) hemos de poner la propiedad image.schema.definition de nuestra configuración a cadena vacía, como sigue:
images.schema.definition=
Y finalmente necesitamos definir el mapeo en nuestro archivo hibernate/hibernate.cfg.xml, así:
<hibernate-configuration>
    <session-factory>
        ...
        <mapping resource="GalleryImage.hbm.xml"/>
        ...
    </session-factory>
</hibernate-configuration>
Después de todo esto ya podemos usar el estereotipo GALERIA_IMAGENES en los componentes de nuestra aplicación.

Concurrencia y propiedad versión (nuevo en v2.2.3)

Concurrencia es la habilidad de una aplicación para permitir que varios usuarios graben datos al mismo tiempo sin perder información. OpenXava usa un esquema de concurrencia optimista. Usando concurrencia optimista los registros no se bloquean permitiendo un alto nivel de concurrencia sin perder la integridad de la información.
Por ejemplo, si un usuario A lee un registro y entonces un usuario B lee el mismo registro, lo modifica y graba los cambios, cuando el usuario A intente grabar el registro recibirá un error y tendrá que refrescar los datos y reintentar su modificación.
Para activar el soporte de concurrencia para un componente OpenXava solo necesitamos declarar una propiedad usando version="true", de esta manera:
<propiedad nombre="version" tipo="int" version="true"/>
Esta propiedad es para uso del mecanismo de persistencia (Hibernate o JPA), ni nuestra aplicación ni usuarios deberían acceder directamente a ella.

Valores posibles

El elemento <valores-posibles/> permite definir una propiedad que solo puede contener los valores indicados. Digamos que es algo así como el enum de C (o Java 5).
Es fácil de usar, veamos un ejemplo:
<propiedad nombre="distancia">
    <valores-posibles>
        <valor-posible valor="local"/>
        <valor-posible valor="nacional"/>
        <valor-posible valor="internacional"/>
    </valores-posibles>
</propiedad>
La propiedad distancia solo puede valer local, nacional o internacional, y como no hemos puesto requerido="true" también la podemos dejar en blanco. Como se ve no es necesario indicar el tipo, asume int por defecto.
A nivel de interfaz gráfico la implementación web actual usa un combo. La etiqueta para cada valor se obtienen de los archivos i18n.
A nivel de código Java generado crea una propiedad distancia de tipo int que puede tener valor 0 (sin valor), 1 (local), 2 (nacional) o 3 (internacional).
A nivel de base datos por defecto guarda el entero, pero esto se puede configurar fácilmente para poder usar sin problemas bases de datos legadas. Ver más de esto último en el capítulo sobre mapeo.

Calculador

Un calculador indica que lógica hay que usar cuando se llame al método getter de la propiedad. Las propiedades que definen un calculador son de solo lectura (solo tienen getter) y no son persistentes (no tienen correspondencia con ninguna columna de la tabla de base de datos).
Así se define una propiedad calculada:
<propiedad nombre="precioUnitarioEnPesetas" tipo="java.math.BigDecimal" longitud="18">
    <calculador clase="org.openxava.test.calculadores.CalculadorEurosAPesetas">
        <poner propiedad="euros" desde="precioUnitario"/>
    </calculador>
</propiedad>
Ahora cuando nosotros (o el OpenXava para llenar una interfaz gráfica) llamamos a getPrecioUnitarioEnPesetas() el sistema ejecuta el calculador CalculadorEurosAPesetas, pero antes llena el valor de la propiedad euros de CalculadorEurosAPesetas con el valor de precioUnitario del objeto actual.
Puede ser instructivo ver el código del calculador:
package org.openxava.test.calculadores;
 
import java.math.*;
import org.openxava.calculators.*;
 
/**
 * @author Javier Paniza
 */
public class CalculadorEurosAPesetas implements ICalculator {  // 1
 
    private BigDecimal euros;
 
    public Object calculate() throws Exception {               // 2
        if (euros == null) return null;
        return euros.multiply(new BigDecimal("166.386")).
            setScale(0, BigDecimal.ROUND_HALF_UP);
    }
 
    public BigDecimal getEuros() {
        return euros;
    }
    public void setEuros(BigDecimal euros) {
        this.euros = euros;
    }
 
}
Notamos dos cosas, primero (1) que un calculador ha de implementar org.openxava.calculators.ICalculator, y que (2) el método calculate() es el que ejecuta la lógica que devolverá la propiedad.
Según lo visto ahora podemos usar el código generado de la siguiente forma:
Producto producto = ...
producto.setPrecioUnitario(2);
BigDecimal result = producto.getPrecioUnitarioEnPesetas();
Y result contendría 332.772.
Podemos definir un calculador sin poner desde al definir valores para sus propiedades, como sigue:
<propiedad nombre="cantidadLineas" tipo="int" longitud="3">
    <calculador clase="org.openxava.test.calculadores.CalculadorCantidadLineas">
        <poner propiedad="año"/>
        <poner propiedad="numero"/>
    </calculador>
</propiedad>
En este caso la propiedad año y numero de calculador CalculadorCantidadLineas se llenan desde las propiedades del mismo nombre en el objeto actual.
El atributo desde soporta propiedades calificadas, como sigue:
<agregado nombre="Direccion">
    <propiedad nombre="calle" tipo="String" longitud="30" requerido="true"/>
    <propiedad nombre="codigoPostal" tipo="int" longitud="5" requerido="true"/>
    <propiedad nombre="poblacion" tipo="String" longitud="20" requerido="true"/>
    <reference nombre="provincia" requerido="true"/>
    <propiedad nombre="comoCadena" tipo="String">
        <calculador clase="org.openxava.calculators.ConcatCalculator">
            <poner propiedad="string1" desde="calle"/>
            <poner propiedad="int2" desde="codigoPostal"/>
            <poner propiedad="string3" desde="poblacion"/>
            <poner propiedad="string4" desde="provincia.nombre"/>  <!-- 1 -->
            <poner propiedad="int5" desde="cliente.codigo"/>       <!-- 2 -->
        </calculador>
    </propiedad>
</agregado>
La propiedad string4 (1) del calculador se llena usando el valor de nombre de la provincia (que es una referencia), esto es una propiedad calificada (referencia.propiedad). En el caso de int5 (2) podemos ver como la referencia cliente no esta declarada en Direccion, porque es referenciada desde la entidad Cliente, por lo tanto Direccion tiene un referencia implicita a su modelo contenedor (su padre) que podemos usar en el atributo desde (nuevo en v2.0.4). Esto es, la propiedad int5 se llena con el codigo del cliente el cual tiene esta dirección.
También podemos asignar un valor fijo a una propiedad de un calculador:
<propiedad nombre="nombreCompleto" tipo="String">
    <calculador clase="org.openxava.calculators.ConcatCalculator">
        <poner propiedad="string1" desde="id"/>
        <poner propiedad="separator" valor=" - "/>
        <poner propiedad="string2" desde="name"/>
    </calculador>
</propiedad>
En este caso la propiedad separator de ConcatCalculator se le da un valor fijo.
Otra característica interesante de los calculadores es que podemos acceder directamente al objeto modelo (entidad o agregado) que contiene el calculador:
<propiedad nombre="sumaImportes" estereotype="DINERO">
    <calculador clase="org.openxava.test.calculadores.CalculadorSumaImportes"/>
</propiedad>
Y el calculador:
package org.openxava.test.calculators;
 
import java.math.*;
import java.rmi.*;
import java.util.*;
 
import javax.rmi.*;
 
import org.openxava.calculators.*;
import org.openxava.test.ejb.*;
 
/**
 * @author Javier Paniza
 */
 
public class CalculadorSumaImportes implements IModelCalculator { // 1
 
    private IFactura factura;
 
    public Object calculate() throws Exception {
        Iterator itLineas = factura.getLineas().iterator();
        BigDecimal result = new BigDecimal(0);
        while (itLineas.hasNext()) {
            ILineaFactura linea = (ILineaFactura) itLineas.next();
            result = result.add(linea.getImporte());
        }
        return result;
    }
 
    public void setModel(Object modelo) throws RemoteException { // 2
        factura = (IFactura) modelo;
    }
 
}
Este calculador implementa IModelCalculator (1) (nuevo en v2.0) y por ello tiene un método setModel() (2), este método se llama antes de llamar al método calculate() y así desde el método calculate() podemos acceder al modelo (en este caso a la factura) que contiene la propiedad o método. A pesar de su nombre también puede recibir un objeto que esté actuando como agregado.
Entre el código generado por OpenXava encontramos una interfaz por cada concepto de negocio que es implementada por la clase POJO, por la interfaz remota y por la clase del bean. Esto es para Factura tendriamos una interfaz IFactura implementada por Factura (clase POJO), FacturaRemote (interfaz remota EJB2) y FacturaBean (clase del bean EJB2), estos dos últimos solo si generamos código EJB2. En los calculadores IModelCalculator es aconsejable moldear a esta interfaz, de esta forma el mismo calculador funcionará con POJOs, interfaces remotos EJB2 y clases de bean EJB2. Si desarrollamos usando solo POJOs (puede que esto sea lo normal) podemos elegir moldear directamente a la clase POJO, que en esta caso sería Factura.
Este tipo de calculadores es menos reutilizable que los que reciben propiedades simples, pero a veces es práctico usarlos. ¿Por qué es menos reutilizable? Por ejemplo, si usamos IFactura para calcular un descuento, ese calculador solo podría se aplicado a facturas, pero si usamos un calculador que recibe cantidad y porcentajeDescuento como propiedades simples este último calculador podría ser aplicado a facturas, albaranes, pedidos, etc.
Desde un calculador también se puede acceder directamente a una conexión JDBC, aquí un ejemplo:
<propiedad nombre="cantidadLineas" tipo="int" longitud="3">
    <calculador clase="org.openxava.test.calculadores.CalculadorCantidadLineas">
        <poner propiedad="año"/>
        <poner propiedad="numero"/>
    </calculador>
</propiedad>
Y la clase del calculador:
package org.openxava.test.calculadores;
 
import java.sql.*;
 
import org.openxava.calculators.*;
import org.openxava.util.*;
 
/**
 * @author Javier Paniza
 */
public class CalculadorCantidadLineas implements IJDBCCalculator {     // 1
 
    private IConnectionProvider provider;
    private int año;
    private int numero;
 
    public void setConnectionProvider(IConnectionProvider provider) {  // 2
        this.provider = provider;
    }
 
    public Object calculate() throws Exception {
        Connection con = provider.getConnection();
        try {
            PreparedStatement ps = con.prepareStatement(
                "select count(*) from XAVATEST_LINEAFACTURA ? +
                ?where FACTURA_AÑO = ? and FACTURA_NUMERO = ?");
            ps.setInt(1, getAño());
            ps.setInt(2, getNumero());
            ResultSet rs = ps.executeQuery();
            rs.next();
            Integer result = new Integer(rs.getInt(1));
            ps.close();
            return result;
        }
        finally {
            con.close();
        }
    }
 
    public int getAño() {
        return año;
    }
 
    public int getNumero() {
        return numero;
    }
 
    public void setAño(int año) {
        this.año = año;
    }
 
    public void setNumero(int numero) {
        this.numero = numero;
    }
 
}
Para usar JDBC tenemos que implementar IJDBCCalculator (1) y entonces recibiremos un IConnectionProvider (2) que podremos usar dentro de calculate(). Ya sé que el código JDBC es feo y engorroso, pero en ocasiones puede ayudar a resolver algún problema de rendimiento.
Los calculadores nos permiten de forma elegante insertar nuestra propia lógica en un sistema en el que todo el código es generado; y como se puede ver promueven la creación de código reutilizable ya que la naturaleza de los calculadores (simples y configurables) permite que se usen una y otra vez para definir propiedades calculadas o métodos. Esta filosofía, la de clases simples y configurables que se pueden enchufar en varios lugares es lo que sustenta todo el marco de trabajo OpenXava.
OpenXava dispone de un conjunto de calculadores incluidos de uso genérico, que se pueden encontrar en org.openxava.calculators.

Calculador valor defecto

Con <calculador-valor-defecto/> podemos asociar una lógica a una propiedad, pero en este caso la propiedad es de lectura y escritura y persistente. Este calculador sirve para calcular su valor inicial. Por ejemplo:
<propiedad nombre="año" tipo="int" clave="true" longitud="4" requerido="true">
    <calculador-valor-defecto
        clase="org.openxava.calculators.CurrentYearCalculator"/>
</propiedad>
En este caso cuando el usuario intente crear una factura nueva (por ejemplo) se encontrará que el campo año ya tiene un valor, que el usuario puede cambiar si desea.
Podemos indicar que calcule el valor justo antes de crear (insertar en la base datos) el objeto por primera vez; eso se hace así:
<propiedad nombre="oid" tipo="String" clave="true" oculta="true">
    <calculador-valor-defecto
        clase="org.openxava.calculators.UUIDCalculator"
        al-crear="true"/>
</propiedad>
Al poner al-crear="true" conseguimos este efecto.
Un uso típico de al-crear="true" es para generar identificadores automáticamente. En el ejemplo anterior, un identificador único de tipo String y 32 caracteres es generado. Podemos usar otras técnicas de generación, por ejemplo, una sequence de base de datos se puede definir así:
<propiedad nombre="id" clave="true" tipo="int" oculta="true">
    <calculador-valor-defecto
        clase="org.openxava.calculators.SequenceCalculator" al-crear="true">
            <poner propiedad="sequence" valor="XAVATEST_SIZE_ID_SEQ"/>
    </calculador-valor-defecto>
</propiedad>
O quizás queramos usar una columna identity (auto incremento) como clave:
<propiedad nombre="id" clave="true" tipo="int" oculta="true">
    <calculador-valor-defecto
        clase="org.openxava.calculators.IdentityCalculator" al-crear="true"/>
</propiedad>
SequenceCalculator (nuevo en v2.0.1) e IdentityCalculator (nuevo en v2.0.2) no funcionan con EJB2. Funcionan con Hibernate y EJB3.
Si definimos una propiedad clave y oculta sin calculador valor defecto con al-crear="true" entonces se usan las técnicas identity, sequence o hilo automáticamente dependiendo de las capacidades de la base de datos subyacente. Así:
<propiedad nombre="oid" tipo="int" oculta="true" clave="true"/>
Esto solo funciona con Hibernate y EJB3, no con EJB2.
Por lo demás funciona exactamente igual que <calculador/> visto en la sección anterior.

Validador

El validador ejecuta la lógica de validación sobre el valor que se vaya a asignar a esa propiedad antes de grabar. Una propiedad puede tener varios validadores.
<propiedad nombre="description" tipo="String" longitud="40" requerido="true">
    <validador clase="org.openxava.test.validadores.ValidadorExcluirCadena">
        <poner propiedad="cadena" valor="MOTO"/>
    </validador>
    <validador clase="org.openxava.test.validadores.ValidadorExcluirCadena"
        solo-al-crear="true">
        <poner propiedad="cadena" valor="COCHE"/>
    </validador>
</propiedad>
La forma de configurar el validador (con los <poner/>) es exactamente igual como en los calculadores. Con el atributo solo-al-crear="true" se puede definir que esa validación solo se ejecute cuando se crea el objeto, y no cuando se modifica.
El código del validador es:
package org.openxava.test.validadores;
 
import org.openxava.util.*;
import org.openxava.validators.*;
 
/**
 * @author Javier Paniza
 */
 
public class ValidadorExcluirCadena implements IPropertyValidator { // 1
 
    private String cadena;
 
    public void validate(
        Messages errores,               // 2
        Object valor,                   // 3
        String nombreObjecto,           // 4
        String nombrePropiedad)         // 5
        throws Exception {
        if (valor==null) return;
        if (valor.toString().indexOf(getCadena()) >= 0) {
            errores.add("excluir_cadena",
                nombrePropiedad, nombreObjeto, getCadena());
        }
    }
 
    public String getCadena() {
        return cadena==null?"":cadena;
    }
 
    public void setCadena(String cadena) {
        this.cadena = cadena;
    }
 
}
Un validador ha de implementar IPropertyValidator (1), esto le obliga a tener un método validate() en donde se ejecuta la validación de la propiedad. Los argumentos del método validate() son:
Como se ve cuando encontramos un error de validación solo tenemos que añadirlo (con errores.add()) enviando un identificador de mensaje y los argumentos. Para que este validador produzca un mensaje significativo tenemos que tener en nuestro archivo de mensajes i18n la siguiente entrada:
excluir_cadena={0} no puede contener {2} en {1}
Si el identificador que se envía no está en el archivo de mensajes, sale tal cual al usuario; pero lo recomendado es siempre usar identificadores del archivo de mensajes.
La validación es satisfactoria si no se añaden mensajes y se supone fallida si se añaden. El sistema recolecta todos los mensajes de todos los validadores antes de grabar y si encuentra los visualiza al usuario y no graba.
El paquete org.openxava.validators contiene algunos validadores de uso común.

Validador por defecto (nuevo en v2.0.3)

Podemos definir validadores por defecto para las propiedades de cierto tipo o estereotipo. Para esto se usa el archivo xava/validadores.xml de nuestro proyecto para definir en él los validadores por defecto.
Por ejemplo, podemos definir en nuestro xava/validadores.xml lo siguiente:
<validadores>
    <validador-defecto>
        <clase-validador
            clase="org.openxava.test.validadores.ValidadorNombrePersona"/>
        <para-estereotipo stereotipo="NOMBRE_PERSONA"/>
    </validador-defecto>
</validadores>
En este caso estamos asociando el validador ValidadorNombrePersona al estereotipo NOMBRE_PERSONA. Ahora si definimos una propiedad como la siguiente:
<propiedad nombre="nombre" estereotipo="NOMBRE_PERSONA" requerido="true"/>
Esta propiedad será validada usando ValidadorNombrePersona aunque la propiedad misma no defina ningun validador. ValidadorNombrePersona se aplica a todas las propiedades con el estereotipo NOMBRE_PERSONA.
Podemos también asignar validadores por defecto a un tipo.
En el archivo validadores.xml podemos definir también los validadores para determinar si un valor requerido está presente (ejecutado cuando usamos requerido="true"). Además podemos asignar nombre (alias) a las clases de los validadores.
Podemos aprender más sobre los validadores examinando OpenXava/xava/validators.xml y OpenXavaTest/xava/validators.xml.

Cálculo (nuevo en v5.7)

Con <calculo/> podemos definir una expresión aritmética para hacer el cálculo de la propiedad. La expresión puede contener +, -, *, (), valores numéricos y nombres de propiedades de la misma entidad. Por ejemplo:
<propiedad nombre="total" tipo="BigDecimal">
    <calculo>((horas * trabajador.precioHora) + desplazamiento - descuento) * (1 + porcenajeIVA / 100)</calculo>
</propiedad>
Fíjate como trabajador.precioHora se usa para obtener el valor de una referencia.
El cálculo se ejecuta y visualiza cuando el usuario cambia cualquier valor de las propiedades usadas en la expresión en la interfaz de usuario, sin embargo el valor no se graba hasta que el usuario no pulsa en el botón de grabar.

Referencia (5)

Una referencia hace que desde una entidad o agregado se pueda acceder otra entidad o agregado. Una referencia se traduce a código Java como una propiedad (con su getter y su setter) cuyo tipo es el del modelo al que se referencia. Por ejemplo un Cliente puede tener una referencia a su Comercial, y así podemos escribir código Java como éste:
ICliente cliente = ...
cliente.getComercial().getNombre();
para acceder al nombre del comercial de ese cliente.
La sintaxis para definir referencias es:
<referencia
    nombre="nombre"                      <!-- 1 -->
    etiqueta="etiqueta"                  <!-- 2 -->
    modelo="modelo"                      <!-- 3 -->
    requerido="true|false"               <!-- 4 -->
    clave="true|false"                   <!-- 5 -->
    clave-busqueda="true|false"          <!-- 6  Nuevo en v3.0.2 -->
    cometido-destino="cometido destino"  <!-- 7 -->
>
    <calculador-valor-defecto .../>      <!-- 8 -->
</referencia>
  1. nombre (opcional, obligada si no se especifica modelo): Es el nombre que tendrá la referencia en Java, por lo tanto ha de seguir la normativa para nombres de miembros Java, entre ellas empezar por minúscula. Si no especificamos nombre asume el nombre del modelo pero en con la primera letra minúscula. Se desaconseja usar subrayado (_).
  2. etiqueta (opcional): Etiqueta que se mostrará al usuario final. Es mucho mejor usar los archivos i18n.
  3. modelo (opcional, obligada si no se especifica nombre): Es el nombre del modelo a referenciar. Puede ser el nombre de otro componente, en cuyo caso es una referencia a entidad, o el nombre de un agregado del componente en el que estamos. Si no especificamos modelo asume el nombre de la referencia pero con la primera letra en mayúscula.
  4. requerido (opcional): Indica si la referencia es requerida. Al grabar OpenXava comprobará si las referencias requeridas están presentes, si no lo están no se producirá la grabación y se devolverá una lista de errores de validación.
  5. clave (opcional): Para indicar si la referencia forma parte de la clave. La combinación de propiedades y referencias clave se debe mapear a un conjunto de campos en la base de datos que no tengan valores repetidos, tipicamente con la clave primaria.
  6. clave-busqueda (opcional): (Nuevo en v3.0.2) Las referencias clave de búsqueda se usan por los usuarios para buscar los objetos. Son editables en la interfaz de usuario de las referencias permitiendo al usuario teclear su valor para buscar. OpenXava usa los miembros clave (clave="true") para buscar por defecto, y si los miembros clave (clave="true") están ocultas usa la primera propiedad en la vista. Con clave-busqueda podemos elegir referencias para buscar explicitamente.
  7. cometido-destino (opcional): Usado solo en referencia dentro de colecciones. Ver más adelante.
  8. calculador-valor-defecto (uno, opcional): Para implementar la lógica para calcular el valor inicial de la referencia. Este calculador ha de devolver el valor de la clave, que puede ser un dato simple (solo si la clave del objeto referenciado es simple) o un objeto clave (un objeto especial que envuelve la clave primaria y que genera OpenXava).
Un pequeño ejemplo de uso de referencias:
<referencia modelo="Direccion" requerido="true"/>              <!-- 1 -->
<referencia nombre="comercial"/>                               <!-- 2 -->
<referencia nombre="comercialAlternativo" model="Comercial"/>  <!-- 3 -->
  1. Una referencia a un agregado llamado Direccion, el nombre de la referencia será direccion.
  2. Una referencia a la entidad del componente Comercial. Se deduce el modelo a partir del nombre.
  3. Una referencia llamada comercialAlternativo a la entidad del componente Comercial.
Suponiendo que esto está en un componente llamado Cliente, podriamos escribir:
ICliente cliente = ...
Direccion direccion = cliente.getDireccion();
IComercial comercial = cliente.getComercial();
IComercial comercialAlternativo = cliente.getComercialAlternativo();

Calculador valor por defecto para una referencia

En una referencia <calculador-valor-defecto/> funciona como en una propiedad, solo que hay que devolver el valor de la clave de la referencia, y no se admite al-crear="true".
Por ejemplo, en el caso de una referencia con clave simple podemos poner:
<referencia nombre="familia" modelo="Familia2" requerido="true">
    <calculador-valor-defecto clase="org.openxava.calculators.IntegerCalculator">
        <poner propiedad="value" valor="2"/>
    </calculador-valor-defecto >
</referencia>
El método calculate() de este calculador es:
public Object calculate() throws Exception {
    return new Integer(value);
}
Como se puede ver se devuelve un entero, es decir, el valor para familia por defecto es la familia cuyo código es el 2.
En el caso de clave compuesta sería así:
<referencia nombre="almacen" modelo="Almacen">
    <calculador-valor-defecto
        clase="org.openxava.test.calculadores.CalculadorDefectoAlmacen"/>
</referencia>
Y el código del calculador:
package org.openxava.test.calculadores;
 
import org.openxava.calculators.*;
import org.openxava.test.ejb.*;
 
/**
 * @author Javier Paniza
 */
public class CalculadorDefectoAlmacen implements ICalculator {
 
    public Object calculate() throws Exception {
        Almacen clave = new Almacen();
        clave.setCodigo(4);
        clave.setCodigoZona(4);
        return clave; // Funciona con POJOs y EJB2
        // return new AlmacenKey(new Integer(4), 4); // Funciona solo con EJB2
    }
 
}
Devuelve un objeto de tipo Almacen (o AlmacenKey, si usamos solo EJB).

Colección (6)

Con <coleccion/> definimos una colección de referencias a entidades o agregados. Esto se traduce en una propiedad Java que devuelve java.util.Collection.
Aquí la sintaxis para definir una colección:
<coleccion
    nombre="nombre"               <!-- 1 -->
    etiqueta="etiqueta"           <!-- 2 -->
    minimo="N"                    <!-- 3 -->
    maximo="N"                    <!-- 4  nuevo en v2.0.3 -->
>
    <referencia ... />            <!-- 5 -->
    <condicion ... />             <!-- 6 -->
    <orden ... />                 <!-- 7 -->
    <calculador ... />            <!-- 8 -->
    <calculador-posborrar ... />  <!-- 9 -->
</coleccion>
  1. nombre (obligado): Es el nombre que tendrá la colección en Java, por lo tanto ha de seguir la normativa para nombres de miembro Java, entre ellas empezar por minúscula. Se desaconseja usar subrayado (_).
  2. etiqueta (opcional): Etiqueta que se mostrará al usuario final. Es mucho mejor usar los archivos i18n.
  3. minimo (opcional): Indica el número mínimo de elementos esperados. Esto se valida antes de grabar.
  4. maximo (opcional): (nuevo en v2.0.3) Indica el número máximo de elementos esperados.
  5. referencia (obligada): Con la sintaxis vista en el subtema anterior.
  6. condicion (opcional): Para restringir los elementos que aparecen en la colección.
  7. orden (opcional): Para que los elementos de la colección aparezcan en un determinado orden.
  8. calculador (opcional): Permite especificar nuestra propia lógica Java para generar la colección. Si se especifica calculador no se puede poner ni condicion ni orden.
  9. calculador-posborrar (opcional): Permite ejecutar cierta lógica de negocio justo después de haber eliminado un elemento de la colección.
Vamos a ver algunos ejemplos. Empecemos por uno simple:
<coleccion nombre="albaranes">
    <referencia modelo="Albaran"/>
</coleccion>
Si ponemos esto dentro de una Factura, estamos definiendo una colección de los albaranes asociados a esa Factura. La forma de relacionarlo se hace en la parte del mapeo objeto-relacional.
Ahora podemos escribir código como este:
IFactura factura = ...
for (Iterator it = factura.getAlbaranes().iterator(); it.hasNext();) {
    IAlbaran albaran = (IAlbaran) it.next();
    albaran.hacerAlgo();
}
Para hacer algo con todos los albaranes asociados a una factura.
Vamos a ver otro ejemplo más complejo, también dentro de Factura:
<coleccion nombre="lineas" minimo="1">    <!-- 1 -->
    <referencia modelo="LineaFactura"/>
    <orden>${tipoServicio} desc</orden>   <!-- 2 -->
    <calculador-posborrar                 <!-- 3 -->
        clase="org.openxava.test.calculadores.CalculadorPosborrarLineaFactura"/>
</coleccion>
En este caso tenemos una colección de agregados, las líneas de las facturas. La diferencia entre una colección de agregados y una de referencia, es que cuando se borrar la entidad principal los elementos de las colecciones de agregados también se borran. Esto es al borrar una factura sus líneas se borran automáticamente.
  1. La restricción de minimo="1" hace que sea obligado que haya al menos una línea para que la factura sea válida.
  2. Con orden obligamos a que las lineas se devuelvan ordenadas por tipoServicio.
  3. Con calculador-posborrar indicamos un calculador a ejecutar justo después de borrar un elemento de la colección.
Veamos el código de este 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;
    }
 
}
Como se ve es un calculador normal y corriente, como el que hemos visto que se puede asignar a una propiedad calculada. Lo único que hay que tener en cuenta es que el calculador se aplica a la entidad contenedora (es este caso a Factura) y no al elemento de la colección. Es decir, si nuestro calculador implementa IModelCalculator recibirá una Factura y no una LineaFactura. Esto es lógico porque como se ejecuta después de borrar la línea esa línea ya no existe.
Tenemos libertad completa para definir como se obtienen los datos de una colección, con condicion podemos sobreescribir la condición por defecto que genera OpenXava:
<!-- Otros transportistas del mismo almacén -->
<coleccion nombre="compañeros">
    <referencia modelo="Transportista"/>
    <condicion>
        ${almacen.codigoZona} = ${this.almacen.codigoZona} AND
        ${almacen.numero} = ${this.almacen.numero} AND
        NOT (${numero} = ${this.numero})
    </condicion>
</coleccion>
Si ponemos esta colección dentro de Transportista, podemos obtener todos los transportistas del mismo almacén menos él mismo, es decir, la lista de sus compañeros. Es de notar como podemos usar this en la condición para referenciar al valor de una propiedad del objeto actual.
Si con esto no tenemos suficiente, podemos escribir completamente la lógica que devuelve la colección. La colección anterior también se podría haber definido así:
<!-- Lo mismo que 'compañeros' pero implementado con un calculador  -->
<coleccion nombre="compañerosCalculados">
    <referencia modelo="Transportista"/>
    <calculador
        clase="org.openxava.test.calculadores.CalculadorCompañerosTransportista"/>
</coleccion>
Y aquí el código del calculador:
package org.openxava.test.calculadores;
 
import java.rmi.*;
 
import org.openxava.calculators.*;
import org.openxava.test.ejb.*;
 
/**
 * @author Javier Paniza
 */
public class CalculadorCompañerosTransportista implements IModelCalculator {
 
    private ITransportista transportista;
 
    public Object calculate() throws Exception {
        // Usando Hibernate
        int codigoZonaAlmacen = transportista.getAlmacen().getCodigoZona();
        int codigoAlmacen = transportista.getAlmacen().getCodigo();
        Session sesion = XHibernate.getSession();
        Query query = sesion.createQuery("from Transportista as o where " +
                "o.almacen.codigoZona = :zonaAlmacen AND " +
                "o.almacen.codigo = :codigoAlmacen AND " +
                "NOT (o.codigo = :codigo)");
        query.setInteger("zonaAlmacen", codigoZonaAlmacen);
        query.setInteger("codigoAlmacen", codigoAlmacen);
        query.setInteger("codigo", transportista.getCodigo());
        return query.list();
 
        /* Usando EJB3 JPA
        EntityManager manager = XPersistence.getManager();
        Query query = manager.createQuery("from Transportista o where " +
            "o.almacen.codigoZona = :zonaAlmacen AND " +
            "o.almacen.codigo = :codigoAlmacen  AND " +
            "NOT (o.codigo = :codigo) ");
        query.setParameter("zonaAlmacen", codigoZonaAlmacen);
        query.setParameter("codigoAlmacen", codigoAlmacen);
        query.setParameter("codigo", transportista.getCodigo());
        return query.getResultList();
        */
 
        /* Usando EJB2
        return TransportistaUtil.getHome().findCompañerosOfTransportista(
            transportista.getAlmacenKey().getCodigoZona(),
            transportista.getAlmacenKey().get_Codigo(),
            new Integer(transportista.getCodigo())
        );
        */
    }
 
    public void setModel(Object modelo) throws RemoteException {
        transportista = (ITransportista) modelo;
    }
 
}
Como se ve es un calculador convencional. Obviamente ha de devolver una java.util.Collection cuyos elementos sean de tipo ITransportista.
Las referencias de las colecciones se asumen bidireccionales, esto quiere decir que si en un Comercial tengo una colección clientes, en Cliente tengo que tener una referencia a Comercial. Pero si en Cliente tengo más de una referencia a Comercial (por ejemplo, comercial y comercialAlternativo) OpenXava no sabe cual escoger, para eso tenemos el atributo cometido-destino de referencia. En este caso pondríamos:
<coleccion nombre="clientes">
    <referencia modelo="Cliente" cometido-destino="comercial"/>
</coleccion>
Para indicar que es la referencia comercial y no comercialAlternativo la que vamos a usar para esta colección.
En el caso de colección de referencias a entidad tenemos que definir nosotros las referencia en la otra parte, pero en el caso de colección de referencias a agregados no es necesario, porque en los agregados se genera automáticamente una referencia a su contenedor.

Método (7)

Con <metodo/> podemos definir un método qué será incluido en el código generado.
La sintaxis para definir un método es:
<metodo
    nombre="nombre"            <!-- 1 -->
    tipo="tipo"                <!-- 2 -->
    argumentos="argumentos"    <!-- 3 -->
    excepciones="excepciones"  <!-- 4 -->
>
    <calculador ... />         <!-- 5 -->
</metodo>
  1. nombre (obligado): Es el nombre que tendrá el método en Java, por lo tanto ha de seguir la normativa para miembros Java, entre ellas empezar por minúscula.
  2. tipo (opcional, por defecto void): Corresponde a un tipo Java. Todos los tipos válidos como tipo de retorno de un método en Java son válidos aquí.
  3. argumentos (opcional): Lista de argumentos del método en formato Java.
  4. excepciones (opcional): Lista de excepciones que puede lanzar el método en formato Java.
  5. calculador (obligado): Implementa la lógica que ejecuta el método.
Definir un método es sencillo:
<metodo nombre="incrementarPrecio">
    <calculador clase="org.openxava.test.calculadores.CalculadorIncrementarPrecio"/>
</metodo>
E implementarlo depende de lo que se quiera hacer. En este caso:
package org.openxava.test.calculadores;
 
import java.math.*;
import java.rmi.*;
 
import org.openxava.calculators.*;
import org.openxava.test.modelo.*;
 
/**
 * @author Javier Paniza
 */
public class CalculadorIncrementarPrecio implements IModelCalculator {
 
    private IProducto producto;
 
    public Object calculate() throws Exception {
        producto.setPrecioUnitario(  // 1
            producto.getPrecioUnitario().
                multiply(new BigDecimal("1.02")).setScale(2));
        return null;                 // 2
    }
 
    public void setModel(Object modelo) throws RemoteException {
        this.producto = (IProducto) modelo;
    }
 
}
Todo lo que se dijo para los calculadores cuando se habló de las propiedades aplica a los métodos también, con los siguientes matices:
  1. Un calculador para un método tiene autoridad moral para cambiar el estado del objeto.
  2. Si el tipo que se devuelve es void hay que acabar con un return null.
Ahora podemos usar el método de la forma esperada:
IProducto producto = ...
producto.setPrecioUnitario(new BigDecimal(?100?));
producto.incrementarPrecio();
BigDecimal nuevoPrecio = producto.getPrecioUnitario();
 
Y en nuevoPrecio tendremos 102.
Otro ejemplo de método, ahora un poco más complejo:
<metodo nombre="getPrecio" tipo="BigDecimal"
    argumentos="String pais, BigDecimal tarifa"
    excepciones="ProductoException, PrecioException">
    <calculador clase="org.openxava.test.calculadores.CalculadorPrecioExportacion">
        <poner propiedad="euros" desde="precioUnitario"/>
    </calculador>
</metodo>
En este caso es de notar que tanto en argumentos como en excepciones se usa el formato Java, ya que lo que se pone ahí es insertado directamente en el código generado.
El calculador quedaría de la siguiente forma:
package org.openxava.test.calculadores;
 
import java.math.*;
 
import org.openxava.calculators.*;
import org.openxava.test.ejb.*;
 
/**
 * @author Javier Paniza
 */
 
public class CalculadorPrecioExportacion implements ICalculator {
 
    private BigDecimal euros;
    private String pais;
    private BigDecimal tarifa;
 
    public Object calculate() throws Exception {
        if ("España".equals(pais) || "Guatemala".equals(pais)) {
            return euros.add(tarifa);
        }
        else {
            throw new PriceException("Pais no registrado");
        }
    }
 
    public BigDecimal getEuros() {
        return euros;
    }
    public void setEuros(BigDecimal decimal) {
        euros = decimal;
    }
 
    public BigDecimal getTarifa() {
        return tarifa;
    }
    public void setTarifa(BigDecimal decimal) {
        tarifa = decimal;
    }
 
    public String getPais() {
        return pais;
    }
    public void setPais(String string) {
        pais = string;
    }
}
Cada argumento se asigna a una propiedad del mismo nombre en el calculador, es decir el valor del primer argumento, pais, se asigna a la propiedad pais, y el valor del segundo, tarifa, a la propiedad tarifa. Y, por supuesto, se pueden configurar valores a otras propiedades de calculador con <poner/> como es usual para los calculadores.
Y para usar el método:
IProducto producto = ...
BigDecimal precio = producto.getPrecio("España", new BigDecimal("100")); // funciona
producto.getPrecio("El Puig", new BigDecimal("100")); // lanza PrecioException
Los métodos son la salsa de los objetos, sin ellos solo serían caparazones tontos alrededor de los datos. Cuando sea posible es mejor poner la lógica de negocio en los métodos (capa del modelo) que en las acciones (capa del controlador).

Buscador (8)

Un buscador es un método especial que nos permite encontrar un objeto o una colección de objetos que cumplen un solo criterio. En la versión POJO un buscador es un método estático generado en la clase POJO. En la versión EJB2 un buscador corresponde con un finder del home.
La sintaxis para definir buscadores es:
<buscador
    nombre="nombre"           <!-- 1 -->
    argumentos="argumentos"   <!-- 2 -->
    coleccion="(true|false)"  <!-- 3 -->
>
    <condicion ... />         <!-- 4 -->
    <orden ... />             <!-- 5 -->
</buscador>
  1. nombre (obligado): Es el nombre que tendrá el método finder en Java, por lo tanto ha de seguir la normativa para miembros Java, entre ellas empezar por minúscula.
  2. argumentos (obligado): Lista de argumentos del método en formato Java. Lo más aconsejable es usar tipos de datos simples.
  3. coleccion (opcional, por defecto false): Indica si el resultado va a ser un solo objeto o una colección.
  4. condicion (opcional): Una condición con sintaxis SQL/EJBQL en la que podemos usar los nombres de las propiedades entre ${}.
  5. orden (opcional): Una ordenación con sintaxis SQL/EJBQL en la que podemos usar los nombres de las propiedades entre ${}.
Algunos ejemplos:
<buscador nombre="byCodigo" argumentos="int codigo">
    <condicion>${codigo} = {0}</condicion>
</buscador>
 
<buscador nombre="byNombreLike" argumentos="String nombre" coleccion="true">
    <condicion>${nombre} like {0}</condicion>
    <orden>${nombre} desc</orden>
</buscador>
 
<buscador
    nombre="byNombreLikeYRelacionConComercial"
    argumentos="String nombre, String relacionConComercial"
    coleccion="true">
    <condicion>${nombre} like {0} and ${relacionConComercial} = {1}</condicion>
    <orden>${nombre} desc</orden>
</buscador>
 
<buscador nombre="normales" argumentos="" coleccion="true">
    <condicion>${tipo} = 1</condicion>
</buscador>
 
<buscador nombre="fijos" argumentos="" coleccion="true">
    <condicion>${tipo} = 2</condicion>
</buscador>
 
<buscador nombre="todos" argumentos="" coleccion="true"/>
Esto genera un conjunto de métodos finders disponibles desde la clase POJO y el home EJB2 correspondiente. Estos métodos se pueden usar así:
// POJO, tanto JPA como Hibernate
ICliente cliente = Cliente.findByCodigo(8);
Collection javieres = Cliente.findByNombreLike("%JAVI%");
 
// EJB2
ICliente cliente = ClienteUtil.getHome().findByCodigo(8);
Collection javieres = ClienteUtil.getHome().findByNombreLike("%JAVI%");

Calculador poscrear (9)

Con <calculador-poscrear/> podemos indicar que se ejecute cierta lógica justo después de crear el objeto como persistente.
Su sintaxis es:
<calculador-poscrear
    clase="clase">  <!-- 1 -->
    <poner ... />   <!-- 2 -->
</calculador-poscrear>
  1. clase (obligado): Clase del calculador. Un calculador que implemente ICalculator u alguno de sus derivados.
  2. poner (varios, opcional): Para establecer valor a las propiedades del calculador antes de ejecutarse.
Un ejemplo sencillo sería:
<calculador-poscrear
    clase="org.openxava.test.calculadores.CalculadorPoscrearTipoAlbaran">
    <poner propiedad="sufijo" valor="CREADO"/>
</calculador-poscrear>
Y ahora la clase del calculador:
package org.openxava.test.calculadores;
 
import java.rmi.*;
 
import org.openxava.calculators.*;
import org.openxava.test.ejb.*;
 
/**
 * @author Javier Paniza
 */
public class CalculadorPoscrearTipoAlbaran implements IModelCalculator {
 
    private ITipoAlbaran tipoAlbaran;
    private String sufijo;
 
    public Object calculate() throws Exception {
        tipoAlbaran.setDescripcion(tipoAlbaran.getDescripcion() + " " + sufijo);
        return null;
    }
 
    public void setModel(Object modelo) throws RemoteException {
        tipoAlbaran = (ITipoAlbaran) modelo;
    }
 
    public String getSufijo() {
        return sufijo;
    }
    public void setSufijo(String sufijo) {
        this.sufijo = sufijo;
    }
 
}
En este caso cada vez que se graba por primera vez un TipoAlbaran, justo después se añade un sufijo a su descripción.
Como se ve es exactamente igual que cualquier otro calculador (como para propiedades calculadas o métodos) solo que este se ejecuta después de crear.

Calculador posmodificar (11)

Con <calculador-posmodificar/> podemos indicar que se ejecute cierta lógica justo después de modificar un objeto y justo antes de actualizar su contenido en la base de dato, esto es justo antes de hacer el UPDATE.
Su sintaxis es:
<calculador-posmodificar
    clase="clase">  <!-- 1 -->
    <poner ... />   <!-- 2 -->
</calculador-posmodificar>
  1. clase (obligado): Clase del calculador. Un calculador que implemente ICalculator u alguno de sus derivados.
  2. poner (varios, opcional): Para establecer valor a las propiedades del calculador antes de ejecutarse.
Un ejemplo sencillo sería:
<calculador-posmodificar
    clase="org.openxava.test.calculators.CalculadorPosmodificarTipoAlbaran"/>
Y ahora la clase del calculador:
package org.openxava.test.calculadores;
 
import java.rmi.*;
 
import org.openxava.calculators.*;
import org.openxava.test.ejb.*;
 
/**
 * @author Javier Paniza
 */
 
public class CalculadorPosmodificarTipoAlbaran implements IModelCalculator {
 
    private ITipoAlbaran tipoAlbaran;
 
    public Object calculate() throws Exception {
        tipoAlbaran.setDescripcion(tipoAlbaran.getDescripcion() + " MODIFICADO");
        return null;
    }
 
    public void setModel(Object modelo) throws RemoteException {
        tipoAlbaran = (ITipoAlbaran) modelo;
    }
 
}
En este caso cada vez que se modifica un TipoAlbaran se añade un sufijo a su descripción.
Como se ve es exactamente igual que cualquier otro calculador (como para propiedades calculadas o métodos) solo que este se ejecuta después de modificar.

Calculadores poscargar y preborrar (10, 12)

La sintaxis y comportamiento de los calculadores poscargar y preborrar son iguales que en el caso de los calculadores poscrear y posmodificar.

Validador (13)

Este validador permite poner una validación a nivel de modelo. Cuando necesitamos hacer una validación sobre varias propiedades del modelo, y esta validación no corresponde lógicamente a ninguna de ellas se puede usar este tipo de validación.
Su sintaxis es:
<validador
    clase="validador"           <!-- 1 -->
    nombre="nombre"             <!-- 2 -->
    solo-al-crear="true|false"  <!-- 3 -->
>
    <poner ... />               <!-- 4 -->
</validador>
  1. clase (opcional, obligada si no se especifica nombre): Clase que implementa la validación. Ha de ser del tipo IValidator.
  2. nombre (opcional, obligada si no se especifica clase): Este nombre es el que tenga el validador en el archivos xava/validators.xml o xava/validadores.xml, del proyecto OpenXava o de nuestro propio proyecto.
  3. solo-al-crear (opcional): Si true el validador es ejecutado solo cuando estamos creando un objeto nuevo, no cuando modificamos uno existente. El valor por defecto es false.
  4. poner (varios, opcional): Para establecer valor a las propiedades del validador antes de ejecutarse.
Un ejemplo:
<validador clase="org.openxava.test.validadores.ValidadorProductoBarato">
    <poner propiedad="limite" valor="100"/>
    <poner propiedad="descripcion"/>
    <poner propiedad="precioUnitario"/>
</validador>
Y el código del validador:
package org.openxava.test.validadores;
 
import java.math.*;
 
import org.openxava.util.*;
import org.openxava.validators.*;
 
/**
 * @author Javier Paniza
 */
public class ValidadorProductoBarato implements IValidator {        // 1
 
    private int limite;
    private BigDecimal precioUnitario;
    private String descripcion;
 
    public void validate(Messages errores) {                        // 2
        if (getDescripcion().indexOf("CHEAP") >= 0
            getDescripcion().indexOf("BARATO") >= 0
            getDescripcion().indexOf("BARATA") >= 0) {
            if (getLimiteBd().compareTo(getPrecioUnitario()) < 0) {
                errores.add("producto_barato", getLimiteBd());      // 3
            }
        }
    }
 
    public BigDecimal getPrecioUnitario() {
        return precioUnitario;
    }
 
    public void setPrecioUnitario(BigDecimal decimal) {
        precioUnitario = decimal;
    }
 
    public String getDescripcion() {
        return descripcion==null?"":descripcion;
    }
 
    public void setDescripcion(String string) {
        descripcion = string;
    }
 
    public int getLimite() {
        return limite;
    }
 
    public void setLimite(int i) {
        limite = i;
    }
 
    private BigDecimal getLimiteBd() {
        return new BigDecimal(limit);
    }
 
}
Este validador ha de implementar IValidator (1), lo que le obliga a tener un método validate(Messages messages) (2). En este método solo hay que añadir identificadores de mensajes de error (3) (cuyos textos estarán en los archivos i18n), si en el proceso de validación (es decir en la ejecución de todos los validadores) hubiese al menos un mensaje de error, OpenXava no graba la información y visualiza los mensajes al usuario.
En este caso vemos como se accede a descripcion y precioUnitario, por eso la validación se pone a nivel de módelo y no a nivel de propiedad individual, porque abarca más de una propiedad.

Validador borrar (14)

El <validador-borrar/> también es un validador a nivel de modelo, la diferencia es que se ejecuta antes de borrar el objeto, y tiene la posibilidad de vetar el borrado.
Su sintaxis es:
<validador-borrar
    clase="validador"  <!-- 1 -->
    nombre="nombre"    <!-- 2 -->
>
    <poner ... /> ...  <!-- 3 -->
</validador-borrar>
  1. clase (opcional, obligada si no se especifica nombre): Clase que implementa la validación. Ha de ser del tipo IRemoveValidator.
  2. nombre (opcional, obligada si no se especifica clase): Este nombre es el que tenga el validador en el archivos xava/validators.xml o xava/validadores.xml, del proyecto OpenXava o de nuestro propio proyecto.
  3. poner (varios, opcional): Para establecer valor a las propiedades del calculador antes de ejecutarse.
Un ejemplo puede ser:
<validador-borrar
    clase="org.openxava.test.validadores.ValidadorBorrarTipoAlbaran"/>
Y el validador:
package org.openxava.test.validadores;
 
import java.util.*;
 
import org.openxava.test.ejb.*;
import org.openxava.util.*;
import org.openxava.validators.*;
 
/**
 * @author Javier Paniza
 */
public class ValidadorBorrarTipoAlbaran implements IRemoveValidator {  // 1
 
    private ITipoAlbaran tipoAlbaran;
 
    public void setEntity(Object entity) throws Exception {            // 2
        this.tipoAlbaran = (ITipoAlbaran) entity;
    }
 
    public void validate(Messages errores) throws Exception {
        if (!tipoAlbaran .getAlbaranes().isEmpty()) {
            errores.add("no_borrar_tipo_albaran_si_albaranes");        // 3
        }
    }
 
}
Como se ve tiene que implementar IRemoveValidator (1) lo que le obliga a tener un método setEntity() (2) con el recibirá el objeto que va a ser borrado. Si hay algún error de validación se añade al objeto de tipo Messages enviado a validate() (3). Si después de ejecutar todas las validaciones el OpenXava detecta al menos 1 error de validación no realizará el borrado del objeto y enviará la lista de mensajes al usuario.
En este caso si se comprueba si hay albaranes que usen este tipo de albarán antes de poder borrarlo.

Agregado

La sintaxis de un agregado es como sigue:
<agregado nombre="agregado">        <!-- 1 -->
    <bean clase="claseBean"/>       <!-- 2 -->
    <ejb ... />                     <!-- 3 -->
    <implementa .../>
    <propiedad .../> ...
    <referencia .../> ...
    <coleccion .../> ...
    <metodo .../> ...
    <buscador .../> ...
    <calculador-poscrear .../> ...
    <calculador-posmodificar .../> ...
    <validador .../> ...
    <validador-borrar .../> ...
</agregado>
  1. nombre (obligado): Cada agregado tiene que tener un nombre único. La normativa para poner el nombre es la misma que para un nombre de clase Java, es decir, empieza por mayúscula y cada palabra nueva también.
  2. bean (uno, opcional): Permite especificar una clase escrita por nosotros para implementar el agregado. La clase ha de ser un JavaBean, es decir una clase de Java normal y corriente con getters y setters para las propiedades. Normalmente no se suele usar ya que es mucho mejor que OpenXava genere el código por nosotros.
  3. ejb (uno, opcional): Permite usar un EJB2 existente para implementar un agregado. Esto se usa en el caso de querer tener una colección de agregados. Normalmente no se suele usar ya que es mucho mejor que OpenXava genere el código por nosotros.
En un componente puede haber cuantos agregados deseemos. Y podemos referenciarlos desde la entidad o desde otro agregado.

Referencia a agregado

El primer ejemplo es un agregado Direccion que es referenciado desde la entidad principal.
En la entidad principal pondremos:
<referencia nombre="direccion" modelo="Direccion" requerido="true"/>
Y a nivel de componente definiremos el agregado.
<agregado nombre="Direccion">
    <implementa interfaz="org.openxava.test.ejb.IConMunicipio"/>                  <!-- 1 -->
    <propiedad nombre="calle" tipo="String" longitud="30" requerido="true"/>
    <propiedad nombre="codigoPostal" tipo="int" longitud="5" requerido="true"/>
    <propiedad nombre="municipio" tipo="String" longitud="20" requerido="true"/>
    <referencia nombre="provincia" requerido="true"/>                             <!-- 2 -->
</agregado>
Como se ve un agregado puede implementar una interfaz (1) y contener referencias (2), entre otras cosas, en realidad todo lo que se soporta en <entidad/> se soporta aquí.
El código resultante se puede usar así, para leer:
ICliente cliente = ...
Direccion direccion = cliente.getDireccion();
direccion.getCalle(); // para obtener el valor
O así para establecer una nueva dirección
// para establecer una nueva dirección
Direccion direccion = new Direccion(); // es un JavaBean, nunca un EJB2
direccion.setCalle(?Mi calle?);
direccion.setCodigoPostal(46001);
direccion.setMunicipio(?Valencia?);
direccion.setProvincia(provincia);
cliente.setDireccion(direccion);
En este caso que tenemos una referencia simple, el código generado es un simple JavaBean, cuyo ciclo de vida esta asociado a su objeto contenedor, es decir, la Direccion se borrará y creará junto al Cliente, jamas tendrá vida propia ni podrá ser compartida por otro Cliente.

Colección de agregados

Ahora un ejemplo de una colección de agregados. En la entidad principal (por ejemplo de Factura) podemos poner:
<coleccion nombre="lineas" minimo="1">
    <referencia modelo="LineaFactura"/>
</coleccion>
Y definimos el agregado LineaFactura:
<agregado nombre="LineaFactura">
    <propiedad nombre="oid" tipo="String" clave="true" oculta="true">
        <calculador-valor-defecto
            clase="org.openxava.test.calculadores.CalculadorOidLineaFactura"
            al-crear="true"/>
    </propiedad>
    <propiedad nombre="tipoServicio">
        <valores-posibles>
            <valor-posible valor="especial"/>
            <valor-posible valor="urgente"/>
        </valores-posibles>
    </propiedad>
    <propiedad nombre="cantidad" tipo="int"
        longitud="4" requerido="true"/>
    <propiedad nombre="precioUnitario"
        estereotipo="DINERO" requerido="true"/>
    <propiedad nombre="importe"
        estereotype="DINERO">
        <calculador
            clase="org.openxava.test.calculadores.CalculadorImporteLinea">
            <poner propiedad="precioUnitario"/>
            <poner propiedad="cantidad"/>
        </calculador>
    </propiedad>
    <referencia modelo="Producto" requerido="true"/>
    <propiedad nombre="fechaEntrega" tipo="java.util.Date">
        <calculador-valor-defecto
            clase="org.openxava.calculators.CurrentDateCalculator"/>
    </propiedad>
    <referencia nombre="vendidoPor" modelo="Comercial"/>
    <propiedad nombre="observaciones" estereotipo="MEMO"/>
 
    <validador clase="org.openxava.test.validadores.ValidadorLineaFactura">
        <poner propiedad="factura"/>
        <poner propiedad="oid"/>
        <poner propiedad="producto"/>
        <poner propiedad="precioUnitario"/>
    </validador>
 
</agregado>
Como podemos ver un agregado es tan complejo como una entidad, con calculadores, validadores, referencias y todo lo que queramos. En el caso de un agregado usado en una colección, como este caso, automáticamente se añade una referencia al contenedor, es decir, aunque no lo hayamos definido, LineaFactura tiene una referencia a Factura.
En el código generado nos encontraremos en Factura una colección LineaFactura. La diferencia entre una colección de referencias y una de agregados está en que al borrar la factura se borraran sus líneas asociadas, y también en el estilo de la interfaz gráfica (la interfaz gráfica se ve en el capítulo 4).
(Nuevo en la guía de referencia v2.1.1: Explicación de las referencias implícitas) Cada agregado tiene una referencia implícita a su modelo contenedor. Es decir, LineaFactura tiene una referencia llamada factura, incluso si la referencia no está declarada (aunque puede declararse, de forma opcional, por propósito de refinamiento, por ejemplo, para hacerla clave). Esta referencia puede usarse en el código Java, como sigue:
LineaFactura linea = ... ;
linea.getFactura(); // Para obtener el padre
O puede usarse en el código XML, de esta forma:
<propiedad nombre="oid" tipo="String" clave="true" oculta="true">
    <calculador-valor-defecto
        clase="org.openxava.test.calculadores.CalculadorLineaFacturaOid"
        al-crear="true">
        <poner propiedad="añoFactura" desde="factura.año"/>         <!-- 1 -->
        <poner propiedad="numeroFactura" desde="factura.numeror"/>  <!-- 1 -->
    </calculador-valor-defecto>
</propiedad>
En este caso usamos factura en el atributo desde (1) aunque factura no está declarada en LineaFactura. Nuevo en v2.1.1: usar referencia a propiedades clave del modelo padre en el atributo desde (es decir, desde="factura.año")

Relaciones de muchos-a-muchos

En OpenXava no existe el concepto directo de relación muchos-a-muchos, en lugar de eso en OpenXava solo existen colecciones. Aun así modelar una relación muchos-a-muchos con OpenXava is fácil. Solo necesitamos definir colecciones en ambas partes de la relación.
Por ejemplo, si tenemos clientes y provincias, y un cliente puede trabajar en varias provincias, y, obviamente, en una provincia pueden trabajar varios clientes, tenemos un caso de relación muchos-a-muchos (usando la nomenclatura relacional). Suponiendo que tenemos una tabla CLIENTE (sin referencia a provincia), una tabla PROVINCIA (sin referencia a clientes) y una tabla CLIENTE_PROVINCIA (para vincular ambas tablas), este caso podemos modelarlo así:
<componente nombre="Cliente">
    <entidad>
    ...
        <coleccion nombre="provincias">                  <!-- 1 -->
            <referencia modelo="ClienteProvincia"/>
        </coleccion>
    ...
    </entidad>
 
    <agregado nombre="ClienteProvincia">                 <!-- 2 -->
        <referencia nombre="cliente" clave="true"/>      <!-- 3 -->
        <referencia nombre="provincia" clave="true"/>    <!-- 4 -->
    </agregado>
    ...
</componente>
Estamos definiendo en Cliente una colección de agregados (1), cada agregado (ClienteProvincia) (2) contiene una referencia a Provincia (4) y, por supuesto, una referencia a su entidad contenedora (Cliente) (3).
Ahora podemos mapear la colección de la forma habitual:
<componente nombre="Cliente">
    ...
    <mapeo-agregado agregado="ClienteProvincia" tabla="CLIENTE_PROVINCIA">
        <mapeo-referencia referencia="cliente">
            <detalle-mapeo-referencia
                columna-tabla="CLIENTE"                        <!-- 1 -->
                propiedad-modelo-referenciado="codigo"/>
        </mapeo-referencia>
        <mapeo-referencia referencia="provincia">
            <detalle-mapeo-referencia
                columna-tabla="PROVINCIA"                      <!-- 2 -->
                propiedad-modelo-referenciado="id"/>
        </mapeo-referencia>
    </mapeo-agregado>
</componente>
ClienteProvincia lo mapeamos a CLIENTE_PROVINCIA, una tabla que contiene dos columnas, una para apuntar a CLIENTE (1) y otra para apuntar a PROVINCIA (2).
A nivel de modelo y mapeo ya lo tenemos todo listo, pero la intefaz gráfica que genera OpenXava por defecto es un poco fea en este caso. Aunque con los siguientes retoques en la parte de la vista nuestra colección muchos-a-muchos puede quedar bastante bien:
<componente nombre="Cliente">
    ...
    <vista>
        ...
        <vista-coleccion coleccion="provincias">
            <propiedades-lista>
                provincia.id, provincia.nombre                    <!-- 1 -->
            </list-properties>
        </vista-coleccion>
 
        <miembros>
            ...
            provincias
            ...
        </miembros>
 
    </vista>
 
    <vista modelo="ClienteProvincia">
        <vista-referencia referencia="provincia" marco="false"/>  <!-- 2 -->
    </vista>
    ...
</componente>
En esta vista podemos ver como definimos explicitamente (1) las propiedades a mostrar en la lista de la colección provincias. Esto es necesario porque tenemos que mostrar las propiedades de Provincia, no las de ClienteProvincia. Adicionalmente, definimos que la referencia a Provincia en ClienteProvincia se muestre sin marcos (2), de esta forma evitamos dos feos marcos anidados.

De esta manera podemos definir una colección que mapea a una relación de muchos a muchos en la base de datos. Si queremos que la relación sea bidireccional solo tenemos que crear una colección clientes en la entidad Provincia, esta colección puede ser de tipo agregado ProvinciaCliente y tiene que mapearse a la tabla CLIENTE_PROVINCIA. Todo exactamente igual que en el ejemplo aquí mostrado.