From 859e91538ad7b45d10421989e3efad554d4e6839 Mon Sep 17 00:00:00 2001 From: Viktor Ardelean Date: Sat, 17 Sep 2022 20:30:26 +0300 Subject: [PATCH] BAEL-5649 (#12677) * add hexagonal architecture example project * rename class * add unit test and integration test * add new line * reorder pom * remove maven wrapper * [BAEL-5649] add article code * remove files added by mistake * get parameter type using reflection and serioalize JSON into POJOs * clean code * clean code * reformat * implement PR feedback * reformat with contiunation space eq to 2 * replace UserDto with firstName and lastName --- .../spring-mvc-basics-5/pom.xml | 11 ++++ .../com/baeldung/jsonargs/AddressDto.java | 55 +++++++++++++++++++ .../java/com/baeldung/jsonargs/JsonArg.java | 12 ++++ .../jsonargs/JsonArgumentResolver.java | 50 +++++++++++++++++ .../com/baeldung/jsonargs/UserController.java | 33 +++++++++++ .../java/com/baeldung/jsonargs/UserDto.java | 46 ++++++++++++++++ .../baeldung/spring/web/config/WebConfig.java | 18 +++++- .../UserControllerIntegrationTest.java | 52 ++++++++++++++++++ 8 files changed, 274 insertions(+), 3 deletions(-) create mode 100644 spring-web-modules/spring-mvc-basics-5/src/main/java/com/baeldung/jsonargs/AddressDto.java create mode 100644 spring-web-modules/spring-mvc-basics-5/src/main/java/com/baeldung/jsonargs/JsonArg.java create mode 100644 spring-web-modules/spring-mvc-basics-5/src/main/java/com/baeldung/jsonargs/JsonArgumentResolver.java create mode 100644 spring-web-modules/spring-mvc-basics-5/src/main/java/com/baeldung/jsonargs/UserController.java create mode 100644 spring-web-modules/spring-mvc-basics-5/src/main/java/com/baeldung/jsonargs/UserDto.java create mode 100644 spring-web-modules/spring-mvc-basics-5/src/test/java/com/baeldung/jsonargs/UserControllerIntegrationTest.java diff --git a/spring-web-modules/spring-mvc-basics-5/pom.xml b/spring-web-modules/spring-mvc-basics-5/pom.xml index 5e8cd1b44e..0f2b0bc7bd 100644 --- a/spring-web-modules/spring-mvc-basics-5/pom.xml +++ b/spring-web-modules/spring-mvc-basics-5/pom.xml @@ -38,6 +38,17 @@ spring-boot-starter-test test + + org.apache.commons + commons-io + 1.3.2 + + + com.jayway.jsonpath + json-path + 2.7.0 + + diff --git a/spring-web-modules/spring-mvc-basics-5/src/main/java/com/baeldung/jsonargs/AddressDto.java b/spring-web-modules/spring-mvc-basics-5/src/main/java/com/baeldung/jsonargs/AddressDto.java new file mode 100644 index 0000000000..b8bd8b9218 --- /dev/null +++ b/spring-web-modules/spring-mvc-basics-5/src/main/java/com/baeldung/jsonargs/AddressDto.java @@ -0,0 +1,55 @@ +package com.baeldung.jsonargs; + +public class AddressDto { + + private String streetName; + private String streetNumber; + private String postalCode; + private String city; + private String country; + + public String getStreetName() { + return streetName; + } + + public void setStreetName(String streetName) { + this.streetName = streetName; + } + + public String getStreetNumber() { + return streetNumber; + } + + public void setStreetNumber(String streetNumber) { + this.streetNumber = streetNumber; + } + + public String getPostalCode() { + return postalCode; + } + + public void setPostalCode(String postalCode) { + this.postalCode = postalCode; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + @Override + public String toString() { + return "Address{" + "streetName='" + streetName + '\'' + ", streetNumber='" + streetNumber + '\'' + ", postalCode='" + postalCode + '\'' + ", city='" + city + '\'' + ", country='" + country + '\'' + '}'; + } +} diff --git a/spring-web-modules/spring-mvc-basics-5/src/main/java/com/baeldung/jsonargs/JsonArg.java b/spring-web-modules/spring-mvc-basics-5/src/main/java/com/baeldung/jsonargs/JsonArg.java new file mode 100644 index 0000000000..1ee614982e --- /dev/null +++ b/spring-web-modules/spring-mvc-basics-5/src/main/java/com/baeldung/jsonargs/JsonArg.java @@ -0,0 +1,12 @@ +package com.baeldung.jsonargs; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface JsonArg { + String value() default ""; +} diff --git a/spring-web-modules/spring-mvc-basics-5/src/main/java/com/baeldung/jsonargs/JsonArgumentResolver.java b/spring-web-modules/spring-mvc-basics-5/src/main/java/com/baeldung/jsonargs/JsonArgumentResolver.java new file mode 100644 index 0000000000..3cb01dae32 --- /dev/null +++ b/spring-web-modules/spring-mvc-basics-5/src/main/java/com/baeldung/jsonargs/JsonArgumentResolver.java @@ -0,0 +1,50 @@ +package com.baeldung.jsonargs; + +import java.io.IOException; +import java.util.Objects; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.io.IOUtils; +import org.springframework.core.MethodParameter; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +import com.jayway.jsonpath.JsonPath; + +public class JsonArgumentResolver implements HandlerMethodArgumentResolver { + + private static final String JSON_BODY_ATTRIBUTE = "JSON_REQUEST_BODY"; + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(JsonArg.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + String body = getRequestBody(webRequest); + String jsonPath = Objects.requireNonNull(Objects.requireNonNull(parameter.getParameterAnnotation(JsonArg.class)) + .value()); + Class parameterType = parameter.getParameterType(); + return JsonPath.parse(body) + .read(jsonPath, parameterType); + } + + private String getRequestBody(NativeWebRequest webRequest) { + HttpServletRequest servletRequest = Objects.requireNonNull(webRequest.getNativeRequest(HttpServletRequest.class)); + String jsonBody = (String) servletRequest.getAttribute(JSON_BODY_ATTRIBUTE); + if (jsonBody == null) { + try { + jsonBody = IOUtils.toString(servletRequest.getInputStream()); + servletRequest.setAttribute(JSON_BODY_ATTRIBUTE, jsonBody); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return jsonBody; + } +} + diff --git a/spring-web-modules/spring-mvc-basics-5/src/main/java/com/baeldung/jsonargs/UserController.java b/spring-web-modules/spring-mvc-basics-5/src/main/java/com/baeldung/jsonargs/UserController.java new file mode 100644 index 0000000000..eb1abbb675 --- /dev/null +++ b/spring-web-modules/spring-mvc-basics-5/src/main/java/com/baeldung/jsonargs/UserController.java @@ -0,0 +1,33 @@ +package com.baeldung.jsonargs; + +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping("/user") +public class UserController { + + @PostMapping("/process/custom") + public ResponseEntity process(@JsonArg("firstName") String firstName, @JsonArg("address.city") String city) { + /* business processing */ + return ResponseEntity.ok() + .body(String.format("{\"firstName\": %s, \"city\" : %s}", firstName, city)); + } + + @PostMapping("/process") + public ResponseEntity process(@RequestBody UserDto user) { + /* business processing */ + return ResponseEntity.ok() + .body(user.toString()); + } + + @PostMapping("/process/custompojo") + public ResponseEntity process(@JsonArg("firstName") String firstName, @JsonArg("lastName") String lastName, @JsonArg("address") AddressDto address) { + /* business processing */ + return ResponseEntity.ok() + .body(String.format("{\"firstName\": %s, \"lastName\": %s, \"address\" : %s}", firstName, lastName, address)); + } +} \ No newline at end of file diff --git a/spring-web-modules/spring-mvc-basics-5/src/main/java/com/baeldung/jsonargs/UserDto.java b/spring-web-modules/spring-mvc-basics-5/src/main/java/com/baeldung/jsonargs/UserDto.java new file mode 100644 index 0000000000..55ac683ebc --- /dev/null +++ b/spring-web-modules/spring-mvc-basics-5/src/main/java/com/baeldung/jsonargs/UserDto.java @@ -0,0 +1,46 @@ +package com.baeldung.jsonargs; + +public class UserDto { + + private String firstName; + private String lastName; + private String age; + private AddressDto address; + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getAge() { + return age; + } + + public void setAge(String age) { + this.age = age; + } + + public AddressDto getAddress() { + return address; + } + + public void setAddress(AddressDto address) { + this.address = address; + } + + @Override + public String toString() { + return "User{" + "firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + ", age='" + age + '\'' + ", address=" + address + '}'; + } +} diff --git a/spring-web-modules/spring-mvc-basics-5/src/main/java/com/baeldung/spring/web/config/WebConfig.java b/spring-web-modules/spring-mvc-basics-5/src/main/java/com/baeldung/spring/web/config/WebConfig.java index 53865d02d5..a1b7d469c1 100644 --- a/spring-web-modules/spring-mvc-basics-5/src/main/java/com/baeldung/spring/web/config/WebConfig.java +++ b/spring-web-modules/spring-mvc-basics-5/src/main/java/com/baeldung/spring/web/config/WebConfig.java @@ -1,7 +1,10 @@ package com.baeldung.spring.web.config; +import java.util.List; + import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; @@ -11,18 +14,26 @@ import org.springframework.web.servlet.view.BeanNameViewResolver; import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.JstlView; +import com.baeldung.jsonargs.JsonArgumentResolver; + @Configuration public class WebConfig implements WebMvcConfigurer { @Override - public void addViewControllers(final ViewControllerRegistry registry) { + public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/") .setViewName("index"); } + @Override + public void addArgumentResolvers(List argumentResolvers) { + JsonArgumentResolver jsonArgumentResolver = new JsonArgumentResolver(); + argumentResolvers.add(jsonArgumentResolver); + } + @Bean public ViewResolver viewResolver() { - final InternalResourceViewResolver bean = new InternalResourceViewResolver(); + InternalResourceViewResolver bean = new InternalResourceViewResolver(); bean.setViewClass(JstlView.class); bean.setPrefix("/WEB-INF/view/"); bean.setSuffix(".jsp"); @@ -41,9 +52,10 @@ public class WebConfig implements WebMvcConfigurer { } @Bean - public BeanNameViewResolver beanNameViewResolver(){ + public BeanNameViewResolver beanNameViewResolver() { BeanNameViewResolver beanNameViewResolver = new BeanNameViewResolver(); beanNameViewResolver.setOrder(1); return beanNameViewResolver; } + } \ No newline at end of file diff --git a/spring-web-modules/spring-mvc-basics-5/src/test/java/com/baeldung/jsonargs/UserControllerIntegrationTest.java b/spring-web-modules/spring-mvc-basics-5/src/test/java/com/baeldung/jsonargs/UserControllerIntegrationTest.java new file mode 100644 index 0000000000..acb0214e35 --- /dev/null +++ b/spring-web-modules/spring-mvc-basics-5/src/test/java/com/baeldung/jsonargs/UserControllerIntegrationTest.java @@ -0,0 +1,52 @@ +package com.baeldung.jsonargs; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; + +import com.fasterxml.jackson.databind.ObjectMapper; + +@WebMvcTest(controllers = UserController.class) +class UserControllerIntegrationTest { + + @Autowired + private MockMvc mockMvc; + + @Test + void whenSendingAPostJSON_thenReturnFirstNameAndCity() throws Exception { + String jsonString = "{\"firstName\":\"John\",\"lastName\":\"Smith\",\"age\":10,\"address\":{\"streetName\":\"Example Street\",\"streetNumber\":\"10A\",\"postalCode\":\"1QW34\",\"city\":\"Timisoara\",\"country\":\"Romania\"}}"; + mockMvc.perform(post("/user/process/custom").content(jsonString) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(MockMvcResultMatchers.jsonPath("$.firstName") + .value("John")) + .andExpect(MockMvcResultMatchers.jsonPath("$.city") + .value("Timisoara")); + } + + @Test + void whenSendingAPostJSON_thenReturnUserAndAddress() throws Exception { + String jsonString = "{\"firstName\":\"John\",\"lastName\":\"Smith\",\"address\":{\"streetName\":\"Example Street\",\"streetNumber\":\"10A\",\"postalCode\":\"1QW34\",\"city\":\"Timisoara\",\"country\":\"Romania\"}}"; + ObjectMapper mapper = new ObjectMapper(); + UserDto user = mapper.readValue(jsonString, UserDto.class); + AddressDto address = user.getAddress(); + + String mvcResult = mockMvc.perform(post("/user/process/custompojo").content(jsonString) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(); + + assertEquals(String.format("{\"firstName\": %s, \"lastName\": %s, \"address\" : %s}", user.getFirstName(), user.getLastName(), address), mvcResult); + } +} \ No newline at end of file