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.
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.
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.

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.
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.
<?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>
- 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.

<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);
<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.
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.