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.
Table of Contents
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.
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:
- https://blog.smartbear.com/apis/understanding-soap-and-rest-basics/
- http://mh-journal.blogspot.in/2015/08/generating-xsds-for-xml-interfaces.html
- https://www.getpostman.com/docs/postman/sending_api_requests/making_soap_requests
8. Download the Source Code
You can download the full source code of this example here: surcharge.zip