Validación de beans en JPA

La validación de datos es una tarea común que se produce en todas las capas de una aplicación, incluida la persistencia. La Java™ Persistence API (JPA) proporciona soporte para la API de validación de bean para que la validación de datos se pueda realizar durante el tiempo de ejecución. En este tema se incluye un caso de uso en que la validación de beans se utiliza en el entorno de JPA de una aplicación de galería de imágenes digital de muestra.

La API de validación de bean proporciona una validación sin fisuras entre tecnologías en entornos Java Enterprise Edition (Java EE ) y Java Platform, Standard Edition (JSE). Además de JPA, estas tecnologías incluyen JavaServer Faces (JSF) y Java EE Connector Architecture (JCA). Puede obtener más información sobre la validación de beans en el tema API de validación de beans.

Hay tres conceptos principales de la validación de beans: las restricciones, el manejo de las infracciones de restricciones y el validador. Si está ejecutando aplicaciones en un entorno integrado como JPA, no hay necesidad de establecer una interfaz directamente con el validador.

Las restricciones de validación son anotaciones o código XML que se añaden a una clase, un campo o método de un componente de JavaBeans. Las restricciones pueden ser incorporadas o definidas por el usuario. Se utilizan para definir las definiciones de restricciones regulares y para crear las restricciones. Las restricciones incorporadas las define la especificación de validación de beans y están disponibles con cada proveedor de validación. Para obtener una lista de restricciones incorporadas, consulte el tema Restricciones incorporadas de validación de beans. Si necesita una restricción distinta de la restricciones incorporadas, puede crear sus propias restricciones definidas por el usuario.

Restricciones y JPA

El siguiente caso de uso ilustra cómo se utiliza una restricción incorporada en la arquitectura JPA de una aplicación de galería de imágenes digital de muestra.

En el primer ejemplo de código, se añade una restricción incorporada a una entidad simple del modelo JPA denominada imagen. Una imagen tiene un ID, un tipo de imagen, un nombre de archivo y datos de imagen. El tipo de imagen se debe especificar y el nombre de archivo de la imagen debe incluir una extensión GIF o JPEG válida. El código de muestra la entidad de la imagen anotada con algunas restricciones de validación de beans incorporadas.
package org.apache.openjpa.example.gallery.model;

import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;

@Entity
public class Image {

    private long id;
    private ImageType type;
    private String fileName;
    private byte[] data;

    @Id
    @GeneratedValue
    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    @NotNull(message="Se debe especificar el tipo de imagen.")
    @Enumerated(EnumType.STRING)
    public ImageType getType() {
        return type;
    }

    public void setType(ImageType type) {
        this.type = type;
    }

    @Pattern(regexp = ".*\\.jpg|.*\\.jpeg|.*\\.gif",
        message="Solamente se da soporte a las imágenes de tipos JPEG o GIF.")
    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public byte[] getData() {
        return data;
    }

    public void setData(byte[] data) {
        this.data = data;
    }
}

La clase Imagen utiliza dos restricciones incorporadas, @NotNull y @Pattern. La restricción @NotNull garantiza que se haya especificado un elemento ImageType y la restricción @Pattern utiliza la coincidencia de patrón de expresión regular para garantizar que el nombre de archivo de la imagen es el sufijo de un formato de imagen soportado. Cada restricción tiene la lógica de validación correspondiente que se inicia en tiempo de ejecución cuando se valida la entidad de la imagen. Si alguna restricción no se cumple, el proveedor JPA genera una excepción ConstraintViolationException con el mensaje definido. La especificación JSR-303 también realiza provisiones para el uso de una variable en el atributo de mensajes. La variable hace referencia a un mensaje en clave en un paquete de recursos. El paquete de recursos da soporte a mensajes específicos del entorno y a la globalización, traducción y soporte multicultural de mensajes.

Puede crear su propio validador y restricciones personalizados. En el ejemplo anterior, la entidad Imagen utilizaba la restricción @Pattern para validar el nombre de archivo de la imagen. Sin embargo, no se comprobaban las restricciones sobre los propios datos de la imagen real. Puede utilizar una restricción basada en un patrón; no obstante, no tiene la flexibilidad que tendría si hubiera creado una restricción específicamente para la comprobación de restricciones de los datos. En este caso, puede crear una anotación de restricciones de nivel de método personalizado. A continuación encontrará una restricción personalizado o definida por el usuario denominada ImageContent.
package org.apache.openjpa.example.gallery.constraint;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import javax.validation.Constraint;
import javax.validation.Payload;

import org.apache.openjpa.example.gallery.model.ImageType;

@Documented
@Constraint(validatedBy = ImageContentValidator.class)
@Target({ METHOD, FIELD })
@Retention(RUNTIME)
public @interface ImageContent {
    	String message() default "Los datos de imagen no tienen un formato soportado.";
    	Class<?>[] groups() default {};
		Class<? extends Payload>[] payload() default {};
		ImageType[] value() default { ImageType.GIF, ImageType.JPEG };
}
A continuación, debe crear la clase del validador, ImageContentValidator. La lógica de este validador se ve implementada por el proveedor de validación cuando se valida la restricción. La clase de validador se enlaza a la anotación de la restricción mediante el atributo validatedBy en la anotación @Constraint tal como se muestra en el código siguiente:
package org.apache.openjpa.example.gallery.constraint;
import java.util.Arrays;
import java.util.List;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.apache.openjpa.example.gallery.model.ImageType;
/**
 * Realice una simple comprobación de que el formato del archivo sea un tipo soportado
 */
public class ImageContentValidator implements ConstraintValidator<ImageContent, byte[]> {
    private List<ImageType> allowedTypes = null;
    /**      
     * Configure el validador de la restricción en función de los      
     * tipos de imagen que debe soportar.      
     * @param restringe la definición de restricción      
     */     
    public void initialize(ImageContent constraint) {         
        allowedTypes = Arrays.asList(constraint.value());     
    }      
    /**      
     *Validar un valor especificado.      
     */     
    public boolean isValid(byte[] value, ConstraintValidatorContext context) {
        if (value == null) {             
            return false;
        }
        // Verificar si la cabecera GIF es GIF87 o GIF89
        if (allowedTypes.contains(ImageType.GIF)) {
            String gifHeader = new String(value, 0, 6);
            if (value.length >= 6 &&
                (gifHeader.equalsIgnoreCase("GIF87a") ||
                 gifHeader.equalsIgnoreCase("GIF89a"))) {
                return true;
            }
        }
        // Verificar si JPEG comienza con SOI y termina con EOI
        if (allowedTypes.contains(ImageType.JPEG)) {
            if (value.length >= 4 &&
                value[0] == 0xff && value[1] == 0xd8 &&
                value[value.length - 2] == 0xff && value[value.length -1] == 0xd9) {
                return true;
            }
        }
        // Formato de archivo desconocido
        return false;     
    }
}
Aplique esta nueva restricción al método getData() en la clase Imagen; por ejemplo:
@ImageContent
    public byte[] getData() {
        return data;
    }
Cuando se produce la validación del atributo de datos, se inicia el método isValid() de ImageContentValidator. Este método contiene la lógica para realizar la validación simple del formato de los datos de imagen binaria. Una característica que se puede pasar por alto en ImageContentValidator es que también puede validarse para un tipo de imagen específica. Por definición, se aceptan los formatos JPEG o GIF, pero también es posible realizar la validación para un formato específico. Por ejemplo, cambiando la anotación en el siguiente ejemplo de código, se indica al validador que sólo se permitan los datos de imágenes con un contenido JPEG válido:
@ImageContent(ImageType.JPEG)
    public byte[] getData() {
        return data;
    }
Las restricciones Tipo-nivel también hay que tenerlas en cuenta porque tal vez tenga que validar combinaciones de atributos en una entidad. En los ejemplos anteriores las restricciones de validación se han utilizado en atributos individuales. Las restricciones de tipo de nivel permiten que se proporcione una validación colectiva. Por ejemplo, las restricciones aplicadas a la entidad de imagen aseguran que se establece un tipo de imagen (no nulo), que la extensión del nombre de archivo de la imagen es de un tipo soportado y que el formato de datos es correcto para el tipo indicado. Pero, por ejemplo, no se valida en conjunto que un archivo denominado img0.gif sea del tipo GIF y el formato de los datos sea de una imagen de archivo GIF válida. Para obtener más información acerca de las restricciones de tipo de nivel, consulte el documento técnico, OpenJPA Bean Validation Primer, y la sección acerca de las restricciones de tipo de nivel.

Grupos de validación

La validación de beans utiliza grupos de validación para determinar el tipo de validación y cuándo se produce la validación.

No hay interfaces especiales para implementar ni anotaciones que se tengan que aplicar para crear un grupo de validación. Un grupo de validación se indica mediante una definición de clase.
Best practice Best practice: Cuando se utilizan grupos, utilice interfaces simples. Mediante una interfaz simple se consigue que los grupos de validación se puedan utilizar más en varios entornos. En cambio, si una definición de clase o entidad se utiliza como grupo de validación, se puede contaminar el modelo de objetos de otra aplicación incluyendo las clases de dominio y lógica sin sentido en la aplicación. De forma predeterminada, si no se especifica un grupo o varios grupos de validación en una restricción individual, se valida utilizando el grupo de javax.validation.groups.Default. La creación de un grupo personalizado resulta tan simple como crear una nueva definición de interfaz. bprac

Si desea más información sobre grupos de validación con OpenJPA, consulte el documento técnico, OpenJPA Bean Validation Primer, y la sección "Validation groups."

Modelo de dominio JPA

Además de la entidad Imagen están los tipos persistentes Álbum, Creador y Ubicación. Una entidad Álbum contiene una referencia a una colección de sus entidades Imagen. La entidad Creador contiene una referencia a las entidades de álbum con las que ha contribuido el Creador de imágenes y una referencia a las entidades Imagen creadas. Así se proporcionan prestaciones completas de navegación en cada entidad del dominio. Se ha añadido a la imagen una ubicación incorporable para dar soporte al almacenamiento de la información de ubicación con la imagen.

Las entidades Álbum y Creador tienen restricciones estándar integradas. La ubicación incorporable es más exclusiva porque muestra el uso de la anotación @Valid para validar objetos incorporados. Para incorporar una ubicación en una imagen, se añadirá un cambio nuevo y las correspondientes propiedades persistentes a la clase Imagen; por ejemplo:
private Location location;

    @Valid
    @Embedded
    public Location getLocation() {
        return location;
    }

    public void setLocation(Location location) {
        this.location = location;
    }
La anotación @Valid proporciona una validación encadenada de los objetos incorporables en un entorno JPA. Por consiguiente, cuando se valida una imagen, las restricciones de la ubicación a la que hace referencia también se validan. Si no se especifica @Valid, no se validará la ubicación. En un entorno de JPA, la validación encadenada mediante @Valid solamente está disponible para objetos incorporables. Las entidades y colecciones de entidades referidas se validan por separado para evitar una validación circular.

Validación de beans y el entorno JPA

La especificación JPA realiza la integración con la API de validación de bean simple. En un entorno JSE, la validación de beans se habilita de forma predeterminada cuando se proporciona la API Validación de beans y un proveedor de validación de beans en la classpath en tiempo de ejecución. En un entorno Java EE, el servidor de aplicaciones incluye un proveedor de valdiación de bean, así que no es necesario empaquetarlo con la aplicación. En ambos entornos, debe utilizar una versión 2.0 o posterior del archivo persistence.xml.

Un archivo persistence.xml de la versión 1.0 no proporciona ninguna forma de configurar la validación de beans. Exigir una versión 2.0 o posterior de persistence.xml evita que una aplicación JPA 1.0 incurra en costes de tiempo de ejecución e inicio de la validación. Esto resulta importante ya que no existe ningún modo estándar para una aplicación basada en la versión 1.0 para inhabilitar la validación. En un entorno Java EE, habilite la validación en una aplicación 1.0 existente modificando el elemento raíz del archivo persistence.xml. En el ejemplo siguiente se representa el archivo persistence.xml:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence        
				http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
    version="2.0" >
...
</persistence>
La validación de beans proporciona tres modalidades de funcionamiento en el entorno de JPA:
  • Auto

    Habilita la validación de beans si un proveedor de validación está disponible en la classpath. Auto es el valor predeterminado.

  • Retorno de llamada

    Cuando se especifica la modalidad de retorno de llamada, un proveedor de validación de beans debe estar disponible para por el proveedor JPA lo utilice. De no ser así, el proveedor JPA emite una excepción tras la creación de la instancia de una fábrica del gestor de entidades JPA nueva.

  • Ninguna

    Inhabilita la validación de beans para una unidad de persistencia en particular.

La modalidad automática simplifica el despliegue, pero puede ocasionar problemas si la validación no se lleva a cabo debido a un problema de configuración.
Best practice Best practice: Utilice de forma explícita la modalidad de retorno de llamada o ninguna modalidad para un comportamiento coherente.bprac
Además, si se especifica la modalidad Ninguna, JPA se optimiza en el arranque y no intenta llevar a cabo una validación inesperada. La inhabilitación explícita de la validación es especialmente importante en un entorno Java EE donde el contenedor es obligatorio para proporcionar un proveedor de validación. Por lo tanto, a menos que se especifique, una aplicación JPA 2.0 o posterior iniciada en un contenedor tiene la validación habilitada. Este proceso añade procesamiento adicional durante los sucesos del ciclo de vida.
Existen dos formas para configurar modalidades de validación en JPA. La forma más sencilla consiste en añadir un elemento de modalidad de validación en el archivo persistence.xml con la modalidad de validación que desee tal como se muestra en el ejemplo siguiente:
 <persistence-unit name="auto-validation">
		        	... 
								<!-- Modalidades de validación: AUTO, CALLBACK, NONE -->
				<validation-mode>AUTO</validation-mode>
				...
		</persistence-unit> 
La otra forma es configurar la modalidad de validación mediante programación especificando la propiedad javax.persistence.validation.mode con el valor automático, retorno de llamada o ninguno al crear una fábrica de gestión de entidades JPA nueva como se muestra en el ejemplo siguiente:
Map<String, String> props = new HashMap<String, String>();
		props.put("javax.persistence.validation.mode", "callback");
        EntityManagerFactory emf = 
            Persistence.createEntityManagerFactory("validation", props);
La validación de beans en JPA se produce durante el proceso de sucesos del ciclo de vida de JPA. Si se ha habilitado, la validación se produce en la fase final de los sucesos del ciclo de vida PrePersist, PreUpdate y PreRemove. La validación se produce solamente después de todos los sucesos del ciclo de vida definidos por el usuario, puesto que estos sucesos pueden modificar la entidad que se está validando. De forma predeterminada, JPA habilita la validación para el grupo de validación predeterminado para los sucesos de ciclo de vida PrePersist y PreUpdate. Si debe validar otros grupos de validación o habilitar la validación para el suceso PreRemove, puede especificar los grupos de validación para validarlo en cada suceso del ciclo de vida en el archivo persistence.xml tal como se muestra en el ejemplo siguiente:
<persistence-unit name="non-default-validation-groups">
		<class>my.Entity</class>
		<validation-mode>CALLBACK</validation-mode>
		<properties>
			<property name="javax.persistence.validation.group.pre-persist"
       value="org.apache.openjpa.example.gallery.constraint.SequencedImageGroup"/>
			<property name="javax.persistence.validation.group.pre-update"
       value="org.apache.openjpa.example.gallery.constraint.SequencedImageGroup"/>
			<property name="javax.persistence.validation.group.pre-remove"
       value="javax.validation.groups.Default"/>
		</property>
	</persistence-unit>
En el ejemplo siguiente se muestran varias fases del ciclo de vida de JPA, incluidas la persistencia, la actualización y la eliminación:
EntityManagerFactory emf = 
            Persistence.createEntityManagerFactory("BeanValidation");
        EntityManager em = emf.createEntityManager();

        Location loc = new Location();
        loc.setCity("Rochester");
        loc.setState("MN");
        loc.setZipCode("55901");
        loc.setCountry("USA");

        // Crear una Imagen con el tipo no coincidente y la extensión de archivo
        Image img = new Image();
        img.setType(ImageType.JPEG);
        img.setFileName("Winter_01.gif");
        loadImage(img);
        img.setLocation(loc);
        
        // *** PERSIST ***
        try {
            em.getTransaction().begin();
            // Persistir la entidad con la extensión y el tipo no coincidentes
            em.persist(img);
        } catch (ConstraintViolationException cve) {
            // La transacción se ha marcado para la retrotracción, retrotráigala e
            // inicie una nueva
            em.getTransaction().rollback();
            em.getTransaction().begin();
            // Solucione el tipo de archivo y vuelva a intentar la acción de persistir.
            img.setType(ImageType.GIF);
            em.persist(img);
            em.getTransaction().commit();
        }

        // *** UPDATE ***
        try {
            em.getTransaction().begin();
            // Modifique el nombre de archivo por un nombre de archivo no coincidente 
            // y confirme desencadenar una actualización
            img.setFileName("Winter_01.jpg");
            em.getTransaction().commit();
        }  catch (ConstraintViolationException cve) {
            // Maneje la excepción.  La confirmación ha fallado por lo que la transacción
            // ya se había retrotraído.
            handleConstraintViolation(cve);
        }
        // El error de actualización ha hecho que se desconecte img. Deberá volverla a fusionar 
        // en el contexto de persistencia.
        img = em.merge(img);

        // *** REMOVE ***
        em.getTransaction().begin();
        try {
            // Elimine el tipo y confirme que se desencadene la eliminación
            img.setType(ImageType.GIF);
            em.remove(img);
        }  catch (ConstraintViolationException cve) {
            // Retrotraiga la transacción activa y maneje la excepción
            em.getTransaction().rollback();
            handleConstraintViolation(cve);
        }
        em.close();
        emf.close();

Excepciones

Los errores de validación se pueden producir en cualquier parte del ciclo de vida de JPA.

Si una o más restricciones no se validan durante un suceso del ciclo de vida, el proveedor JPA emitirá una excepción ConstraintViolationException. La excepción ConstraintViolationException que ha generado el proveedor JPA incluye un conjunto de restricciones ConstraintViolations que se han producido. Las violaciones de restricciones individuales contienen información relacionada con la restricción, incluido un mensaje, el bean de raíz o la entidad JPA, el bean de hoja que es útil en la validación de objetos JPA incorporables, el atributo que no se pudo validar y el valor que provocó la anomalía. A continuación encontrará un ejemplo de la rutina de manejo de excepciones:
private void handleConstraintViolation(ConstraintViolationException cve) {
      Set<ConstraintViolation<?>> cvs = cve.getConstraintViolations();
			for (ConstraintViolation<?> cv : cvs) {
					System.out.println("------------------------------------------------");
          	System.out.println("Violación: " + cv.getMessage());
          	System.out.println("Entidad: " + cv.getRootBeanClass().getSimpleName());
          	// La violación se ha producido en un bean de hoja (incorporable)
          	if (cv.getLeafBean() != null && cv.getRootBean() != cv.getLeafBean()) {
              System.out.println("Embeddable: " + 
cv.getLeafBean().getClass().getSimpleName());
          }
          System.out.println("Attribute: " + cv.getPropertyPath());
          System.out.println("Invalid value: " + cv.getInvalidValue());
      }
    }
El proceso de violaciones suele ser simple cuando se utilizan restricciones a nivel de atributo. Si utiliza un validador de nivel de nivel de tipo con restricciones de nivel de tipo, puede ser más difícil determinar qué atributo o combinación de atributos no ha sido posible validar. Además, el objeto completo se devuelve como valor no válido en lugar de un atributo individual. En los casos en que sea necesaria la información específica del error, es posible utilizar una restricción de nivel de atributo o una violación personalizada de restricciones puede proporcionar, tal como se describe en la especificación de validación de beans.

Ejemplo

El modelo JPA y el caso de ejemplo de uso de la aplicación de galería de imágenes que se proporciona en este tema se pueden implementar a través de un ejemplo que se proporciona en el documento técnico, OpenJPA Bean Validation primer.


Icon that indicates the type of topic Concept topic



Timestamp icon Last updated: last_date
http://www14.software.ibm.com/webapp/wsbroker/redirect?version=cord&product=was-nd-mp&topic=cdat_beanvaljpa
File name: cdat_beanvaljpa.html