This tutorial will show you how we can create and publish SOAP based webservice in Contract-first approach using Spring and Gradle. There are mainly two approaches to create the Webservice – Contract-first & Contract-last. The Contract-first approach tells us to create first XSD/WSDL and then create end-point interface and implementation class. The Contract-last approach tells us to create first end-point interface and implementation class then create WSDL file. This example will show you mainly Spring SOAP Webservice Producers using Gradle, i.e., it will only publish or deploy the web service into the server.

Prerequisites

Java at least version 8 needs to be installed and configured
Gradle plugin needs to be installed into Eclipse
Gradle 4.x needs to installed and configured
Dependencies : Spring boot, wsdl

Creating and setting up Gradle project

Create gradle project called SpringBootSoapGradle using the following gradle dependencies

buildscript {
	ext {
		springBootVersion = '1.5.9.RELEASE'
	}

    repositories {
        mavenCentral()
    }

    dependencies {
        classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
    }
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'

repositories {
    mavenCentral()
}

sourceCompatibility = 1.8
targetCompatibility = 1.8

sourceSets.main.java.srcDirs "src/generated-sources/java"
sourceSets.main.resources.excludes = ['user.xsd']

configurations {
    jaxb
}

dependencies {
	compile("org.springframework.boot:spring-boot-starter-web-services")
	compile("wsdl4j:wsdl4j:1.6.2")
    jaxb (
		'com.sun.xml.bind:jaxb-xjc:2.2.7',
		'com.sun.xml.bind:jaxb-impl:2.2.7'
    )
}

task jaxb {
    System.setProperty('javax.xml.accessExternalSchema', 'all')
    def jaxbTargetDir = file("src/generated-sources/java")

    doLast {
        jaxbTargetDir.mkdirs()

        ant.taskdef(
			name: 'xjc',
			classname: 'com.sun.tools.xjc.XJCTask',
			classpath: configurations.jaxb.asPath
        )

        ant.jaxbTargetDir = jaxbTargetDir

        ant.xjc(
			destdir: '${jaxbTargetDir}',
			package: 'com.jeejava.jaxb',
			schema: 'src/main/resources/xsd/user.xsd'
        )
    }
}

compileJava.dependsOn jaxb

Create main class

package com.jeejava.main;

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

@SpringBootApplication(scanBasePackages = "com.jeejava")
public class Application {
	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

You should be able to build the blank project. Please ensure that the overall state is “BUILD SUCCESS” before continuing.

Note: You won’t be able to import Spring Boot dependencies until your project downloads all dependencies. So first create main class with empty main method and later when your project is successfully built then you can import required dependencies.

Execute command – gradle clean build on the project root directory from cmd prompt.

You will see the required jar files get downloaded and finally you would get “BUILD SUCCESSFUL” message.

Create an XSD (schema definition file) under src/main/resources/xsd/user.xsd

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
	xmlns:tns="https://www.jeejava.com/UserService" targetNamespace="https://www.jeejava.com/UserService"
	elementFormDefault="qualified">

	<xs:element name="getUserDetailsRequest">
		<xs:complexType>
			<xs:sequence>
				<xs:element name="name" type="xs:string" />
			</xs:sequence>
		</xs:complexType>
	</xs:element>

	<xs:element name="getUserDetailsResponse">
		<xs:complexType>
			<xs:sequence>
				<xs:element name="users" type="tns:user" minOccurs="0"
					maxOccurs="unbounded" />
			</xs:sequence>
		</xs:complexType>
	</xs:element>

	<xs:complexType name="user">
		<xs:sequence>
			<xs:element name="id" type="xs:int" />
			<xs:element name="name" type="xs:string" />
			<xs:element name="email" type="xs:string" />
			<xs:element name="address" type="tns:address" />
		</xs:sequence>
	</xs:complexType>

	<xs:complexType name="address">
		<xs:sequence>
			<xs:element name="street" type="xs:string" />
			<xs:element name="city" type="xs:string" />
			<xs:element name="state" type="xs:string" />
			<xs:element name="zip" type="xs:int" />
			<xs:element name="country" type="xs:string" />
			<xs:element name="addressType" type="tns:addressType" />
		</xs:sequence>
	</xs:complexType>

	<xs:simpleType name="addressType">
		<xs:restriction base="xs:string">
			<xs:enumeration value="PERMANENT" />
			<xs:enumeration value="COMMUNICATION" />
			<xs:enumeration value="OFFICIAL" />
		</xs:restriction>
	</xs:simpleType>
</xs:schema>

In the above XSD we have basically two main elements – getUserDetailsRequest and getUserDetailsResponse. So getUserDetailsRequest will act as an input and getUserDetailsResponse will act as an output based on input.

Generate domain classes based on an XML schema

The next step is to generate Java classes from the XSD file. The right approach is do this automatically during build time using a gradle plugin. For this you need to write below code(which is already there in the above build file) into build.gradle file. As gradle does not have a JAXB plugin (yet), it involves an ant task, which makes it a bit more complex than in maven.

sourceSets.main.java.srcDirs "src/generated-sources/java"
sourceSets.main.resources.excludes = ['user.xsd']

configurations {
    jaxb
}

dependencies {
	compile("org.springframework.boot:spring-boot-starter-web-services")
	compile("wsdl4j:wsdl4j:1.6.2")
    jaxb (
		'com.sun.xml.bind:jaxb-xjc:2.2.7',
		'com.sun.xml.bind:jaxb-impl:2.2.7'
    )
}

task jaxb {
    System.setProperty('javax.xml.accessExternalSchema', 'all')
    def jaxbTargetDir = file("src/generated-sources/java")

    doLast {
        jaxbTargetDir.mkdirs()

        ant.taskdef(
			name: 'xjc',
			classname: 'com.sun.tools.xjc.XJCTask',
			classpath: configurations.jaxb.asPath
        )

        ant.jaxbTargetDir = jaxbTargetDir

        ant.xjc(
			destdir: '${jaxbTargetDir}',
			package: 'com.jeejava.jaxb',
			schema: 'src/main/resources/xsd/user.xsd'
        )
    }
}

compileJava.dependsOn jaxb

Create user repository

In order to provide data to the web service, create a user repository. In this guide we create a dummy user repository implementation with dummy data.

package com.jeejava.repository;

import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Component;

import com.jeejava.jaxb.Address;
import com.jeejava.jaxb.AddressType;
import com.jeejava.jaxb.User;

@Component
public class UserRepository {

	private List<User> users = new ArrayList<User>();

	public UserRepository() {
		User u1 = new User();
		u1.setId(1);
		u1.setName("Sumit Ghosh");
		u1.setEmail("sumit.ghosh@email.com");
		Address a1 = new Address();
		a1.setStreet("Garfa");
		a1.setCity("Kolkata");
		a1.setState("WB");
		a1.setCountry("India");
		a1.setZip(700030);
		a1.setAddressType(AddressType.COMMUNICATION);
		u1.setAddress(a1);

		User u2 = new User();
		u2.setId(2);
		u2.setName("Loku Poddar");
		u2.setEmail("debabrata.poddar@email.com");
		Address a2 = new Address();
		a2.setStreet("Birati");
		a2.setCity("Kolkata");
		a2.setState("WB");
		a2.setCountry("India");
		a2.setZip(700130);
		a2.setAddressType(AddressType.COMMUNICATION);
		u2.setAddress(a2);

		User u3 = new User();
		u3.setId(3);
		u3.setName("Souvik Sanyal");
		u3.setEmail("souvik.sanyal@email.com");
		Address a3 = new Address();
		a3.setStreet("Kalighat");
		a3.setCity("Kolkata");
		a3.setState("WB");
		a3.setCountry("India");
		a3.setZip(700150);
		a3.setAddressType(AddressType.COMMUNICATION);
		u3.setAddress(a3);

		User u4 = new User();
		u4.setId(4);
		u4.setName("Liton Sarkar");
		u4.setEmail("liton.sarkar@email.com");
		Address a4 = new Address();
		a4.setStreet("Sukanta Nagar");
		a4.setCity("Kolkata");
		a4.setState("WB");
		a4.setCountry("India");
		a4.setZip(700098);
		a4.setAddressType(AddressType.COMMUNICATION);
		u4.setAddress(a4);

		User u5 = new User();
		u5.setId(5);
		u5.setName("Rushikesh Mukund Fanse");
		u5.setEmail("rushikesh.fanse@email.com");
		Address a5 = new Address();
		a5.setStreet("Nasik");
		a5.setCity("Mumbai");
		a5.setState("MH");
		a5.setCountry("India");
		a5.setZip(400091);
		a5.setAddressType(AddressType.COMMUNICATION);
		u5.setAddress(a5);

		users.add(u1);
		users.add(u2);
		users.add(u3);
		users.add(u4);
		users.add(u5);
	}

	public List<User> getUsers(String name) {
		List<User> userList = new ArrayList<>();
		for (User user : users) {
			if (user.getName().toLowerCase().contains(name.toLowerCase())) {
				userList.add(user);
			}
		}
		return userList;
	}

}

Create user service endpoint

To create a service endpoint, we only need a POJO with a few Spring WS annotations to handle the incoming SOAP requests.

package com.jeejava.soap;

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;

import com.jeejava.jaxb.GetUserDetailsRequest;
import com.jeejava.jaxb.GetUserDetailsResponse;
import com.jeejava.repository.UserRepository;

@Endpoint
public class UserServiceEndpoint {

	private final String NAMESPACE = "https://www.jeejava.com/UserService";

	@Autowired
	private UserRepository userRepository;

	@PayloadRoot(namespace = NAMESPACE, localPart = "getUserDetailsRequest")
	@ResponsePayload
	public GetUserDetailsResponse getUser(@RequestPayload final GetUserDetailsRequest request) {
		GetUserDetailsResponse response = new GetUserDetailsResponse();
		response.getUsers().addAll(userRepository.getUsers(request.getName()));
		return response;
	}

}

@Endpoint registers the class with Spring WS as a potential candidate for processing incoming SOAP messages.

@PayloadRoot is then used by Spring WS to pick the handler method based on the message’s namespace and localPart.

@RequestPayload indicates that the incoming message will be mapped to the method’s request parameter.

The @ResponsePayload annotation makes Spring WS map the returned value to the response payload.

Configure web service beans

Create a new class with Spring WS related beans configuration:

package com.jeejava.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.config.annotation.WsConfigurerAdapter;
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 WebServiceConfig extends WsConfigurerAdapter {

	@Bean
	public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
		MessageDispatcherServlet servlet = new MessageDispatcherServlet();
		servlet.setApplicationContext(applicationContext);
		servlet.setTransformWsdlLocations(true);
		return new ServletRegistrationBean(servlet, "/ws/*");
	}

	@Bean(name = "users")
	public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema countriesSchema) {
		DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
		wsdl11Definition.setPortTypeName("UserPort");
		wsdl11Definition.setLocationUri("/ws");
		wsdl11Definition.setTargetNamespace("https://www.jeejava.com/UserService");
		wsdl11Definition.setSchema(countriesSchema);
		return wsdl11Definition;
	}

	@Bean
	public XsdSchema helloSchema() {
		return new SimpleXsdSchema(new ClassPathResource("xsd/user.xsd"));
	}
}

Spring WS uses a different servlet type for handling SOAP messages: MessageDispatcherServlet. It is important to inject and set ApplicationContext to MessageDispatcherServlet. Without that, Spring WS will not detect Spring beans automatically.

By naming this bean messageDispatcherServlet, it does not replace Spring Boot’s default DispatcherServlet bean.

DefaultMethodEndpointAdapter configures annotation driven Spring WS programming model. This makes it possible to use the various annotations like @Endpoint mentioned earlier.

DefaultWsdl11Definition exposes a standard WSDL 1.1 using XsdSchema.

It’s important to notice that we need to specify bean names for MessageDispatcherServlet and DefaultWsdl11Definition. Bean names determine the URL under which web service and the generated WSDL file is available. In this case, the WSDL will be available under http://<host>:<port>/ws/user.wsdl.

Change default port

We don’t want tomcat server in spring boot application to be started on random port, so set the server port in SpringBootWebSocketAngularJSGradle/src/main/resources/application.properties

server.port=9999

Test the application

Open SOAPUI and use the WSDL URL as http://localhost:9999/ws/users.wsdl and Endpoint as http://localhost:9999/ws

Now request with below input

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:user="https://www.jeejava.com/UserService">
   <soapenv:Header/>
   <soapenv:Body>
      <user:getUserDetailsRequest>
         <user:name>l</user:name>
      </user:getUserDetailsRequest>
   </soapenv:Body>
</soapenv:Envelope>

The corresponding output you should get

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP-ENV:Header/>
   <SOAP-ENV:Body>
      <ns2:getUserDetailsResponse xmlns:ns2="https://www.jeejava.com/UserService">
         <ns2:users>
            <ns2:id>2</ns2:id>
            <ns2:name>Loku Poddar</ns2:name>
            <ns2:email>debabrata.poddar@email.com</ns2:email>
            <ns2:address>
              <ns2:street>Birati</ns2:street>
               <ns2:city>Kolkata</ns2:city>
               <ns2:state>WB</ns2:state>
               <ns2:zip>700130</ns2:zip>
               <ns2:country>India</ns2:country>
               <ns2:addressType>COMMUNICATION</ns2:addressType>
            </ns2:address>
         </ns2:users>
         <ns2:users>
            <ns2:id>3</ns2:id>
            <ns2:name>Souvik Sanyal</ns2:name>
            <ns2:email>souvik.sanyal@email.com</ns2:email>
            <ns2:address>
               <ns2:street>Kalighat</ns2:street>
               <ns2:city>Kolkata</ns2:city>
               <ns2:state>WB</ns2:state>
               <ns2:zip>700150</ns2:zip>
               <ns2:country>India</ns2:country>
               <ns2:addressType>COMMUNICATION</ns2:addressType>
            </ns2:address>
         </ns2:users>
         <ns2:users>
            <ns2:id>4</ns2:id>
            <ns2:name>Liton Sarkar</ns2:name>
            <ns2:email>liton.sarkar@email.com</ns2:email>
            <ns2:address>
               <ns2:street>Sukanta Nagar</ns2:street>
               <ns2:city>Kolkata</ns2:city>
               <ns2:state>WB</ns2:state>
               <ns2:zip>700098</ns2:zip>
               <ns2:country>India</ns2:country>
               <ns2:addressType>COMMUNICATION</ns2:addressType>
            </ns2:address>
         </ns2:users>
      </ns2:getUserDetailsResponse>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Now request with below input

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:user="https://www.jeejava.com/UserService">
   <soapenv:Header/>
   <soapenv:Body>
      <user:getUserDetailsRequest>
         <user:name>souvik</user:name>
      </user:getUserDetailsRequest>
   </soapenv:Body>
</soapenv:Envelope>

The corresponding output you should get

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP-ENV:Header/>
   <SOAP-ENV:Body>
      <ns2:getUserDetailsResponse xmlns:ns2="https://www.jeejava.com/UserService">
         <ns2:users>
            <ns2:id>3</ns2:id>
            <ns2:name>Souvik Sanyal</ns2:name>
            <ns2:email>souvik.sanyal@email.com</ns2:email>
            <ns2:address>
               <ns2:street>Kalighat</ns2:street>
               <ns2:city>Kolkata</ns2:city>
               <ns2:state>WB</ns2:state>
               <ns2:zip>700150</ns2:zip>
               <ns2:country>India</ns2:country>
               <ns2:addressType>COMMUNICATION</ns2:addressType>
            </ns2:address>
         </ns2:users>
      </ns2:getUserDetailsResponse>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

If you face issue like “java.lang.NoClassDefFoundError: javax/activation/DataSource” or “java.lang.Error: java.lang.reflect.InvocationTargetException” in JDK 9 then add below line to gradle.properties file and build

org.gradle.jvmargs=--add-modules=java.xml.bind,java.activation

Now while refreshing project in Eclipse, if you face problem creating virtual machine then you can remove the above line from gradle.properties after build and refresh the project in Eclipse.

Thanks for reading.

Tags:

I am a professional Web developer, Enterprise Application developer, Software Engineer and Blogger. Connect me on Roy Tutorials | TwitterFacebook Google PlusLinkedin | Reddit

Leave a Reply

Your email address will not be published. Required fields are marked *