Spring MVC: Deserialise query parameters to POJO using Jackson objectMapper - spring-mvc

Spring web application configuration contains Jackson ObjectMapper configured like this
objectMapper.disable(ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
objectMapper.registerModule(new JavaTimeModule())
JavaTimeModule is added to handle deserialisation of ZonedDateTime. There are two endpoint which handle a POJO which contains ZonedDateTime. The POJO is like this:
class MyRequest {
ZonedDateTime from
ZonedDateTime to
}
and controller with endpoints is:
#Slf4j
#RestController
class MyController {
#GetMapping('/pojo')
void getPojo(MyRequest myRequest) {
log.debug("Request received: $myRequest")
}
#PostMapping('/pojo')
void postPojo(#RequestBody MyRequest myRequest) {
log.debug("Request received: $myRequest")
}
}
When I send POST /pojo with body
{"from": "2017-03-15T00:00:00Z", "to": "2017-03-16T00:00:00Z"}
The response is 200 and deserialisation is successful.
Contrary, when I send
GET /pojo?from=2017-03-15T00:00:00Z&to=2017-03-15T00:00:00Z
The 400 Bad Request is received with error
Failed to convert from type [java.lang.String] to type [java.time.ZonedDateTime] for value '2017-03-15T00:00:00Z'
This make sense, since in GET request, I'm not sending JSON and therefore JSON object mapper is not called.
Is there a way to use objectMapper for GET requests also, so query parameters are converted into POJO object?
By the way, I know that it can be deserialised for GET endpoint like below, but I want to use same converter for GET and POST endpoint
#DateTimeFormat(iso = ISO.DATE_TIME)
ZonedDateTime from
#DateTimeFormat(iso = ISO.DATE_TIME)
ZonedDateTime to

Injecting objectMapper and converting query parameters map into object solves the problem
#Slf4j
#RestController
class MyController {
#Autowired
private ObjectMapper objectMapper
#GetMapping('/pojo')
void getPojo(#RequestParam Map<String, String> allRequestParams) {
MyRequest request = objectMapper.convertValue(allRequestParams, MyRequest)
log.debug("Request received: $myRequest")
}
...

Related

Integration Test: MockMvc is returning empty body for POST request

I do not know why the MvcResult returns an empty JSON for a GET request when the httpStatus is Ok. Moreover, through Postman I can tell that the data is indeed there and should be retrievable. I want to use the JSON data and map it to objects using the ObjectMapper's readValue() method. But in order to do that, I obviously need to be able to retrieve it first... help
The test does not fail, but when I try to System.out.println(json) - there is an empty line in the console where presumably the printed JSON should be. (I do not have the same issue when doing unit tests.)
`#SpringBootTest
#AutoConfigureMockMvc (addFilters = false) //in order to disable security: error 401
public class IntegrationTests {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper objectMapper;
#Test
public void test() {
String url = "/url";
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get(url))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON))
.andReturn();
String json = result.getResponse().getContentAsString();
System.out.println(json);
}`

How to access HttpServletRequest inside Spring component class

I am trying to access httpServletRequest inside a component class. I tried it in several ways.
#Component
public class MyService{
#Resource
WebServiceContext wsCtxt;
public void myWebMethod(){
MessageContext msgCtxt = wsCtxt.getMessageContext();
HttpServletRequest req = (
(HttpServletRequest)msgCtxt.get(MessageContext.SERVLET_REQUEST);
String clientIP = req.getRemoteAddr();
}
This didn't work for me. because WebServiceContext is always null. Then I tried same code inside Web service class. Then that code is working. My Requirement it to get HttpServletRequest inside component class. (ultimately What i am trying to do it get client host from request header).
It this possible to do ?. Are there any alternatives for this ?
Method #1
Have you tried passing the request object into your component by passing it in as an argument to your service method, and from your service to your component method?
// in your controller... Spring provides the request object
public String myController(HttpServletRequest request, ...) {
//...
myService.myServiceMethod(request,...);
}
// in your service...
public void myServiceMethod(HttpServletRequest request, ...) {
//...
myComponent.myWebMethod(request,...);
}
// in your component
public String myWebMethod(HttpServletRequest request, ...) {
// use the raw request object
}
Method #2
Also, DispatcherServlet exposes the request object by wrapping it in a ServletRequestAttributes object, which in turn is stored in a ThreadLocal variable. The actual storing takes place in RequestContextHolder and its static methods. You can access it as follows:
public void myWebMethod(){
//...
RequestAttributes reqAttr = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes servlReqAttr = (ServletRequestAttributes)reqAttr;
HttpServletRequest req = servlReqAttr.getRequest();
//...
}
Although a little verbose, you can see what's going on.
You could also condense it:
((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
I hope this helps!

SpringWebMvcTest - Test Requestbody using #Valid and custom validation

I am trying to test my controller endpoint and my requestbody annotated with #Valid annotation. My Testclass looks like the follow:
#RunWith(SpringRunner.class)
#WebMvcTest(value = BalanceInquiryController.class, secure = false)
public class BalanceInquiryControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private BalanceInquiryController balanceInquiryController;
#Test
public void testGetBalanceInquiry() throws Exception {
RequestBuilder requestBuilder = MockMvcRequestBuilders
.post("/com/balanceInquiry")
.accept(MediaType.APPLICATION_JSON)
.content("{\"comGiftCard\":{\"cardNumber\":\"1234567890\",\"pinNumber\":\"0123\"},\"comMerchant\":\"MERCHANT1\"}")
.contentType(MediaType.APPLICATION_JSON);
MvcResult mvcResult = mockMvc.perform(requestBuilder).andReturn();
MockHttpServletResponse response = mvcResult.getResponse();
assertEquals(HttpStatus.OK.value(), response.getStatus());
}
}
My Controller - #PostMapping looks like that:
#PostMapping(value = "/com/balanceInquiry")
public ResponseEntity<?> getBalanceInquiry(#Valid #RequestBody BalanceInquiryModel balanceInquiry, Errors errors) {
if (errors.hasErrors()) {
return new ResponseEntity<String>("Validation error", HttpStatus.BAD_REQUEST);
}
//do any stuff...
return new ResponseEntity<BalanceInquiryResponse>(balanceInquiryResponse, HttpStatus.OK);
}
My BalanceInquiryModel is annotated with #Valid and has some hibernate and custom validations behind. Those validations are all ok and already unit tested.
What I like to test is my endpoint where I send a valid json request body expecting a 200 response and also an invalid json request body expecting a 400 response validated by the set #Valid implementation.
For example an unvalid call is to send no pinNumber or length < 4.
I have read some threads and some uses MockMvcBuilders.standaloneSetup() to mock the full controller. But I wont do a full integration test.
Not quite sure how to go on with this situation and if I should go on.
P.S.: At the moment I get always a 200 response no matter if the validation should give an error or not.
Here a gist for more code and the validation classes/models.
Here's one of my example I work on my project
hope it help you out:
I have a global exception handler to handler my MethodArgumentNotValidException and throw it
#RequestMapping(value = "/add", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<User> createUser(#Valid #RequestBody User user) {
User savedUser = userService.save(user);
return new ResponseEntity<User>(savedUser, HttpStatus.CREATED);
}
public void testAdduser() throws Exception{
final User request = new User();
request.setFirstName("Test");
request.setLastName("some description");
mockMvc.perform(post(END_POINT+"/add")
.contentType(MediaType.APPLICATION_JSON)
.content(stringify(request))
).andDo(print()).andExpect(status().isUnprocessableEntity())
;
}
private String stringify(Object object) throws JsonProcessingException {
return new ObjectMapper().writeValueAsString(object);
}
Update:
I think your main problem is that you are using #WebMvcTest in stead of #SpringBootTest.
the different between 2 of them is that:
#SpringBootTest annotation will loads complete application and injects all the beans which is can be slow.
#WebMvcTest - for testing the controller layer. it doesn't inject other bean beside the #RestController
so if you are just testing just pure controller to see u can reach the endpont then you can just use #WebMvcTest which will make your test run faster.
but in your case, you want it to run the spring validation, you will need to use #SpringBootTest
for detailed: https://spring.io/guides/gs/testing-web/

how to manage multiple http response status code while using spring message converters

In our api we are using spring Jackson http message converter to automatically convert java object to json. I am enjoying that feature,but what I personally feel is that I've lost control over the response http status code.if I want to return the response with different status codes ,I have the choice of using #responsestatus(httpstatus),but I cannot specify the status dynamically,as annotation is expecting a enum const expression. The other choice is http server response.set status(),but I don't like that.spring's responseentity(jsonstring,statuscode) is a great thing to solve but if I want to use Jackson httpmessageconverter is any way to configure the response status code dynamically.
You can return ResponseEntity<MyObject> from your controller method and it will still use the configured message converters, example:
#RestController
#RequestMapping("/foo")
public class FooController {
#RequestMapping
public ResponseEntity<MyObject> foo() {
MyObject myObject = new MyObject();
// You can dynamically set the status based on your needs
HttpStatus status = HttpStatus.OK;
return new ResponseEntity<>(myObject, status);
}
}

Error response in json format for Spring interceptor

I am writing a REST based web service. I need to return all the responses as JSON format. I have an interceptor to validate my authentication parameters. On authentication failure scenario, I have to return the error response in JSON format.
Currently i am doing
response.setHeader("Content-Type","application/json");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "{\"error\":\"Missing Authentication Parameters\"}");
The response body is coming as below.
JBoss Web/2.1.3.GA - Error report HTTP Status 401 - {"error":"Missing Authentication Parameters"}type Status reportmessage {"error":"Missing Authentication Parameters"}description This request requires HTTP authentication ({"error":"Missing Authentication Parameters"}).JBoss Web/2.1.3.GA
I need just the JSON string in response. Please help me.
You should probably be using spring-security for this. If you want to do it by hand, an alternative to using sendError on the response is to use spring MVC's #ExceptionHandler along with content negotiation to return JSON.
First define an error class*:
public class Error {
public message;
public exception;
public Error(String message, Exception ex) {
this.message = message;
this.exception = ex;
}
}
And an exception:
public class NotAuthenticatedException extends Exception {
// ...
}
Then in your controller you throw an exception at the appropriate time, catch it with #ExceptionHandler and return a ResponseEntity containing an Error instance and the appropriate error code.
#Controller
public class SimpleController {
#RequestMapping(...)
public String aMethod() {
// ...
throw new NotAuthenticatedException("Missing Authentication Parameters");
}
#ExceptionHandler(NotAuthenticatedException.class)
public ResponseEntity<Error> handleNotAuthenticatedException(
NotAuthenticatedException ex,
HttpServletRequest request) {
return new ResponseEntity<Error>(
new Error(ex.getMessage(), ex),
HttpStatus.UNAUTHORIZED
);
}
}
*use getters/setters to please the java convention gods

Resources