Enterprise Java

Creating contract-first web services with Spring WS

1 Introduction

This article explains how to implement and test a SOAP web service using the
Spring Web Services project. This example uses JAXB2 for (un)marshalling. To develop the service, I’ll use the contract-first approach, which consists in definning the service contract first, and based on this contract implement the service.

The article is divided into the following sections:

  • 2 Explaining the application
  • 3 Implementing the service
  • 3.1 Creating the contract
  • 3.2 Generating Java classes
  • 3.3 Implementing the SOAP endpoint
  • 3.4 Configuring the application
  • 4 Testing the service
  • 5 Additional information
  • 5.1 Implementing the client
  • 5.2 How it works internally

2 Explaining the application

The example application processes orders. We’ve got a front controller (messageDispatcher servlet) that will handle order requests, invoke the service to process the order and return a result.

aplicacio

You can get the source code at github.

3 Implementing the service

3.1   Creating the contract

Since we will use the contract-first approach, the simplest way to create the contract is by first definning sample xml documents and from that, we will generate the contract using a tool. Below are the sample xml documents:

client-request.xml

<clientDataRequest xmlns="http://www.xpadro.spring.samples.com/orders"
    clientId="A-123"
    productId="C5FH"
    quantity="5" />

client-response.xml

<clientDataResponse xmlns="http://www.xpadro.spring.samples.com/orders" 
    confirmationId="7890B"
    orderDate="2013-09-22"
    amount="15.50" />

In order to create the schema, we can use Trang, which is an open source tool that will allow us to generate the xsd schema from the xml documents. I’ve included this library into the project build path (you can get this jar from Trang web site) and I’ve created an Ant task for executing the conversion:

generate-schema.xml

<project name="Ant-Generate-Schema-With-Trang" default="generate" basedir=".">
    <property name="src.dir" location="src" />
    <property name="trang.dir" location="lib" />
    <property name="source.dir" location="${src.dir}/main/webapp/WEB-INF/schemas/samples" />
    <property name="schema.dir" location="${src.dir}/main/webapp/WEB-INF/schemas/xsd" />
    
    <target name="generate" description="generates order schema">
        <delete dir="${schema.dir}" />
        <mkdir dir="${schema.dir}" />
        
        <java jar="${trang.dir}/trang.jar" fork="true">
            <arg value="${source.dir}/client-request.xml" />
            <arg value="${schema.dir}/client-request.xsd" />
        </java>
        
        <java jar="${trang.dir}/trang.jar" fork="true">
            <arg value="${source.dir}/client-response.xml" />
            <arg value="${schema.dir}/client-response.xsd" />
        </java>
    </target>
</project>

Once the Ant task is executed, it will generate the schemas. Since schemas have been automatically generated, it is possible that we need to make some modifications to adapt it to our needs. Let’s take a look:

client-request.xsd

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    elementFormDefault="qualified" 
    targetNamespace="http://www.xpadro.spring.samples.com/orders" 
    xmlns:orders="http://www.xpadro.spring.samples.com/orders">
    
    <xs:element name="clientDataRequest">
        <xs:complexType>
            <xs:attribute name="clientId" use="required" type="xs:NCName"/>
            <xs:attribute name="productId" use="required" type="xs:NCName"/>
            <xs:attribute name="quantity" use="required" type="xs:integer"/>
        </xs:complexType>
    </xs:element>
</xs:schema>

client-response.xsd

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    elementFormDefault="qualified" 
    targetNamespace="http://www.xpadro.spring.samples.com/orders" 
    xmlns:orders="http://www.xpadro.spring.samples.com/orders">
    
    <xs:element name="clientDataResponse">
        <xs:complexType>
            <xs:attribute name="amount" use="required" type="xs:decimal"/>
            <xs:attribute name="confirmationId" use="required" type="xs:NMTOKEN"/>
            <xs:attribute name="orderDate" use="required" type="xs:NMTOKEN"/>
        </xs:complexType>
    </xs:element>
</xs:schema>

We can add different validations to these schemas, but in this example I’ll just modify several types like clientId, productId and confirmationId (xs:string) and orderDate (xs:date). The mapping of XML data types to Java types is done by JAXB. You can check which are the mappings provided
here.

To finish with the schema, we will copy the response element into the request schema. I’ve created a third schema with both response and request:

client-service.xsd

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    elementFormDefault="qualified" targetNamespace="http://www.xpadro.spring.samples.com/orders"
    xmlns:orders="http://www.xpadro.spring.samples.com/orders">
    
    <xs:element name="clientDataRequest">
        <xs:complexType>
            <xs:attribute name="clientId" use="required" type="xs:string" />
            <xs:attribute name="productId" use="required" type="xs:string" />
            <xs:attribute name="quantity" use="required" type="xs:integer" />
        </xs:complexType>
    </xs:element>
    
    <xs:element name="clientDataResponse">
        <xs:complexType>
            <xs:attribute name="amount" use="required" type="xs:decimal" />
            <xs:attribute name="confirmationId" use="required" type="xs:string" />
            <xs:attribute name="orderDate" use="required" type="xs:date" />
        </xs:complexType>
    </xs:element>
</xs:schema>

The last step would consist in writting the contract, generally expressed as a WSDL file. If you don’t want to create it by hand, the Spring-ws project provides us with a way to generate this file from an XSD schema. We will use this second approach as you will see in the configuring the application section.

3.2 Generating Java classes

We will use JAXB2 to generate request and response objects. The XJC compiler from JAXB will be responsible of converting these objects from the XSD schema that we generated before. It will be executed as an Ant task:

<project name="Ant-Generate-Classes-With-JAXB2" default="generate" basedir=".">
    <property name="src.dir" location="src" />
    <property name="java.dir" location="src/main/java" />
    <property name="schema.dir" location="${src.dir}/main/webapp/WEB-INF/schemas/xsd" />
    
    <target name="generate">
        <exec executable="xjc">
            <arg line=" -d ${java.dir} -p xpadro.spring.ws.types ${schema.dir}/client-service.xsd" />
        </exec>
    </target>
</project>

This task will create Java classes in the xpadro.spring.ws.types package (you may need to refresh the project).

types

3.3 Implementing the SOAP endpoint

The endpoint receives the unmarshalled message payload and uses this data to invoke the order service. It will then return the service response, which will be marshalled by the endpoint adapter:

@Endpoint
public class OrderEndpoint {
    @Autowired
    private OrderService orderService;
    
    @PayloadRoot(localPart="clientDataRequest", namespace="http://www.xpadro.spring.samples.com/orders")
    public @ResponsePayload ClientDataResponse order(@RequestPayload ClientDataRequest orderData) {
        OrderConfirmation confirmation = 
            orderService.order(orderData.getClientId(), orderData.getProductId(), orderData.getQuantity().intValue());
        
        ClientDataResponse response = new ClientDataResponse();
        response.setConfirmationId(confirmation.getConfirmationId());
        BigDecimal amount = new BigDecimal(Float.toString(confirmation.getAmount()));
        response.setAmount(amount);
        response.setOrderDate(convertDate(confirmation.getOrderDate()));
        
        return response;
    }
    
    //date conversion
}

Here’s a brief description of the annotations used by the endpoint:

@Endpoint: Registers the class as a component. In this way, the class will be detected by component scan.

@PayloadRoot: Registers the endpoint method as a handler for a request. This annotation will define what type of request message can be handled by the method. In our example, it will receive messages where its payload root element has the same namespace as defined in the XSD schema we created, and its local name is the one defined for the request (clientDataRequest).

@RequestPayload: Indicates the payload of the request message to be passed as a parameter to the method.

@ResponsePayload, indicates that the return value is used as the payload of the response message.

3.4 Configuring the application

web.xml
Application configuration (like datasource, transactionManager…)

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:xpadro/spring/ws/config/root-config.xml</param-value>
</context-param>

Loads the application context

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

This is the servlet that will act as a Front Controller to handle all SOAP calls. Its function is to derive incoming XML messages to endpoints, much like the DispatcherServlet of Spring MVC.

<servlet>
    <servlet-name>orders</servlet-name>
    <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:xpadro/spring/ws/config/servlet-config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>orders</servlet-name>
    <url-pattern>/orders/*</url-pattern>
</servlet-mapping>

servlet-config.xml
This configuration contains web service infrastructure beans.

<!-- Detects @Endpoint since it is a specialization of @Component -->
<context:component-scan base-package="xpadro.spring.ws"/>

<!-- detects @PayloadRoot -->
<ws:annotation-driven/>

<ws:dynamic-wsdl id="orderDefinition" portTypeName="Orders" locationUri="http://localhost:8081/spring-ws">
    <ws:xsd location="/WEB-INF/schemas/xsd/client-service.xsd"/>
</ws:dynamic-wsdl>

In the dynamic wsdl, it doesn’t matter what value you put in the locationUri attribute because it will be handled by the MessageDispatcherServlet. Hence, the wsdl will be available at:

http://localhost:8081/spring-ws/orders/whatever/orderDefinition.wsdl

4 Testing the service

The following example creates a mock client which will access the web service:

@ContextConfiguration("classpath:xpadro/spring/ws/test/config/test-server-config.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class TestWebService {
    @Autowired
    ApplicationContext context;
    
    private MockWebServiceClient mockClient;
    
    @Test
    public void testValidOrderRequest() {
        Source requestPayload = new StringSource(
            "<clientDataRequest xmlns='http://www.xpadro.spring.samples.com/orders' " +
            "clientId='123' productId='XA-55' quantity='5'/>");
        
        Source responsePayload = new StringSource(
            "<clientDataResponse xmlns='http://www.xpadro.spring.samples.com/orders' " +
            "amount='55.99' confirmationId='GHKG34L' orderDate='2013-10-26+02:00'/>");
        
        RequestCreator creator = RequestCreators.withPayload(requestPayload);
        mockClient = MockWebServiceClient.createClient(context);
        mockClient.sendRequest(creator).andExpect(ResponseMatchers.payload(responsePayload));
    }
    
    @Test
    public void testInvalidOrderRequest() {
        Source requestPayload = new StringSource(
            "<clientDataRequest xmlns='http://www.xpadro.spring.samples.com/orders' " +
            "clientId='456' productId='XA-55' quantity='5'/>");
        
        Source responsePayload = new StringSource(
            "<SOAP-ENV:Fault xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/'>" +
            "<faultcode>SOAP-ENV:Server</faultcode><faultstring xml:lang='en'>Client [456] not found</faultstring></SOAP-ENV:Fault>");
        
        RequestCreator creator = RequestCreators.withPayload(requestPayload);
        mockClient = MockWebServiceClient.createClient(context);
        mockClient.sendRequest(creator).andExpect(ResponseMatchers.payload(responsePayload));
    }
}

The configuration file used on this test is pretty simple, just contains scanning of the service components:

<context:component-scan base-package="xpadro.spring.ws"/>
<ws:annotation-driven/>

5 Additional information

5.1 Implementing a client

To facilitate the client to access the web service, Spring provides us with the WebServiceTemplate class. This class contains methods for sending and receiving messages and it also uses converters to (un)marshal objects.

I’ve created a test that acts as a client of the service:

@ContextConfiguration("classpath:xpadro/spring/ws/test/config/client-config.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class TestClient {
    @Autowired 
    WebServiceTemplate wsTemplate;
    
    @Test
    public void invokeOrderService() throws Exception {
        ClientDataRequest request = new ClientDataRequest();
        request.setClientId("123");
        request.setProductId("XA-55");
        request.setQuantity(new BigInteger("5", 10));
        
        ClientDataResponse response = (ClientDataResponse) wsTemplate.marshalSendAndReceive(request);
        
        assertNotNull(response);
        assertEquals(new BigDecimal("55.99"), response.getAmount());
        assertEquals("GHKG34L", response.getConfirmationId());
    }
}

The configuration test file contains WebServiceTemplate configuration:

<oxm:jaxb2-marshaller id="marshaller" contextPath="xpadro.spring.ws.types"/>

<bean class="org.springframework.ws.client.core.WebServiceTemplate">
    <property name="marshaller" ref="marshaller" />
    <property name="unmarshaller" ref="marshaller" />
    <property name="defaultUri" value="http://localhost:8081/spring-ws/orders" /> 
</bean>

Just remember to start the server with the deployed web service application before executing this test.

5.2 How it works internally

If you just want to implement a web service, the article finished in the previous section. For those curious about how this really works, I will try to explain how a request is mapped to the endpoint, just a little more low-level than explained until this point.

When a request arrives to the MessageDispatcher, it relies on two components:

  1. It asks the EndpointMapping which is the appropriate endpoint.
  2. With the information received from the mapping it uses an endpoint adapter to invoke the endpoint. The adapter also support argument resolvers and return type handlers.

Endpoint mapping

MessageDispatcher contains a list of endpoint mappings, each of them, containing a map of previously registered method endpoints. In our case, the JAXB mapping PayloadRootAnnotationMethodEndpointMapping has registered all methods annotated with @PayloadRoot. If the qualified name of the payload of the message resolves as a registered method, it will be returned to the MessageDispatcher. If we didn’t annotate our method it would fail to process the request.
sequencia1
Endpoint adapter

MessageDispatcher will then ask each of its endpoint adapters if it supports the current request. In our case, the adapter checks if the following conditions are both true:

  • At least one of the arguments passed to the method is annotated with @RequestPayload
  • If the endpoint method returns a response, it must be annotated with @ResponsePayload

If an adapter is returned, it will then invoke the endpoint, unmarshalling the parameter before passing it to the method. When the method returns a response, the adapter will marshal it.

The following diagram is a much reduced version of this step in order to keep it simple:

sequencia2

Conclusion

We’ve seen an introduction on how to implement a simple web service and then test it. If you are interested, you can also take a look at how to test the client-side with MockWebServiceServer.

Xavier Padro

Xavier is a software developer working in a consulting firm based in Barcelona. He is specialized in web application development with experience in both frontend and backend. He is interested in everything related to Java and the Spring framework.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

7 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Marcos
Marcos
8 years ago

Don’t you have a source code to download?

Xavier Padró
8 years ago
Reply to  Marcos

Sure, you have a link to the source code at the end of section 2.

Regards

Andrea Ligios
Andrea Ligios
5 years ago
Reply to  Xavier Padró

Source code URL is not reflecting what seems to be a refactoring. The correct URL is https://github.com/xpadro/spring-integration/tree/master/webservices/spring-ws instead of https://github.com/xpadro/spring-integration/tree/master/spring-ws , please consider fixing it in the article

Sumit
Sumit
8 years ago

Nice Tutorial. Very easy to understand.
Please also provide tutorial for Spring – WS security.

Regards

Amit
Amit
8 years ago

Hi,
It’s very use full tutorial.

I have one doubt..

How to generate XSD from xml files using trang.jar.

I have found a way to do it through command line but i wanted to know how to do this in project i mean at very first time while building the project.

Sriram
Sriram
8 years ago

Nice article!!

Will this not work if I export a war file out of a dynamic web project created using eclipse ,with the required jar files made available in the projects build path?

I have tried the below steps and and exported a war file onto tomcat ; However when I tried to access the wsdl , it invariably gives a 404.

Praharaj
6 years ago

with respect to my development env I am getting the wsdl file
@http://localhost:8080/SprngSpWs/orders/rqst/orderDefinition.wsdl

but No endpoint mapping found for [SaajSoapMessage {http://localhost:8080/SprngSpWs/orders/rqst}clientDataRequest]

pls suggest.

Back to top button