Curso: 1. Primeros pasos | 2. Modelar con Java | 3. Pruebas automáticas | 4. Herencia | 5. Lógica de negocio básica | 6. Validación avanzada | 7. Refinar el comportamiento predefinido | 8. Comportamiento y lógica de negocio | 9. Referencias y colecciones | A. Arquitectura y filosofía | B. Java Persistence API | C. Anotaciones

Apéndice C: Anotaciones

Las anotaciones son la herramienta que Java provee para definir metadatos en tus aplicaciones, en otras palabras, es la forma de hacer desarrollo declarativo con Java, donde dices el “qué” y no el “cómo”.
En esta lección verás las anotaciones que puedes usar en una aplicación OpenXava para definir validaciones, la interfaz de usuario y otros aspectos para ajustar la aplicación a tus necesidades.
El objetivo de esta lección es introducirte a estas anotaciones, pero no te muestra todas su sutilezas y posibles casos de uso; lo cual requeriría varios libros de los gordos.

Validación

OpenXava incluye un marco de validación fácil de usar y extensible. Además soporta Bean Validation e Hibernate Validator.

Validación declarativa

La forma preferida de hacer validación en OpenXava es mediante anotaciones, es decir, de manera declarativa. Por ejemplo, sólo has de marcar una propiedad como @Required:
@Required  // Esto fuerza a validar esta propiedad como requerida al grabar
private String nombre;
Y OpenXava hará la validación correspondiente al grabar:
annotations_es010.png

Validaciones predefinidas

Las anotaciones de validación que OpenXava tiene incluidas son:
Anotación
Aplica a
Validación
@Required
Propiedad
Comprueba si la propiedad tiene valor
@PropertyValidator
Propiedad
Permite definir una lógica de validación personalizada
@EntityValidator
Entidad
Permite definir una lógica de validación personalizada
@RemoveValidator
Entidad
Permite definir una lógica de validación personalizada al borrar
Las anotaciones de Bean Validation son reconocidas por OpenXava, por tanto puedes usar todas las anotaciones predefinidas de Bean Validation en tus aplicaciones OpenXava:
Anotación
Aplica a
Validación
@Max(value=)
Propiedad (numérica)
Comprueba que el valor sea igual o menor al máximo
@Min(value=)
Propiedad (numérica)
Comprueba que el valor sea igual o mayor al mínimo
@DecimalMax(value=)
Propiedad (numérica o cadena representando un valor numérico)
Comprueba que el valor sea igual o menor al máximo el cual puede contener decimales
@DecimalMin(value=)
Propiedad (numérica o cadena representando un valor numérico)
Comprueba que el valor sea igual o mayor al mínimo el cual puede contener decimales
@NotNull
Propiedad
Comprueba que el valor no sea nulo
@Null
Propiedad
Comprueba que el valor sea nulo
@Past
Propiedad (fecha o calendario)
Comprueba que la fecha esté en el pasado
@Future
Propiedad (fecha o calendario)
Comprueba que la fecha esté en el futuro
@Pattern(regexp="regexp", flags=)
Propiedad (cadena)
Comprueba que la propiedad cumpla la expresión regular dado un match flag
@Size(min=, max=)
Propiedad (cadena, array, colección, mapa)
Comprueba que la cantidad de elementos esté entre min y max (ambos incluidos)
@AssertFalse
Propiedad
Comprueba que el método se evalúe a falso (útil para restricciones expresadas con código en vez de por anotaciones)
@AssertTrue
Propiedad
Comprueba que el método se evalúe a cierto (útil para restricciones expresadas con código en vez de por anotaciones)
@Digits(integer=, fraction=)
Propiedad (numérica o cadena representando un valor numérico)
Comprueba que la propiedad sea un número con como máximo los enteros indicados en integer y los decimales indicados en fraction
También puedes usar las anotaciones de Hibernate Validator:
@Length(min=, max=)
Propiedad (cadena)
Comprueba que la longitud de la cadena esté dentro del rango
@NotEmpty
Propiedad (cadena, array, colección, mapa)
Comprueba que el valor no sea nulo ni esté vacío
@Range(min=, max=)
Propiedad (numérica o cadena representando un valor numérico)
Comprueba que el valor este entre min y max (ambos incluidos)
@Email
Propiedad (cadena)
Comprueba que la cadena cumpla con la especificación de formato para una dirección de correo electrónico
@CreditCardNumber(ignoreNonDigitCharacters=)
Propiedad (cadena)
Comprueba si la cadena es un número de tarjeta de crédito bien formateado (derivado del algoritmo del Luhn)
@EAN
Propiedad (cadena)
Comprueba que la cadena sea un código EAN o UPC-A correctamente formateado
@LuhnCheck(startIndex=, endIndex=, checkDigitIndex=, ignoreNonDigitCharacters=)
Propiedad (cadena)
Comprueba que los dígitos en la cadena pasen el algoritmo de Luhn
@Mod10Check(multiplier=, weight=, startIndex=, endIndex=, checkDigitIndex=, ignoreNonDigitCharacters=)
Propiedad (cadena)
Comprueba que los dígitos en la cadena pasen el algoritmo genérico mod 10
@Mod11Check(threshold=, startIndex=, endIndex=, checkDigitIndex=, ignoreNonDigitCharacters=, treatCheck10As=, treatCheck11As=)
Propiedad (cadena)
Comprueba que los dígitos en la cadena pasen el algoritmo genérico mod 11
@NotBlank
Propiedad (cadena)
Comprueba que la cadena no sea nulo y su longitud sea mayor de cero después de hacer un trim()
@SafeHtml(whitelistType=, additionalTags=, additionalTagsWithAttributes=)
Propiedad (cadena)
Comprueba que la cadena no contenga fragmentos potencialmente peligrosos como <script/>
@ScriptAssert(lang=, script=, alias=)
Propiedad (cualquier tipo)
Comprueba que el script provisto pueda ser evaluado con éxito contra el valor de la propiedad
@URL(protocol=, host=, port= regexp=, flags=)
Propiedad (cadena)
Comprueba que la cadena sea una URL válida según RFC2396

Validación propia

Añadir tu propia lógica de validación a tu entidad es muy fácil porque las anotaciones @PropertyValidator, @EntityValidator y @RemoveValidator te permiten indicar una clase (el validador) con la lógica de validación.
Por ejemplo, si quieres tu propia lógica para validar una propiedad precioUnitario, has de escribir algo parecido a esto:
@PropertyValidator(ValidadorPrecioUnitario.class)  // Contiene la lógica de validación
private BigDecimal precioUnitario;
Y ahora puedes escribir la lógica que quieras dentro de la clase ValidadorPrecioUnitario:
public class ValidadorPrecioUnitario
    implements IPropertyValidator {  // Tiene que implementar IPropertyValidator (1)
 
    public void validate(  // Requerido por IPropertyValidator (2)
        Messages errores,  // Aquí añades los mensajes de error(3)
        Object objeto,  // El valor a validar
        String nombreObjeto,  // El nombre de entidad, normalmente para usar en el mensaje
        String nombrePropiedad)  // El nombre de propiedad, normalmente para usar en el mensaje
    {
        if (objeto == null) return;
        if (!(objeto instanceof BigDecimal)) {
            errores.add(  // Si añades un error la validación fallará
                "tipo_esperado",  // Id de mensaje en el archivo i18n
                nombrePropiedad,  // Argumentos para el mensaje i18n
                nombreObjeto,
                "bigdecimal");
            return;
        }
        BigDecimal n = (BigDecimal) objeto;
        if (n.intValue() > 1000) {
            errors.add("no_mayor_de_1000");  // Id de mensaje en el archivo i18n
        }
    }
}
Como ves tu clase validador ha de implementar IPropertyValidator (1), esto te obliga a tener un método validate() (2) que recibe un objeto Messages, que llamamos errores (3); que es un contenedor de los mensajes de error. Solo necesitas añadir algún mensaje de error para hacer que falle la validación.
Esta es una forma sencilla de hacer tus propias validaciones, además la lógica de validación en tus validadores puede reutilizarse por toda tu aplicación. Aunque, si lo que quieres es crear validaciones reutilizables una opción mejor es crear tu propia anotación de validación usando Bean Validation; es más largo que usar una clase validador, pero es más elegante si reutilizas la validación muchas veces.

Interfaz de usuario

Aunque OpenXava genera automáticamente una interfaz de usuario bastante funcional a partir de una entidad JPA desnuda, esto es solo útil para casos muy básicos. En aplicaciones de la vida real es necesario refinar la manera en que la interfaz de usuario es generada. En OpenXava esto se hace con anotaciones que, con un nivel de abstracción alto, definen el aspecto de la interfaz de usuario.

La interfaz de usuario por defecto

Por defecto, OpenXava genera una interfaz de usuario que muestra todos los miembros de la entidad en secuencia. Con una entidad como esta:
@Entity
public class Comercial {
 
    @Id @Column(length=3)
    private int numero;
 
    @Column(length=40) @Required
    private String nombre;
 
    @OneToMany(mappedBy="comercial")
    private Collection<Cliente> clientes;
 
    // Getters y setters
    ...
}
OpenXava producirá para ti la siguiente interfaz de usuario:
annotations_es020.png
Como ves, muestra los miembros (numero, nombre y clientes en este caso) en el mismo orden en que fueron declarados en el código Java. OpenXava usa las anotaciones JPA y de validación para generar la mejor interfaz de usuario posible, por ejemplo, determina el tamaño del editor a partir de @Column(length), muestra la llavecita de clave para la propiedad con @Id y muestra un icono para indicar que es requerido si la propiedad está marcada con @Required y así por el estilo.
Esta interfaz por defecto es útil para casos simples, pero para interfaces de usuario más avanzadas necesitas una forma de personalizar. OpenXava te proporciona anotaciones para hacerlo, tal como @View para definir la disposición de los miembros.

La anotación @View

@View sirve para definir la disposición de los miembros en la interfaz de usuario. Se define a nivel de entidad:
@Entity
@View(members=
    "ano, numero, fecha;" +  // Coma indica en la misma línea
    "descuentos [" +  // Entre corchetes indica dentro de un marco
    "    descuentoCliente, descuentoTipoCliente;" +
    "];" +
    "observaciones;" +  // Punto y coma indica nueva línea
    "cliente { cliente }" +     // Entre llaves indica dentro de una pestaña
    "detalles { detalles }" +
    "importes { sumaImportes; porcentajeIVA; iva }" +
    "albaranes { albaranes }"
)
public class Factura {
La interfaz de usuario resultante es:
annotations_es030.png
Como ves, definir la disposición de los miembros es fácil, solo necesitas enumerarlos dentro de una cadena, usando comas para separar los elementos, punto y coma para salto de línea, corchetes para grupos (marcos), llavecitas para secciones (pestañas) y así por el estilo.
Puedes tener varias vistas por cada entidad, para eso usa la anotación @Views, dando un nombre a cada vista:
@Entity
@Views({
    @View(
        members="codigo, nombre; direccion; facturas"
    ),
    @View(
        name="Simple", members="codigo, nombre"
    )
})
public class Cliente {
Si dejas una vista sin nombre, será la vista por defecto. Los nombres de vista se usarán desde otras partes de la aplicación para escoger que vista usar.
Con @View defines la distribución, pero también necesitas definir la forma en que cada miembro es visualizado, para eso, OpenXava te proporciona un conjunto de anotaciones bastante útiles que verás en la siguiente sección.

Refinando la presentación de los miembros

OpenXava te permite refinar la interfaz de usuario para cualquier propiedad, referencia o colección de infinidad de maneras. Sólo necesitas añadir la anotación correspondiente. Por ejemplo, por defecto una referencia (una relación @ManyToOne) se visualiza usando un marco con una vista detallada, si quieres mostrar esa referencia usando un combo solo has de anotar la referencia con @DescriptionsList:
annotations_es040.png
Puede que quieras el efecto de una anotación solo para algunas vistas. Para ello usa el atributo forViews disponible en todas las anotaciones de interfaz de usuario:
@Views ({     // Tienes varias vistas para Comercial
    @View(members=" ... "),
    @View(name="Simplisima", members=" ... "),
    @View(name="Simple", members=" ... "),
    @View(name="Completa", members=" ... ")
})
public class Comercial {
 
    @DescriptionsList(forViews="Simplisima, Simple")  // El combo solo se usará
    @ManyToOne(fetch=FetchType.LAZY)           // para 'nivel' en la vistas Simplisima y Simple
    private NivelComercial nivel;
La siguiente tabla muestra todas las anotaciones OpenXava para personalizar la interfaz de usuario de los miembros de una entidad:
Anotación
Descripción
Aplica a
@Action
Asocia una acción a una propiedad o referencia en la vista
Propiedades y referencias
@AsEmbedded
Hace que comportamiento en la vista de una referencia (o colección) a entidad sea como en el caso de un objeto incrustado (o colección de entidades con CascadeType.REMOVE)
Referencias y colecciones
@Collapsed
El marco que envuelve al miembro estará cerrado inicialmente
Referencias y colecciones
@CollectionView
La vista del objeto referenciado (cada elemento de la colección) que será usada para visualizar el detalle
Colecciones
@Condition
Restringe los elementos que aparecen en la colección
Colecciones
@DescriptionsList
Para visualizar una referencia como una lista de descripciones (un combo)
Referencias
@DetailAction
Añade una acción al detalle que está siendo editado en una colección
Colecciones
@DisplaySize
El tamaño en caracteres del editor en la interfaz de usuario usado para visualizar esta propiedad
Propiedades
@EditAction
Permite definir una acción propia para editar el elemento de la colección
Colecciones
@EditOnly
El usuario final podrá modifica los elementos existentes en la colección, pero no añadir o quitar elementos
Colecciones
@Editor
Nombre del editor a usar para visualizar el miembro en esta vista
Propiedades, referencias y colecciones.
@HideDetailAction
En una colección permite definir una acción propia para ocultar la vista de detalle
Colecciones
@LabelFormat
Formato para visualizar la etiqueta de esta propiedad o referencia (visualizada como lista descripciones)
Propiedades y referencias
@LabelStyle
Estilo para visualizar la etiqueta
Propiedades y referencias como lista descripciones
@ListAction
Para añadir acciones a la lista en una colección
Colecciones
@ListProperties
Propiedades a mostrar en la lista que visualiza la colección
Colecciones
@NewAction
Permite definir una acción propia para añadir un nuevo elemento a la colección
Colecciones
@NoCreate
El usuario final no podrá crear nuevos objetos del tipo referenciado desde aquí
Referencias y colecciones
@NoFrame
La referencia no se visualizará dentro de un marco
Referencias
@NoModify
El usuario final no podrá modificar el objeto actual referenciado desde aquí
Referencias y colecciones
@NoSearch
El usuario no tendrá el vínculo para hacer búsquedas con una lista, filtros, etc.
Referencias
@OnChange
Acción a ejecutar cuando el valor de la propiedad o referencia cambia
Propiedades y referencias
@OnChangeSearch
Acción a ejecutar para hacer la búsqueda de la referencia cuando el usuario teclea los valores clave
Referencias
@OnSelectElementAction
Permite definir una acción a ejecutar cuando un elemento de la colección es seleccionado o deseleccionado
Colecciones
@ReadOnly
El miembro nunca será editable por el usuario final en las vistas indicadas
Propiedades, referencias y colecciones
@ReferenceView
Vista del objeto referenciado a usar para visualizar esta referencia
Referencia
@RemoveAction
Permite definir una acción propia para quitar el elemento de la colección
Colecciones
@RemoveSelectedAction
Permite definir una acción propia para quitar los elementos seleccionados de la colección
Colecciones
@RowAction
En las colecciones añade una acción en cada fila, pero no en la barra de botones de la colección
Colecciones
@RowStyle
Para indicar el estilo de la fila para la lista y colecciones
Entidad (mediante @Tab) y colecciones
@SaveAction
Permite definir una acción propia para grabar el elemento de la colección
Colecciones
@SearchAction
Permite definir una acción propia para buscar
Referencias
@SearchListCondition
Define una condición para ser usada cuando se muestra la lista de elementos seleccionables para añadir a una colección o asignar a una referencia
Referencias y colecciones
@Tree
Visualiza la colección usando un árbol
Colecciones
@ViewAction
Permite definir una acción propia para visualizar el elemento de la colección
Colecciones
@XOrderBy
La versión eXtendida de @OrderBy (JPA)
Colecciones
Puedes pensar que hay muchas anotaciones, pero en realidad hay todavía más, porque la mayoría de estas anotaciones tienen una versión en plural para definir diferentes valores para diferentes vistas:
@DisplaySizes({  // Para usar varias veces @DisplaySize
    @DisplaySize(forViews="Simple", value=20),  // nombre tiene  20 para display
                                        // size en la vista Simple
    @DisplaySize(forViews="Completa", value=40)  // nombre tiene 40 para display
                                        // size en la vista Complete
})
private String nombre;
No te preocupes si aún no sabes como usar todas las anotaciones. Las irás aprendiendo poco a poco a medida que desarrolles aplicaciones OpenXava.

Aprende más acerca de la interfaz de usuario

Esta sección te introduce brevemente a la generación de interfaz de usuario con OpenXava. Por desgracia, muchas e interesantes cosas se nos han quedado en el tintero, como por ejemplo, los grupos y secciones anidados, la herencia de vistas, detalles sobre como usar todas las anotaciones de IU, etc.
A través de este curso aprenderás técnicas avanzadas sobre la interfaz de usuario.

Otras anotaciones

Aparte de las validaciones y de la interfaz de usuario, OpenXava dispone de algunas otras anotaciones interesantes:
Anotación
Descripción
Aplica a
@DefaultValueCalculator
Para calcular el valor inicial
Propiedades y referencias
@Hidden
Una propiedad oculta tiene significado para el desarrollador pero no para el usuario
Propiedades
@Depends
Declara que una propiedad depende de otra(s)
Propiedades
@Stereotype
Un estereotipo es la forma de determinar un comportamiento específico para un tipo
Propiedades
@Tab
Define el comportamiento para la presentación de los datos tabulares (modo lista)
Entidades
@SearchKey
Una propiedad o referencia marcada como clave de búsqueda se usará por el usuario para buscar
Propiedades y referencias

Resumen

Este capítulo ha mostrado como usar anotaciones Java para hacer programación declarativa con OpenXava. Cosas como la interfaz de usuario o las validaciones, que típicamente son pura programación, se pueden hacer con tan solo anotar nuestro código.
Puedes aprender más detalles sobre estas anotaciones e incluso aprender más anotaciones en la Guía de Referencia de OpenXava y en OpenXava API Doc de org.openxava.annotations. Sin embargo, la mejor forma de aprender es con ejemplos, y el resto del curso es justo eso, un conjunto de ejemplos y más ejemplos que te mostrarán como usar estas anotaciones.
Empecemos a aprender.

¿Problemas con la lección? Pregunta en el foro