Validação de Bean no JPA

A validação de dados é uma tarefa comum que ocorre em todas as camadas de um aplicativo, incluindo persistência. A API de Persistência Java™ (JPA) fornece suporte para o Bean Validation API, de modo que a validação de dados possa ser feita no tempo de execução. Este tópico inclui um cenário de uso em que a validação de bean é usada no ambiente do JPA de um aplicativo de galeria de imagens digitais de amostra.

O Bean Validation API fornece validação contínua através de tecnologias nos ambientes do Java Enterprise Edition (Java EE) e Java Platform, Standard Edition (JSE). Além do JPA, essas tecnologias incluem o JavaServer Faces (JSF) e o Java EE Connector Architecture (JCA). É possível ler mais sobre validação de bean no tópico, Bean Validation API.

Existem três conceitos principais de validação de bean: restrições, manipulação de violação de restrição e o validador. Se você estiver executando aplicativos em um ambiente integrado como JPA, não há necessidade de conectar por meio de interface diretamente com o validador.

Restrições de validação são anotações ou código XML que são incluídos em uma classe, um campo ou um método de um componente JavaBeans. As restrições podem ser integradas ou definidas pelo usuário. Elas são usadas para fazer definições de restrições regulares e para compor restrições. As restrições integradas são definidas pela especificação de validação de bean e estão disponíveis com cada provedor de validação. Para obter uma lista de restrições integradas, consulte o tópico Restrições Integradas de Validação de Bean. Se você precisar de uma restrição diferente das restrições integradas, poderá construir sua própria restrição definida pelo usuário.

Restrições e JPA

O cenário de uso a seguir ilustra como uma restrição integrada é usada na arquitetura de JPA de um aplicativo de galeria de imagens digitais de amostra.

No primeiro exemplo de código,uma restrição integrada é incluída em uma entidade simples do modelo de JPA chamado imagem. Uma imagem possui um ID, um tipo de imagem e dados de imagem. O tipo de imagem deve ser especificado e o nome do arquivo de imagem deve incluir uma extensão JPEG ou GIF válida. O código mostra a entidade de imagem anotada com algumas restrições de validação de bean integradas aplicadas.
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="Image type must be specified.")
    @Enumerated(EnumType.STRING)
    public ImageType getType() {
        return type;
    }

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

    @Pattern(regexp = ".*\\.jpg|.*\\.jpeg|.*\\.gif",
        message="Only images of type JPEG or GIF are supported.")
    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;
    }
}

A classe Imagem usa duas restrições integradas, @NotNull e @Pattern. A restrição @NotNull assegura que um elemento ImageType seja especificado e que a restrição @Pattern use um padrão de expressão regular correspondente para assegurar que o nome do arquivo de imagem tenha como sufixo um formato de imagem suportado. Cada restrição possui a lógica de validação correspondente que é iniciada no tempo de execução quando a entidade de imagem é validada. Se a restrição não for atendida, o provedor JPA lançará uma ConstraintViolationException com a mensagem definida. A especificação JSR-303 faz também provisões para o uso de uma variável dentro do atributo da mensagem. A variável faz referência a uma mensagem com chave em um pacote configurável de recursos. O pacote configurável de recursos suporta mensagens específicas do ambiente e suporte de globalização, conversão e multicultural de mensagens.

É possível criar o seu próprio validador customizado e restrições. No exemplo anterior, a entidade Imagem usava a restrição @Pattern para validar o nome do arquivo da imagem. Entretanto, ela não verificava restrições nos próprios dados de imagem real. É possível usar uma restrição baseada em padrão, entretanto, você não possui a flexibilidade que teria se uma restrição for criada especialmente para as restrições de verificação nos dados. Nesse caso, é possível construir uma anotação de restrição no nível de método customizada. A seguir há uma restrição customizada ou definida pelo usuário chamada 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 "Image data is not a supported format.";
    	Class<?>[] groups() default {};
		Class<? extends Payload>[] payload() default {};
		ImageType[] value() default { ImageType.GIF, ImageType.JPEG };
}
A seguir, você deve criar a classe do validador, ImageContentValidator. A lógica dentro desse validador é implementada pelo provedor de validação quando a restrição for validada. A classe do validador é limitada à anotação da restrição por meio do atributo validatedBy na anotação @Constraint, conforme mostrado no seguinte código:
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;
/**
 * Simple check that file format is of a supported type
 */
public class ImageContentValidator implements ConstraintValidator<ImageContent, byte[]> {
    private List<ImageType> allowedTypes = null;
    /**      
     * Configure the constraint validator based on the image      
     * types it should support.      
     * @param constraint the constraint definition      
     */     
    public void initialize(ImageContent constraint) {         
        allowedTypes = Arrays.asList(constraint.value());     
    }      
    /**      
     *Validate a specified value.      
     */     
    public boolean isValid(byte[] value, ConstraintValidatorContext context) {
        if (value == null) {             
            return false;
        }
        // Verify the GIF header is either GIF87 or 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;
            }
        }
        // Verify the JPEG begins with SOI and ends with 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;
            }
        }
        // Unknown file format
        return false;     
    }
}
Aplique esta nova restrição no método getData() na classe Image, por exemplo:
@ImageContent
    public byte[] getData() {
        return data;
    }
Quando ocorrer a validação do atributo de dados, o método isValid() no ImageContentValidator será iniciado. Este método contém lógica para execução de validação simples do formato dos dados de imagem binários. Um recurso potencialmente negligenciado no ImageContentValidator é que ele pode também validar um tipo de imagem específico. Por definição, ele aceita formatos JPEG ou GIF, mas pode também validar um formato específico. Por exemplo, ao alterar a anotação para o seguinte exemplo de código, o validador será instruído a permitir apenas os dados da imagem com conteúdo JPEG válido:
@ImageContent(ImageType.JPEG)
    public byte[] getData() {
        return data;
    }
As restrições Nível de Tipo são também uma consideração, pois talvez você precise validar combinações de atributos em uma entidade. Nos exemplos anteriores, as restrições de validação eram usadas em atributos individuais. As restrições de nível de tipo possibilitam o fornecimento de validação coletiva. Por exemplo, as restrições aplicadas para a entidade de imagem validam que um tipo de imagem foi configurado (não nulo), a extensão no nome do arquivo de imagem é de um tipo suportado e que o formato de dados esteja correto para o tipo indicado. Porém, por exemplo, ele não valida coletivamente se um arquivo chamado img0.gif é de um tipo GIF e que o formato dos dados destina-se a uma imagem de arquivo GIF válida. Para obter informações adicionais sobre restrições de nível de tipo, consulte o White Paper, OpenJPA Bean Validation Primer e a seção "Restrições de Nível de Tipo".

Grupos de Validação

A validação de bean usa os grupos de validação para determinar qual é o tipo de validação e quando a validação ocorre.

Não há interfaces especiais para implementar ou anotações para serem aplicadas para criar um grupo de validação. Um grupo de validação é estipulado por uma definição de classe.
Boas Práticas Boas Práticas: Quando usar grupos, use interfaces simples. O uso de uma interface simples torna os grupos de validação mais utilizáveis em diversos ambientes. Enquanto que, se uma definição de classe ou entidade for usada como um grupo de validação, ela poderá poluir o modelo do objeto de outro aplicativo trazendo classes de domínio e lógica que não tenham sentido para o aplicativo. Por padrão, se um grupo de validação ou diversos grupos não estiverem especificados em uma restrição individual, ele será validado usando o grupo javax.validation.groups.Default. A criação de um grupo customizado é tão simples quanto criar uma nova definição de interface. bprac

Para obter mais informações sobre grupos de validação com o OpenJPA, leia o White Paper, o OpenJPA Bean Validation Primer e a seção "Grupos de validação".

Modelo de Domínio JPA

Além da entidade Imagem, existem os tipos persistentes Álbum, Criador e Localização. Uma entidade Álbum contém uma referência à coleção de suas entidades Imagem. A entidade Criador contém uma referência às entidades de álbum com as quais o Criador da imagem contribuiu e uma referência às entidades Imagem criadas. Isso fornece recursos completos de navegação para e de cada uma das entidades do domínio. Um local integrável foi incluído na imagem para suportar o armazenamento de informações de local com a imagem.

As entidades Álbum e Criador possuem restrições integradas padrão. O local integrável é mais exclusivo por demonstrar o uso da anotação @Valid para validar os objetos integrados. Para integrar o local em uma imagem, um novo campo e propriedades persistentes correspondentes são incluídos na classe Image; por exemplo:
private Location location;

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

    public void setLocation(Location location) {
        this.location = location;
    }
A anotação @Valid fornece validação em cadeia de objetos integráveis em um ambiente JPA. Portanto, quando uma imagem é validada, quaisquer restrições no local que ela referencia também são validadas. Se @Valid não for especificado, o local não será validado. Em um ambiente do JPA, a validação encadeada por meio de @Valid estará disponível apenas para objetos incorporáveis. As entidades e coleções de entidades referidas são validadas separadamente para evitar validação circular.

Validação de Bean e o Ambiente do JPA

A especificação do JPA faz a integração com o Bean Validation API simples. Em um ambiente do JSE, a validação de bean é ativada por padrão quando você fornece o Bean Validation API e um provedor de validação de bean em seu caminho de classe de tempo de execução. Em um ambiente do Java EE, o servidor de aplicativos inclui um provedor de validação de bean, portanto, não é necessário incluir tal provedor em um pacote configurável com seu aplicativo. Nos dois ambientes, você deve usar um arquivo persistence.xml da Versão 2.0 ou posterior.

Um persistence.xml da Versão 1.0 não permite configurar a validação de bean. Requerer uma Versão 2.0 ou posterior do arquivo persistence.xml evita que um aplicativo JPA 1.0 puro incorra em custos de inicialização e tempo de execução de validação. Isso é importante, uma vez que não há meios padrão para um aplicativo baseado em 1.0 desativar a validação. Em um ambiente Java EE, ative a validação em um aplicativo 1.0 existente modificando o elemento-raiz do arquivo persistence.xml. O exemplo a seguir representa o arquivo 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>
A validação do bean oferece três modos de operação dentro do ambiente do JPA:
  • Automático

    Ativa a validação do bean se um provedor de validação estiver disponível dentro do caminho de classe. Automático é o padrão.

  • Retorno de Chamada

    Quando o modo retorno de chamada estiver especificado, um provedor de validação de bean deverá estar disponível para uso pelo provedor JPA. Caso contrário, o provedor JPA emitirá uma exceção na instanciação de um novo factory do gerenciador de entidades JPA.

  • Nenhum

    Desativa a validação do bean para uma unidade de persistência específica.

O modo automático simplifica a implementação, mas pode gerar problemas se a validação não ocorrer devido a um problema na configuração.
Boas Práticas Boas Práticas: Use o modo nenhum ou de retorno de chamada explicitamente para obter um comportamento consistente.bprac
Além disso, se nenhum for especificado, o JPA otimizará na inicialização e não tentará executar validação inesperada. Desativar explicitamente a validação é importante principalmente em um ambiente do Java EE onde o contêiner é obrigado a fornecer um provedor de validação. Portanto, a menos que seja especificado, um aplicativo JPA 2.0 ou posterior iniciado em um contêiner terá a validação ativada. Esse processo inclui processamento adicional durante os eventos do ciclo de vida.
Há duas maneiras de configurar os modos de validação no JPA. A maneira mais fácil é incluir um elemento de modo de validação no arquivo persistence.xml com o modo de validação desejado, como mostra o exemplo a seguir:
 <persistence-unit name="auto-validation">
		        	... 
				<!-- Validation modes: AUTO, CALLBACK, NONE -->
				<validation-mode>AUTO</validation-mode>
				...
		</persistence-unit> 
A outra maneira é configurar o modo de validação programaticamente especificando a propriedade javax.persistence.validation.mode com o valor automático, retorno de chamada ou nenhum ao criar um novo factory do gerenciador de entidades JPA, conforme mostrado no seguinte exemplo:
Map<String, String> props = new HashMap<String, String>();
		props.put("javax.persistence.validation.mode", "callback");
        EntityManagerFactory emf = 
            Persistence.createEntityManagerFactory("validation", props);
A validação de bean dentro do JPA ocorre durante o processamento do evento de ciclo de vida do JPA. Se ativado, a validação ocorrerá no estágio final dos eventos de ciclo de vida PrePersist, PreUpdate e PreRemove. A validação ocorre apenas após todos os eventos do ciclo de vida definidos pelo usuário, uma vez que alguns desses eventos podem modificar a entidade que está sendo validada. Por padrão, o JPA ativa a validação para o grupo de validação padrão para os eventos de ciclo de vida PrePersist e PreUpdate. Se tiver que validar outros grupos de validação ou ativar a validação para o evento PreRemove, poderá especificar os grupos de validação para validar cada evento de ciclo de vida no arquivo persistence.xml, como mostra o seguinte exemplo:
<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>
O seguinte exemplo mostra vários estágios do ciclo de vida do JPA, incluindo persistência, atualização e remoção:
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");

        // Create an Image with non-matching type and file extension
        Image img = new Image();
        img.setType(ImageType.JPEG);
        img.setFileName("Winter_01.gif");
        loadImage(img);
        img.setLocation(loc);
        
        // *** PERSIST ***
        try {
            em.getTransaction().begin();
            // Persist the entity with non-matching extension and type
            em.persist(img);
        }  catch (ConstraintViolationException cve) {
            // Transaction was marked for rollback, roll it back and
            // start a new one
            em.getTransaction().rollback();
            em.getTransaction().begin();
            // Fix the file type and re-try the persist.
            img.setType(ImageType.GIF);
            em.persist(img);
            em.getTransaction().commit();
        }

        // *** UPDATE ***
        try {
            em.getTransaction().begin();
            // Modify the file name to a non-matching file name 
            // and commit to trigger an update
            img.setFileName("Winter_01.jpg");
            em.getTransaction().commit();
        }  catch (ConstraintViolationException cve) {
            // Handle the exception.  The commit failed so the transaction
            // was already rolled back.
            handleConstraintViolation(cve);
        }
        // The update failure caused img to be detached. It must be merged back 
        // into the persistence context.
        img = em.merge(img);

        // *** REMOVE ***
        em.getTransaction().begin();
        try {
            // Remove the type and commit to trigger removal
            img.setType(ImageType.GIF);
            em.remove(img);
        }  catch (ConstraintViolationException cve) {
            // Rollback the active transaction and handle the exception
            em.getTransaction().rollback();
            handleConstraintViolation(cve);
        }
        em.close();
        emf.close();

Exceções

Erros de validação podem ocorrer em qualquer parte do ciclo de vida do JPA.

Se uma ou mais restrições falharem na validação durante um evento de ciclo de vida, uma ConstraintViolationException será lançada pelo provedor JPA. A ConstraintViolationException lançada pelo provedor JPA inclui um conjunto de ConstraintViolations ocorridas. As violações de restrição individuais contêm informações referentes à restrição, incluindo: uma mensagem, o bean raiz ou a entidade do JPA, o bean de folha que é útil quando validar objetos integráveis do JPA, o atributos que falharam ao validar e o valor que causou a falha. A seguir, está uma rotina de manipulação de exceção de amostra:
private void handleConstraintViolation(ConstraintViolationException cve) {
      Set<ConstraintViolation<?>> cvs = cve.getConstraintViolations();
			for (ConstraintViolation<?> cv : cvs) {
					System.out.println("------------------------------------------------");
          	System.out.println("Violation: " + cv.getMessage());
          	System.out.println("Entity: " + cv.getRootBeanClass().getSimpleName());
          	// The violation occurred on a leaf bean (embeddable)
          	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());
      }
    }
O processamento de violação de restrição normalmente é simples quando usar as restrições no nível do atributo. Se estiver usando um validador no nível de tipo com restrições no nível de tipo, poderá ser mais difícil determinar qual atributo ou combinação de atributos falhou ao validar. Além disso, o objeto inteiro é retornado como o valor inválido em vez de um atributo individual. Nos casos em que forem necessárias informações de falha específicas, o uso de uma restrição no nível do atributo ou de uma violação de restrição customizada pode ser fornecido, conforme descrito na especificação de Validação de Bean.

Exemplo

O senário de uso do modelo JPA e do aplicativo de galeria de imagens fornecido neste tópico pode ser implementado por meio de uma amostra que é fornecida no White Paper, OpenJPA Bean Validation Primer.


Ícone que indica o tipo de tópico Tópico de Conceito



Ícone de registro de data e hora Última atualização: last_date
http://www14.software.ibm.com/webapp/wsbroker/redirect?version=cord&product=was-nd-mp&topic=cdat_beanvaljpa
Nome do arquivo: cdat_beanvaljpa.html