MessageConverter issue while using RestTemplate with StreamingResponseBody - spring-boot

We have a REST API (server side) implemented using Spring Boot. This API is streaming a PDF file as StreamingResponseBody wrapped in ResponseEntity where content-type is given as MediaType.APPLICATION_OCTET_STREAM.
I am trying to access this API from client application with the help of RestTemplate. This client application is again a Spring Boot app. This client application is existing and this was supporting MappingJackson2HttpMessageConverter with two supportive media types so far.
application/json and application/x-www-form-urlencoded
I followed few suggestions and tried with these items
Added MediaType.APPLICATION_OCTET_STREAM to existing
MappingJackson2HttpMessageConverter
Added ByteArrayHttpMessageConverter which has a default support to MediaType.APPLICATION_OCTET_STREAM
Added ResourceHttpMessageConverter which supporting streaming response.
But with all these suggestions I was facing the following errors. At this point of time I am not really sure if is there anything that I am missing from configuration. Team, it will be of a great help really if you can redirect me to a short examples or solutions in achieving this integration.
org.springframework.web.client.RestClientException: Error while extracting response for type [interface org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody] and content type [application/octet-stream]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Unexpected character ('%' (code 37)): expected a valid value (JSON String, Number, Array, Object or token 'null', 'true' or 'false'); nested exception is com.fasterxml.jackson.core.JsonParseException: Unexpected character ('%' (code 37)): expected a valid value (JSON String, Number, Array, Object or token 'null', 'true' or 'false') at [Source: (PushbackInputStream); line: 1, column: 2]
This following error was when I tried with ByteArrayHttpMessageConverter (or) ResourceHttpMessageConverter
org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [interface org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody] and content type [application/octet-stream]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:123) ~[spring-web-5.2.6.RELEASE.jar!/:5.2.6.RELEASE]
Updating Question with the current implementation:
This is how resttemplate bean I am creating.
#Bean
public RestTemplate restTemplate() {
final RestTemplate restTemplate = new RestTemplate(httpRequestFactory());
final List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
final MappingJackson2HttpMessageConverter converter = new
MappingJackson2HttpMessageConverter();
final List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.APPLICATION_JSON);
mediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
//mediaTypes.add(MediaType.APPLICATION_OCTET_STREAM)
converter.setSupportedMediaTypes(mediaTypes);
messageConverters.add(converter);
restTemplate.setMessageConverters(messageConverters);
return restTemplate;
}
And my API client call is
ResponseEntity<StreamingResponseBody> response = reportRestTemplate.exchange(builder.buildAndExpand(uriParams).toUriString(),HttpMethod.GET,entity,StreamingResponseBody.class,uriParams);

Related

How can SOAP be used with Spring Reactor's WebClient?

I've managed to build an SSL connection to the sandbox server and to send the object as a serialised XML object by applying the content type MediaType.APPLICATION_XML. However, this is not enough as the target service only supports SOAP and expects the message properly wrapped in an envelope.
final var webClient = WebClient.builder()
.baseUrl(fmdConfiguration.getSinglePackUrl())
.clientConnector(connector)
.exchangeStrategies(exchangeStrategies)
.filter(logResponseStatus())
.filter(logRequest())
.build();
return webClient
.method(GET)
.contentType(MediaType.APPLICATION_XML)
.body(BodyInserters.fromObject(request))
.retrieve()
.bodyToMono(SinglePackPingResponse.class);
This is the response from the service:
Unable to create envelope from given source because the root element is not named "Envelope"
Unfortunately the the WebClient doesn't support the media type application/soap+xml. When I try to use it, then the WebClient throws the following error:
org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'application/soap+xml;charset=UTF-8' not supported for bodyType=eu.nmvs.SinglePackPingRequest
at org.springframework.web.reactive.function.BodyInserters.unsupportedError(BodyInserters.java:300)
I use:
private void acceptedCodecs(ClientCodecConfigurer clientCodecConfigurer) {
clientCodecConfigurer.customCodecs().encoder(new Jackson2JsonEncoder(new ObjectMapper(), TEXT_XML));
clientCodecConfigurer.customCodecs().decoder(new Jackson2JsonDecoder(new ObjectMapper(), TEXT_XML));
}
and:
webClient = webClientBuilder
.baseUrl(baseUrL)
.filter(logRequest())
.exchangeStrategies(ExchangeStrategies.builder().codecs(this::acceptedCodecs).build()).build();

Could not write request: no suitable HttpMessageConverter found for request type

I have used to RestTemplate class to call a rest service, I am getting the below exception.
org.springframework.web.client.RestClientException: Could not write request: no suitable HttpMessageConverter found for request type [UmlerUpdateRequest]
The below is my code snippet that I have tried:
RestTemplate restTemplate = new RestTemplate();
UmlerUpdateRequest obj= new UmlerUpdateRequest();
obj.setTrnspEqpId(equipId);
obj.setLstMaintId(lesseeScac);
obj.setRecLseScac(userId);
obj.setTareWgt(tareWeight);
obj.setBldD(builtDate);
String returns = restTemplate.postForObject(url, obj, String.class);
Can anyone help me on this?

Spring RequestEntity<Object> needs Accept header mandatorily when the server fails

I am working with Spring Framework version 4.3.6.
It works with RestTemplate and thus offers support for XML and JSON
Reminder: about HTTP methods, for POST and PUT has sense mandatorily define the Content-Type header. It either for XML or JSON.
I have a situation for RequestEntity<Object> about testing:
I have the following creation for POST
RequestEntity<Object> requestEntity =
RequestEntity.post(uri).contentType(MediaType.APPLICATION_XML)
.header("Accept-Language", locale.toString())
.body(entity);
Until here it works fine when none exception is thrown by the server. It means, the entity is saved or persisted in the database without problem.
I have a problem when in other testing scenario the entity already exists in the database. I have a #ControllerAdvice that works through #ExceptionHandler. The server side works how is expected. It catchs the exception and generates an error message but...
...the problem only happens with XML, I get the following error message (the #Test method fails of course)
org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: com.ctc.wstx.exc.WstxUnexpectedCharException: Unexpected character '{' (code 123) in prolog; expected '<'
at [row,col {unknown-source}]: [1,1]; nested exception is java.io.IOException: com.ctc.wstx.exc.WstxUnexpectedCharException: Unexpected character '{' (code 123) in prolog; expected '<'
at [row,col {unknown-source}]: [1,1]
I did realize the unique way to avoid that error message is through the following edition:
RequestEntity<Object> requestEntity =
RequestEntity.post(uri).contentType(MediaType.APPLICATION_XML)
.accept(MediaType.APPLICATION_XML)//<---- new
.header("Accept-Language", locale.toString())
.body(entity);
I am confused why it works, it because for POST is not mandatory define the Accept header. Even when in the server side I can define for the ResponseEntity<Object> (response) the Content-Type and Accept headers to XML value, the #Test fails. The unique way to solve this is adding for the RequestEntity<Object> (request) the .accept(MediaType.APPLICATION_XML) part.
Here the other problem is that we must assume other Java developers using RestTemplate are going to create the RequestEntity<Object> object with just defining the Content-Type and without defining the Accept header.
Note: it fails even when the RestTemplate uses the setErrorHandler method
Again, it only happens with XML, for JSON I have the following:
RequestEntity<Object> requestEntity =
RequestEntity.post(uri).contentType(MediaType.APPLICATION_JSON_UTF8)
.header("Accept-Language", locale.toString())
.body(entity);
and always works for valid and invalid (an entity already persisted) scenarios.
What is the best approach to around this situation?
Alpha
Even If I do the following edition how was suggested:
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(true)
.favorParameter(false)
.ignoreAcceptHeader(false)
//.defaultContentType(new MediaType(MediaType.TEXT_PLAIN, StandardCharsets.UTF_8))
.defaultContentType(new MediaType(MediaType.APPLICATION_XML, StandardCharsets.UTF_8))
.mediaType("html", MediaType.TEXT_HTML)
.mediaType("xml", new MediaType(MediaType.APPLICATION_XML, StandardCharsets.UTF_8))
.mediaType("json", MediaType.APPLICATION_JSON_UTF8);
}
I get the same situation
About XML, I don't use JAXB2 because it does not support Generic Collections, thus I use Jackson jackson-dataformat-xml
Important I only have this situation when is used RequestEntity<Object> with RestTemplate, for other testings working around
ResultActions resultActions =
mockMvc.perform(post(uri).contentType(MediaType.APPLICATION_XML)
.header("Accept-Language", locale.toString())
.content(entity)).andDo(print());
Observe, there is no a .accept(MediaType.APPLICATION_XML) sentence.
All work fine.
Application/json is probably configured as the default media type in your services application somewhere. That is why unless you specify the Accept header to be application/xml, you are getting json response.
If you want to make application/xml your default response media type, you will need to configure the ContentNegotiationManagerFactoryBean (Maybe you are already doing this but for application/json). Something like this:
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
/**
* Setup a simple strategy: use all the defaults and return XML by default when not sure.
*/
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_XML);
}
}
You can find this in spring documentation here.
The other thing you need to look at is the entity you are using in error scenarios. If it is properly annotated with Jaxb annotations (#XmlRootElement)

RestTemplate RestClientException Could not extract response: no suitable HttpMessageConverter found

I am getting this error when calling the RestTemplate method
GetStatusRestfulResponse response = restTemplate.getForObject(restRequest.getUrl(), GetStatusRestfulResponse.class,restRequest.getParams());
>org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class GetStatusRestfulResponse] and content type [application/json]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:108)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:550)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:511)
at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:248)
RestTemplate restTemplate = new RestTemplate();
HttpClient httpClient = HttpClientBuilder.create().setDefaultCredentialsProvider(setupAuthentication(restRequest)).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
requestFactory.setReadTimeout(restRequest.getReqTimeOut());
restTemplate.setRequestFactory(requestFactory);
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
response = restTemplate.getForObject(restRequest.getUrl(), GetStatusRestfulResponse.class,restRequest.getParams());
I was able to solve the issue . the culprit was the java object I was using GetStatusRestfulResponse.
I took the following steps to debug the issue.
Got the source codes for spring-web and jackson-databind.
on debugging in the spring and jackson source code, realized that the issue was with the ObjectMapper not able to deserialize the java object.
The issue was that my Java object had inner classes .
in order to fix the problem with ObjectMapper not able to deserialize the java object , I had to
Add default no parameter constructors for the main java class and inner classes.
Made the inner classes static.
this solved the issue :)

Spring RestTemplate with Jackson throws "Can not resolve BeanPropertyFilter" when using #JsonFilter

Can I specify the Jackson ObjectMapper that Spring's RestTemplate uses?
I'm not 100% that's what I need to do but see below for details.
Background:
With help from this StackOverflow post I added #JsonFilter to my domain class and edited my jax-rs web service (implemented in CXF). I'm now successfully able to dynamically select which domain class fields to return in my RESTful API. So far so good.
I'm using Spring's RestTemplate in my JUnit tests to test my RESTful API. This was working fine until I added #JasonFilter to my domain class. Now I'm getting the following exception:
org.springframework.web.client.ResourceAccessException: I/O error: Can not resolve BeanPropertyFilter with id 'apiFilter'; no FilterProvider configured; nested exception is org.codehaus.jackson.map.JsonMappingException: Can not resolve BeanPropertyFilter with id 'apiFilter'; no FilterProvider configured
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:453)
rest of stack trace omitted for brevity
Caused by: org.codehaus.jackson.map.JsonMappingException: Can not resolve BeanPropertyFilter with id 'apiFilter'; no FilterProvider configured
at org.codehaus.jackson.map.ser.BeanSerializer.findFilter(BeanSerializer.java:252)
I was getting a similar problem on the server side and was able to resolve it (with help from this post) by giving a FilterProvider to the Jackson ObjectMapper as follows:
ObjectMapper mapper = new ObjectMapper();
FilterProvider filters = new SimpleFilterProvider().addFilter("apiFilter", SimpleBeanPropertyFilter.filterOutAllExcept(filterProperties));
Can I do something similar on the RestTemplate side? Any ideas of how to solve this issue are appreciated.
Just to be clear, on the client RestTemplate side I do not want to filter the domain object properties at all.
Can I specify the Jackson ObjectMapper that Spring's RestTemplate uses?
I was able to force RestTemplate to use a customized ObjectMapper by doing the following:
ObjectMapper mapper = new ObjectMapper();
// set a custom filter
Set<String> filterProperties = new HashSet<String>();
FilterProvider filters = new SimpleFilterProvider().addFilter("apiFilter", SimpleBeanPropertyFilter.serializeAllExcept(filterProperties));
mapper.setFilters(filters);
MappingJacksonHttpMessageConverter messageConverter = new MappingJacksonHttpMessageConverter();
messageConverter.setObjectMapper(mapper);
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
messageConverters.add(messageConverter);
restTemplate.setMessageConverters(messageConverters);
This website provided example for part of the above code.
Just adding to the answer. If you are using TestRestTemplate then you can actually get the underlying RestTemplate class and then modify its MappingJackson2HttpMessageConverter to include your filter:
var jackson2HttpMessageConverter = testRestTemplate.getRestTemplate().getMessageConverters().stream()
.filter(mc -> mc instanceof MappingJackson2HttpMessageConverter)
.map(mc -> (MappingJackson2HttpMessageConverter) mc)
.findFirst()
.orElseThrow();
jackson2HttpMessageConverter.getObjectMapper().setFilterProvider(
new SimpleFilterProvider().addFilter("MyFilterName", SimpleBeanPropertyFilter.serializeAll())
);

Resources