Introduction

This tutorial will show you how we can create and publish SOAP based webservice in Contract-first approach using Apache cxf, Spring and Maven. There are mainly two approaches to create the Webservice – Contract-first & Contract-last. The Contract-first approach tells us to create first 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.

For this tutorial we will create one maven web project in Eclipse.

If you already have an idea on how to create a maven project in Eclipse will be great otherwise I will tell you here how to create a maven project in Eclipse.

Prerequisites

The following configurations are required in order to run the application

Eclipse Neon, JDK 1.8, Tomcat 8, Maven 3.6.0
Apache CXF 3.3.2 and Spring 5.1.8

Example Implementation

Now we will see the below steps how to implement contract first SOAP web service.

Creating Project

Create a maven based web project in Eclipse.

Go to File -> New -> Other. On popup window under Maven select Maven Project. Then click on Next. Select the workspace location – either default or browse the location. Click on Next. Now in next window select the row as highlighted from the below list of archtypes and click on Next button.

maven-arctype-webapp

Now enter the required fields (Group Id, Artifact Id) as shown below:

Group Id : com.jeejava
Artifact Id : soap-contract-first-webservice

Updating Build File

Modify the pom.xml file as shown below to include required dependencies and plugins.

<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/maven-v4_0_0.xsd">

	<modelVersion>4.0.0</modelVersion>

	<groupId>com.jeejava</groupId>
	<artifactId>soap-contract-first-webservice</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<jdk.version>1.8</jdk.version>
		<cxf.version>3.3.2</cxf.version>
		<spring.version>5.1.8.RELEASE</spring.version>
	</properties>

	<dependencies>
		<!-- Spring dependencies -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<!-- Spring dependencies -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<!-- Apache cxf dependencies -->
		<dependency>
			<groupId>org.apache.cxf</groupId>
			<artifactId>cxf-rt-frontend-jaxws</artifactId>
			<version>${cxf.version}</version>
		</dependency>
		<dependency>
			<groupId>org.apache.cxf</groupId>
			<artifactId>cxf-rt-transports-http</artifactId>
			<version>${cxf.version}</version>
		</dependency>
		<!-- servlet & jsp -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.0.1</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>javax.servlet.jsp</groupId>
			<artifactId>javax.servlet.jsp-api</artifactId>
			<version>2.3.1</version>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.1.2</version>
		</dependency>
	</dependencies>

	<build>
		<finalName>soap-contract-first-webservice</finalName>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.8.1</version>
				<configuration>
					<source>${jdk.version}</source>
					<target>${jdk.version}</target>
					<configuration>
						<excludes>
							<exclude>/xsd/*</exclude>
						</excludes>
					</configuration>
				</configuration>
			</plugin>
			<!-- Generate Java classes from WSDL during build -->
			<plugin>
				<groupId>org.apache.cxf</groupId>
				<artifactId>cxf-codegen-plugin</artifactId>
				<version>${cxf.version}</version>
				<executions>
					<execution>
						<id>generate-sources</id>
						<phase>generate-sources</phase>
						<configuration>
							<!-- which source folder the generated classes should be placed in 
								a package -->
							<sourceRoot>${project.basedir}/src/generated-sources/java</sourceRoot>
							<wsdlOptions>
								<wsdlOption>
									<!-- put the wsdl file in this location -->
									<wsdl>${project.basedir}/src/main/resources/wsdl/user.wsdl</wsdl>
									<extraargs>
										<extraarg>-p</extraarg>
										<extraarg>com.jeejava.soap.contract.first</extraarg>
									</extraargs>
								</wsdlOption>
							</wsdlOptions>
						</configuration>
						<goals>
							<goal>wsdl2java</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>

Configuring Build Path

If you see JRE System Library[J2SE-1.5] then change the version by below process

Do right-click on the project and go to Build -> Configure build path, under Libraries tab click on JRE System Library[J2SE-1.5], click on Edit button and select the appropriate jdk 1.8 from the next window. Click on Finish then Ok.

Creating XSD

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

<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="id" type="xs:int" />
			</xs:sequence>
		</xs:complexType>
	</xs:element>

	<xs:element name="getUserDetailsResponse">
		<xs:complexType>
			<xs:sequence>
				<xs:element name="user" type="tns:user" />
			</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.

Creating WSDL

Now create a WSDL (web service definition language) file user.wsdl under src/main/resources folder.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions
	xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
	xmlns:tns="https://www.jeejava.com/UserService"
	xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
	targetNamespace="https://www.jeejava.com/UserService">

	<wsdl:types>
		<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
			<xsd:import
				namespace="https://www.jeejava.com/UserService"
				schemaLocation="../xsd/user.xsd" />
		</xsd:schema>
	</wsdl:types>

	<wsdl:message name="getUserDetailsRequest">
		<wsdl:part element="tns:getUserDetailsRequest"
			name="getUserDetailsRequest" />
	</wsdl:message>

	<wsdl:message name="getUserDetailsResponse">
		<wsdl:part element="tns:getUserDetailsResponse"
			name="getUserDetailsResponse" />
	</wsdl:message>

	<wsdl:portType name="UserPort">
		<wsdl:operation name="getUserDetailsRequest">
			<wsdl:input message="tns:getUserDetailsRequest"
				name="userDetailsInput" />
			<wsdl:output message="tns:getUserDetailsResponse"
				name="userDetailsOutput" />
		</wsdl:operation>
	</wsdl:portType>

	<wsdl:binding name="UserBinding" type="tns:UserPort">
		<soap:binding style="document"
			transport="http://schemas.xmlsoap.org/soap/http" />
		<wsdl:operation name="getUserDetailsRequest">
			<soap:operation soapAction="" />
			<wsdl:input name="userDetailsInput">
				<soap:body use="literal" />
			</wsdl:input>
			<wsdl:output name="userDetailsOutput">
				<soap:body use="literal" />
			</wsdl:output>
		</wsdl:operation>
	</wsdl:binding>

	<wsdl:service name="UserService">
		<wsdl:port binding="tns:UserBinding" name="UserService">
			<soap:address location="/UserService" />
		</wsdl:port>
	</wsdl:service>

</wsdl:definitions>

In the definition file <wsdl:types/> defines the types of input and output requests, so in this file we have included the external XSD file user.xsd otherwise we had to define the types inside <wsdl:types/>.

In <wsdl:message name=”…”> the name is different from what is declared as element’s name in user.xsd file.

In <wsdl:part element=”…” name=”…”> the name must be same as what is declared as element’s name in user.xsd file.

Then we have <wsdl:portType/>, here the operation name could be as per your choice. The name in <wsdl:operation/> could be anything. The message in <wsdl:input/> or <wsdl:output/> must match with <wsdl:message/> name but the name in <wsdl:input/> or <wsdl:output/> could be anything.

The name in <wsdl:binding/> could be anything but type must match with the name in <wsdl:portType/>. Then inside <wsdl:operation/> we have <wsdl:input/> & <wsdl:output/> and their names must match with the names of <wsdl:input/> and <wsdl:output/> inside <wsdl:portType/>.

The name could be anything for <wsdl:service/>. The binding in <wsdl:port/> must be same as name of <wsdl:binding/>.

Lastly <soap:address/> where host & port have been removed because it will be running in the same server where deployed.

Building Project

Now build the maven project, you will see there are few classes generated under the package com.jeejava.soap.contract.first.

Creating Some Data

We are not using any database here to fetch data so we will create one mock data class which will give us data based on matching input.

package com.jeejava.soap.contract.first.data;

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

import com.jeejava.soap.contract.first.Address;
import com.jeejava.soap.contract.first.AddressType;
import com.jeejava.soap.contract.first.User;

public class UserMockData {

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

	public UserMockData() {
		User u1 = new User();
		u1.setId(1);
		u1.setName("Sumit");
		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");
		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");
		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");
		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("Debina");
		u5.setEmail("debina.guha@email.com");
		Address a5 = new Address();
		a5.setStreet("Kestopur");
		a5.setCity("Kolkata");
		a5.setState("WB");
		a5.setCountry("India");
		a5.setZip(700091);
		a5.setAddressType(AddressType.COMMUNICATION);
		u5.setAddress(a5);

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

	public User getUser(int id) {
		for (User user : users) {
			if (id == user.getId()) {
				return user;
			}
		}
		return null;
	}

}

SOAP Service Implementation Class

Maven already generated end-point interface for us, so we will create an implementation class.

package com.jeejava.soap.contract.first.service.impl;

import javax.jws.WebService;

import com.jeejava.soap.contract.first.User;
import com.jeejava.soap.contract.first.UserPort;
import com.jeejava.soap.contract.first.data.UserMockData;

@WebService(endpointInterface = "com.jeejava.soap.contract.first.UserPort")
public class UserPortImpl implements UserPort {

	private UserMockData mockData;

	public UserPortImpl(UserMockData mockData) {
		this.mockData = mockData;
	}

	@Override
	public User getUserDetailsRequest(int id) {
		return mockData.getUser(id);
	}

}

Configuring Address Endpoint

Create cxf-config.xml file inside src/main/resources folder for configuring beans.

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:jaxws="http://cxf.apache.org/jaxws"
	xsi:schemaLocation="
 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
 http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

	<import resource="classpath:META-INF/cxf/cxf.xml" />

	<bean id="mockData"
		class="com.jeejava.soap.contract.first.data.UserMockData" />
	<bean id="userPort"
		class="com.jeejava.soap.contract.first.service.impl.UserPortImpl">
		<constructor-arg ref="mockData" />
	</bean>

	<jaxws:endpoint id="UserService" implementor="#userPort"
		address="/user" />

</beans>

In the above spring configuration we have defined the endpoint for webservice.

Updating Deployment Descriptor

Now update the web.xml file as below to define our servlet configurations.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
	id="WebApp_ID" version="3.0">
	<display-name>SOAP Web Service Contract First</display-name>

	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:cxf-config.xml</param-value>
	</context-param>

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

	<servlet>
		<description>CXF Servlet</description>
		<servlet-name>CXFServlet</servlet-name>
		<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>CXFServlet</servlet-name>
		<url-pattern>/services/*</url-pattern>
	</servlet-mapping>

</web-app>

Deploying into Tomcat

Now deploy the project into Tomcat 8 server, you will see output similar to the below:

Initializing Spring root WebApplicationContext
7:49:54 PM org.springframework.web.context.ContextLoader initWebApplicationContext
INFO: Root WebApplicationContext: initialization started
7:49:57 PM org.apache.cxf.wsdl.service.factory.ReflectionServiceFactoryBean buildServiceFromClass
INFO: Creating Service {http://impl.service.first.contract.soap.jeejava.com/}UserPortImplService from class com.jeejava.soap.contract.first.UserPort
7:49:58 PM org.apache.cxf.endpoint.ServerImpl initDestination
INFO: Setting the server's publish address to be /user
7:49:58 PM org.springframework.web.context.ContextLoader initWebApplicationContext
INFO: Root WebApplicationContext initialized in 4521 ms

Accessing WSDL File

You can access WSDL file using URL http://localhost:8080/soap-contract-first-webservice/services/user?wsdl in the browser.

contract first web service

Testing the Service

Creating Client CLass

Create a client class in to test the SOAP service.

package com.jeejava.soap.contract.first.webservice.client;

import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;

import com.jeejava.soap.contract.first.User;
import com.jeejava.soap.contract.first.UserPort;

public class UserClient {
	public static void main(String[] args) {
		final String endpointAddress = "http://localhost:8080/soap-contract-first-webservice/services/user";
		JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
		factory.setServiceClass(UserPort.class); // the SEI
		factory.setAddress(endpointAddress);
		UserPort port = (UserPort) factory.create();
		User user = port.getUserDetailsRequest(2);
		System.out.println(user.getId());
		System.out.println(user.getName());
		System.out.println(user.getEmail());
		System.out.println(user.getAddress().getAddressType());
		System.out.println(user.getAddress().getStreet());
		System.out.println(user.getAddress().getCity());
		System.out.println(user.getAddress().getState());
		System.out.println(user.getAddress().getCountry());
		System.out.println(user.getAddress().getZip());
	}

}

Running Client Class

Run the client class you will get the output in console as shown below.

2
Loku
debabrata.poddar@email.com
COMMUNICATION
Birati
Kolkata
WB
India
700130

Source Code

You can download source code.

Thanks for reading.

Tags:

Leave a Reply

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