Validating requests

JAX-RS offers an integration of HTTP endpoints into our system. This includes mapping of requests and responses into Java types of our application. However, the client requests need to be validated in order to prevent misuse of the system.

The Bean Validation standard provides validation of all kind of sorts. The idea is to declare validation constraints, such as this field must not be null, this integer must not be negative or this salary raise must align with the company policies, to Java types and properties. The standard already ships the typically required technically motivated constraints. Custom constraints, especially those that are motivated by the business functionality and validation, can be added as well. This becomes interesting not only from a technical, but a domain perspective. Validation logic that is motivated by the domain can be implemented using this standard.

The validation is activated by annotating method parameters, return types, or properties with @Valid. Whereas validation can be applied in many points in the application, it is particularly important to endpoints. Annotating a JAX-RS resource method parameter with @Valid will validate the request body or parameter, respectively. If the validation fails, JAX-RS automatically responds to the HTTP request with a status code indicating a client error.

The following demonstrates the integration of a user validation:

import javax.validation.Valid;
import javax.validation.constraints.NotNull;

@Path("users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UsersResource {

    ...

    @POST
    public Response createUser(@Valid @NotNull User user) {
        ...
    }
}

The user type is annotated with validation constraints:

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;

public class User {

    @JsonbTransient
    private long id;

    @NotBlank
    private String name;

    @Email
    private String email;

    ...
}

The annotations placed on the JAX-RS method tell the implementation to validate the request body as soon as a client request arrives. The request body must be available, not null, and valid following the configuration of the user type. The user's name property is constrained to not be blank; that is, it should not be null or not just containing whitespace, respectively. The user's email property has to comply with a valid email address format. These constraints are enforced when validating a user object.

Internally, a Validator included in Bean Validation validates the objects. The validator will throw ConstraintViolationExceptions if the validation fails. This validator functionality can also be obtained by dependency injection and called programmatically. JAX-RS automatically calls the validator and sends an appropriate response to the client if the validation fails.

This example would fail on illegal HTTP POST invocations to the /users/ resource, such as providing user representations without a name. This results in 400 Bad Request status codes, the JAX-RS default behavior for failed client validations.

If the clients need more information about why a request was declined, the default behavior can be extended. The violation exceptions which are thrown by the validator can be mapped to HTTP responses with the JAX-RS exception mapper functionality. Exception mappers handle exceptions that are thrown from JAX-RS resource methods to appropriate client responses. The following demonstrates an example of such an ExceptionMapper for ConstraintViolationExceptions:

import javax.validation.ConstraintViolationException;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

@Provider
public class ValidationExceptionMapper implements ExceptionMapper<ConstraintViolationException> {

    @Override
    public Response toResponse(ConstraintViolationException exception) {
        Response.ResponseBuilder builder = Response.status(Response.Status.BAD_REQUEST);

        exception.getConstraintViolations()
                .forEach(v -> {
                    builder.header("Error-Description", ...);
                });
        return builder.build();
    }
}

Exception mappers are providers for the JAX-RS runtime. Providers are either configured programmatically in the JAX-RS base application class or, as shown here, in a declarative way using the @Provider annotation. The JAX-RS runtime will scan the classes for providers and apply them automatically.

The exception mapper is registered for the given exception type and sub-types. All the constraint violation exceptions thrown by a JAX-RS resource method here are mapped to a client response including a basic description of which fields caused the validation to fail. The violation messages are a functionality of Bean Validation providing human readable, global messages.

If the built-in validation constraints are not sufficient for the validation, custom validation constraints can be used. This is especially required for validation rules that are specific to the domain. For example, usernames could need more sophisticated validation based on the current state of the system. In this example, the usernames must not be taken when creating new users. Other constraints on the format or allowed characters could be set as well, obviously:

public class User {

    @JsonbTransient
    private long id;

    @NotBlank
    @UserNameNotTaken
    private String name;

    @Email
    private String email;
    ...
}

The @UserNameNotTaken annotation is a custom validation constraint defined by our application. Validation constraints delegate to a constraint validator, the actual class that performs the validation. Constraint validators have access to the annotated object, such as the class or field in this case. The custom functionality checks whether the provided object is valid. The validation method can use the ConstraintValidatorContext to control custom violations including messages and further information.

The following shows the custom constraint definition:

import javax.validation.Constraint;
import javax.validation.Payload;

@Constraint(validatedBy = UserNameNotTakenValidator.class)
@Documented
@Retention(RUNTIME)
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
public @interface UserNameNotTaken {

    String message() default "";

    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

Our constraint is validated by the UserNameNotTakenValidator class:

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class UserNameNotTakenValidator implements ConstraintValidator<UserNameNotTaken, String> {

    @Inject
    UserStore userStore;

    public void initialize(UserNameNotTaken constraint) {
        // nothing to do
    }

    public boolean isValid(String string, ConstraintValidatorContext context) {
        return !userStore.isNameTaken(string);
    }
}

As with other standards, constraint validators can use dependency injection to use managed beans. This is very often required for custom validation logic that makes calls to controls. In this example, the validator injects the UserStore. Once again, we can reuse different standards within the Java EE umbrella.

Custom validation constraints are very often motivated by the business domain. It can make sense to encapsulate complex, composed validation logic into such custom constraints. When applied, this approach also leverages the single responsibility principle, separating the validation logic into a single validator rather than spreading them in atomic constraints.

Bean Validation offers more complex functionality for scenarios where different ways of validation are required for the same types. Therefore, the concept of groups is used to group certain constraints together into groups which can possibly be validated individually. For more information on this, I refer the reader to the Bean Validation specification.

As shown previously, HTTP JSON payloads can also be mapped in JAX-RS using the JSON-P standard. This is also true for HTTP request bodies. The request bodies parameters can be provided as JSON-P types containing JSON structures that are read dynamically. As well as for response bodies, it makes sense to represent request bodies using JSON-P types if the object structure differs from the model types or needs more flexibility, respectively. For this scenario, validation of the provided objects is even more important, since the JSON-P structures can be arbitrary. To rely on certain JSON properties being existent on the request object, these objects are validated using a custom validation constraint.

Since JSON-P objects are built programmatically and there are no pre-defined types, programmers have no way of annotating fields in the same way as for Java types. Therefore, custom validation constraints are used on the request body parameters that are bound to a custom validator. The custom constraints define the structure of a valid JSON object for the specific request bodies. The following code shows the integration of a validated JSON-P type in a JAX-RS resource method:

@Path("users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UsersResource {

    ...

    @POST
    public Response createUser(@Valid @ValidUser JsonObject json) {

        User user = readUser(json);
        long id = userStore.create(user);
        ...
    }

    private User readUser(JsonObject object) {
        ...
    }
}

The custom validation constraint ValidUser references the used constraint validator. Since the structure of the provided JSON-P objects is arbitrary, the validator has to check for the presence and type of properties:

@Constraint(validatedBy = ValidUserValidator.class)
@Documented
@Retention(RUNTIME)
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
public @interface ValidUser {

    String message() default "";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

The custom constraint validator is applicable on JSON-P types, as well:

public class ValidUserValidator implements ConstraintValidator<ValidUser, JsonObject> {

    public void initialize(ValidUser constraint) {
        // nothing to do
    }

    public boolean isValid(JsonObject json, ConstraintValidatorContext context) {
        ...
    }
}

After the provided JSON-P object has been validated, the defined properties can safely be extracted. This example showcases how the flexible, programmatic types are integrated and validated in JAX-RS methods. The resource class extracts the request body into a domain entity type and uses the boundary to invoke the business use case.

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

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