How to change WebClient behaviour concerning LocalDateTime? - spring-boot

I am using Spring Boot 2.4.5 and try to request a REST API of another application using WebClient. I know that the other application provides the requested information as a Collection.
When I use an Object[] to receive the response:
Object[] ob = (Object[]) webClient
.get()
.uri(endpoint)
// .bodyValue(criteria)
.exchangeToMono(response -> {
if (response.statusCode()
.equals(HttpStatus.OK)) {
return response.bodyToMono(Object[].class);
} else if (response.statusCode()
.is4xxClientError()) {
return Mono.just("Error response");
} else {
return response.createException()
.flatMap(Mono::error);
}
}).block();
I can see that I receive a LinkedHashMap with all the values including a field:
date_of_declaration -> 2020-03-02T08:43:10
However, if possible, I want to let WebClient immediately convert the response into the designated DTOs...
DeclarationDTO[] ob = (DeclarationDTO[]) webClient
.get()
.uri(endpoint)
// .bodyValue(criteria)
.exchangeToMono(response -> {
if (response.statusCode()
.equals(HttpStatus.OK)) {
return response.bodyToMono(DeclarationDTO[].class);
} else if (response.statusCode()
.is4xxClientError()) {
return Mono.just("Error response");
} else {
return response.createException()
.flatMap(Mono::error);
}
}).block();
...I get an exception when a LocalDateTime object shall be deserialized.
org.springframework.core.codec.DecodingException: JSON decoding error: Cannot deserialize value of type `java.time.LocalDateTime` from String "02-03-2020 01:20:00": Failed to deserialize java.time.LocalDateTime: (java.time.format.DateTimeParseException) Text '02-03-2020 01:20:00' could not be parsed: Unable to obtain LocalDateTime from TemporalAccessor: {HourOfAmPm=1, NanoOfSecond=0, SecondOfMinute=0, MicroOfSecond=0, MinuteOfHour=20, MilliOfSecond=0},ISO resolved to 2020-03-02 of type java.time.format.Parsed; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.time.LocalDateTime` from String "02-03-2020 01:20:00": Failed to deserialize java.time.LocalDateTime: (java.time.format.DateTimeParseException) Text '02-03-2020 01:20:00' could not be parsed: Unable to obtain LocalDateTime from TemporalAccessor: {HourOfAmPm=1, NanoOfSecond=0, SecondOfMinute=0, MicroOfSecond=0, MinuteOfHour=20, MilliOfSecond=0},ISO resolved to 2020-03-02 of type java.time.format.Parsed
at [Source: (io.netty.buffer.ByteBufInputStream); line: 1, column: 1438] (through reference chain: java.lang.Object[][0]->de.xxx.myportal.api.infrastructure.dto.MyDTO["a_person"]->de.xxx.myportal.api.infrastructure.dto.APersonDTO["foobar"])
I think WebClient has an internal ObjectMapper, so maybe it is possible to modify this ObjectMapper during WebClient instantiation? ...or is there a better way to tell Webclient how to handle LocalDateTime? ...maybe a WebClient customized by a configuration or...?
I can't explain but after removing any JsonFormat annotation it works out of the box. (Weird!)

This may help https://www.baeldung.com/spring-boot-formatting-json-dates
In your DeclarationDTO you can use something like:
#JsonFormat(pattern="yyyy-MM-dd'T'HH:mm:ss")
#JsonProperty("date_of_declaration")
private LocalDateTime dateOfDeclaration;

Related

Problem with Kotlin inline class deserialization to JSON in tests

I'm trying to write tests for routes in a WebFlux application, but I encountered a problem. I got an error during parsing to JSON:
13:38:20.601 [parallel-1] DEBUG org.springframework.web.server.handler.ResponseStatusExceptionHandler - [248c6c90] Resolved [ServerWebInputException: "400 BAD_REQUEST "Failed to read HTTP message"; nested exception is org.springframework.core.codec.DecodingException: JSON decoding error: Cannot construct instance of `com.test.GetItemRequest` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.test.GetItemRequest` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)<EOL> at [Source: (org.springframework.core.io.buffer.DefaultDataBuffer$DefaultDataBufferInputStream); line: 1, column: 2]"] for HTTP POST /v1/download
I have a request that uses an inline class:
#JvmInline
value class ItemName(val rawValue: String)
data class GetItemRequest(val name: ItemName)
The test:
#Test
fun `Request is parsed successfully`() {
//...
val client = WebTestClient.bindToRouterFunction(router.eimApiRoutes()).build()
val request = """{"name":"item1"}"""
val resp = client
.post()
.uri(EimApiRouter.DOWNLOAD_PATH)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.body(Mono.just(request), String::class.java)
.exchange()
.expectStatus()
.isOk
.expectBody()
.returnResult()
.responseBody
val expectedResponse = "OK"
assertEquals(expectedResponse, String(resp!!))
}
I can work the problem around by adding a default constructor:
data class GetItemRequest(val name: ItemName) {
constructor() : this(ItemName(""))
}
When I change the type of the parameter to String, it works.
The question:
Could somebody give the real solution? Maybe there is something missing in the test web client configuration (I tried to configure the Kotlin codecs manually, but without success)?

How can I get data from a response after `RestTemplate` throws a `RestClientException` because it couldn't parse it

Using restTemplate.exchange(uri, method, entity, responseType) to make a REST call fails with a RestClientException when the response is of the wrong responseType. E.g.,
org.springframework.web.client.RestClientException: Error while extracting response for type [java.util.List<java.lang.Byte>] and content type [application/json;charset=UTF-8];
nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Numeric value (281759) out of range of Java byte
Since this is not a RestClientResponseException we don't seem to have access to the response data like status code and body (not even in their raw form).
Is there a way to get (raw) data from the original (unparsable) response? (for logging)
Try parsing the response as String. See this answer - the similar concept can be used with the exchange method.
EDIT: If the exception does not occur always and you still want to be able to map the correct responses easily, you could override the corresponding MessageConverter (which is actually throwing the exception) and do anything you want afterwards, because the converter gives you a raw HttpInputMessage.
Assuming you are using MappingJackson2HttpMessageConverter it should look sth. like this (not tested though):
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
return new MappingJackson2HttpMessageConverter(objectMapper) {
#Override
public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
try {
return super.read(type, contextClass, inputMessage);
} catch (HttpMessageNotReadableException e) {
// LOG here...
throw e;
}
}
};
}
Try to add StringHttpMessageConverter in RestTemplate's messageConverters
StringHttpMessageConverter stringHttpMessageConverter
= new StringHttpMessageConverter(StandardCharsets.UTF_8);
MappingJackson2HttpMessageConverter jackson2HttpMessageConverter
= new MappingJackson2HttpMessageConverter(objectMapper);
RestTemplate restTemplate = new RestTemplate(factory);
restTemplate.setMessageConverters(
List.of(stringHttpMessageConverter, jackson2HttpMessageConverter));
final RestTemplate restTemplate = new RestTemplate();
try {
restTemplate.exchange(uri, method, entity, responseType);
} catch (RestClientException e) {
//for logging exact message
restTemplate.getForObject("https://httpbin.org/ip", String.class);
}

JSON parserError while creating user in Openfire through spring boot application

In my project I have to create a chat box. For that I have to use Open-fire server. I am having the service for creating users in this server.
I am facing the problem when I am trying to access the openfire service from my spring boot application.
I am have created the model for the user also created the service and provided implementation to it.
This is my model class,
public class OpenFireUser {
private String firstname;
private String username;
private String password;
private String email;
<----getters and setters--->
}
This is my service,
public UserCreationResponse createOpenFireUser(String authorization,User createUser) {
OpenFireUser user= new OpenFireUser();
user.setEmail(createUser.getEmail());
user.setFirstname(createUser.getFirstname().toLowerCase());
user.setPassword("Passw0rd");
user.setUsername(createUser.getUsername().toLowerCase());
UserCreationResponse response=new UserCreationResponse();
RestTemplate restTemplate = new RestTemplate();
try{
MultiValueMap<String, String> headers = new LinkedMultiValueMap<String, String>();
headers.add("Authorization", authorization);
headers.add("Content-Type", "application/json");
headers.add("Accept", "application/json");
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
HttpEntity<OpenFireUser> requestObject = new HttpEntity<OpenFireUser>(user, headers);
ResponseEntity<String> responseEntity=restTemplate.postForEntity(OPENFIRE_REST_ENDPOINT, requestObject,String.class);
int statusCode = responseEntity.getStatusCodeValue();
if(statusCode==201){
response.setResponseCode(statusCode);
return response;
}else{
response.setResponseCode(MessageConstant.ResponseCode.ProcessFail.value());}
return response;
}catch(HttpClientErrorException clientErr){
response.setUserMessage(clientErr.getMessage());
response.setResponseCode(clientErr.getStatusCode().value());
response.setResponseMessage(MessageConstant.CodeMessage.ProcessFail.value());
return response;
}
catch(Exception e)
{
response.setResponseCode(MessageConstant.ResponseCode.ProcessFail.value());
response.setResponseMessage(MessageConstant.CodeMessage.ProcessFail.value());
return response;
}
}
This is controller code for calling service,
#RequestMapping(value = "/createOpenFireUser", method = RequestMethod.POST, consumes = "application/json;charset=UTF-8",produces = "application/json;charset=UTF-8")
public UserCreationResponse createOpenFireUser(#RequestBody User createUser) {
logger.debug("Entering inside createOpenFireUser(#RequestBody OpenFireUser createUser) method");
//logger.debug("Create user request : {}" , createUserRequestEntity);
return userService.createOpenFireUser("authorizationKey",createUser);
}
while sending data from postman I am getting error like,
Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Can not deserialize instance of com.exelatech.printshop.model.User out of START_ARRAY token; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of com.exelatech.printshop.model.User out of START_ARRAY token
at [Source: java.io.PushbackInputStream#6c88daca; line: 1, column: 1]
2018-07-20 15:59:13.535 WARN 9988 --- [nio-9090-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved exception caused by Handler execution: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Can not deserialize instance of model.User out of START_ARRAY token; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of model.User out of START_ARRAY token
at [Source: java.io.PushbackInputStream#6c88daca; line: 1, column: 1]
On postman I am getting response like,
"timestamp": 1532082553781,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.http.converter.HttpMessageNotReadableException",
"message": "JSON parse error: Can not deserialize instance of model.User out of START_ARRAY token; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of model.User out of START_ARRAY token\n at [Source: java.io.PushbackInputStream#6c88daca; line: 1, column: 1]",
Can anyone help me to solve my issue?
Error was in sending object through postman.
Previously I was sending request object like,
[
{
"firstname" : "Jyoti",
"username" : "JyotiNH",
"password":"Passw0rd",
"email" : "jyoti.kanor#exelaonline.com"
}
]
which is object.
But my method receiving parameter as array of strings,
So when I sent request with object like following structure, it successfully created the user.
{
"firstname" : "Jyoti",
"username" : "JyotiNH",
"password":"Passw0rd",
"email" : "jyoti.kanor#exelaonline.com"
}

Spring MVC Map Request Body to Primitive Type

I want to handle simple POST request by spring mvc. The request body contain only a single int value. Can anyone help me to write the action method. My code is below:
#PostMapping(value = "/update")
#ResponseBody
public ResponseEntity updateLogLevel(#RequestBody int level) {
try {
//process request
} catch (Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
}
return ResponseEntity.ok(Constant.STATUS_SUCCESS);
}
But this code throw exception:
org.springframework.http.converter.HttpMessageNotReadableException",
"message":"JSON parse error: Can not deserialize instance of int out of START_OBJECT token;
nested exception is
com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of int out of START_OBJECT token
My request body:
{
"level" : 100
}
You are sending in your payload an object not integer, just send the number
100

How to post a date as a parameter using MockHttpServletRequestBuilder with jUnit?

I've been able to do tests normally with MockHttpServletRequestBuilder while I was only posting strings as parameters.
How can I post a date as a parameter, as it seems that my FormattingConversionService below does not work into jUnit tests, and my spring infrastructure is not converting String values to Calendar ones.
#Bean
public FormattingConversionService mvcConversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
DateFormatterRegistrar formatterRegistrar = new DateFormatterRegistrar();
formatterRegistrar.setFormatter(new DateFormatter("yyyy-MM-dd"));
formatterRegistrar.registerFormatters(conversionService);
return conversionService;
}
The test I'm trying to do is:
#Test #Transactional
public void shouldSaveABill() throws Exception {
String dueDate = "05/25/2017";
MockHttpServletRequestBuilder request = MockMvcRequestBuilders
.post("/saveBill")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.param("id", "1")
.param("description", "Food")
.param("dueDate", "05/25/2017")
.param("value", "150");
mockMvc.perform(request)
.andExpect(MockMvcResultMatchers.status().is(302))
.andExpect(MockMvcResultMatchers.forwardedUrl("/someUrl"));
}
While debugging the test, I got the error:
Field error in object 'bill' on field 'dueDate': rejected value [05/25/2017]; codes [typeMismatch.bill.dueDate,typeMismatch.dueDate,typeMismatch.java.util.Calendar,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [bill.dueDate,dueDate]; arguments []; default message [dueDate]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.util.Calendar' for property 'dueDate'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.util.Calendar] for value '05/25/2017'; nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [05/25/2017]]
Please, what is the best approach to test the persistence of an entity that has a field of Calendar type using jUnit, spring and MockHttpServletRequestBuilder ? Is there another way to do this and achieve the same goal ? I appreciate any help. Thanks.

Resources