The minimal web app that we have discussed in Part 1 has been limited to support the minimum functionality of a data management app only. For instance, it did not take care of preventing the user from entering invalid data into the app’s database. In this chapter, we show how to express integrity constraints in a Java EE entity class, and how to have constraints automatically validated on critical life cycle events of entity objects with JPA, and on form submission with JSF.
The integrity constraints of a distributed app have to be checked both in model classes and in the underlying database, and possibly also in the UI. However, this requirement for three-fold validation should not imply having to define the same constraints three times in three different languages: in Java, in SQL and in HTML5/JavaScript. Rather, the preferred approach is to define the constraints only once, in the model classes, and then reuse these constraint definitions also in the underlying database and in the UI. Java EE apps support this goal to some degree. There are two types of constraint annotations:
In this section we discuss how to use some of the predefined constraint annotations and how to define a custom constraint annotation for the year
property of the Book
class, since its value has an upper bound defined by an expression (’next year’).
The JPA constraint annotations specify constraints to be used by the underlying database management system after generating the database schema, but not for Java validation. Consider the @Id
annotation in the following definition of an entity class Item
:
The @ Id
annotation of the itemCode
attribute is mapped to a SQL primary key declaration for this attribute in the corresponding database table schema. As a consequence, the itemCode
column of the generated items
table must have a value in each row and these values have to be unique. However, these conditions are not checked in the Java runtime environment. JPA generates the following CREATE TABLE statement:
Since nothing has been specified about the length of itemCode
strings, the length is set to 255 by default. However, in our case we know that itemCode
has a fixed length of 10, which can be enforced by using the @Column
annotation, which has the following parameters:
–name
allows to specify a name for the column to be used when the table is created (by default, the attribute name of the entity class is used);
–nullable
is a boolean parameter that defines if the column allows NULL
values (by default, it is true
);
–length
is a positive integer, specifying the maximum number of characters allowed for string values of that column (by default, this is 255);
–unique
is a boolean parameter that defines if the values of the column must be unique (by default, it is false
).
Using the @Column
annotation, the improved Java/JPA code of the model class is:
As a result, the generated CREATE TABLE statement now contains the additional constraints expressed for the columns itemCode
and quantity
:
In the Java EE Bean Validation125 approach, Java runtime validation can be defined in the form of bean validation annotations placed on a property, method, or class.
Table 9.1 Bean Validation annotations for properties
Constraint Type | Annotations | Examples |
String Length Constraints | @Size |
@Size( min=8, max=80) String message; |
Cardinality Constraints (for arrays, collections and maps) | @Size |
@Size( min=2, max=3) List<Member> coChairs; |
Mandatory Value Constraints | @NotNull |
@NotNull String name |
Range Constraints for numeric attributes | @Digits |
@Digits( integer=6, fraction= 2) BigDecimal price; |
Interval Constraints for integer-valued attributes | @Min and @Max |
@Min(5) int teamSize |
Interval Constraints for decimal-valued attributes | @DecimalMin and @DecimalMax |
@DecimalMax("30.00") double voltage |
Pattern Constraints | @Pattern |
@Pattern( regexp="\b\d{10} ") String isbn; |
In addition, there are annotations that require a date value to be in the future (@Future
) or in the past (@Past
).
All Bean Validation annotations have an optional message
attribute for defining a custom error message. In the following example, we add two @NotNull
annotations with messages, a @Size
and a @Min
annotation to the JPA constraint annotations. The @NotNull
annotations constrain the itemCode
and the quantity
attributes to be mandatory, while the @Min
annotation constrains the quantity
attribute to have a minimum value of 0:
Notice that that we need some duplicate logic in this example because the same constraints may have to be defined twice: as a JPA constraint and as a Bean Validation constraint. For instance, for a mandatory attribute like quantity
we have both a @Column( nullable=false)
JPA constraint annotation and a @NotNull Bean Validation annotation.
Compared to the Minimal App126 discussed in Chapter 4 we have to deal with a number of new issues:
Checking the constraints in the user interface on user input is important for providing immediate feedback to the user. Using JSF and Bean Validation requires to submit the form before the validation checks are performed. It would be preferable to define the validation checks in the model classes only and use them in the user interface before form submission, without having to duplicate the validation logic in the JSF facelets. However, at this point in time, JSF does not support this, and the validation is performed only after the form is submitted.
Using HTML5 validation attributes in the JSF facelets to enforce HTML5 validation before submitting the form requires an undesirable duplication of validation logic. The effect of such a duplication would be duplicate maintenance of the validation code, once in the model classes and once more in the user interface. In a simple application like our example app, this is doable, but in a larger application this quickly becomes a maintenance nightmare.
Using the information design model shown in Figure 7.2 above as the starting point, we make an Entity class model with getters/ setters and corresponding Java datatypes.
The Entity class model shown on the right hand side in Figure 9.1 defines getters and setters for all properties and the following property constraint annotations:
isbn
attribute is declared to be a standard identifier, implying that it is mandatory and unique.isbn
attribute has a pattern constraint requiring its values to match the ISBN-10 format (simplified to the case of 10digit strings).title
attribute is mandatory and has a string length maximum constraint of at most 50 characters.year
attribute is mandatory and has an interval constraint, of a special form where the minimum is 1459 and the maximum is not fixed, but provided by a custom annotation implementing the calendar arithmetic function nextYear()
.Since there is no predefined Bean Validation annotation for checking the uniqueness of an ID value provided when creating a new entity object, we define a static method checkIsbnAsId that can be invoked in a corresponding controller method when creating a new entity object.
In addition, the entity class model defines the static CRUD data management methods retrieveAll, create, update and delete.
The Entity class model shown on the right hand side in Figure 9.1 can be coded step by step for getting the code of the entity classes of our Java EE web app.
When defining the properties, we first need to map the platform-independent datatypes of the information design model to the corresponding implicit Java supported datatypes according to the following table.
Table 9.2 Datatype mapping to Java
Platform-independent datatype | Java datatype |
String | String |
Integer | int, long, Integer, Long |
Decimal | double, Double, java.math.BigDecimal |
Boolean | boolean, Boolean |
Date | java.util.Date |
Notice that for precise computations with decimal numbers, the special datatype java.math.BigDecimal127 is needed.
A second datatype mapping is needed for obtaining the corresponding MySQL datatypes:
Table 9.3 Datatype mapping to MySQL
Platform-independent datatype | MySQL datatype |
String | VARCHAR |
Integer | INT |
Decimal | DECIMAL |
Boolean | BOOL |
Date | DATETIME or TIMESTAMP |
In this section we add JPA constraint annotations and Bean Validation annotations for implementing the property constraints defined for the Book
class in the Java Entity class model. For the standard identifier attribute isbn
,we add the JPA constraint annotations @Id
and @Column( length=10)
, as well as the Bean Validation annotations @NotNull
and @Pattern( regexp="\b\d{10}\b")
. Notice that, for readability, we have simplified the ISBN pattern constraint.
For the attribute title
, we add the JPA constraint annotation @Column( nullable=false)
, as well as the Bean Validation annotations @NotNull
and @Size( max=50)
.
For the attribute year
, we add the JPA constraint annotation @Column( nullable=false)
, as well as the Bean Validation annotations @NotNull
and @Min( value=1459)
. Notice that we cannot express the constraint that year
must not be greater than next year with a standard validation annotation. Therefore, we’ll define a custom annotation for this constraint in Section 6 below.
Coding the integrity constraints with JPA constraint annotations and Bean Validation annotations results in the following annotated bean class:
Notice that for the year
property, the Java Integer
wrapper class is used instead of the primitive int
datatype. This is required for the combined use of JSF and JPA, because if the value of an empty year input field is submitted in the create or update forms, the value which is passed to the year
property by JSF via the setYear
method is null
(more details on Section 4.5, “Requiring non-empty strings”), which is not admitted for primitive datatypes by Java.
We only provide an overview of the methods. For more details, see Chapter 4.
For avoiding duplicate Book
records we have to check that the isbn
values are unique. At the level of the database, this is already checked since the isbn
column is the primary key, and the DBMS makes sure that its values are unique. However, we would like to check this in our Java app before the data is passed to the DBMS. Unfortunately, there is no predefined Bean Validation annotation for this purpose, and it is not clear how to do this with a custom validation annotation. Therefore we need to write a static method, Book. checkIsbnAsId
, for checking if a value for the isbn
attribute is unique. This check method can then be called by the controller for validating any isbn
attribute value before trying to create a new Book
record. The Book. checkIsbnAsId
method code is shown below:
The method throws a UniquenessConstraintViolation
exception in case that a Book
record was found for the given ISBN value. The exception can then be caught and a corresponding error message displayed in the UI. In the sequel of this chapter we show how to define the controller validation method and inform JSF facelets that it must be used to validate the isbn
form input field.
Notice that in this case we also need to check the isbn
value and reject null
values, because the @NotNull
validation triggers only later, when the isbn
property of the Book
is set, thus at this point we could get NullPointerException
, from the Book. retrieve
method.
The Book.checkIsbnAsId
method discussed in the previous sub-section is designed to be used in combination with a controller so the user gets an error message when trying to duplicate a Book
record (i. e., if the provided isbn
value is already used in an existing record). However, if the Book.create
method is used directly (i. e. by another piece of code, where the uniqueness constraint is not performed by calling Book. checkIsbnAsId
), then uniqueness constraint validation may fail. Lets have a look on the Book.create
code:
The method may throw a number of exceptions when trying to run the persist or the commit method. One of the exceptions (i. e. EntityExistsException
) is thrown by the ut.commit
call. The method which calls Book. create
may catch this exception and perform specific actions, such as rolling back the transaction. In our case, the Book.create
is called by the create
action method of the BookController
class, and the action performed is to show the exception stack trace in the console, as well as calling the ut.rollback
which takes care of cancelling any database change performed by the current transaction. The rest of the exceptions are caught by using their super class (i. e. Exception
) and the exception stack trace is displayed in the console.
Note: the EntityExistsException
is part of the javax.persistence
package (i. e. javax.persistence.EntityExistsException
). TomEE uses the Apache OpenJPA128 implementation of the JPA API, which means that the EntityExistsException
class (and other exceptions classes too) are part of the org. apache.openjpa.persistence
package. Therefore, using this exception with our code, requires to import org.apache.openjpa.persistence.EntityExistsException
instead of import javax.persistence.EntityExistsException
as well as adding the openjpa-xxx.jar
(located in the lib
subfolder of the TomEE installation folder) to the Java application class path for being able to have the code compiled with Eclipse or other IDE tools.
Normally a mandatory string-valued attribute, such as title
, requires a non-empty string, which is expressed in our model above by the range NonEmptyString
. For treating empty strings as no value, the context parameter javax. faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL
must be set to true
in web.xml
:
After we have defined the constraints in the Java EE model layer and the database layer, we need to take care of validation in the user interface. In particular, we need to make sure that the user gets informed about issues by rendering visual indicators and informative validation error messages.
The WebContent/views/books/create.xhtml
file contains the JSF facelet code for creating a new Book
record. We now use the JSF validator
attribute for performing the uniqueness validation and JSF message
elements for displaying validation error messages.
There are only a few changes compared to the same view used for the minimal app, where no validation was performed. The first change is the new h:message
element which is bound to a specific form element by the for
attribute. We create such an element for each of our form input elements. Notice that we don’t have to do anything else for seeing the validation errors for all integrity constraint checks which are performed by using the (built-in and custom) Bean Validation annotations. As soon as a constraint validation fails, the message set by using the message
property of the integrity constraint annotation (e. g. @Pattern
, @NotNull
, etc) is displayed in an HTML span
element generated by JSF as a result of using the h:message
element.
For all the integrity constraints we have used Bean Validation annotations, but for the uniqueness constraint we have used custom code, therefore no error message will be shown for it. In the view code we can see that a new attribute, validator
in h:inputText
, was used for the isbn
input field. It specifies which custom method is used to perform validation of the provided value in this form field. In our case, we use the checkIsbnAsId
method defined in the BookController
as shown below:
The controller’s check method throws a ValidatorException
which is also used to deliver the error message (the third parameter of the ValidatorException
constructor) to the corresponding JSF facelet for being displayed in the UI. Methods used as JSF validators must have a specific syntax. The first two parameters of type FacesContext
, respectively UIComponent
are used by the container to invoke the method with references to the right view component and context, and they can be used in more complex validation methods. The last one, of type Object
, represents the value to be validated by the method. This value has to be casted to the expected type (to String
, in our example). It is important to know that, if a cast to a non-compatible type is performed, the validation method fails and an exception is thrown.
In the Update use case, the facelet file update.xhtml
in WebContent/views/ books
was updated so it uses the h:message
elements for being able to display validation errors:
Since we do not allow to change the ISBN of a book, we create an output field for the isbn
attribute with the JSF element h:outputText
. This implies that no validation is performed.
Using an h:outputText
element for showing the value of an entity attribute results in an HTML span
element. This implies that the HTTP form submission message contains no information about that attribute. If the validation fails, we expect to see the form content together with the error messages. To get the expected result, we need to use the annotation @ViewScoped
for the entity class pl.m.Book
instead of @RequestScoped
, otherwise our bean instance referenced by the book
variable is initialized with a new value on every request, implying that the expression #{book. isbn}
evaluates to null
and the ISBN value is not displayed. The @ViewScoped
annotation specifies that the entity bean is alive as long as the associated view is alive, so the ISBN value stored by the book
is available during this time and it can be displayed in the view.
By contrast, h: inputText
elements result in HTML input
elements which are part of the form submission content, so the response contains the already existing values because these values are known in this case. This consideration shows that it is important to choose the right bean scope.
One other integrity constraint we have to consider is about the allowed values of the year
property, which must be in the interval [1459, nextYear()] where nextYear() is a function invocation expression. We may have the idea to use @Min
and @Max
to specify the interval constraint, but this is not possible because the @Max
annotation (as well as any other annotation) does not allow expressions, but only data literals. So, while we can express the interval’s lower bound with @Min( value=1459)
, we need another solution for expressing the upper bound.
Fortunately, the Bean Validation API allows to define custom validation annotations with custom code performing the constraint checks. This means that we are free to express any kind of validation logic in this way. Creating and using a custom validation annotation requires the following steps:
1.Create the annotation interface UpToNextYear
with the following code:
The interface needs to define three methods, message
(returns the default key or error message if the constraint is violated), groups
(allows the specification of validation groups, to which this constraint belongs) and payload
(used by clients of the Bean Validation API to assign custom payload objects to a constraint – this attribute is not used by the API itself). Notice the @Target
annotation, which defines the element types that can be annotated (fields/properties and methods in our case). The @Constraint
annotation allows to specify the implementation class that will perform the validation, i. e. UpToNextYearImpl
in our case.
2.Create an implementation class with the validation code:
The implementation class implements the ConstraintValidator
interface, which requires two type parameters: the annotation interface defined before (i. e. UpToNextYear
), and the type of elements the validator can handle (i. e. Integer
, so implicitly also the compatible primitive type int
). The initialize
method allows initializing variables required for performing the validation check. The isValid
method is responsible for performing the validation: it must return true
if the validation succeeds and (wie "true")
otherwise. The first parameter of the isValid
method represents the value to be validated and its type must be compatible with the type defined by the second type parameter of the ConstraintValidator
(Integer
in our case).
3.Annotate the property or method concerned:
You can run the validation app129 on our server or download the code130 as a ZIP archive file.
Follow our instructions131 for getting your environment prepared for running Java EE web applications.
As an example of a constraint that is not bound to a specific property, but must be checked by inspecting several properties of an object, we consider the validation of the attribute Author::dateOfDeath
. First, any value for this attribute must be in the past, which can be specified with the @Past
Bean Validation annotation, and second, any value of dateOfDeath
must be after the dateOfBirth
value of the object concerned. This object-level constraint cannot be expressed with a predefined Bean Validation annotation. We can express it with the help of a custom class-level annotation, like the following AuthorValidator
annotation interface:
Compared to a property constraint annotation definition, there is only one difference, the parameter of the @Target
annotation. While in the case of a property and method level custom constraint annotation the values are ElementType.FIELD
and ElementType.METHOD
, for the case of a class it must be ElementType.TYPE
.
The corresponding implementation class, i. e., AuthorValidatorImpl
, has the same structure as in the case of a property constraint annotation , but now, we can access all properties of an entity bean, so we can compare two or more properties when required. In our case, we have to compare the values of dateOfBirth
and dateOfDeath
in the isValid
method:
Using class-level JPA validators in facelets requires a bit of tweaking because they are not directly supported by JSF. For the specific form field to be validated, we have to specify a controller method in charge of the validation, as the value of the @validator
attribute:
The controller method checkDateOfDeath
has to invoke the Bean Validation API validator, catch the validation exceptions and translate them to exceptions of type javax.faces.validator.ValidatorException
, which are then managed by JSF and displayed in the view. Its code is as follows:
While the method looks complicated, it is responsible for the following simple tasks:
–get access to form data and extract the user input values with the help of the context.getViewRoot().findComponent
method. Notice that the component name has the pattern: formName:formElementName
.
–create the Author instance and set the corresponding data as extracted from the form, by using the FacesContext
instance provided by the JSF specific validator method
–manually invoke the Bean Validation API validator by using the javax.validation.Validator class.
–loop trough the validator exception, select the ones which corresponds to the custom validated field and map them to javax.faces.validator.ValidatorException
exceptions. The selection can be made by looking for specific data in the exception message.
As a result, the custom Bean Validation class validator is not used, and the facelet is able to render the corresponding error messages when the validation fails, in the same way as is possible for single property validation situations.
An alternative approach to object-level validation is to using JSF custom validators. They have the advantage that they are directly supported in facelets, but the downside of this approach is that it violates the onion architecture principle by defining business rules in the UI instead of defining them in the model.
For our example, the validator for the Author class that is responsible for validating dateOfDeath
by comparing it with dateOfBirth
is shown below:
Then, in the facelet, for the corresponding field, the validator has to be specified:
The purpose of the app to be built is managing information about movies. The app deals with just one object type: Movie
, as depicted in the following class diagram.
In this model, the following constraints have been expressed:
movieId
attribute is declared to be the standard identifier of Movie
, it is mandatory and unique.title
attribute is mandatory, as indicated by its multiplicity expression [1], and has a string length constraint requiring its values to have at most 120 characters.releaseDate
attribute has an interval constraint: it must be greater than or equal to 1895-12-28.Notice that the releaseDate
attribute is not mandatory, but optional, as indicated by its multiplicity expression [0..1]. In addition to the constraints described in this list, there are the implicit range constraints defined by assigning the datatype PositiveInteger
to movieId
, NonEmptyString
to title
, and Date
to releaseDate
.
You can use the sample data shown in Table 8.2 for testing your app.
The purpose of the app to be built is managing information about countries. The app deals with just one object type: Country
, as depicted in the following class diagram.
In this model, the following constraints have been expressed:
name
attribute is declared to be the standard identifier of Country
, it is mandatory and unique.name
attribute has a string length constraint requiring its values to have at least 3 and at most 50 characters.population
attribute is mandatory, as indicated by the multiplicity expression [1] appended to the attribute name.lifeExpectancy
attribute is also mandatory and has an interval constraint: its values must be less than or equal to 100.Notice that the militaryExpenditure
attribute is not mandatory, but optional, as indicated by its multiplicity expression [0..1].
In addition to the constraints described in this list, there are the implicit range constraints defined by assigning the datatypes NonEmptyString
to name
, PositiveInteger
to population
, PositiveDecimal
to lifeExpectancy
and Percentage
to militaryExpenditure
(hint: a percentage value is a decimal number between 0 and 100). In our Java example app, such property constraints were coded in the entity class by using the Bean Validation API.
You can use the sample data shown in Table 8.3 for testing your app.
If you would like to look up the answers for the following quiz questions, you can check our discussion forum132. If you don’t find an answer in the forum, you may create a post asking for an answer to a particular question.
Complete the following code, with the correct Java Bean validation annotation, so that the attribute title
has a minimum length of 2 and a maximum length of 40:
Which validation annotations are needed for requiring a (not-null) value for an attribute both in the Java EE model layer and in the database of an app?
Complete the following code for defining a LessThan100YearsOld
annotation that checks if a given Date
attribute value does not date back more than 100 years and the Java implementation class is LessThan100YearsOldImpl:
Complete the following JSF snippet such that the title
attribute of the Book
entity is validated by the custom method Book::checkTitle
:
Complete the following JSF snippet, such that any (possible) error messages resulting from validating the title
attribute value of the book
entity, are displayed in the user interface: