© Adam L. Davis 2020
A. L. DavisSpring Quick Reference Guidehttps://doi.org/10.1007/978-1-4842-6144-6_10

10. Spring Web Services

Adam L. Davis1 
(1)
Oviedo, FL, USA
 

Spring Web Services (Spring WS) is focused on building contract-first SOAP web services, with flexible XML mappings, loose coupling between contract and implementation, and easy integration with Spring. It has an architecture similar to that of Spring MVC.

Features

Spring WS has the following features:
  • Powerful mappings – You can distribute incoming XML request to any object, depending on the message payload, SOAP Action header, or an XPath expression.

  • XML API support – Incoming XML messages can be handled in standard JAXP APIs such as DOM, SAX, and StAX, but also JDOM, dom4j, XOM, or even marshalling technologies.

  • Flexible XML marshalling – The Object/XML Mapping module in the Spring Web Services distribution supports JAXB 1 and 2, Castor, XMLBeans, JiBX, and XStream.

  • Supports WS-Security – WS-Security allows you to sign SOAP messages, encrypt and decrypt them, or authenticate against them.

  • Integrates with Spring Security – The WS-Security implementation of Spring Web Services provides integration with Spring Security.

Getting Started

To get started, add the following dependencies to your Maven pom file:
<dependencies>
    <dependency>
        <groupId>org.springframework.ws</groupId>
        <artifactId>spring-ws-core</artifactId>
        <version>3.0.9.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>jdom</groupId>
        <artifactId>jdom</artifactId>
        <version>2.0.2</version>
    </dependency>
    <dependency>
        <groupId>jaxen</groupId>
        <artifactId>jaxen</artifactId>
        <version>1.2.0</version>
    </dependency>
</dependencies>
Or if using Gradle, add the following:
implementation 'org.springframework.ws:spring-ws-core:3.0.9.RELEASE'
implementation 'org.jdom:jdom:2.0.2'
implementation 'jaxen:jaxen:1.2.0'

Use the @EnableWs annotation on a Java configuration class to enable the spring-ws to register the default EndpointMappings, EndpointAdapter, and EndpointExceptionResolver.

You will need to create a web.xml file like the following:
<web-app xmlns:="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
             http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
         version="2.4">
    <display-name>MyCompany Web Service</display-name>
    <servlet>
        <servlet-name>no-boot-spring-ws</servlet-name>
        <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet
        </servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>spring-ws</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>
Listing 10-1

WEB-INF/web.xml

Based on the name of the servlet, Spring will look for a corresponding Spring XML configuration file named <servlet_name>-servlet.xml. In this case, it will look for a WEB-INF/no-boot-spring-ws-servlet.xml file.

Spring Boot Config

To include Spring-WS in a Spring Boot Gradle project, add the following dependencies:
implementation 'org.springframework.boot:spring-boot-starter-web-services'
implementation 'org.jdom:jdom:2.0.2'
implementation 'jaxen:jaxen:1.2.0'
The Spring Boot WS starter (spring-boot-starter-web-services) will automatically do the following:
  • Configure a MessageDispatcherServlet in the servlet container

  • Scan all .wsdl and .xsd documents for WSDL and schema-defined beans

Contract First

Writing the contract first enables more features of the actual schema (such as limiting the allowed values of String values), allows for easier upgrading in the future, and allows for better interoperability with non-Java systems.

There are four different ways of defining such a contract for XML:
  • DTDs

  • XML Schema (XSD)

  • RELAX NG1

  • Schematron2

For this book, we will use the XML Schema for the Course domain. For example (assuming you want to use the namespace, "http://mycompany.com/schemas"), create a file named “my.xsd” and put it in the “src/main/resources” directory of your project with these contents:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           elementFormDefault="qualified"
           targetNamespace="http://mycompany.com/schemas"
           xmlns:my="http://mycompany.com/schemas">
    <xs:element name="Course">
        <xs:complexType>
            <xs:sequence>
                <xs:element ref="my:Number"/>
                <xs:element ref="my:Title"/>
                <xs:element ref="my:Subtitle"/>
                <xs:element ref="my:Description"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:element name="Number" type="xs:integer"/>
    <xs:element name="Title" type="xs:string"/>
    <xs:element name="Subtitle" type="xs:string"/>
    <xs:element name="Description" type="xs:string"/>
</xs:schema>

In Spring-WS, writing the WSDL by hand is not required. We will show how to generate the WSDL in a later section.

Writing the Endpoint

In Spring-WS, you will implement Endpoints to handle incoming XML messages. An endpoint is typically created by annotating a class with the @Endpoint annotation with one or more methods that handle incoming requests. The method signatures can be quite flexible: you can include just about any sort of parameter type related to the incoming XML message, as will be explained later.

Start by creating a class annotated with @Endpoint that will either be component scanned (@Endpoint marks it as a special @Component) or directly use Java configuration to configure it as a Spring Bean. Then add a method or methods that handle different elements of an XML request, for example:
import org.jdom2.*;
import org.jdom2.filter.Filters;
import org.jdom2.xpath.XPathExpression;
import org.jdom2.xpath.XPathFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;
@Endpoint
public class CourseEndpoint {
    private XPathExpression<Element> numberExpression;
    private XPathExpression<Element> titleExpression;
    private XPathExpression<Element> subtitleExpression;
    private XPathExpression<Element> descriptionExpression;
    @Autowired
    public CourseEndpoint() throws JDOMException {
        Namespace namespace = Namespace.getNamespace("my",
          "http://mycompany.com/my/schemas");               //1
        XPathFactory xPathFactory = XPathFactory.instance();
        numberExpression = xPathFactory.compile("//my:Number", Filters.element(), null, namespace);    //2
        titleExpression = xPathFactory.compile("//my:Title", Filters.element(), null, namespace);
        subtitleExpression = xPathFactory.compile("//my:Subtitle", Filters.element(), null, namespace);
        descriptionExpression = xPathFactory.compile("//my:Description", Filters.element(), null, namespace);
    }
    @PayloadRoot(namespace = "http://mycompany.com/my/schemas",
                              localPart = "CourseRequest")  //3
    public void handleRequest(@RequestPayload Element courseRequest) throws Exception {
        Long number = Long.parseLong(numberExpression.evaluateFirst(courseRequest).getText());
        String description = descriptionExpression.evaluateFirst(courseRequest).getText();
        String fullTitle = titleExpression.evaluateFirst(courseRequest).getText() + ":"
                + subtitleExpression.evaluateFirst(courseRequest).getText();
        // handleCourse(number, fullTitle, description)
    }
}
  1. 1.

    Since we are using JDOM2, we define the Namespace to be used in the Xpath definitions.

     
  2. 2.

    We define the XPathExpression instances we will use later to evaluate parts of the XML payload.

     
  3. 3.

    We use @PayloadRoot to define the namespace and element of the SOAP payload we want to match with this method. The @RequestPayload annotation on the Element parameter gets injected with the matched payload which we can then process in this method.

     

Generating the WSDL

Here is how we define WSDL generation within XML configuration:
<sws:dynamic-wsdl id="courses"
    portTypeName="CourseResource"
    locationUri="/courseService/"
    targetNamespace="http://mycompany.com/definitions">
  <sws:xsd location="/WEB-INF/my.xsd"/>
</sws:dynamic-wsdl>
  1. 1.

    First, the id determines the name of the wsdl resource (courses.wsdl) in this case.

     
  2. 2.

    The portTypeName determines the name of the WSDL port type.

     
  3. 3.

    The locationUri describes the relative location of the service itself.

     
  4. 4.

    The targetNamespace is optional, but defines the namespace within the WSDL itself.

     

EndpointMappings and EndpointExceptionResolvers

Spring-WS (through the WsConfigurationSupport class) registers the following EndpointMappings by default:
  • PayloadRootAnnotationMethodEndpointMapping ordered at 0 for mapping requests to @PayloadRoot annotated controller methods

  • SoapActionAnnotationMethodEndpointMapping ordered at 1 for mapping requests to @SoapAction annotated controller methods

  • AnnotationActionEndpointMapping ordered at 2 for mapping requests to @Action annotated controller methods

It also registers one EndpointAdapter, the DefaultMethodEndpointAdapter, for processing requests with annotated endpoint methods and the following EndpointExceptionResolvers:
  • SoapFaultAnnotationExceptionResolver for handling exceptions annotated with @SoapFault

  • SimpleSoapExceptionResolver for creating default exceptions

Customizing

You can customize the Spring-WS configuration by implementing the WsConfigurer interface or by extending the WsConfigurerAdapter base class and overriding individual methods, for example:
@Configuration
@EnableWs
@ComponentScan
public class CustomWsConfiguration extends WsConfigurerAdapter {
    @Override
    public void addInterceptors(List<EndpointInterceptor> interceptors)  {
        interceptors.add(new MyInterceptor());
    }
    @Override
    public void addArgumentResolvers(
         List<MethodArgumentResolver> argumentResolvers) {
            argumentResolvers.add(myArgumentResolver());
    }
    @Bean
    public MethodArgumentResolver myArgumentResolver() {
        return new MyArgumentResolver();
    }
 }
Listing 10-2

CustomWsConfiguration.java

WsConfigurerAdapter methods available to override:

void addArgumentResolvers(

List<MethodArgumentResolver> argumentResolvers)

Adds resolvers to support custom endpoint method argument types.

void addInterceptors(

List<EndpointInterceptor> interceptors)

Adds EndpointInterceptors for pre- and postprocessing of endpoint method invocations.

void addReturnValueHandlers(

List<MethodReturnValueHandler> returnValueHandlers)

Adds handlers to support custom controller method return value types.

EndpointInterceptor

The EndpointInterceptor interface has methods that are called for the request, response, faults, and after completion and has the ability to clear the response, modify the response, give a completely different response, or halt processing.

void afterCompletion(

MessageContext messageContext, Object endpoint, Exception ex)

Callback after completion of request and response (or fault if any) processing.

boolean handleFault(

MessageContext messageContext, Object endpoint)

Processes the outgoing response fault.

boolean handleRequest(

MessageContext messageContext, Object endpoint)

Processes the incoming request message.

boolean handleResponse(

MessageContext messageContext, Object endpoint)

Processes the outgoing response message.

../images/498572_1_En_10_Chapter/498572_1_En_10_Figa_HTML.jpg Each “handle” method is called as a chain, and the return value determines if processing should stop. True indicates to continue processing; false indicates to block processing at this point. If a handleRequest method returns false from any EndpointInterceptor, the endpoint itself will not be processed.