What we will have in this post?
- Create RESTFul web service exposing GET & POST API
- POST API accepting JSON string.
- Applying bean validations using jersey framework.
- Validate REST parameters (query or request parameters)
- Create custom annotations to validate the bean or POJO.
- We have already discussed validation of request parameters.
RESTFul web service using Jersey framework
- Controller: REST resource exposing GET & POST API. We have created StudentResource class.
- Custom Annotation: We will create custom annotation to validate bean or POJO.
- Model Class: We will create Address model class and we will apply custom annotation.
- 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