9Implementing Constraint Validation in a Java EE Web App

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.

9.1Java Annotations for Persistent Data Management and Constraint Validation

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:

  1. JPA constraint annotations, specify constraints to be used for generating the database schema (with CREATE TABLE statements) such that they are then checked by the DBMS, and not by the Java runtime environment;
  2. Bean Validation annotations specify constraints to be checked by the Java runtime environment

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

9.1.1JPA constraint annotations

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:

9.1.2Bean Validation annotations

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.

9.2New Issues

Compared to the Minimal App126 discussed in Chapter 4 we have to deal with a number of new issues:

  1. In the model layer we have to take care of adding for every property the constraints that must be checked before allowing a record to be saved to the database
  2. In the user interface (view) we have to take care of form validation providing feedback to the user whenever data entered in the form is not valid.

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.

9.3Make an Entity Class Model

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.

Figure 9.1 Deriving an Entity class model from an information design model

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:

  1. Using @Id and @NotNull, the isbn attribute is declared to be a standard identifier, implying that it is mandatory and unique.
  2. Using @Pattern(“\b\d{10}\b”), the isbn attribute has a pattern constraint requiring its values to match the ISBN-10 format (simplified to the case of 10digit strings).
  3. Using @NotNull and @Size( max=50), the title attribute is mandatory and has a string length maximum constraint of at most 50 characters.
  4. Using @NotNull, @Min( 1459) and the custom annotation @UpToNextYear, the 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.

9.4Write the Model Code

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.

9.4.1Type mapping

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

9.4.2Code the constraints as annotations

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.

9.4.3Checking uniqueness constraints

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.

9.4.4Dealing with model-related exceptions

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.

9.4.5Requiring non-empty strings

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:

9.5Write the View Code

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.

9.5.1Validation in the Create use case

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.

9.5.2Validation in the Update use case

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.

9.6Defining a Custom Validation Annotation

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:

9.7Run the App and Get the Code

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.

9.8Possible Variations and Extensions

9.8.1Object-level constraint validation

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.

9.8.2JSF custom validators

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:

9.9Practice Projects

9.9.1Project 1Validate movie data

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:

  1. Due to the fact that the movieId attribute is declared to be the standard identifier of Movie, it is mandatory and unique.
  2. The 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.
  3. The 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.

9.9.2Project 2Validate country data

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:

  1. Due to the fact that the name attribute is declared to be the standard identifier of Country, it is mandatory and unique.
  2. The name attribute has a string length constraint requiring its values to have at least 3 and at most 50 characters.
  3. The population attribute is mandatory, as indicated by the multiplicity expression [1] appended to the attribute name.
  4. The 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.

9.10Quiz Questions

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.

9.10.1Question 1: String length validation

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:

9.10.2Question 2: Requiring a value

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?

9.10.3Question 3: Defining a custom validation annotation

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:

9.10.4Question 4: JSF custom validator method

Complete the following JSF snippet such that the title attribute of the Book entity is validated by the custom method Book::checkTitle:

9.10.5Question 5: Show validation error messages with JSF

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:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset