diff --git a/jersey/pom.xml b/jersey/pom.xml
index e248f9cf90..b55bdc5330 100644
--- a/jersey/pom.xml
+++ b/jersey/pom.xml
@@ -39,6 +39,11 @@
jersey-mvc-freemarker
${jersey.version}
+
+ org.glassfish.jersey.ext
+ jersey-bean-validation
+ ${jersey.version}
+
org.glassfish.jersey.test-framework
jersey-test-framework-core
diff --git a/jersey/src/main/java/com/baeldung/jersey/server/config/ViewApplicationConfig.java b/jersey/src/main/java/com/baeldung/jersey/server/config/ViewApplicationConfig.java
index d4744066c4..b6b9853aae 100644
--- a/jersey/src/main/java/com/baeldung/jersey/server/config/ViewApplicationConfig.java
+++ b/jersey/src/main/java/com/baeldung/jersey/server/config/ViewApplicationConfig.java
@@ -1,12 +1,14 @@
package com.baeldung.jersey.server.config;
import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.ServerProperties;
import org.glassfish.jersey.server.mvc.freemarker.FreemarkerMvcFeature;
public class ViewApplicationConfig extends ResourceConfig {
public ViewApplicationConfig() {
packages("com.baeldung.jersey.server");
+ property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
property(FreemarkerMvcFeature.TEMPLATE_BASE_PATH, "templates/freemarker");
register(FreemarkerMvcFeature.class);;
}
diff --git a/jersey/src/main/java/com/baeldung/jersey/server/constraints/SerialNumber.java b/jersey/src/main/java/com/baeldung/jersey/server/constraints/SerialNumber.java
new file mode 100644
index 0000000000..ca49797e31
--- /dev/null
+++ b/jersey/src/main/java/com/baeldung/jersey/server/constraints/SerialNumber.java
@@ -0,0 +1,35 @@
+package com.baeldung.jersey.server.constraints;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.regex.Pattern;
+
+import javax.validation.Constraint;
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+import javax.validation.Payload;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Constraint(validatedBy = { SerialNumber.Validator.class })
+public @interface SerialNumber {
+
+ String message()
+
+ default "Fruit serial number is not valid";
+
+ Class>[] groups() default {};
+
+ Class extends Payload>[] payload() default {};
+
+ public class Validator implements ConstraintValidator {
+ @Override
+ public void initialize(final SerialNumber serial) {
+ }
+
+ @Override
+ public boolean isValid(final String serial, final ConstraintValidatorContext constraintValidatorContext) {
+ final String serialNumRegex = "^\\d{3}-\\d{3}-\\d{4}$";
+ return Pattern.matches(serialNumRegex, serial);
+ }
+ }
+}
diff --git a/jersey/src/main/java/com/baeldung/jersey/server/model/Fruit.java b/jersey/src/main/java/com/baeldung/jersey/server/model/Fruit.java
index 30620e331d..c55362487b 100644
--- a/jersey/src/main/java/com/baeldung/jersey/server/model/Fruit.java
+++ b/jersey/src/main/java/com/baeldung/jersey/server/model/Fruit.java
@@ -1,20 +1,57 @@
package com.baeldung.jersey.server.model;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.Size;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
public class Fruit {
- private final String name;
- private final String colour;
+ @Min(value = 10, message = "Fruit weight must be 10 or greater")
+ private Integer weight;
+ @Size(min = 5, max = 200)
+ private String name;
+ @Size(min = 5, max = 200)
+ private String colour;
+ private String serial;
+
+ public Fruit() {
+ }
public Fruit(String name, String colour) {
this.name = name;
this.colour = colour;
}
-
+
public String getName() {
return name;
}
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setColour(String colour) {
+ this.colour = colour;
+ }
public String getColour() {
return colour;
}
+
+ public Integer getWeight() {
+ return weight;
+ }
+
+ public void setWeight(Integer weight) {
+ this.weight = weight;
+ }
+
+ public String getSerial() {
+ return serial;
+ }
+
+ public void setSerial(String serial) {
+ this.serial = serial;
+ }
}
diff --git a/jersey/src/main/java/com/baeldung/jersey/server/providers/FruitExceptionMapper.java b/jersey/src/main/java/com/baeldung/jersey/server/providers/FruitExceptionMapper.java
new file mode 100644
index 0000000000..cea61c897b
--- /dev/null
+++ b/jersey/src/main/java/com/baeldung/jersey/server/providers/FruitExceptionMapper.java
@@ -0,0 +1,26 @@
+package com.baeldung.jersey.server.providers;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+
+public class FruitExceptionMapper implements ExceptionMapper {
+
+ @Override
+ public Response toResponse(final ConstraintViolationException exception) {
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(prepareMessage(exception))
+ .type("text/plain")
+ .build();
+ }
+
+ private String prepareMessage(ConstraintViolationException exception) {
+ final StringBuilder message = new StringBuilder();
+ for (ConstraintViolation> cv : exception.getConstraintViolations()) {
+ message.append(cv.getPropertyPath() + " " + cv.getMessage() + "\n");
+ }
+ return message.toString();
+ }
+
+}
diff --git a/jersey/src/main/java/com/baeldung/jersey/server/rest/FruitResource.java b/jersey/src/main/java/com/baeldung/jersey/server/rest/FruitResource.java
index 4e1fa4aa11..ee34cdd3ca 100644
--- a/jersey/src/main/java/com/baeldung/jersey/server/rest/FruitResource.java
+++ b/jersey/src/main/java/com/baeldung/jersey/server/rest/FruitResource.java
@@ -5,7 +5,13 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
@@ -15,7 +21,9 @@ import org.glassfish.jersey.server.mvc.ErrorTemplate;
import org.glassfish.jersey.server.mvc.Template;
import org.glassfish.jersey.server.mvc.Viewable;
+import com.baeldung.jersey.server.constraints.SerialNumber;
import com.baeldung.jersey.server.model.Fruit;
+import com.baeldung.jersey.service.SimpleStorageService;
@Path("/fruit")
public class FruitResource {
@@ -52,4 +60,49 @@ public class FruitResource {
return name;
}
+ @POST
+ @Path("/create")
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+ public void createFruit(
+ @NotNull(message = "Fruit name must not be null") @FormParam("name") String name,
+ @NotNull(message = "Fruit colour must not be null") @FormParam("colour") String colour) {
+
+ Fruit fruit = new Fruit(name, colour);
+ SimpleStorageService.storeFruit(fruit);
+ }
+
+ @PUT
+ @Path("/update")
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+ public void updateFruit(@SerialNumber @FormParam("serial") String serial) {
+ Fruit fruit = new Fruit();
+ fruit.setSerial(serial);
+ SimpleStorageService.storeFruit(fruit);
+ }
+
+ @POST
+ @Path("/create")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public void createFruit(@Valid Fruit fruit) {
+ SimpleStorageService.storeFruit(fruit);
+ }
+
+ @GET
+ @Valid
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/search/{name}")
+ public Fruit findFruitByName(@PathParam("name") String name) {
+ return SimpleStorageService.findByName(name);
+ }
+
+ @GET
+ @Produces(MediaType.TEXT_HTML)
+ @Path("/exception")
+ @Valid
+ public Fruit exception() {
+ Fruit fruit = new Fruit();
+ fruit.setName("a");
+ fruit.setColour("b");
+ return fruit;
+ }
}
diff --git a/jersey/src/main/java/com/baeldung/jersey/service/SimpleStorageService.java b/jersey/src/main/java/com/baeldung/jersey/service/SimpleStorageService.java
new file mode 100644
index 0000000000..e21dd584a1
--- /dev/null
+++ b/jersey/src/main/java/com/baeldung/jersey/service/SimpleStorageService.java
@@ -0,0 +1,25 @@
+package com.baeldung.jersey.service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.baeldung.jersey.server.model.Fruit;
+
+public class SimpleStorageService {
+
+ private static final Map fruits = new HashMap();
+
+ public static void storeFruit(final Fruit fruit) {
+ fruits.put(fruit.getName(), fruit);
+ }
+
+ public static Fruit findByName(final String name) {
+ return fruits.entrySet()
+ .stream()
+ .filter(map -> name.equals(map.getKey()))
+ .map(map -> map.getValue())
+ .findFirst()
+ .get();
+ }
+
+}
diff --git a/jersey/src/test/java/com/baeldung/jersey/server/rest/FruitResourceIntegrationTest.java b/jersey/src/test/java/com/baeldung/jersey/server/rest/FruitResourceIntegrationTest.java
index a0b6daed51..2eeb5710cb 100644
--- a/jersey/src/test/java/com/baeldung/jersey/server/rest/FruitResourceIntegrationTest.java
+++ b/jersey/src/test/java/com/baeldung/jersey/server/rest/FruitResourceIntegrationTest.java
@@ -2,41 +2,116 @@ package com.baeldung.jersey.server.rest;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Form;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
import org.glassfish.jersey.test.JerseyTest;
-import org.junit.Assert;
+import org.glassfish.jersey.test.TestProperties;
import org.junit.Test;
import com.baeldung.jersey.server.config.ViewApplicationConfig;
+import com.baeldung.jersey.server.model.Fruit;
+import com.baeldung.jersey.server.providers.FruitExceptionMapper;
public class FruitResourceIntegrationTest extends JerseyTest {
@Override
protected Application configure() {
- return new ViewApplicationConfig();
+ enable(TestProperties.LOG_TRAFFIC);
+ enable(TestProperties.DUMP_ENTITY);
+
+ ViewApplicationConfig config = new ViewApplicationConfig();
+ config.register(FruitExceptionMapper.class);
+ return config;
}
@Test
- public void testAllFruit() {
+ public void givenGetAllFruit_whenCorrectRequest_thenAllTemplateInvoked() {
final String response = target("/fruit/all").request()
.get(String.class);
- Assert.assertThat(response, allOf(containsString("banana"), containsString("apple"), containsString("kiwi")));
+ assertThat(response, allOf(containsString("banana"), containsString("apple"), containsString("kiwi")));
}
@Test
- public void testIndex() {
+ public void givenGetFruit_whenCorrectRequest_thenIndexTemplateInvoked() {
final String response = target("/fruit").request()
.get(String.class);
- Assert.assertThat(response, containsString("Welcome Fruit Index Page!"));
+ assertThat(response, containsString("Welcome Fruit Index Page!"));
}
@Test
- public void testErrorTemplate() {
+ public void givenGetFruitByName_whenFruitUnknown_thenErrorTemplateInvoked() {
final String response = target("/fruit/orange").request()
.get(String.class);
- Assert.assertThat(response, containsString("Error - Fruit not found: orange!"));
+ assertThat(response, containsString("Error - Fruit not found: orange!"));
+ }
+
+ @Test
+ public void givenCreateFruit_whenFormContainsNullParam_thenResponseCodeIsBadRequest() {
+ Form form = new Form();
+ form.param("name", "apple");
+ form.param("colour", null);
+ Response response = target("fruit/create").request(MediaType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+
+ assertEquals("Http Response should be 400 ", 400, response.getStatus());
+ assertThat(response.readEntity(String.class), containsString("Fruit colour must not be null"));
+ }
+
+ @Test
+ public void givenUpdateFruit_whenFormContainsBadSerialParam_thenResponseCodeIsBadRequest() {
+ Form form = new Form();
+ form.param("serial", "2345-2345");
+
+ Response response = target("fruit/update").request(MediaType.APPLICATION_FORM_URLENCODED)
+ .put(Entity.form(form));
+
+ assertEquals("Http Response should be 400 ", 400, response.getStatus());
+ assertThat(response.readEntity(String.class), containsString("Fruit serial number is not valid"));
+ }
+
+ @Test
+ public void givenCreateFruit_whenFruitIsInvalid_thenResponseCodeIsBadRequest() {
+ Fruit fruit = new Fruit("Blueberry", "purple");
+ fruit.setWeight(1);
+
+ Response response = target("fruit/create").request(MediaType.APPLICATION_JSON_TYPE)
+ .post(Entity.entity(fruit, MediaType.APPLICATION_JSON_TYPE));
+
+ assertEquals("Http Response should be 400 ", 400, response.getStatus());
+ assertThat(response.readEntity(String.class), containsString("Fruit weight must be 10 or greater"));
+ }
+
+ @Test
+ public void givenFruitExists_whenSearching_thenResponseContainsFruit() {
+ Fruit fruit = new Fruit();
+ fruit.setName("strawberry");
+ fruit.setWeight(20);
+ Response response = target("fruit/create").request(MediaType.APPLICATION_JSON_TYPE)
+ .post(Entity.entity(fruit, MediaType.APPLICATION_JSON_TYPE));
+
+ assertEquals("Http Response should be 204 ", 204, response.getStatus());
+
+ final String json = target("fruit/search/strawberry").request()
+ .get(String.class);
+ assertThat(json, containsString("{\"name\":\"strawberry\",\"weight\":20}"));
+ }
+
+ @Test
+ public void givenFruit_whenFruitIsInvalid_thenReponseContainsCustomExceptions() {
+ final Response response = target("fruit/exception").request()
+ .get();
+
+ assertEquals("Http Response should be 400 ", 400, response.getStatus());
+ String responseString = response.readEntity(String.class);
+ assertThat(responseString, containsString("exception..colour size must be between 5 and 200"));
+ assertThat(responseString, containsString("exception..name size must be between 5 and 200"));
}
}