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:
- Clases de Java convencionales (los famosos POJOs) para el modelo
usando Hibernate para la persistencia (nuevo en v2.1).
- Clases de Java convencionales para el modelo usando EJB3 JPA (Java
Persistence API) para la persistencia.
- Los clásicos EntityBeans de EJB2 para el modelo y la persistencia.
- 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:
- Podemos decir que A tiene un B.
- Si A es borrado su B es borrado también.
- B no es compartido.
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>
- 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.
- 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.
- implementa (varios, opcional): Para que el código
generado implemente una o varias interfaces Java cualquiera.
- propiedad (varias, opcional): Las propiedades
representan propiedades Java (con su setter y getter) en el código
generado.
- referencia (varias, opcional): Referencias a otros
modelos, podemos referenciar a la entidad de otro componente o a un
agregado del nuestro.
- coleccion (varias, opcional): Colecciones de
referencias, en el código generado se convierten en una propiedad que
devuelve un java.util.Collection.
- 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).
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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:
- La clase ha de descender de org.openxava.ejbx.EJBReplicableBase
- 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>
- 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 (_).
- etiqueta (opcional): Etiqueta que se mostrará al
usuario final. Es mucho mejor usar los archivos i18n.
- 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.
- estereotipo (opcional): Permite especificar un
comportamiento especial para cierta propiedades.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- valores-posibles (uno, opcional): Para indicar que
la propiedad en cuestión solo puede tener un conjunto de valore fijos.
- 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.
- 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.
- 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.
- 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:
- DINERO, MONEY
- FOTO, PHOTO, IMAGEN, IMAGE
- TEXTO_GRANDE, MEMO, TEXT_AREA
- ETIQUETA, LABEL
- ETIQUETA_NEGRITA, BOLD_LABEL
- HORA, TIME
- FECHAHORA, DATETIME
- GALERIA_IMAGENES, IMAGES_GALLERY nuevo en v2.0
- RELLENADO_CON_CEROS, ZEROS_FILLED nuevo en v2.0.2
- TEXTO_HTML, HTML_TEXT (texto con formato editable) nuevo en
v2.0.3
- ETIQUETA_IMAGEN, IMAGE_LABEL (imagen que depende del contenido de la
propiedad) nuevo en v2.1.5
- EMAIL nuevo en 2.2.3
- TELEFONO, TELEPHONE nuevo en 2.2.3
- WEBURL nuevo en 2.2.3
- IP nuevo en 2.2.4
- ISBN nuevo en 2.2.4
- TARJETA_CREDITO, CREDIT_CARD nuevo en 2.2.4
- LISTA_EMAIL, EMAIL_LIST nuevo en 2.2.4
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:
- (2) Messages errores: Un objeto de tipo Messages
que representa un conjunto de mensajes (una especie de colección
inteligente) y es donde podemos añadir los problemas de validación que
encontremos.
- (3) Object valor: El valor a validar.
- (4) String nombreObjeto: Nombre del objeto al que
pertenece la propiedad a validar. Útil para usarlo en los mensajes de
error.
- (5) String nombrePropiedad: Nombre de la propiedad
a validar. Útil para usarlo en los mensajes de error.
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>
- 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 (_).
- etiqueta (opcional): Etiqueta que se mostrará al
usuario final. Es mucho mejor usar los archivos i18n.
- 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.
- 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.
- 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.
- 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.
- cometido-destino (opcional): Usado solo en
referencia dentro de colecciones. Ver más adelante.
- 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 -->
- Una referencia a un agregado llamado Direccion, el nombre
de la referencia será direccion.
- Una referencia a la entidad del componente Comercial. Se
deduce el modelo a partir del nombre.
- 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>
- 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 (_).
- etiqueta (opcional): Etiqueta que se mostrará al
usuario final. Es mucho mejor usar los archivos i18n.
- minimo (opcional): Indica el número mínimo de
elementos esperados. Esto se valida antes de grabar.
- maximo (opcional): (nuevo en v2.0.3)
Indica el número máximo de elementos esperados.
- referencia (obligada): Con la sintaxis vista en el
subtema anterior.
- condicion (opcional): Para restringir los elementos
que aparecen en la colección.
- orden (opcional): Para que los elementos de la
colección aparezcan en un determinado orden.
- 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.
- 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.
- La restricción de minimo="1" hace que sea obligado que
haya al menos una línea para que la factura sea válida.
- Con orden obligamos a que las lineas se devuelvan ordenadas por tipoServicio.
- 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>
- 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.
- 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í.
- argumentos (opcional): Lista de argumentos del
método en formato Java.
- excepciones (opcional): Lista de excepciones que
puede lanzar el método en formato Java.
- 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:
- Un calculador para un método tiene autoridad moral para cambiar el
estado del objeto.
- 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>
- 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.
- argumentos (obligado): Lista de argumentos del
método en formato Java. Lo más aconsejable es usar tipos de datos
simples.
- coleccion (opcional, por defecto false): Indica si
el resultado va a ser un solo objeto o una colección.
- condicion (opcional): Una condición con sintaxis
SQL/EJBQL en la que podemos usar los nombres de las propiedades entre
${}.
- 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>
- clase (obligado): Clase del calculador. Un
calculador que implemente ICalculator u alguno de sus derivados.
- 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>
- clase (obligado): Clase del calculador. Un
calculador que implemente ICalculator u alguno de sus derivados.
- 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>
- clase (opcional, obligada si no se especifica
nombre): Clase que implementa la validación. Ha de ser del tipo IValidator.
- 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.
- 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.
- 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>
- clase (opcional, obligada si no se especifica
nombre): Clase que implementa la validación. Ha de ser del tipo IRemoveValidator.
- 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.
- 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>
- 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.
- 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.
- 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.