Monday, 12 January 2015

Using groups with Bean Validation in the context of JAX-RS

Introduction

Using Bean Validation for the validation of the JSON you send to a Java EE server is very easy. With the @Valid annotation, you can easily specify that all validations specified in the java object must be checked.

But how should you proceed when the same JSON is send to different controllers in your application and that each of these endpoint should validate the JSON differently?  After all, you can’t specify the groups in the @Valid annotation.

Describing the use case

So lets imagine we have a resource, employee, which you can create and update in our application.  In the case of the update command, you should specify the id of employee which you obviously don’t have in the case of the create. 

   "firstName": "Rudy",
   "lastName": "De busscher"
}
 for the creation and the following JSON for the update:

   "id":123,
   "firstName":"Rudy",
   "lastName":"De Busscher"
}
(see also appendix at the end, for a best practice remark)

In this case, I can use the entity class for Employee and have the @NotNull and @Size annotations to indicate the required first and last name fields.

@NotNull
@Size(min = 1)
private String firstName;
@NotNull
@Size(min = 1)
private String lastName;

In the JAX-RS controller, where we have defined the @Path annotation, we can take the JSON from a POST request by using a method definition like this.

@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response create(@Valid Employee employee) {

And as shown in the example, by specifying the @Valid annotation on the method parameter, we can guarantee that the first and last name will be specified and is at least 1 character long.  If not, a response with a HTTP status 4xx will be thrown.

Using validation groups

So how can we now support the use case of the update of an employee name? 

Each bean validation constraint, like @NotNull and @Size has a groups member where you can specify to which group this validation belongs.  These groups are classes, interfaces are mostly used in this situation. Remember classes and not just Strings because the type safety aspect of your code is very important topic for Java EE.

So in the case we have defined an UpdateGroup interface, we can specify the id property of Employee as follows:

@NotNull(groups = UpdateGroup.class)
private Long id;

But there is no option to specify the validation groups together with the @Valid annotation.  So we have to trigger this validation ‘manually’, but as the next code snippet shows you, it is very easy. 

@Inject
private Validator validator;
@PUT
@Consumes(MediaType.APPLICATION_JSON)
public Response update(@Valid Employee employee) {
    validate(employee, UpdateGroup.class);

}

protected void validate(Object data, Class... groups) {
    Set<ConstraintViolation<Object>> violations = validator.validate(data, groups);
    if (!violations.isEmpty()) {

        // Inform client of validation issue
    }
}
The Validation object, central in Bean Validation, can be easily injected in your controller.  Asking for validating your object is then as simple as calling a method where you supply the validation group interface class, in our case UpdateGroup.

Informing client of problem

The last step we then have to perform is inform the client in the case he forgot to supply the id property in the JSON object when he asked for an update of the resource.

Also this can be achieved in a few lines of code.  In our simple example here, we just take the default message, or the message we specify on the constraint with the message member, and combine this together with a HTTP Status 409.

When we assembling that in a Response object of JAX-RS, we can just throw that object with a WebApplicationException (funny name, don't you find that also ?). And that is all we need to inform the client (HTTP status 4xx with an indication what went wrong)

protected void validate(Object data, Class... groups) {
    Set<ConstraintViolation<Object>> violations = validator.validate(data, groups);
    if (!violations.isEmpty()) {

        String message = buildMessage(violations);
        Response response = Response.status(409).entity(message).build();
        throw new WebApplicationException(response);    }
}

Conclusion

Although @Valid annotation has no possibility to specify the validation group it should take, with a few lines of codes, different validations of the same object can be performed easily. 
And the best of all, it can be encapsulated in a method located in your super class of the controller and you just need to supply the data with a list of validation groups which are classes.

Appendix

The example with the update and the create of an employee isn’t the best possible example.  Because in both cases you could use the id-less version of the JSON and put the id in the URL path for the update.  This is according to the REST way of working better.

So
POST to URL xxx/employee with 
   "firstName": "Rudy",
   "lastName": "De busscher"
}

and PUT to URL xxx/employee/123 with

   "firstName": "Rudy",
   "lastName": "De Busscher"

}

No comments:

Post a Comment