Site icon

Jersey Bean validation of REST request/query parameters using custom annotation (example) – II

What we will have in this post?

RESTFul web service using Jersey framework

  1. Controller: REST resource exposing GET & POST API. We have created StudentResource class.
  2. Custom Annotation: We will create custom annotation to validate bean or POJO.
  3. Model Class: We will create Address model class and we will apply custom annotation.
  4. Validation exception mapper: We will create validation exception mapper class to catch validation exceptions.

1.) REST Resource StudentResource:

The student resource is exposing the REST API(s), which will be invoked REST client. We have exposed the post interface which is deployed at “/student/address” path. We have used @ValidAddress which is our custom annotation (shortly we will discuss). So, pojo validation will be validate using our custom annotation. Also, ,we have used @Valid annotation, which is standard javax annotation (to validate field). Let us look into the code.

package org.learn.resource;

import java.util.Date;
import javax.validation.Valid;
import javax.validation.ValidationException;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.learn.annotation.ValidAddress;
import org.learn.model.Address;

@Path("/student")
public class StudentResource {

	private static final String text = "Message from Server :\n%s";

	@GET
	@Consumes(MediaType.TEXT_PLAIN)
	public Response registerStudent() {
		String response = String.format(text, new Date());
		return Response.status(Response.Status.OK).entity(response).type(MediaType.TEXT_PLAIN).build();
	}

	@POST
	@Path("/address")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response registerAddress(@Valid @ValidAddress Address address) throws ValidationException {

		String response = String.format(text, address);
		return Response.status(Response.Status.OK).entity(response).type(MediaType.TEXT_PLAIN).build();
	}
}

2.) Custom annotation @ValidAddress:

We have defined the custom annotation class. In ValidAddress annotation, we have defined our validator class to validate the zip and phone number of address class.  In validator class, we are validating zip and phone using regular expression.

package org.learn.annotation;

import org.learn.model.Address;

import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ValidAddress.Validator.class)
public @interface ValidAddress {

    String message() default "{Invalid address: Check your "
    		+ "								phone number or zip code}";

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

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

    public class Validator implements ConstraintValidator<ValidAddress, Address> {

        //Valid phone number (123) 123-1234
        // Invalid phone number is (123)123-1234.
        private String US_PHONE_PATTERN =
        		"^(\\([0-9]{3}\\) |[0-9]{3}-)[0-9]{3}-[0-9]{4}$";
        //A valid postal code should match 12345 and 12345-6789,
        //Invalid postal code: 1234, 123456, 123456789, or 1234-56789.
        private String US_ZIP_PATTERN = "^[0-9]{5}(?:-[0-9]{4})?$";

        @Override
        public void initialize(final ValidAddress hasId) {
        }

        @Override
        public boolean isValid(final Address address, final
        			ConstraintValidatorContext constraintValidatorContext) {
            Matcher matcher;
            Pattern pattern = Pattern.compile(US_ZIP_PATTERN);
            matcher = pattern.matcher(address.getZip());
            if(!matcher.matches())
                return false;

            pattern = Pattern.compile(US_PHONE_PATTERN);
            matcher = pattern.matcher(address.getPhone());
            if(!matcher.matches())
                return false;

            return true;
        }
    }
}

 

3.) Model class Address:

We have created Address model class, which we have validated using @ValidAddress annotation (“/address” path in StudentResource). We are validating only zip and phone number using this @ValidAddress annotations. Also, We are validating name, street and city in Address class only.

package org.learn.model;

import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotNull;

public class Address {
    @NotNull(message = "Address:name cannot be empty")
    @Length(min = 2, max = 20)
    private String name;

    @NotNull(message = "Address:street cannot be empty")
    @Length(min = 2, max = 20)
    private String street;

    @NotNull(message = "Address:city cannot be empty")
    @Length(min = 2, max = 20, message = "City length should "
    		+ "be between 2 to 20 characters")
    private String city;

    private String phone;
    private String zip;

    public void setName(String name) {
        this.name = name;
    }

    public void setStreet(String street) {
        this.street = street;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public void setZip(String zip) {
        this.zip = zip;
    }

    public String getName() {
        return name;
    }

    public String getStreet() {
        return street;
    }

    public String getCity() {
        return city;
    }

    public String getPhone() {
        return phone;
    }

    public String getZip() {
        return zip;
    }

    public String toString() {
        return String.format("Name : %s, Street: %s, Phone : %s,"
        		+ " city: %s",name,street,phone,city);
    }
}

 

4.) Exception mapper class – ValidationException:

Validation exception raised if there is any violation request parameter. This exception will catch the validation exception. Jersey bean validation also have Validation exception mapper, so to override that behavior, We need to create our own Validation exception mapper, so that we can create custom exception message etc.

package org.learn.exception;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

@Provider
public class ValidationException implements ExceptionMapper<
									javax.validation.ValidationException> {

    @Override
    public Response toResponse(javax.validation.ValidationException e) {
        final StringBuilder strBuilder = new StringBuilder();
        for (ConstraintViolation<?> cv : ((ConstraintViolationException) e)
        										.getConstraintViolations()) {
            strBuilder.append(cv.getPropertyPath().
            							toString() + " " + cv.getMessage());
        }
        return Response
                .status(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode())
                .type(MediaType.TEXT_PLAIN)
                .entity(strBuilder.toString())
                .build();
    }
}

Dependencies used in our project

	
        
            junit
            junit
            3.8.1
            test
        

        
            org.slf4j
            slf4j-api
            ${org.slf4j-version}
        
        
            org.slf4j
            jcl-over-slf4j
            ${org.slf4j-version}
            runtime
        
        
            org.slf4j
            slf4j-log4j12
            ${org.slf4j-version}
            runtime
        
        
            log4j
            log4j
            1.2.15
            
                
                    javax.mail
                    mail
                
                
                    javax.jms
                    jms
                
                
                    com.sun.jdmk
                    jmxtools
                
                
                    com.sun.jmx
                    jmxri
                
            
            runtime
        

        
        
            org.glassfish.jersey.containers
            jersey-container-servlet
            ${jersey_version}
        
        
            org.glassfish.jersey.core
            jersey-client
            ${jersey_version}
        
        
            org.glassfish.jersey.media
            jersey-media-json-jackson
            ${jersey_version}
        
        
            org.glassfish.jersey.ext
            jersey-bean-validation
            ${jersey_version}
        

        
            javax.servlet
            javax.servlet-api
            ${servlet_api_version}
        

    

Requests & responses flow REST resource is as follows:

http://localhost:9090/student/address          
[The Content-Type should be set to "application/json" for request raised to server]

1. Request
{"name":"Donald","street":"Stree no 27","city":"Sydney",
                            "phone":"(123) 123-1234","zip":"12345-6789"}
Response:
Message from Server :
Name : Donald, Street: Stree no 27, Phone : (123) 123-1234, city: Sydney

2. Request
{"name":"Donald","street":"Stree no 27","city":"Sydney",
                           "phone":"(123)123-1234","zip":"12345-6789"}

Response:
registerAddress.arg0 {Invalid address: Check your phone number or zip}

Download Complete Code – REST Parameter Validation

 

Exit mobile version