Spring SOAP Web services – Add SOAP Fault Exception handling – Part III

In this post, we will learn how to add detail information to your exceptions. Spring WS enables easy translation of errors penetrated from business logic. Exceptions are automatically thrown up the chain with the use of EndpointExceptionResolvers. Rather than exposing the internals of your application by giving an exception and stack trace, you can handle the exceptions programmatically and add an appropriate error code and description. This example shows you how to add detailed information to your SoapFault Exception.

In this post, we will implement

  • MovieNotFoundException – Throw this exception when we know if a movie not present in the database
  • ServiceFaultException – Add Custom code and error messages

Here are the list of posts on SOAP web services using Spring framework for your reference

 

  1. Publish SOAP web services – perform CRUD operation and consume SOAP web services using SOAP UI : We will explore these topic in this post – Publish SOAP Web services using Spring Boot – Part 1
  2. Consume SOAP web services using client application – Consume Spring SOAP web services using client application – Part II
  3. Exception handling in SOAP web services- We will learn about this topic in here
  4. Exception handling in CRUD SOAP web services – Spring SOAP Web services – Add SOAP Fault Exception handling for CRUD operations – Part IV
  5. Securing SOAP web services – In upcoming tutorial
  6. Testing SOAP web services – In upcoming tutorial

List of Contents

  1. Tools and Environment
  2. Project Structure
  3. Steps to create SOAP Server and SOAP Client with Exception handling
  4. Screenshots
  5. Download
  6. References

1. Tools and Environment

Following tools and environments are used to consume SOAP web services in Spring Boot

  • Spring Tool Suite (version: 3.9.4.RELEASE)
  • Spring Boot (version: 2.0.4)
  • Java version (version 8)
  • Maven (version 3.5.2)
  • Mysql (version: 5.1.17)

2. Project Structure

 

3. Steps to create SOAP Server and SOAP Client with Exception handling

  1. Create sample data in the database
  2. New Spring Starter project
  3. Add “movies.xsd” under /src/main/resources/xsd folder
  4. Edit pom.xml
    1. Add “wsdl4j” dependency (The Web Services Description Language for Java Toolkit (WSDL4J) allows the creation, representation, and manipulation of WSDL documents)
    2. jaxb2-mavenplugin (to generate java source classes from XSD))
    3. Do Maven-> update project to generate java class files
  5. Create SOAP Server related files
    1. Create Movie Service class to perform CRUD operations on Movie object – “MovieService.java”
    2. Create SOAP Server configuration file – “SoapServerConfig.java”
    3. Create SOAP Endpoint file – “MovieEndpoint.java”
    4. Create SOAP Server Exceptions – “MovieNotFoundException”, “ServiceFaultException”, “DetailSoapFaultDefinitionExceptionResolver”
    5. Create an executable server class to run the Server – “RunServer.java”
  6. Run Server
    1. Right click on the “RunServer” and do Run As -> Java Application to start the server on default/specified port
  7. Test WDSL on web browser
  8. Test in SOAP UI or Create a Client application to test the exception

4. ScreenShots

Step 1: Create sample data in the database

CREATE TABLE movies (
  movie_id BIGINT(5) NOT NULL AUTO_INCREMENT,
  title VARCHAR(45) NULL,
  category VARCHAR(45) NULL,
  PRIMARY KEY (movie_id));
  
  
INSERT INTO movies(movie_id, title, category) VALUES
  (101, 'Mama mia', 'drama'),
  (102, 'Mission Impossible', 'action'),
  (103, 'Coco', 'animation');

Step 2: New Spring Starter project

 

 

 

Step 3:  Add “movies.xsd” under /src/main/resources/xsd folder

movies.xsd

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
	xmlns:tns="http://www.javaspringclub.com/movies-ws"
	targetNamespace="http://www.javaspringclub.com/movies-ws"
	elementFormDefault="qualified">

	<xs:element name="getMovieByIdRequest">
		<xs:complexType>
			<xs:sequence>
				<xs:element name="movieId" type="xs:long" />
			</xs:sequence>
		</xs:complexType>
	</xs:element>

	<xs:element name="getMovieByIdResponse">
		<xs:complexType>
			<xs:sequence>
				<xs:element name="movieType" type="tns:movieType" />
			</xs:sequence>
		</xs:complexType>
	</xs:element>
	<xs:complexType name="movieType">
		<xs:sequence>
			<xs:element name="movieId" type="xs:long" />
			<xs:element name="title" type="xs:string" />
			<xs:element name="category" type="xs:string" />
		</xs:sequence>
	</xs:complexType>

 <xs:element name="getAllMoviesRequest">
        <xs:complexType/>
    </xs:element>    
    <xs:element name="getAllMoviesResponse">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="movieType" type="tns:movieType" maxOccurs="unbounded"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>   
    <xs:complexType name="serviceStatus">
        <xs:sequence>
            <xs:element name="statusCode" type="xs:string"/>
            <xs:element name="message" type="xs:string"/>
        </xs:sequence>
    </xs:complexType>     
    <xs:element name="addMovieRequest">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="title" type="xs:string"/>
                <xs:element name="category" type="xs:string"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:element name="addMovieResponse">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="serviceStatus" type="tns:serviceStatus"/>            
                <xs:element name="movieType" type="tns:movieType"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:element name="updateMovieRequest">
        <xs:complexType>
            <xs:sequence>
               <xs:element name="title" type="xs:string"/>
                <xs:element name="category" type="xs:string"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:element name="updateMovieResponse">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="serviceStatus" type="tns:serviceStatus"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:element name="deleteMovieRequest">
        <xs:complexType>
            <xs:sequence>
               <xs:element name="movieId" type="xs:long"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:element name="deleteMovieResponse">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="serviceStatus" type="tns:serviceStatus"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

</xs:schema>

Step 4: Edit pom.xml to include wsdl4j and jaxb2-maven-plugin

Do Maven update project after saving pom.xml

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>com.javaspringclub</groupId>
	<artifactId>SpringBoot_SOAP_Fault_Exceptions</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>SpringBoot_SOAP_Fault_Exceptions</name>
	<description>WebService Example</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.4.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>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>wsdl4j</groupId>
			<artifactId>wsdl4j</artifactId>
		</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>2.3.1</version>
				<executions>
					<execution>
						<id>xjc-schema</id>
						<goals>
							<goal>xjc</goal>
						</goals>
					</execution>
				</executions>
				<configuration>
					<sources>
						<source>src/main/resources/xsd/movies.xsd</source>
					</sources>
					<packageName>com.javaspringclub.gs_ws</packageName>
					<clearOutputDir>false</clearOutputDir>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>


Maven plugin will generate java source classes under “com.javaspringclub.gs_ws”. This package name is specified in pom.xml file

Alternatively, running the following command in parent directory, the Java Classes will be generated in the target/generated-sources/jaxb/<package-name> folder. <package-name> is specified in the pom.xml file

mvn package

Step 5: Create SOAP Server related files

  • Create “MovieService.java”
  • Create “SoapServerConfig.java”
  • Create “MovieEndpoint.java”
  • Create Custom Exceptions(optional)
  • Create an executable java class “RunServer.java” to run server

MovieService.java

package com.javaspringclub.server;

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

import org.springframework.stereotype.Component;

import com.javaspringclub.gs_ws.MovieType;
import com.javaspringclub.gs_ws.ServiceStatus;

@Component
public class MovieService {

	public enum Status {
		SUCCESS, FAILURE;
	}

	private static List movies = new ArrayList<>();

	static {
		MovieType movie1 = new MovieType();
		movie1.setMovieId(101);
		movie1.setTitle("Mission Impossible");
		movie1.setCategory("Action");

		movies.add(movie1);

		MovieType movie2 = new MovieType();
		movie2.setMovieId(102);
		movie2.setTitle("Argo");
		movie2.setCategory("Drama");
		movies.add(movie2);

		MovieType movie3 = new MovieType();
		movie3.setMovieId(103);
		movie3.setTitle("Hang over");
		movie3.setCategory("Comedy");
		movies.add(movie3);

	}

	// Get Movie By Id - 1
	public MovieType findById(long id) {
		for (MovieType movie : movies) {
			if (movie.getMovieId() == id)
				return movie;
		}
		return null;
	}

	// Get All movies
	public List findAll() {
		return movies;
	}

	public ServiceStatus deleteById(int id) {
		Iterator iterator = movies.iterator();
		ServiceStatus status = new ServiceStatus();
		while (iterator.hasNext()) {
			MovieType movie = iterator.next();
			if (movie.getMovieId() == id) {
				iterator.remove();
				// return Status.SUCCESS;

				status.setStatusCode("OK");
				status.setMessage("SUCCESS");
				return status;
			}
		}
		// return Status.FAILURE;
		status.setStatusCode("ERROR");
		status.setMessage("FAILURE");
		return status;
	}

	// updating course & new course
}

SoapServerConfig.java

package com.javaspringclub.server;

import java.util.Properties;

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.soap.server.endpoint.SoapFaultDefinition;
import org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver;
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;

import com.javaspringclub.exception.DetailSoapFaultDefinitionExceptionResolver;
import com.javaspringclub.exception.ServiceFaultException;

@EnableWs
@Configuration
public class SoapServerConfig extends WsConfigurerAdapter {

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

	@Bean(name = "movies")
	public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema schema) {
		DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
		wsdl11Definition.setPortTypeName("MoviesPort");
		wsdl11Definition.setLocationUri("/ws");
		wsdl11Definition.setTargetNamespace(MovieEndpoint.NAMESPACE_URI);
		wsdl11Definition.setSchema(schema);
		return wsdl11Definition;
	}

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

	@Bean
	public SoapFaultMappingExceptionResolver exceptionResolver() {
		SoapFaultMappingExceptionResolver exceptionResolver = new DetailSoapFaultDefinitionExceptionResolver();

		SoapFaultDefinition faultDefinition = new SoapFaultDefinition();
		faultDefinition.setFaultCode(SoapFaultDefinition.SERVER);
		exceptionResolver.setDefaultFault(faultDefinition);

		Properties errorMappings = new Properties();
		errorMappings.setProperty(Exception.class.getName(), SoapFaultDefinition.SERVER.toString());
		errorMappings.setProperty(ServiceFaultException.class.getName(), SoapFaultDefinition.SERVER.toString());
		exceptionResolver.setExceptionMappings(errorMappings);
		exceptionResolver.setOrder(1);
		return exceptionResolver;
	}

}

Note:

We will use Spring Java Configuration to configure our soap services in this example.

  • The @EnableWs annotation enables support for the @Endpoint and related Spring Ws annotations.
  • The @Configuration annotations tells spring that this Java Class is used as configuration file.
  • 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
  • Next, we map the service on the /ws/*URI. We do this by creating a ServletRegistrationBean with a MessageDispatcherServletwhich automatically transforms the WSDL location when deployed, together with the URI endpoint
  • We create a DefaultWsdl11Definition bean which we provide information about the portTypelocationUritargetNamespace and schema. It is important to notice that you need to specify bean name for DefaultWsdl11Definition. Bean name 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/movies.wsdl
  • For the soap exceptions to be propagated properly we must register our SoapFaultMappingExceptionResolver. We can define a default SoapFaultDefinition. This default is used when the SoapFaultMappingExceptionResolver does not have any appropriate mappings to handle the exception. We can map our Custom Exception by setting the mapping properties to the exception resolver. We do this by providing the fully qualified class name of the exception as the key and SoapFaultDefinition.SERVER as the value. Finally, we have to specify an order, otherwise for some reason the mappings will not work.

 

MovieEndpoint.java

package com.javaspringclub.server;

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.javaspringclub.exception.MovieNotFoundException;
import com.javaspringclub.exception.ServiceFaultException;
import com.javaspringclub.gs_ws.DeleteMovieRequest;
import com.javaspringclub.gs_ws.DeleteMovieResponse;
import com.javaspringclub.gs_ws.GetMovieByIdRequest;
import com.javaspringclub.gs_ws.GetMovieByIdResponse;
import com.javaspringclub.gs_ws.MovieType;
import com.javaspringclub.gs_ws.ServiceStatus;

@Endpoint
public class MovieEndpoint {

	public static final String NAMESPACE_URI = "http://www.javaspringclub.com/movies-ws";

	@Autowired
	MovieService service;

	@PayloadRoot(namespace = NAMESPACE_URI, localPart = "getMovieByIdRequest")
	@ResponsePayload
	public GetMovieByIdResponse getMovieById(@RequestPayload GetMovieByIdRequest request) {
		MovieType movie = service.findById(request.getMovieId());

		if (movie == null)
			throw new MovieNotFoundException("Invalid Movie Id " + request.getMovieId());

		return mapMovieDetails(movie);
	}

	private GetMovieByIdResponse mapMovieDetails(MovieType movie) {
		GetMovieByIdResponse response = new GetMovieByIdResponse();
		response.setMovieType(movie);
		return response;
	}

	@PayloadRoot(namespace = NAMESPACE_URI, localPart = "deleteMovieRequest")
	@ResponsePayload
	public DeleteMovieResponse deleteMovieById(@RequestPayload DeleteMovieRequest request) {
		String errorMessage = "ERROR";
		ServiceStatus serviceStatus = new ServiceStatus();
		serviceStatus.setStatusCode("NOT_FOUND");
		serviceStatus.setMessage("Movie with id: " + request.getMovieId() + " not found. Cannot delete Movie");

		throw new ServiceFaultException(errorMessage, serviceStatus);
	}

}

Note:

  • We can produce Spring Ws Contract First Soap Endpoint by annotating the class with the @Endpointannotation. This makes the class as a special sort of @Component, suitable for handling XML messages in Spring WS and eligible for component scanning
  • The @PayloadRoot annotation informs Spring Ws that the getMovieById method is suitable for handling XML messages. This method can handle messages with localPart equal to getMovieByIdRequest with a namespace of http://www.javaspringclub.com/movies-ws
  • @RequestPayload tells Spring Ws that a XML Payload of type GetMovieByIdRequest is needed as a parameter of the request

MovieNotFoundException.java

package com.javaspringclub.exception;

import org.springframework.ws.soap.server.endpoint.annotation.FaultCode;
import org.springframework.ws.soap.server.endpoint.annotation.SoapFault;

@SoapFault(faultCode = FaultCode.CUSTOM,
        customFaultCode = "{" + MovieNotFoundException.NAMESPACE_URI + "}custom_fault",
        faultStringOrReason = "@faultString")
public class MovieNotFoundException extends Exception {

	private static final long serialVersionUID = 1L;
	public static final String NAMESPACE_URI = "http://javaspringclub.com/exception";

    public MovieNotFoundException(String message) {
        super(message);
    }
}

ServiceFaultException.java

package com.javaspringclub.exception;

import com.javaspringclub.gs_ws.ServiceStatus;

public class ServiceFaultException extends RuntimeException {

	private static final long serialVersionUID = 1L;
	private ServiceStatus serviceStatus;

	public ServiceFaultException(String message, ServiceStatus serviceStatus) {
		super(message);
		this.serviceStatus = serviceStatus;
	}

	public ServiceFaultException(String message, Throwable e, ServiceStatus serviceStatus) {
		super(message, e);
		this.serviceStatus = serviceStatus;
	}

	public ServiceStatus getServiceStatus() {
		return serviceStatus;
	}

	public void setServiceStatus(ServiceStatus serviceStatus) {
		this.serviceStatus = serviceStatus;
	}

}

Note: The ServiceFaultException extends from RuntimeException, so we don’t need to catch or add it to our method signature. With the ServiceStatus object we can add some detailed information about the error that occurred

 

ServiceStatus.java

This class either you implement on your own or specify in movies.xsd so that it generated by jaxb maven plugin. In our example, we took the second approach i.e specify in movies.xsd as shown below

    
    <xs:complexType name="serviceStatus">
        <xs:sequence>
            <xs:element name="statusCode" type="xs:string"/>
            <xs:element name="message" type="xs:string"/>
        </xs:sequence>
    </xs:complexType>        
       

and here is the class, if you would like to write

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlType;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "ServiceStatus", propOrder = {
        "code",
        "description"
})
public class ServiceStatus {

    private String code;
    private String description;

    public ServiceFault() {
    }

    public ServiceFault(String code, String description) {
        this.code = code;
        this.description = description;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

The ServiceStatus class is used to propagate the appropriate error code and description up the chain. Typically the code is used to map the exception on the client side and the message is just a description indicating the user/developer what exactly went wrong.

 

DetailSoapFaultDefinitionExceptionResolver.java

package com.javaspringclub.exception;

import javax.xml.namespace.QName;

import org.springframework.ws.soap.SoapFault;
import org.springframework.ws.soap.SoapFaultDetail;
import org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver;

import com.javaspringclub.gs_ws.ServiceStatus;

public class DetailSoapFaultDefinitionExceptionResolver extends SoapFaultMappingExceptionResolver {

	private static final QName CODE = new QName("statusCode");
	private static final QName MESSAGE = new QName("message");

	@Override
	protected void customizeFault(Object endpoint, Exception ex, SoapFault fault) {
		logger.warn("Exception processed ", ex);
		if (ex instanceof ServiceFaultException) {
			ServiceStatus status = ((ServiceFaultException) ex).getServiceStatus();
			SoapFaultDetail detail = fault.addFaultDetail();
			detail.addFaultDetailElement(CODE).addText(status.getStatusCode());
			detail.addFaultDetailElement(MESSAGE).addText(status.getMessage());
		}
	}

}

Note:

  • DetailSoapFaultDefinitionExceptionResolver extends from the SoapFaultMappingExceptionResolver and is used for enhancing the SoapFault with detailed information about the error that occurred. We can override the customizeFault method to enhance the exception with detail information. We do this by calling the addFaultDetail method of the SoapFault class, and adding QName indicating the element with the addFaultDetailElement.

RunServer.java

package com.javaspringclub.server;

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

@SpringBootApplication
public class RunServer {

    public static void main(String[] args) {
        SpringApplication.run(RunServer.class);
    }

}

Step 6: Run Server application.

Right click on “RunServer.java” and select Run As -> Java Application to run the Server and publish WSDL. In our application, Tomcat is started on port : 8080

 

 

Step 7: Test WSDL in web browser

You can view wsdl on http://localhost:8080/ws/movies.wsdl

 

Step 8: Test in SOAP UI or Create a Client application to test the exception

 

 

 

 

To run the client application:

All the required java classes are enclosed in the attached zip file in Download section

Right click on “RunClient.java” and click on Run As -> Java Application as shown below

5. Download

SpringBoot_SOAP_Fault_Exceptions

6. Reference

https://docs.spring.io/spring-ws/sites/1.5/reference/html/server.html

About: latha

Hi all, This is Latha. Im fond of learning and teaching java and spring framework in easy steps.


One thought on “Spring SOAP Web services – Add SOAP Fault Exception handling – Part III”

Comments are closed.

%d bloggers like this: