Spring WS-Addressing Delegate Response
Previously, we saw how to create spring ws-addressing soap client and server. Next we dive deeper and use another cool feature of WS-Addressing. WS-Addressing specifies a transport-neutral routing mechanism. It is based on a To and Action SOAP header, which indicates the destination and intent of the SOAP message. Additionally, WS-Addressing allows you to define a return address (either successful responses or fault messages). All messages have a unique id which can be used for correlation.
Spring WS-Addressing Server
The @Action
annotation maps the incoming request to the method if the soapAction property corresponds. For simplicity this returns a simple POJO and throws a IllegalArgumentException
when the id property is 0.
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) {
if (request.getId() == 0){
throw new IllegalArgumentException("id cannot be 0");
}
GetBeerResponse response = new GetBeerResponse();
Beer beer = new Beer();
beer.setId(request.getId());
beer.setName("La Chouffe");
response.setBeer(beer);
return response;
}
}
The ResponseServlet
will handle all successful responses delegated from the replyTo URI.
package com.memorynotfound.server;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class ResponseServlet extends HttpServlet {
private static final Log logger = LogFactory.getLog(ResponseServlet.class);
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
logger.info("handling web service response");
}
}
The FaultServlet
will handle all faults delegated from the faultTo URI.
package com.memorynotfound.server;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class FaultServlet extends HttpServlet {
private static final Log logger = LogFactory.getLog(FaultServlet.class);
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
logger.info("handling web service fault");
}
}
To register the previous servlets we register them using the ServletRegistrationBean
and map the servlet with their corresponding URI Segment. Next, we create a AnnotationActionEndpointMapping
and set the message sender for handling the out of bound requests. This message sender will handle the delegation of the outgoing responses.
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.soap.addressing.server.AnnotationActionEndpointMapping;
import org.springframework.ws.transport.http.HttpComponentsMessageSender;
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 responseServlet(){
ResponseServlet servlet = new ResponseServlet();
return new ServletRegistrationBean(servlet, "/response");
}
@Bean
public ServletRegistrationBean faultServlet(){
FaultServlet servlet = new FaultServlet();
return new ServletRegistrationBean(servlet, "/fault");
}
@Bean
public AnnotationActionEndpointMapping annotationActionEndpointMapping(){
AnnotationActionEndpointMapping mapping = new AnnotationActionEndpointMapping();
mapping.setMessageSender(new HttpComponentsMessageSender());
return mapping;
}
@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 soap action 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. So now the server is configured, lets take a look at the client configuration up next.
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-Addressing Client ReplyTo and FaultTo
Previously, we created and configured the server to handle WS-Addressing requests. Next, we configure the client. We start with creating a client by extending from the WebServiceGateWaySupport
class. This gives us easy access to the WebServiceTemplate
instance. We can delegate the successful response or fault response using the ActionCallback
. First we create an instance by passing the soapAction in the constructor. Next we can set the replyTo and faultTo properties by providing a EndpointReference
with the URI location of the delegate.
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 org.springframework.ws.soap.addressing.core.EndpointReference;
import java.net.URI;
import java.net.URISyntaxException;
public class BeerClient extends WebServiceGatewaySupport {
public void sendGetBeerRequest(Integer id) throws URISyntaxException {
GetBeerRequest request = new GetBeerRequest();
request.setId(id);
ActionCallback callback = new ActionCallback(
new URI("https://memorynotfound.com/getBeerRequest"));
callback.setReplyTo(new EndpointReference(
new URI("http://localhost:8080/response")));
callback.setFaultTo(new EndpointReference(
new URI("http://localhost:8080/fault")));
getWebServiceTemplate().marshalSendAndReceive(request, callback);
}
}
To configure the client we must create a marshaller to marshall the incoming requests and outgoing response. Finally, create and initialize the client.
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;
}
}
In this example we create a simple request. The first one will be successful and delegated to the replyTo handler. The next one produces a fault and will be delegated to the faultTo handler.
package com.memorynotfound.client;
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);
client.sendGetBeerRequest(1);
System.out.println("getBeerRequest handled by replyTo");
client.sendGetBeerRequest(0);
System.out.println("getBeerRequest handled by faultTo");
}
}