Spring WS WS-Addressing @Action Example
This example shows how to use Spring WS WS-Addressing to send and receive SOAP requests or responses. We are not covering the details what WS-Addressing is, there are numerous papers describing this in detail.
Spring WS WS-Addressing Server
The @Endpoint
annotation tells spring that this class is eligible for handling soap requests. Next, we create a method and annotate it with @Action
. The @Action
annotation maps this method with a particular soap action described in ws-addressing. For simplicity the method returns a simple POJO.
package com.memorynotfound.server;
import com.memorynotfound.beer.*;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;
import org.springframework.ws.soap.addressing.server.annotation.Action;
@Endpoint
public class BeerEndpoint {
@Action("https://memorynotfound.com/getBeerRequest")
public @ResponsePayload GetBeerResponse getBeer(@RequestPayload GetBeerRequest request) {
GetBeerResponse response = new GetBeerResponse();
Beer beer = new Beer();
beer.setId(request.getId());
beer.setName("La Chouffe");
response.setBeer(beer);
return response;
}
}
Resolve empty soapAction
We configure spring ws with java configuration. By default, the generated WSDL contains an empty soapAction. We can override this by mapping the soapActions with the appropriate properties using the DefaultWsdl11Definition.setSoapActions(soapActions)
method.
package com.memorynotfound.server;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ws.config.annotation.EnableWs;
import org.springframework.ws.config.annotation.WsConfigurerAdapter;
import org.springframework.ws.transport.http.MessageDispatcherServlet;
import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition;
import org.springframework.xml.xsd.SimpleXsdSchema;
import org.springframework.xml.xsd.XsdSchema;
import java.util.Properties;
@EnableWs
@Configuration
public class SoapServerConfig extends WsConfigurerAdapter {
@Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext appContext){
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(appContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(servlet, "/ws/*");
}
@Bean(name = "beers")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema schema){
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("BeersPort");
wsdl11Definition.setLocationUri("/ws");
wsdl11Definition.setTargetNamespace("http://meorynotfound.com/beer");
wsdl11Definition.setSchema(schema);
// fix for adding soapAction to the dynamic generated wsdl
Properties soapActions = new Properties();
soapActions.setProperty("getBeer", "https://memorynotfound.com/getBeerRequest");
wsdl11Definition.setSoapActions(soapActions);
return wsdl11Definition;
}
@Bean
public XsdSchema beersSchema(){
return new SimpleXsdSchema(new ClassPathResource("xsd/beers.xsd"));
}
}
Finally, we bootstrap the application using spring boot. Running this will initialize and start the server. Next, we will implement the client to leverage the ws-addressing features.
package com.memorynotfound.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class RunServer {
public static void main(String[] args) {
SpringApplication.run(RunServer.class);
}
}
Spring WS WS-Addressing Client
Previously we created a server which is configured with Spring WS to handle WS-Addressing requests. Now we will configure a client to make the WS-Addressing enabled soap requests. We create a client by extending from the WebServiceGateWaySupport
, this gives easy acces to the WebServiceTemplate
which we’ll be using to send soap requests to the server. First we create an ActionCallback
class and provide the action URI location. This location is registered in the server method and WSDL. Finally we send the request using the WebServiceTemplate
and marshal and return the response.
package com.memorynotfound.client;
import com.memorynotfound.beer.*;
import org.springframework.ws.client.core.support.WebServiceGatewaySupport;
import org.springframework.ws.soap.addressing.client.ActionCallback;
import java.net.URI;
import java.net.URISyntaxException;
public class BeerClient extends WebServiceGatewaySupport {
public GetBeerResponse getBeer(int id) throws URISyntaxException {
GetBeerRequest request = new GetBeerRequest();
request.setId(id);
ActionCallback callback = new ActionCallback(
new URI("https://memorynotfound.com/getBeerRequest"));
return (GetBeerResponse) getWebServiceTemplate()
.marshalSendAndReceive(request, callback);
}
}
To configure the client we also use spring java configuration. We create a marshaller for marshalling the request and responses to and from XML. We also initialize our client to make the actual requests to the server.
package com.memorynotfound.client;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
@Configuration
public class SoapClientConfig {
@Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("com.memorynotfound.beer");
return marshaller;
}
@Bean
public BeerClient beerClient(Jaxb2Marshaller marshaller) {
BeerClient client = new BeerClient();
client.setDefaultUri("http://localhost:8080/ws/beers");
client.setMarshaller(marshaller);
client.setUnmarshaller(marshaller);
return client;
}
}
We obtain a reference to our client by initializing spring using the AnnotationConfigApplicationContext
.
package com.memorynotfound.client;
import com.memorynotfound.beer.GetBeerResponse;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.net.URISyntaxException;
public class RunClient {
public static void main(String[] args) throws URISyntaxException {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SoapClientConfig.class);
BeerClient client = context.getBean(BeerClient.class);
GetBeerResponse response = client.getBeer(1);
System.out.println(response);
}
}
Demo
When we run the client, it produces the following request and response.
Example Request
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header xmlns:wsa="http://www.w3.org/2005/08/addressing">
<wsa:To SOAP-ENV:mustUnderstand="1">http://localhost:8080/ws/beers</wsa:To>
<wsa:Action>https://memorynotfound.com/getBeerRequest</wsa:Action>
<wsa:MessageID>urn:uuid:853dacfa-40ad-4248-8767-5ae694834cf9</wsa:MessageID>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<ns2:getBeerRequest xmlns:ns2="https://memorynotfound.com/beer">
<ns2:id>1</ns2:id>
</ns2:getBeerRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Example Response
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header xmlns:wsa="http://www.w3.org/2005/08/addressing">
<wsa:To SOAP-ENV:mustUnderstand="1">http://www.w3.org/2005/08/addressing/anonymous</wsa:To>
<wsa:Action>https://memorynotfound.com/getBeerRequestResponse</wsa:Action>
<wsa:MessageID>urn:uuid:aa12d10c-82fa-41b8-b6f9-cced508b685f</wsa:MessageID>
<wsa:RelatesTo>urn:uuid:853dacfa-40ad-4248-8767-5ae694834cf9</wsa:RelatesTo>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<ns2:getBeerResponse xmlns:ns2="https://memorynotfound.com/beer">
<ns2:beer>
<ns2:id>1</ns2:id>
<ns2:name>La Chouffe</ns2:name>
</ns2:beer>
</ns2:getBeerResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Hi, I followed your tutorial and it worked fine, but there is something I’m trying to do but can’t get it to work.
I want to two methods like this:
@Action(“http://localhost/ws/getCountr”)
public void getCountr(@RequestPayload GetCountryRequest request) {
}
@Action(“http://localhost/ws/getCountry”)
@ResponsePayload
public GetCountryResponse getCountry(@RequestPayload GetCountryRequest request) {
}
I add two properties for the soapaction:
Properties soapActions = new Properties();
soapActions.setProperty(“getCountr”, “http://localhost/ws/getCountr”);
soapActions.setProperty(“getCountry”, “http://localhost/ws/getCountry”);
wsdl11Definition.setSoapActions(soapActions);
When I check the wsdl, the second method does not appear.
Can you please tell me what’s wrong?
Thank you!
Dude you are awesome. Small things matter. Thank you.
Dude you are awesome. Small things matter. Thank you.