Network Deployment (Distributed operating systems), v8.0 > Develop and deploying applications > Develop EJB applications > Develop applications that use the Java Persistence API
Bean validation in JPA
Data validation is a common task that occurs in all layers of an application, including persistence. The Java Persistence API (JPA) 2.0 provides support for the Bean Validation API so that data validation can be done at run time. This topic includes a usage scenario where bean validation is used in the JPA environment of a sample digital image gallery application.
The Bean Validation API provides seamless validation across technologies on Java Enterprise Edition 6 (Java EE 6) and Java Platform, Standard Edition (JSE) environments. In addition to JPA 2.0, these technologies include JavaServer Faces (JSF) 2.0 and Java EE Connector Architecture (JCA) 1.6. You can read more about bean validation in the topic, Bean Validation API.
There are three core concepts of bean validation: constraints, constraint violation handling, and the validator. If you are running applications in an integrated environment like JPA, there is no need to interface directly with the validator.
Validation constraints are annotations or XML code that are added to a class, field, or method of a JavaBeans component. Constraints can be built in or user-defined. They are used to define regular constraint definitions and for composing constraints. The built-in constraints are defined by the bean validation specification and are available with every validation provider. For a list of built-in constraints, see the topic, Bean validation built-in constraints. If you need a constraint different from the built-in constraints, you can build your own user-defined constraint.
Constraints and JPA
The following usage scenario illustrates how a built-in constraint is used in the JPA architecture of a sample digital image gallery application.
In the first code example, a built-in constraint is added to a simple entity of the JPA model called image. An image has an ID, image type, file name, and image data. The image type must be specified and the image file name must include a valid JPEG or GIF extension. The code shows the annotated image entity with some built-in bean validation constraints applied.
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; } }The Image class uses two built-in constraints, @NotNull and @Pattern. The @NotNull constraint ensures that an ImageType element is specified and the @Pattern constraint uses regular expression pattern matching to ensure that the image file name is suffixed with a supported image format. Each constraint has corresponding validation logic that gets started at run time when the image entity is validated. If either constraint is not met, the JPA provider throws a ConstraintViolationException with the defined message. The JSR-303 specification also makes provisions for the use of a variable within the message attribute. The variable references a keyed message in a resource bundle. The resource bundle supports environment-specific messages and globalization, translation, and multicultural support of messages.
You can create your own custom validator and constraints. In the previous example, the Image entity used the @Pattern constraint to validate the file name of the image. However, it did not check constraints on the actual image data itself. We can use a pattern-based constraint; however, you do not have the flexibility that you would if you created a constraint specifically for checking constraints on the data. In this case you can build a custom method-level constraint annotation. The following is a custom or user-defined constraint called 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 }; }Next, create the validator class, ImageContentValidator. The logic within this validator gets implemented by the validation provider when the constraint is validated. The validator class is bound to the constraint annotation through the validatedBy attribute on the @Constraint annotation as shown in the following code: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; } }Apply this new constraint to the getData() method on the Image class; for example:@ImageContent public byte[] getData() { return data; }When validation of the data attribute occurs, the isValid() method in the ImageContentValidator is started. This method contains logic for performing simple validation of the format of the binary image data. A potentially overlooked feature in the ImageContentValidator is that it can also validate for a specific image type. By definition, it accepts JPEG or GIF formats, but it can also validate for a specific format. For example, by changing the annotation to the following code example, the validator is instructed to only permit image data with valid JPEG content:@ImageContent(ImageType.JPEG) public byte[] getData() { return data; }Type-level constraints are also a consideration because you might need to validate combinations of attributes on an entity. In the previous examples validation constraints were used on individual attributes. Type-level constraints make it possible to provide collective validation. For example, the constraints applied to the image entity validate that an image type is set (not null), the extension on the image file name is of a supported type, and the data format is correct for the indicated type. But, for example, it does not collectively validate that a file named img0.gif is of type GIF and the format of the data is for a valid GIF file image. For more information about type-level constraints, see the white paper, OpenJPA Bean Validation Primer, and the section "Type-level constraints."
Validation groups
Bean validation uses validation groups to determine what type of validation and when validation occurs.
There are no special interfaces to implement or annotations to apply to create a validation group. A validation group is denoted by a class definition.
When using groups, use simple interfaces. Using a simple interface makes validation groups more usable in multiple environments. Whereas, if a class or entity definition is used as a validation group, it might pollute the object model of another application by bringing in domain classes and logic that do no make sense for the application. By default, if a validation group or multiple groups is not specified on an individual constraint, it is validated using the javax.validation.groups.Default group. Creating a custom group is as simple as creating a new interface definition. bprac
For more information about validation groups, read the white paper, OpenJPA Bean Validation Primer, and the section "Validation groups."
JPA domain model
In addition to the Image entity are Album, Creator and Location persistent types. An Album entity contains a reference to collection of its Image entities. The Creator entity contains a reference to the album entities that the image Creator contributed to and a reference to the Image entities created. This provides full navigational capabilities to and from each of the entities in the domain. An embeddable location, has been added to image to support storing location information with the image.
The Album and Creator entities have standard built-in constraints. The embeddable location is more unique in that it demonstrates the use of the @Valid annotation to validate embedded objects.
To embed location into an image, a new field and corresponding persistent properties are added to the Image class; for example:
private Location location; @Valid @Embedded public Location getLocation() { return location; } public void setLocation(Location location) { this.location = location; }The @Valid annotation provides chained validation of embeddable objects within a JPA environment. Therefore, when image is validated, any constraints on the location it references are also validated. If @Valid is not specified, the location is not validated. In a JPA environment, chained validation through @Valid is only available for embeddable objects. Referenced entities and collections of entities are validated separately to prevent circular validation.
Bean validation and the JPA environment
The JPA 2.0 specification makes integration with the Bean Validation API simple. In a JSE environment, bean validation is enabled by default when provided the Bean Validation API and a bean validation provider on your runtime class path. In a Java EE 6 environment, the application server includes a bean validation provider so there is no need to bundle one with the application. In both environments, use a v2.0 persistence.xml file.
A Version 1.0 persistence.xml provides no means to configure bean validation. Requiring a v2.0 persistence.xml prevents a pure JPA 1.0 application from incurring the validation startup and runtime costs. This is important given that there is no standard means for a 1.0-based application to disable validation. In a Java EE 6 environment, enable validation in an existing 1.0 application by modifying the root element of your persistence.xml file. The following example represents the persistence.xml file:
<?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>Validation modes
Bean validation provides three modes of operation within the JPA environment:
- Auto
Enables bean validation if a validation provider is available within the class path. Auto is the default.
- Callback
When callback mode is specified, a bean validation provider must be available for use by the JPA provider. If not, the JPA provider throws an exception upon instantiation of a new JPA entity manager factory.
- None
Disables bean validation for a particular persistence unit.
Auto mode simplifies deployment, but can lead to problems if validation does not take place because of a configuration problem.
Use either none or callback mode explicitly for consistent behavior. Also, if none is specified, JPA optimizes at startup and does not attempt to perform unexpected validation. Explicitly disabling validation is especially important in a Java EE 6 environment where the container is mandated to provide a validation provider. Therefore, unless specified, a JPA 2.0 application started in a container has validation enabled. This process adds additional processing during life cycle events.
There are two ways to configure validation modes in JPA 2.0. The simplest way is to add a validation-mode element to the persistence.xml with the wanted validation mode as shown in the following example:
<persistence-unit name="auto-validation"> ... <!-- Validation modes: AUTO, CALLBACK, NONE --> <validation-mode>AUTO </validation-mode> ... </persistence-unit>The other way is to configure the validation mode programmatically by specifying the javax.persistence.validation.mode property with value auto, callback, or none when creating a new JPA entity manager factory as shown in the following example:Map <String, String> props = new HashMap <String, String>(); props.put("javax.persistence.validation.mode", "callback"); EntityManagerFactory emf = Persistence.createEntityManagerFactory("validation", props);Validation in the JPA life cycle
Bean validation within JPA occurs during JPA life cycle event processing. If enabled, validation occurs at the final stage of the PrePersist, PreUpdate, and PreRemove life cycle events. Validation occurs only after all user-defined life cycle events, since some of those events can modify the entity that is being validated. By default, JPA enables validation for the default validation group for PrePersist and PreUpdate life cycle events. If validate other validation groups or enable validation for the PreRemove event, you can specify the validation groups to validate each life cycle event in the persistence.xml as shown in the following example:
<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>The following example shows various stages of the JPA life cycle, including persist, update, and remove: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();
Exceptions
Validation errors can occur in any part of JPA life cycle.
If one or more constraints fail to validate during a life cycle event, a ConstraintViolationException is thrown by the JPA provider. The ConstraintViolationException thrown by the JPA provider includes a set of ConstraintViolations that occurred. Individual constraint violations contain information regarding the constraint, including: a message, the root bean or JPA entity, the leaf bean which is useful when validating JPA embeddable objects, the attribute which failed to validate, and the value that caused the failure. The following is a sample exception handling routine:
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()) { .println("Embeddable: " + cv.getLeafBean().getClass().getSimpleName()); } .println("Attribute: " + cv.getPropertyPath()); .println("Invalid value: " + cv.getInvalidValue()); } }Constraint violation processing is typically simple when using attribute-level constraints. If you are using a type-level validator with type-level constraints, it can be more difficult to determine which attribute or combination of attributes failed to validate. Also, the entire object is returned as the invalid value instead of an individual attribute. In cases where specific failure information is required, use of an attribute-level constraint or a custom constraint violation might be provided as described in the Bean Validation specification.
Sample
The JPA model and image gallery application usage scenario provided in this topic can be implemented through a sample provided in the white paper, OpenJPA Bean Validation primer.
JCA 1.6 support for annotations in RAR modules
OpenJPA Bean Validation Primer
JSR 303: Bean Validation
Task overview: Storing and retrieving persistent data with the JPA API
Troubleshoot bean validation in RAR modules
Bean validation troubleshooting tips
Bean validation in RAR modules
Bean Validation
Bean validation built-in constraints
Migration of JPA applications and bean validation