Context
I am using Spring WS for producing web service as mentioned in the link: https://spring.io/guides/gs/producing-web-service/. However, when I am sending xml content wrapped with CDATA I get the response escaped and this is causing problem for non-java clients as they want element content to be within CDATA and everything unescaped.
I have tried
SaajSoapMessageFactory and
AxiomSoapMessageFactory (with payload caching set to false)
Configuration
WebServiceConfig.java
#EnableWs
#Configuration
public class WebServiceConfig extends WsConfigurerAdapter {
#Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext
applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(servlet, "/ws/*");
}
#Bean
public AxiomSoapMessageFactory messageFactory() {
/*SaajSoapMessageFactory messageFactory = new SaajSoapMessageFactory();
messageFactory.setSoapVersion(SoapVersion.SOAP_11);
messageFactory.afterPropertiesSet();*/
AxiomSoapMessageFactory messageFactory = new AxiomSoapMessageFactory();
//messageFactory.
messageFactory.setPayloadCaching(false);
messageFactory.setSoapVersion(SoapVersion.SOAP_11);
return messageFactory;
}
#Bean(name = "countries")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema
countriesSchema) {
DefaultWsdl11Definition wsdl11Definition = new
DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("CountriesPort");
wsdl11Definition.setLocationUri("/ws");
wsdl11Definition.setTargetNamespace("http://spring.io/guides/gs-
producing-web-service");
wsdl11Definition.setSchema(countriesSchema);
return wsdl11Definition;
}
#Bean
public XsdSchema countriesSchema() {
return new SimpleXsdSchema(new ClassPathResource("countries.xsd"));
}
CountryRepository.java
#Component
public class CountryRepository {
private static final Map<String, Country> countries = new HashMap<>();
#PostConstruct
public void initData() {
Country spain = new Country();
spain.setName("Spain");
spain.setCapital("<![CDATA[<a>Madrid</a>]]>");
spain.setCurrency(Currency.EUR);
spain.setPopulation(46704314);
countries.put(spain.getName(), spain);
Country poland = new Country();
poland.setName("Poland");
poland.setCapital("Warsaw");
poland.setCurrency(Currency.PLN);
poland.setPopulation(38186860);
countries.put(poland.getName(), poland);
Country uk = new Country();
uk.setName("United Kingdom");
uk.setCapital("London");
uk.setCurrency(Currency.GBP);
uk.setPopulation(63705000);
countries.put(uk.getName(), uk);
}
public Country findCountry(String name) {
Assert.notNull(name, "The country's name must not be null");
return countries.get(name);
}
}
Soap UI Request
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:gs="http://spring.io/guides/gs-producing-web-service">
<soapenv:Header/>
<soapenv:Body>
<gs:getCountryRequest>
<gs:name>Spain</gs:name>
</gs:getCountryRequest>
</soapenv:Body>
</soapenv:Envelope>
Soap UI response
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<ns2:getCountryResponse xmlns:ns2="http://spring.io/guides/gs-producing-web-service">
<ns2:country>
<ns2:name>Spain</ns2:name>
<ns2:population>46704314</ns2:population>
<ns2:capital><![CDATA[<a>Madrid</a>]]></ns2:capital>
<ns2:currency>EUR</ns2:currency>
</ns2:country>
</ns2:getCountryResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Expected response
<![CDATA[<a>Madrid</a>]]>
Question
Can anyone suggestwhat I am doing wrong and how to correct this behavior?
Thanks in advance.
Related
Hello i have exposed a soap ws with spring boot and i need to add soapenv:Header to the response.
the response that i have actually is
<SOAP-ENV:Envelope
xmlns:SOAP-ENV = "http://www.w3.org/2001/12/soap-envelope"
SOAP-ENV:encodingStyle = "http://www.w3.org/2001/12/soap-encoding">
<SOAP-ENV:Body xmlns:m = "">
<m:GetQuotationResponse>
<m:Quotation>Here is the quotation</m:Quotation>
</m:GetQuotationResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
here is the WebserviceConfiguration class
#EnableWs
#Configuration
public class WebServiceConfiguration extends WsConfigurerAdapter {
#Value("${webservices.endpoint.quotationEndpoint}")
private String quotationEndpoint;
#Bean
public ServletRegistrationBean<CXFServlet> wsDispatcherServlet() {
CXFServlet cxfServlet = new CXFServlet();
return new ServletRegistrationBean<>(cxfServlet, "/services/*");
}
#Bean(name = "cxf")
public SpringBus springBus() {
return new SpringBus();
}
#Bean
public Endpoint quotationEndpoint() {
EndpointImpl endpoint = new EndpointImpl(springBus(), new quotationEndpoint());
endpoint.publish(quotationEndpoint);
return endpoint;
}
}
I have a SOAP service that receives a binary attachment (MTOM). The issue is that I would like to return a non-MTOM response (a "clean" SOAP return). The current code return a multipart response and uses "xop+xml" content-type.
The code is:
#Configuration
public class MyWebServiceConfig {
#Value("${config1}")
private String contextPath;
#Value("${config2}")
private String namespace;
#Value("${config3}")
private String wsdl;
#Bean
ServletRegistrationBean<?> webServicesRegistration(ApplicationContext ctx) {
MessageDispatcherServlet messageDispatcherServlet = new MessageDispatcherServlet();
messageDispatcherServlet.setApplicationContext(ctx);
messageDispatcherServlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean<>(messageDispatcherServlet, contextPath, "*.wsdl");
}
#Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath(namespace);
marshaller.setMtomEnabled(true);
return marshaller;
}
#Bean
public MarshallingPayloadMethodProcessor methodProcessor(Jaxb2Marshaller marshaller) {
return new MarshallingPayloadMethodProcessor(marshaller);
}
#Bean
DefaultMethodEndpointAdapter endpointAdapter(MarshallingPayloadMethodProcessor methodProcessor) {
DefaultMethodEndpointAdapter adapter = new DefaultMethodEndpointAdapter();
adapter.setMethodArgumentResolvers(Collections.singletonList(methodProcessor));
adapter.setMethodReturnValueHandlers(Collections.singletonList(methodProcessor));
return adapter;
}
#Bean
public SimpleWsdl11Definition contentStore() {
SimpleWsdl11Definition definition = new SimpleWsdl11Definition();
definition.setWsdl(new ClassPathResource(wsdl));
return definition;
}
}
I have tried changing to setMtomEnabled(false) - but then the request is not handled correctly.
Is the anyway of receiving MTOM - but returning a non-MTOM response?
Edit:
Endpoint looks like this:
#Endpoint
public class MyEndpoint {
private ObjectFactory objectFactory;
public MyEndpoint() {
this.objectFactory = new ObjectFactory();
}
#PayloadRoot(localPart = "SendMyRequest", namespace = "somenamespace")
#ResponsePayload
public JAXBElement<MyReceipt> store(#RequestPayload JAXBElement<MyRequest> storeContentRequest) throws IOException {
MyReceipt receipt = new MyReceipt();
receipt.setf1(123);
receipt.setf2(456);
return this.objectFactory.createMyResponse(receipt);
}
}
We are migrating Jersey Endpoints to Spring MVC Rest endpoints. These endpoints consumes and produces XML. But Spring MVC is unable to convert the XML input bean because XML tag and Bean's property name is different. I have to take request as String and Convert it using JAXB explicitly. Is there any better way to do that?
Web Configuration
#EnableWebMvc
#Configuration
public class WebConfig implements WebMvcConfigurer {
public static final String JAXB_PACKAGE = "com.comp.platform.dataholder";
#Autowired
private Jackson2ObjectMapperBuilder builder;
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new StringHttpMessageConverter());
converters.add(new FormHttpMessageConverter());
converters.add(createXmlHttpMessageConverter());
converters.add(createMappingJacksonHttpMessageConverter(builder));
// converters.add(createMappingJackson2XmlHttpMessageConverter(xmlMapper));
Jaxb2RootElementHttpMessageConverter jaxb2RootElementHttpMessageConverter = new Jaxb2RootElementHttpMessageConverter();
jaxb2RootElementHttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_XML));
converters.add(jaxb2RootElementHttpMessageConverter);
}
#Bean
public Jackson2ObjectMapperBuilder customJson() {
return new Jackson2ObjectMapperBuilder().indentOutput(true).serializationInclusion(JsonInclude.Include.NON_NULL)
.propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE).failOnUnknownProperties(false);
}
#Bean
#Primary
public ObjectMapper createObjectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper mapper = builder.build();
mapper.setSerializationInclusion(Include.NON_NULL);
return mapper;
}
#Bean
public Jaxb2Marshaller jaxb2Marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setClassesToBeBound(NonFinancialDataHolder.class, JaxbDataHolder.class, ViestimateDataHolder.class,CTRequest.class,com.app.rest.PolicyDetailRequest.class,com.app.rest.TransactionSubmitRequest.class,com.app.rest.AgentSuppressInfoRequest.class);
return marshaller;
}
/**
* #return
*/
#Bean
public MappingJackson2HttpMessageConverter createMappingJacksonHttpMessageConverter(
Jackson2ObjectMapperBuilder builder) {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(builder.build());
return converter;
}
#Autowired
Jaxb2Marshaller jaxb2Marshaller;
private HttpMessageConverter<Object> createXmlHttpMessageConverter() {
Assert.notNull(jaxb2Marshaller, "Jaxb Marshaller dependency required");
MarshallingHttpMessageConverter xmlConverter = new MarshallingHttpMessageConverter(jaxb2Marshaller);
xmlConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_XML));
return xmlConverter;
}
public JAXBContext getMarshaller() throws JAXBException {
return JAXBContext.newInstance(JAXB_PACKAGE);
}
}
Bean
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"_UserId",
"_Password",
"_RequestDateTime",
"_Policy"
})
#XmlRootElement(name = "PolicyDetailRequest")
public class PolicyDetailRequest
{
#XmlElement(name = "UserId")
protected String _UserId;
#XmlElement(name = "Password")
protected String _Password;
#XmlElement(name = "RequestDateTime")
protected String _RequestDateTime;
#XmlElement(name = "Policy")
protected String _Policy;
}
Rest Endpoint
#Slf4j
#RestController
#RequestMapping("/api")
public class api
{
#Autowired
Jaxb2Marshaller jaxb2Marshaller;
#PostMapping(value="/PolicyResponse",produces=MediaType.APPLICATION_XML_VALUE,consumes = MediaType.APPLICATION_XML_VALUE)
#ResponseBody
public PolicyDetailResponse getPolicyResponse(#RequestBody String data)
{
PolicyDetailRequest request = (PolicyDetailRequest) jaxb2Marshaller.unmarshal(new StreamSource(new StringReader(data)));
PolicyDetailResponse response = new PolicyDetailResponse();
InternetRSHandler handler = new InternetRSHandler();
response= handler.getPolicyRSResponse(request);
return response;
}
}
Input Request
<PolicyDetailRequest>
<UserId>1234567890</UserId>
<Password>password</Password>
<RequestDateTime>20200420-15:07:23</RequestDateTime>
<Policy>A76ABCD12</Policy>
</PolicyDetailRequest>
I want do call a SOAP service with Spring WS WebServiceTemplate. I have used this very often and it always worked so far. But now I just get an soap envelope with empty body:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header/><SOAP-ENV:Body/></SOAP-ENV:Envelope>
I have created the request and response classes with the JAXB Maven Plugin. And the generated source code looks exactly like the services which are working.
Example:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(
name = "startRequest_RequestParameters",
propOrder = {"url"}
)
#XmlRootElement(
name = "startRequest"
)
public class StartRequest {
#XmlElement(
required = true
)
#XmlSchemaType(
name = "anyURI"
)
protected String url;
public StartRequest() {
}
public String getUrl() {
return this.url;
}
public void setUrl(String value) {
this.url= value;
}
}
I call the webservice template with marshallSendAndReceive
StartRequest request = new StartRequest();
request.setUrl(url);
StartResponse response = (StartResponse) webServiceTemplate.marshalSendAndReceive(endpointUrl, request);
I configure the WebServiceTemplate with java configuration:
public WebServiceTemplate startRequestWebServiceTemplate() throws Exception {
return createWebServiceTemplate(createMarshaller(), createSecurityInterceptor(username, password), createMessageSender(proxyHost, proxyPort));
}
private WebServiceTemplate createWebServiceTemplate(Jaxb2Marshaller marshaller, ClientInterceptor securityInterceptor, WebServiceMessageSender messageSender) {
WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
webServiceTemplate.setMarshaller(marshaller);
webServiceTemplate.setUnmarshaller(marshaller);
webServiceTemplate.setMessageSender(messageSender);
if (securityInterceptor != null) {
webServiceTemplate.setInterceptors((ClientInterceptor[]) Arrays.asList(securityInterceptor, createLoggingInterceptor()).toArray());
} else {
webServiceTemplate.setInterceptors((ClientInterceptor[]) Arrays.asList(createLoggingInterceptor()).toArray());
}
webServiceTemplate.setCheckConnectionForFault(true);
webServiceTemplate.afterPropertiesSet();
return webServiceTemplate;
}
private Jaxb2Marshaller createMarshaller() throws Exception {
Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
jaxb2Marshaller.setClassesToBeBound(StartRequest.class, StartResponse.class);
jaxb2Marshaller.afterPropertiesSet();
return jaxb2Marshaller;
}
private ClientInterceptor createLoggingInterceptor() {
return new SoapLoggingInterceptor(systemName);
}
private Wss4jSecurityInterceptor createSecurityInterceptor(String username, String password) {
Wss4jSecurityInterceptor wss4jSecurityInterceptor = new Wss4jSecurityInterceptor();
wss4jSecurityInterceptor.setSecurementPasswordType("PasswordText");
wss4jSecurityInterceptor.setSecurementActions("UsernameToken");
wss4jSecurityInterceptor.setSecurementUsername(username);
wss4jSecurityInterceptor.setSecurementPassword(password);
wss4jSecurityInterceptor.setSkipValidationIfNoHeaderPresent(true);
wss4jSecurityInterceptor.setValidateRequest(false);
wss4jSecurityInterceptor.setValidateResponse(false);
return wss4jSecurityInterceptor;
}
private HttpComponentsMessageSender createMessageSender(String proxyHost, String proxyPort) {
HttpComponentsMessageSender httpComponentsMessageSender = new HttpComponentsMessageSender(createHttpClient(proxyHost, proxyPort));
httpComponentsMessageSender.setAcceptGzipEncoding(true);
return httpComponentsMessageSender;
}
private HttpClient createHttpClient(String proxyHost, String proxyPort) {
RequestConfig.Builder configBuilder = RequestConfig.custom()
.setConnectTimeout(DEFAULT_CONNECTION_TIMEOUT_MILLISECONDS)
.setSocketTimeout(DEFAULT_READ_TIMEOUT_MILLISECONDS)
.setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT);
addProxySettings(configBuilder, proxyHost, proxyPort);
HttpClientBuilder clientBuilder = HttpClients.custom().setDefaultRequestConfig(configBuilder.build());
addInterceptor(clientBuilder);
addConnectionManager(clientBuilder);
return clientBuilder.build();
}
private void addProxySettings(RequestConfig.Builder configBuilder, String proxyHost, String proxyPort) {
if (StringUtils.isNotBlank(proxyHost)) {
configBuilder.setProxy(new HttpHost(proxyHost, Integer.valueOf(proxyPort)));
}
}
private void addInterceptor(HttpClientBuilder clientBuilder) {
clientBuilder.addInterceptorFirst(new HttpComponentsMessageSender.RemoveSoapHeadersInterceptor());
}
private void addConnectionManager(HttpClientBuilder clientBuilder) {
if (maxConnections > DEFAULT_MAX_CONNECTIONS_PER_ROUTE) {
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(maxConnections);
cm.setDefaultMaxPerRoute(maxConnections);
clientBuilder.setConnectionManager(cm);
}
}
This configuration worked fine for other soap implementations. But here I just get the soap envelope with the empty body.
Has anyone an idea what's wrong here?
I did something wrong when refactoring the LoggingInterceptor. When handling the request it took the response part from the MessageContext instead of the request part, which caused to overwrite the request with the response. So if you have such a problem check your interceptors if they handle response and request correctly
I am trying to call a soap service with spring integration DSL, have some custom header that needs to be added.
Constructed marshellingoutboundgateway. Trying to override DefaultSoapHeaderMapper but none of the overridden methods are getting called.
Trying to construct some thing like this.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<ObjectType34 >
</ObjectType34>
</soapenv:Header>
<soapenv:Body>
<ObjectType12 >
</ObjectType12>
</soapenv:Body>
</soapenv:Envelope>
There is a sample in : https://github.com/spring-projects/spring-integration/blob/master/src/reference/asciidoc/ws.adoc
looks like only available in 5.0
posted detail code.
Any insights will be helpful.
#Configuration
#SpringBootApplication
#IntegrationComponentScan
#EnableIntegration
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(Application.class, args);
Info Info = ctx.getBean(Info.class);
//Constructing request Payload
ObjectType12 getInfoType = new ObjectFactory().ObjectType12();
JAXBElement<GetInfoType> getInfoTypeJAXBElement = new ObjectFactory().createGetInfo(getInfoType);
JAXBElement<GetInfoResponseType> getInfoResponseType = Info.getInfo(getInfoTypeJAXBElement);
System.out.println(getInfoResponseType.getName());
ctx.close();
}
#MessagingGateway
public interface Info {
#Gateway(requestChannel = "convert.input")
JAXBElement<GetInfoResponseType> getInfo(JAXBElement<GetInfoType> InfoType);
}
#Bean
public IntegrationFlow convert() {
StringResult result = new StringResult();
return flow -> flow
.wireTap(f -> f.<JAXBElement, String>transform(ele -> {
jaxb2Marshaller().marshal(ele, result);
return result.toString();
}).log())
.handle(endpoint());
}
#Bean
public Jaxb2Marshaller jaxb2Marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setPackagesToScan("org.abc", "com.abc");
return marshaller;
}
#Bean
public MarshallingWebServiceOutboundGateway endpoint() {
MarshallingWebServiceOutboundGateway gateway = new MarshallingWebServiceOutboundGateway("https://example.com/v1", jaxb2Marshaller(), jaxb2Marshaller());
gateway.setHeaderMapper(new DefaultSoapHeaderMapper() {
#Override
protected void populateUserDefinedHeader(String headerName, Object headerValue, SoapMessage target) {
super.populateUserDefinedHeader("Content-Type", "application/soap+xml", target);
}
#Override
protected void populateStandardHeaders(Map<String, Object> headers, SoapMessage target) {
headers.put(WebServiceHeaders.SOAP_ACTION,
"http://www.example.com/SOAUI/ServiceHeader/V4");
super.populateStandardHeaders(headers, target);
}
#Override
public void fromHeadersToRequest(MessageHeaders headers, SoapMessage target) {
SaajSoapMessage targetMessage = (SaajSoapMessage) target;
SoapHeader header = targetMessage.getEnvelope().getHeader();
//Constructing SOAP Header
JAXBElement<ObjectType34> trackingHdrTypeJAXBElement = ObjectFactory().createHdr(ObjectType34);
jaxb2Marshaller().marshal(trackingHdrTypeJAXBElement, header.getResult());
System.out.println(header.getResult());
}
#Override
public void setRequestHeaderNames(String... requestHeaderNames) {
super.setRequestHeaderNames("*");
}
});
gateway.setMessageFactory(new SaajSoapMessageFactory() {
#Override
public void setSoapVersion(SoapVersion version) {
super.setSoapVersion(SoapVersion.SOAP_11);
}
});
return gateway;
}
}
UPDATE: Modified the code
#Bean
public DefaultSoapHeaderMapper headerMapper11() {
return new DefaultSoapHeaderMapper() {
#Override
public void fromHeadersToRequest(MessageHeaders headers, SoapMessage target) {
SaajSoapMessage targetMessage = (SaajSoapMessage) target;
SoapHeader header = targetMessage.getEnvelope().getHeader();
//Constructing SOAP Header
JAXBElement<ObjectType34> trackingHdrTypeJAXBElement = ObjectFactory().createHdr(ObjectType34);
jaxb2Marshaller().marshal(trackingHdrTypeJAXBElement, header.getResult());
super.fromHeadersToRequest(headers, target);
}
};
}
and set the header using the method call:
gateway.setHeaderMapper(headerMapper11());
Now the overridden method is getting called and having the header as well.
Code is working as expected now.