spring

Spring SOAP with XML Example

Web services are the distributed computing mechanism designed to work with open standards and protocols, thus facilitating interoperability among disparate systems. Simple Object Access Protocol (SOAP) was developed by Microsoft as an improvement over the existing technologies like Distributed Component Object Model (DCOM), Common Object Request Broker Architecture (CORBA) and Java Remote Method Invocation (RMI) that relied on binary and proprietary formats. A SOAP message chiefly uses the eXtensible Markup Language (XML) for the message format, HyperText Transfer Protocol for the transport protocol and the interface definition in a file using the Web Services Definition Language (WSDL). Other formats and protocols can also be used with SOAP. In summary, SOAP is platform-independent, has built-in error handling and is extensible providing support for security, federation, reliability and other aspects.

1. Introduction

In this article, we will show how to implement a SOAP web service with XML request and response in a Spring Boot application. Using Spring framework’s Dependency Injection and annotation based support in conjunction with the maven plugin jaxb2-maven-plugin, the whole process of extracting XML from the SOAP input, mapping to Java objects and returning the SOAP response becomes very easy so that developers can focus on implementing business logic and unit testing the application.

2. Application

The application we will develop is a web service for calculating the surcharge to be applied to an order. An order has the fields id, value, payment_method and a customer. The fields of customer are name (“first”, “last”) and an address, which in turn has the fields street (“line” which can take values 1 and 2), city, postal code and country.

The surcharge is calculated as a percentage. If the customer’s country is “US”, surcharge is 10%, otherwise it is 15%. If payment method is credit card, indicated by CC, the surcharge will be increased by 3%. If the order value is greater than 1000, a discount of 1% will be given. A discount is a negative surcharge and since the calculation is very simple, it is kept in this service. In real world applications, discounting will be a service of its own. Since all surcharges for this application are in percentages, we will not consider the % symbol either in the input or output and will be returned as an int.

3. Environment

I have used the following technologies for this application:

  • Java 1.8
  • Spring Boot 1.5.9
  • JAXB2 Maven plugin 1.6
  • Maven 3.3.9
  • Ubuntu 16.04 LTS

4. Source Code

Typically you would get a sample XML file from your business analyst or client showing all the elements and data used to send as input to the web service. The following snippet shows a sample order in XML format.

sample1.xml

<?xml version="1.0" encoding="UTF-8"?>
<order>
    <id>S12345</id>
    <value>1250</value>
    <payment_method>CC</payment_method>
    <customer>
        <name part="first">Nancy</name>
        <name part="last">Smith</name>
        <address>
            <street line="1">41 Earnest Road</street>
            <street line="2">Rotamonte Park</street>
            <city>Koregon</city>
            <postalcode>12345</postalcode>
            <country>UK</country>
        </address>
    </customer>
</order>

The first step in implementing the SOAP web service is to create an XML schema definition file. We will name it surcharge.xsd and it can be either hand written or generated with a plugin. I have used the online tool xmlgrid.net/xml2xsd.html. The generated xsd shows a few errors in Eclipse, which had to be corrected. For example, the error for name element is:

Element 'name' has both a 'type' attribute and a 'anonymous type' child. Only one of these is allowed for an element.

The generated XML is:

<xs:element name="name" maxOccurs="unbounded" type="xs:string">
    <xs:complexType>
        <xs:attribute name="part" type="xs:string"></xs:attribute>
    </xs:complexType>
</xs:element>

It has to be corrected to

<xs:element maxOccurs="2" name="name">
    <xs:complexType>
        <xs:simpleContent>
            <xs:extension base="xs:string">
                <xs:attribute name="part" type="xs:string" />
            </xs:extension>
        </xs:simpleContent>
    </xs:complexType>
</xs:element>

In general, the xsd file has two patterns to be applied:
1) element – complexType – sequence – element – complexType – sequence – element / element
2) element – complexType – simpleContent – extension – attribute

Your xsd file will be a combination of these two patterns to cater to any nested XML data. The final corrected xsd file including the response element is shown below:

surcharge.xsd

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.javacodegeeks.org/webservices/soap/surcharge/generated"
xmlns:tns="http://www.javacodegeeks.org/webservices/soap/surcharge/generated"
elementFormDefault="qualified">

    <xs:element name="SurchargeRequest">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="order">
                    <xs:complexType>
                        <xs:sequence>
                            <xs:element name="id" type="xs:string"></xs:element>
                            <xs:element name="value" type="xs:int"></xs:element>
                            <xs:element name="payment_method" type="xs:string"></xs:element>
                            <xs:element name="customer">
                                <xs:complexType>
                                    <xs:sequence>
                                        <xs:element maxOccurs="2" name="name">
                                            <xs:complexType>
                                                <xs:simpleContent>
                                                    <xs:extension base="xs:string">
                                                        <xs:attribute name="part" type="xs:string" />
                                                    </xs:extension>
                                                </xs:simpleContent>
                                            </xs:complexType>
                                        </xs:element>
                                        <xs:element name="address">
                                            <xs:complexType>
                                                <xs:sequence>
                                                    <xs:element maxOccurs="2" name="street">
                                                        <xs:complexType>
                                                            <xs:simpleContent>
                                                                <xs:extension base="xs:string">
                                                                    <xs:attribute name="line" type="xs:string" />
                                                                </xs:extension>
                                                            </xs:simpleContent>
                                                        </xs:complexType>
                                                    </xs:element>
                                                    <xs:element name="city" type="xs:string"></xs:element>
                                                    <xs:element name="postalcode" type="xs:int"></xs:element>
                                                    <xs:element name="country" type="xs:string"></xs:element>
                                                </xs:sequence>
                                            </xs:complexType>
                                        </xs:element>
                                    </xs:sequence>
                                </xs:complexType>
                            </xs:element>
                        </xs:sequence>
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:element name="SurchargeResponse">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="surcharge" type="xs:int" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>

</xs:schema>

The target namespace is specified as “http://www.javacodegeeks.org/webservices/soap/surcharge/generated”. With this, a package called org.javacodegeeks.webservices.soap.surcharge.generated will be created during compile time and the classes generated from the xsd file will be placed there.

Since this is a maven-based project, all the project-level settings and dependencies are given in pom.xml file.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.javacodegeeks.webservices.soap</groupId>
    <artifactId>surcharge</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>surcharge</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.9.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web-services</artifactId>
        </dependency>
        <dependency>
            <groupId>wsdl4j</groupId>
            <artifactId>wsdl4j</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
         </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
             </plugin>
             <plugin>
                 <groupId>org.codehaus.mojo</groupId>
                 <artifactId>jaxb2-maven-plugin</artifactId>
                 <version>1.6</version>
                 <executions>
                     <execution>
                         <id>xjc</id>
                         <goals>
                             <goal>xjc</goal>
                         </goals>
                     </execution>
                 </executions>
                 <configuration>
                     <clearOutputDir>false</clearOutputDir>
                     <schemaDirectory>${project.basedir}/src/main/resources</schemaDirectory>
                     <outputDirectory>${project.basedir}/src/main/java</outputDirectory>
                 </configuration>
             </plugin>
         </plugins>
    </build>
</project>

The configuration for jaxb2-maven-plugin specifies that the output directory should not cleared when the classes are generated, the schema file is located in src/main/resources directory and that the output directory where the package should be created is src/main/java.

SurchargeApplication.java

package org.javacodegeeks.webservices.soap.surcharge;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SurchargeApplication {

	public static void main(String[] args) {
		SpringApplication.run(SurchargeApplication.class, args);
		
		System.out.println("SurchargeApplication now running....");
	}
}

This is the main class of the application that runs on the default Tomcat container of Spring Boot at port 8080.

SoapConfig.java

package org.javacodegeeks.webservices.soap.surcharge.config;

import org.springframework.boot.web.servlet.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.transport.http.MessageDispatcherServlet;
import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition;
import org.springframework.xml.xsd.SimpleXsdSchema;
import org.springframework.xml.xsd.XsdSchema;

@EnableWs
@Configuration
public class SoapConfig {
	@Bean
	public XsdSchema surchargeSchema() {
		return new SimpleXsdSchema(new ClassPathResource("surcharge.xsd"));
	}
	
	@Bean(name = "surcharge")
	public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema surchargeSchema) {
		DefaultWsdl11Definition definition = new DefaultWsdl11Definition();
		definition.setPortTypeName("SurchargePort");
		definition.setTargetNamespace("http://www.javacodegeeks.org/webservices/soap/surcharge/generated");
		definition.setLocationUri("/ws");
		definition.setSchema(surchargeSchema);
		return definition;
	}
	
	@Bean
	public ServletRegistrationBean messageDispatcherServlet(ApplicationContext context) {
		MessageDispatcherServlet messageDispatcherServlet = new MessageDispatcherServlet();
		messageDispatcherServlet.setApplicationContext(context);
		messageDispatcherServlet.setTransformWsdlLocations(true);
		return new ServletRegistrationBean(messageDispatcherServlet, "/ws/*");
	}

}

This class sets up the configures all the beans required as the application infrastructure. @EnableWS enables all the Spring Java web services configuration defined in WsConfigurationSupport class to be imported to our application configuration. It provides facilities for endpoint mapping to annotated controllers, adapters and exception handlers.

First, in the surchargeSchema method, the surcharge.xsd file is used to configure a SimpleXsdSchema bean. Next, in the defaultWsdl11Definition method, this bean set as the schema in a DefaultWsdl11Definition with the name surcharge. Last, in the messageDispatcherServlet method, a MessageDispatcherServlet is created to take the ApplicationContext and then used to create a ServletRegistrationBean to handle the URI path /ws.

SurchargeEndpoint.java

package org.javacodegeeks.webservices.soap.surcharge.endpoint;

import org.javacodegeeks.webservices.soap.surcharge.generated.SurchargeRequest;
import org.javacodegeeks.webservices.soap.surcharge.generated.SurchargeResponse;
import org.javacodegeeks.webservices.soap.surcharge.service.SurchargeService;
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;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;

@Endpoint
public class SurchargeEndpoint {

	@Autowired
	SurchargeService service;

	@PayloadRoot(namespace = "http://www.javacodegeeks.org/webservices/soap/surcharge/generated", localPart = "SurchargeRequest")
	@ResponsePayload
	public SurchargeResponse processCourseDetailsRequest(@RequestPayload SurchargeRequest request) {

		SurchargeResponse response = new SurchargeResponse();

		int surcharge = service.calculateSurcharge(request.getOrder());
		response.setSurcharge(surcharge);

		return response;
	}

}

This class has a SurchargeService bean auto-wired. With the @PayloadRoot annotation, we mark the method processCourseDetailsRequest as the handler for the incoming request. The @ResponsePayload annotation marks that the return value should be bound to the response payload.

In the processCourseDetailsRequest method, a SurchargeResponse object is instantiated. The service bean’s calculateSurcharge method is called passing in the Order object from the SurchargeRequest input. The calculated surcharge returned from the call is set into the SurchargeResponse object that is then returned.

SurchargeService.java

package org.javacodegeeks.webservices.soap.surcharge.service;

import org.javacodegeeks.webservices.soap.surcharge.generated.SurchargeRequest.Order;
import org.springframework.stereotype.Component;

@Component
public class SurchargeService {
	
	public int calculateSurcharge(Order order) {
		
		int surcharge = 15;
		if (order.getCustomer().getAddress().getCountry().equals("US"))
			surcharge = 10;

		if (order.getPaymentMethod().equals("CC"))
			surcharge += 3;

		if (order.getValue() > 1000)
			surcharge -= 1;

		return surcharge;
	}
}

This class has a method calculateSurcharge that implements the business logic of calculating the surcharge for a given order, as given in Section 2 above.

5. How To Run and Test

In a terminal window go to the project root folder and enter

mvn spring-boot:run

There are many client applications like SoapUI or Wizdler that you can use to invoke this service. I have used Postman app. The test request data is formed by just a few changes to the sample XML file we started with.

test1.xml

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
 xmlns="http://schemas.xmlsoap.org/soap/envelope/">
    <soapenv:Body>
        <SurchargeRequest xmlns="http://www.javacodegeeks.org/webservices/soap/surcharge/generated">
            <order>
                <id>S12345</id>
                <value>1250</value>
                <payment_method>CC</payment_method>
                <customer>
                    <name part="first">Nancy</name>
                    <name part="last">Smith</name>
                    <address>
                        <street line="1">41 Earnest Road</street>
                        <street line="2">Rotamonte Park</street>
                        <city>Koregon</city>
                        <postalcode>12345</postalcode>
                        <country>UK</country>
                    </address>
                </customer>
            </order>
        </SurchargeRequest>
    </soapenv:Body>
</soapenv:Envelope>

Given below is the screen shot of Postman showing the input soap XML and the response from the web service.

Output showing sample SOAP Request and Response of SurchargeService.

Since the order country is not “US”, payment method is “CC”, and the value is greater than 1000, the surcharge calculated is 15 + 3 -1 = 17, which is returned in the response.

6. Summary

In this article, we have seen how to use Spring framework in conjunction with maven plugin to implement a SOAP web service that take XML request and returns an XML response. We have also seen how to start from a sample XML to generate the XML schema definition and from there use the maven plugin and Spring annotations to transform the XML input to Java objects that are then used to execute the business logic.

7. Useful Links

Following resources will be very useful to get additional information and insights on concepts discussed in this article:

8. Download the Source Code

Download
You can download the full source code of this example here: surcharge.zip

Mahboob Hussain

Mahboob Hussain graduated in Engineering from NIT Nagpur, India and has an MBA from Webster University, USA. He has executed roles in various aspects of software development and technical governance. He started with FORTRAN and has programmed in a variety of languages in his career, the mainstay of which has been Java. He is an associate editor in our team and has his personal homepage at http://bit.ly/mahboob
Subscribe
Notify of
guest

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

0 Comments
Inline Feedbacks
View all comments
Back to top button