Spring RestTemplate not accepting JAXBElement - spring

I am trying to make a simple POST using RestTemplate to a service. The XSD that I have, does not generate the Root Element but instead has the Root Element Type. But ObjectFactory.createFoo(FooType) gives me the JAXBElement which I am trying to post but failing to do so with below exception:
org.springframework.web.client.RestClientException: Could not write request: no suitable HttpMessageConverter found for request type [com.foo.FooType] and content type [application/xml]
This is my code which calls the rest service
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
HttpEntity<FooType> entity = new HttpEntity<FooType>(request.getValue(),headers);
JAXBElement<ResponseType> response = restTemplate.postForObject(esbReplaceNumberListURI, entity, JAXBElement.class);
I tried adding Jaxb2Marshaller(of Spring O/X jar ) to RestTemplate with setSupportJaxbElementClass set as true after going through an answer in StackOverflow. That did not help.
I am building my request object as below which gives me JAXBElement
JAXBElement<FooType> request = ObjectFactory.createFoo(FooType);
and then while posting I do a request.getValue() as below:
HttpEntity<FooType> entity = new HttpEntity<FooType>(request.getValue(),headers);
My Spring Config as below:
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(getMarshallingHttpMessageConverter());
return restTemplate;
}
#Bean
public MarshallingHttpMessageConverter getMarshallingHttpMessageConverter() {
MarshallingHttpMessageConverter marshallingConverter = new MarshallingHttpMessageConverter();
marshallingConverter.setMarshaller(jaxb2Marshaller());
marshallingConverter.setUnmarshaller(jaxb2Marshaller());
marshallingConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_XML, MediaType.TEXT_XML));
return marshallingConverter;
}
#Bean
public Jaxb2Marshaller jaxb2Marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setSupportJaxbElementClass(Boolean.TRUE);
marshaller.setPackagesToScan("com.foo.domain.*");
return marshaller;
}
Using Spring-Boot 1.3.5.RELEASE which in turn uses Spring 4.2.6.RELEASE.
I am at my wits end in figuring this out. Any help is much appreciated.

I'm about 75% sure that you should have marshaller.setPackagesToScan("com.foo.domain");, not marshaller.setPackagesToScan("com.foo.domain.*");. Note the absence of the * - I believe that it's attempting to find a package whose name literally is *, which obviously doesn't exist.
Source:
Jaxb2Marshaller#setPackagesToScan:
This is using a Spring-bases search and therefore analogous to Spring's component-scan feature ({#link org.springframework.context.annotation.ClassPathBeanDefinitionScanner})
That suggests it's probably the same as described in the Spring Reference, which says:
The following is an alternative using XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.example"/>
</beans>
Which has <context:component-scan base-package="org.example"/>.

Related

Fail to consume SOAP WS with Spring-WS in Spring boot but works from SOAPUI

I'm using Spring Boot to consume a SOAP WS which I generate from a WSDL. I added the spring-ws-security so I can pass the user/password as security header as shown in the configuration:
#Configuration
public class ClientConfig {
public static final String SIEBEL_ENDPOINT = "http://...";
#Bean
public CustomerClient customerClient() {
CustomerClient client = new CustomerClient();
client.setDefaultUri(SIEBEL_ENDPOINT);
client.setWebServiceTemplate(webServiceTemplate(marshaller()));
return client;
}
#Bean
public WebServiceTemplate webServiceTemplate(Jaxb2Marshaller marshaller) {
WebServiceTemplate template = new WebServiceTemplate(marshaller, marshaller);
template.setDefaultUri(SIEBEL_ENDPOINT);
ClientInterceptor[] interceptors = new ClientInterceptor[] {new LogHttpHeaderClientInterceptor(), wsSecurityInterceptor()};
template.setInterceptors(interceptors);
return template;
}
#Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("com.test.dms.gen");
return marshaller;
}
#Bean
public Wss4jSecurityInterceptor wsSecurityInterceptor() {
Wss4jSecurityInterceptor wss4jSecurityInterceptor = new Wss4jSecurityInterceptor();
wss4jSecurityInterceptor.setSecurementActions(WSHandlerConstants.USERNAME_TOKEN);
wss4jSecurityInterceptor.setSecurementPasswordType(WSConstants.PW_TEXT);
wss4jSecurityInterceptor.setSecurementUsername("rf_USER");
wss4jSecurityInterceptor.setSecurementPassword("rf_USER");
return wss4jSecurityInterceptor;
}
}
And the service call:
public class CustomerClient extends WebServiceGatewaySupport {
public CustomerInfoOutput getCustomerInfo(String vin) {
ObjectFactory request = new ObjectFactory();
final CustomerInfoInput custInfoInput = request.createCustomerInfoInput();
custInfoInput.setVINNumber(vin);
return (CustomerInfoOutput) getWebServiceTemplate().marshalSendAndReceive(ClientConfig.SIEBEL_ENDPOINT, custInfoInput);
}
}
everything is well generated, and this logged output:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
SOAP-ENV:mustUnderstand="1">
<wsse:UsernameToken wsu:Id="UsernameToken-e8f183db-44db-4c0b-90d9-ca57e89225fd">
<wsse:Username>rf_USER</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">rf_USER</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<ns3:CustomerInfo_Input xmlns:ns2="http://siebel.com/testdashboard"
xmlns:ns3="http://test.com/rf/customerinfo" xmlns:ns4="http://test.com/rf"
xmlns:ns5="http://www.siebel.com/xml/IBM%20test%20Dashboard">
<ns3:VINNumber>123456789</ns3:VINNumber>
</ns3:CustomerInfo_Input>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
When I send this request using SOAP UI, it works perfectly. But when it's sent using the generated objects from the WSDL, I have this error:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Server</faultcode>
<faultstring>There is no active Web Service with operation named
'http://test.com/rf/customerinfo:CustomerInfo_Input'.(SBL-EAI-04313)
</faultstring>
<detail>
<siebelf:siebdetail xmlns:siebelf="http://www.siebel.com/ws/fault">
<siebelf:logfilename>EAIObjMgr_enu_0023_24117286.log</siebelf:logfilename>
<siebelf:errorstack>
<siebelf:error>
<siebelf:errorcode>SBL-EAI-04313</siebelf:errorcode>
<siebelf:errorsymbol>IDS_EAI_WS_OP_NOT_FOUND</siebelf:errorsymbol>
<siebelf:errormsg>There is no active Web Service with operation named
'http://test.com/rf/customerinfo:CustomerInfo_Input'.(SBL-EAI-04313)
</siebelf:errormsg>
</siebelf:error>
</siebelf:errorstack>
</siebelf:siebdetail>
</detail>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Any ideas please?
PS: please don't focus on URIs because I changed them, but the generated request works fine in SOAPUI.
With the hint of #RanjithR I processed as follow:
I traced the SOAP Call using Wireshark, and I discovered that Spring WS doesn't include the header SOAPAction. The following SOF question talks about this, and how to configure it (it sends you to the spring documentation which explains well the thing). Spring-WS client not setting SOAPAction header
The second thing is that, even if I added the header, it continues to tell me that the endpoint is not sent.
In my WSDL, the SOAPAction is defined as follows:
<soap:operation soapAction="document/http://test.com/rf/customerinfo:CustomerInfo"></soap:operation>
And my Spring ws call was like that:
return getWebServiceTemplate().marshalSendAndReceive(ClientConfig.SIEBEL_ENDPOINT,
customerInfoInput,
webServiceMessage -> ((SoapMessage)webServiceMessage).setSoapAction("document/http://ripl.com/rforce/customerinfo:CustomerInfo"));
In Wireshark, I had:
SOAPAction: "http://ripl.com/rforce/customerinfo:CustomerInfo"
But when I trace the same call from SOAP UI I have:
SOAPAction: "document/http://ripl.com/rforce/customerinfo:CustomerInfo"
So I tried to send a string literal as a SOAPAction:
"\"document/http://ripl.com/rforce/customerinfo:CustomerInfo\"";
Note the \"\" around the action and it works :)
Maybe it can help someone that have to integrate with Jurasik Park systems...
This post saved me a lot of time by pointing me in the right direction. The Action header was not added for me using the method described above, but the following method worked fine:
// add action header
final String ActionHeaderName = "Action";
final String ActionHeaderPrefix = "wsa";
final String ActionHeaderNamespace = "http://www.w3.org/2005/08/addressing";
final String ActionHeaderContent = "http://example/ActionHeader";
var actionName = soapEnvelope.createName(ActionHeaderName, ActionHeaderPrefix, ActionHeaderNamespace);
var actionHeader = soapEnvelope.getHeader().addHeaderElement(actionName);
actionHeader.setTextContent(ActionHeaderContent);
Thanks a lot #Marouane

How do I make spring boot RestTemplate client metrics not create a new tag for query parameters

I've got a spring boot application that is defining a RestTemplate bean as follows:
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
Also, pulling in spring-boot-starter-actuator and io.micrometer:micrometer-registry-prometheus.
When I use the injected RestTemplate as follows:
#Autowired
private RestTemplate restTemplate;
private String uriTemplate = "http://my.domain.com/bookstore-api/books";
public List<Book> getBooksByAuthor(String author) {
// create URI for "http://my.domain.com/bookstore-api/books?author={authorId}"
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder
.fromUriString(uriTemplate)
.queryParam("author", author);
// make the GET
ResponseEntity<Book[]> responseEntity = restTemplate.getForEntity(uriComponentsBuilder.toUriString(),Book[].class);
// rest ommitted for brevity
}
When getBooksByAuthor("Tolkien") is called, we can then hit /metrics/prometheus and see the following:
http_client_requests_seconds_count{clientName="my.domain.com",method="GET",status="200",uri="/bookstore-api/books?author=Tolkien",} 2.0
http_client_requests_seconds_sum{clientName="my.domain.com",method="GET",status="200",uri="/bookstore-api/books?author=Tolkien",} 0.253227898
This would be fine, except that there are lots of authors out there, and eventually I will get the "too many tags" exception.
I would prefer to have the following (similar to how path variables get templated):
http_client_requests_seconds_count{clientName="my.domain.com",method="GET",status="200",uri="/bookstore-api/books?author={author}",} 2.0
http_client_requests_seconds_sum{clientName="my.domain.com",method="GET",status="200",uri="/bookstore-api/books?author={author}",} 0.253227898
Is this possible to achieve by modifying the way I use UriComponentsBuilder? The closest thing I've found is to define my own RestTemplateExchangeTagsProvider, and override the default to do some crazy regex replacement.
Just fixed same issue in SpringBoot 2.4.5 using:
responseEntity = restTemplate.exchange(
config.getDataUrl(),
HttpMethod.GET,
httpEntity,
new ParameterizedTypeReference<String>() {},
rowId);
where getDataUrl resolves to:
https://data-service-dev.apps.cloud.net/api/hbase/getData?rowId={rowId}
metrics:
http_client_requests_seconds_count{clientName="data-service-dev.apps.cloud.net",method="GET",outcome="SUCCESS",status="200",uri="/api/hbase/getData?rowId={rowId}",} 1.0
...
I have had a same problem. Maybe this information will help you.
In my case restTemplate.setUriTemplateHandler(handler) had overwritten annonymous
MetricsClientHttpRequestInterceptor#createUriTemplateHandler.
And the original url templates had not been stored into memory for prometheus.
DefaultUriBuilderFactory builderFactory = new DefaultUriBuilderFactory();
builderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE);
customizer.customize(restTemplate);
restTemplate.setUriTemplateHandler(handler);
So,
I changed order of the commands:
restTemplate.setUriTemplateHandler(handler);
customizer.customize(restTemplate);
Please check that there are no setting commands for restTemplate after MetricsClientHttpRequestInterceptor.customize(restTemplate).

How to Design Jax - RS for two different methods with same path and producing/consuming same media type?

I want to design a Post service with two methods. Method names will be different. These two methods will take different objects for invocations. But Path and Production/Consumption media types will have to be same. How to do that? Please find this code below. It is giving exception like " A resource model has ambiguous (sub-)resource method for HTTP method POST and input mime-types as defined by"#Consumes" and "#Produces" annotations at Java methods" while being deployed into weblogic.
package com.adac.rest.service;
import javax.ws.rs.Path;
import javax.ws.rs.POST;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import com.adac.rest.model.Response;
import com.adac.rest.model.SalesData;
import com.adac.rest.model.Tax;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
#Path("/rest")
public class StageDataLoadingService {
#POST
#Path("/dataloading")
#Consumes("application/json")
#Produces("application/json")
public String resourceMethodPUTSalesData(SalesData sd) throws Exception {
System.out.println("This method called with Sales Data");
Response resp = new Response();
resp.setStatuscode("0");
resp.setStatusmessage("Success");
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
String json_resp = mapper.writeValueAsString(resp);
String str1 = mapper.writeValueAsString(sd);
System.out.println(str1);
//String sd1 = mapper.writeValueAsString(sd);
//System.out.println(sd1);
// JsonNode jsonNodeRoot = mapper.readTree(json_resp);
return json_resp;
}
#POST
#Path("/dataloading")
#Consumes("application/json")
#Produces("application/json") public String resourceMethodPUTTax(Tax tax)
throws Exception {
System.out.println("This method called with Tax");
Response resp = new Response(); resp.setStatuscode("0");
resp.setStatusmessage("Success");
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT); String json_resp =
mapper.writeValueAsString(resp);
String str1 = mapper.writeValueAsString(tax);
System.out.println(str1);
return json_resp;
}
}
Also, suggest web.xml and weblogic.xml configurations for the design you suggest.
Currently web.xml is:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID" version="3.1">
<display-name>ADACStageDataLoadingService</display-name>
<servlet>
<servlet-name>rest.application.config.ApplicationConfig</servlet-name>
</servlet>
<servlet-mapping>
<servlet-name>rest.application.config.ApplicationConfig</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
and weblogic.xml is:
<?xml version="1.0" encoding="UTF-8"?>
<wls:weblogic-web-app
xmlns:wls="http://xmlns.oracle.com/weblogic/weblogic-web-app"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd http://xmlns.oracle.com/weblogic/weblogic-web-app http://xmlns.oracle.com/weblogic/weblogic-web-app/1.9/weblogic-web-app.xsd">
<wls:weblogic-version>12.2.1.3</wls:weblogic-version>
<wls:context-root>ADACStageDataLoadingService</wls:context-root>
<wls:library-ref>
<wls:library-name>jax-rs</wls:library-name>
<wls:specification-version>2.0</wls:specification-version>
<wls:exact-match>false</wls:exact-match>
</wls:library-ref>
</wls:weblogic-web-app>
When you develop the api I suggest you to follow the rest best practices:
if you want insert a resource use POST method and use the following #Path: /SalesData/{resourceId} (you can omit "dataloading" because is implicit in the POST method
if you want update a resource use PUT method with the same path: /SalesData/{resourceId}
if you want delete a resource use DELETE method

Spring Boot RequestMapping with non-standard produces value returning 406 error when returning JAXB annotated object

I'm creating a Spring Boot app to replace a legacy api application, so all the routes/headers/etc are already set in stone. In that legacy app we used custom Accept headers to include both the version and the content type. So our Accept header is like:
catalog.v1.xml or catalog.v2.json etc.
Here is my request mapping for the method that is handling the request. I'm trying to handle the v1.xml one now. Spring is finding the correct method and the whole method is executed and it returns my JAXB annotated object:
#RequestMapping(value = "/catalog", method = RequestMethod.GET, produces="application/catalog.v1.xml")
How do I make sure Spring finds this matching handler method based on my Accept header, but knows that the output should be XML and marshall my JAXB object accordingly?
You need to provide Spring MVC with an HttpMessageConverter for your custom media type. To do so, I'd take advantage of Spring Boot automatically adding any HttpMessageConverter beans to Spring MVC's default configuration by configuring a bean that knows how to convert application/catalog.v1.xml:
#Bean
public Jaxb2RootElementHttpMessageConverter catalogXmlConverter() {
Jaxb2RootElementHttpMessageConverter xmlConverter = new Jaxb2RootElementHttpMessageConverter();
xmlConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("application", "catalog.v1.xml")));
return xmlConverter;
}
So once I realized I was changing the wrong configuration, and from deep debugging into Spring code, I realized I needed to replace or modify the message converter behavior. Here is my solution below. They don't make it super easy. If anyone has a simpler way of doing this, please let me know. This works.
public void extendMessageConverters(List<HttpMessageConverter<?>> converters)
{
for (HttpMessageConverter<?> converter : converters) {
if (converter.getClass() == MappingJackson2HttpMessageConverter.class){
MappingJackson2HttpMessageConverter jacksonConverter = (MappingJackson2HttpMessageConverter)converter;
MediaType jsonType = new MediaType("application", "catalog.v2.json");
MediaType jsonpType = new MediaType("application", "catalog.v2.jsonp");
List<MediaType> mediatTypes = new ArrayList<>(jacksonConverter.getSupportedMediaTypes());
mediatTypes.add(jsonpType);
mediatTypes.add(jsonType);
jacksonConverter.setSupportedMediaTypes(mediatTypes);
}
else if (converter.getClass() == Jaxb2RootElementHttpMessageConverter.class){
Jaxb2RootElementHttpMessageConverter xmlConverter = (Jaxb2RootElementHttpMessageConverter) converter;
MediaType xmlType = new MediaType("application", "catalog.v1.xml");
// Since the SupportMediaTypes list is unmodifiable, we have to create a new one based on it
// and replace it completely
List<MediaType> mediatTypes = new ArrayList<>(xmlConverter.getSupportedMediaTypes());
mediatTypes.add(xmlType);
xmlConverter.setSupportedMediaTypes(mediatTypes);
}
}
}

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