Technology weblog

IT-Essence
Thursday Apr 21, 2011

Spring-WS and Exception Handling through wsdl:fault - Part 2

I have learned, eventhough the spring-ws documentation is pretty clear, still alot of features are not properly documented. In this post I will focus on creating a demo webservice using Spring-WS 2. I will provide code samples for all elements required to get all exception code where you want it. All this information is based on contract-first webservice development. Here we focus on the XSD's required and how to create your WSDL from those XSD's. This post is divided into 3 parts. This post is part 2. Part 1 can be found here 

Contents

During this post we will follow below steps:

  1. Project structure (part 1)
  2. Creating domain related XSDs (part 1)
  3. Creating exception specific XSDs (part 1)
  4. Creating the wsdl specific XSD (part 1)
  5. Creating a Spring-WS webapp (part 2)
  6. Configuring the WSDL (part 2)
  7. Coding Endpoint class to handle a WebService operation (part 2)
  8. Exception resolving (setting fault detail) (part 2)
  9. Handling multiple exceptions via multiple wsdl:fault elements (part 3)
  10. Adding Security (part 3)
  11. Versioning/Backwards compatibility (part 3)
  12. Writing a client... (part 3)

5. Creating a Spring-WS webapp

You need a couple of elements for a Spring-WS webapp to be complete:

  1. Webapp project structure (like paragraph 1. Project Structure in part 1)
  2. pom.xml with Spring-WS dependencies
  3. The XSDs created in part 1
  4. web.xml
  5. Spring config file
  6. Endpoint class to handle the WebService operation

5.1 Webapp project structure

See part 1

5.2 pom.xml with Spring-WS dependencies

See below for a pom.xml I used for this post. This pom will also generate source code for our XSDs to be used in the Endpoint class handling our WebService operation.

<?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/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>nl.test.user</groupId>
	<artifactId>user-ws-war</artifactId>
	<packaging>war</packaging>
	<properties>
		<spring.ws.version>2.0.0.RELEASE</spring.ws.version>
		<jaxb.version>2.1</jaxb.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.ws</groupId>
			<artifactId>spring-ws-core</artifactId>
			<version>${spring.ws.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.ws</groupId>
			<artifactId>spring-ws-support</artifactId>
			<version>${spring.ws.version}</version>
		</dependency>
		<dependency>
			<groupId>org.apache.ws.commons.axiom</groupId>
			<artifactId>axiom-impl</artifactId>
			<version>1.2.9</version>
		</dependency>
		<dependency>
			<groupId>com.sun.xml.bind</groupId>
			<artifactId>jaxb-impl</artifactId>
			<version>${jaxb.version}</version>
		</dependency>
		<dependency>
			<groupId>org.apache.ws.commons.schema</groupId>
			<artifactId>XmlSchema</artifactId>
			<version>1.4.3</version>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>servlet-api</artifactId>
			<version>2.5</version>
			<scope>provided</scope>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>1.5</source>
					<target>1.5</target>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.jvnet.jaxb2.maven2</groupId>
				<artifactId>maven-jaxb21-plugin</artifactId>
				<version>0.7.5</version>
				<executions>
					<execution>
						<goals>
							<goal>generate</goal>
						</goals>
					</execution>
				</executions>
				<configuration>
					<schemaDirectory>src/main/resources</schemaDirectory>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project> 

5.3 XSDs

See part 1

5.4 web.xml

See below

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
	version="2.4">
	<display-name>user-ws-war</display-name>
	<servlet>
		<servlet-name>spring-ws</servlet-name>
		<servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
		<init-param>
			<param-name>transformWsdlLocations</param-name>
			<param-value>true</param-value>
		</init-param>
	</servlet>
	<servlet-mapping>
		<servlet-name>spring-ws</servlet-name>
		<url-pattern>/*</url-pattern>
	</servlet-mapping>
</web-app> 

5.5 Spring config

See paragraph 6

5.6 Endpoint class to handle the WebService operation

See paragraph 7

6.  Configuring the WSDL

Below the spring configuration required to make the WSDL. The <sws:dynamic-wsdl> part creates the WSDL from the XSDs, the rest is generic Spring-WS config.

<?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:context="http://www.springframework.org/schema/context"
	xmlns:sws="http://www.springframework.org/schema/web-services"
	xmlns:oxm="http://www.springframework.org/schema/oxm"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-2.0.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
  http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd">
	<!-- will handle streaming soap messages -->
	<bean id="messageFactory"
		class="org.springframework.ws.soap.axiom.AxiomSoapMessageFactory" />

	<context:component-scan base-package="nl.test.user.ws" />

	<sws:annotation-driven />

	<sws:dynamic-wsdl id="user" portTypeName="User"
		locationUri="/userService/" targetNamespace="http://test.nl/wsdls/userservice/2011/04">
		<sws:xsd location="classpath:/exception.xsd" />
		<sws:xsd location="classpath:/base.xsd" />
		<sws:xsd location="classpath:/user.xsd" />
		<sws:xsd location="classpath:/userservice.xsd" />
	</sws:dynamic-wsdl>
	
	<bean id="exceptionResolver"
		class="nl.test.ws.commons.exception.DetailSoapFaultMappingExceptionResolver">
		<property name="order" value="-1" />
		<property name="defaultFault" value="SERVER" />
		<property name="exceptionMappings">
			<value>
				nl.test.ws.commons.exception.EndpointException=SERVER
			</value>
		</property>
	</bean>
</beans> 

7. Coding an Endpoint class to handle a WSDL operation

Below the implementation to handle the getUserByName operation (nl.test.user.ws.endpoint.UserServiceEndpoint.java): 

package nl.test.user.ws.endpoint;

import nl.test.schemas.base._2011._04.AddressType;
import nl.test.schemas.exception._2011._04.ExceptionsType;
import nl.test.schemas.exception._2011._04.UserNameInvalidExceptionType;
import nl.test.schemas.user._2011._04.GroupType;
import nl.test.schemas.user._2011._04.GroupsType;
import nl.test.schemas.user._2011._04.UserType;
import nl.test.schemas.userservice._2011._04.GetUsersByNameFault;
import nl.test.schemas.userservice._2011._04.GetUsersByNameRequest;
import nl.test.schemas.userservice._2011._04.GetUsersByNameResponse;
import nl.test.schemas.userservice._2011._04.UsersType;
import nl.test.ws.commons.exception.EndpointException;

import org.springframework.util.StringUtils;
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;

/**
 * @author Marcel de Koster
 */
@Endpoint
public class UserServiceEndpoint {

    @PayloadRoot(localPart = "getUsersByNameRequest", namespace = "http://test.nl/schemas/userservice/2011/04")
    @ResponsePayload
    public GetUsersByNameResponse getUsersByName(@RequestPayload GetUsersByNameRequest request) throws EndpointException {
        // validate
        validateRequest(request);

        GetUsersByNameResponse response = new GetUsersByNameResponse();

        // mock implementation....

        if (StringUtils.hasText(request.getName())) {
            UsersType users = new UsersType();

            UserType user = new UserType();
            user.setName("Marcel de Koster");

            AddressType address = new AddressType();
            address.setStreet("blastreet");
            address.setHouseNumber(20);
            address.setZipCode("1234GF");
            address.setCity("Adrop");

            user.setAddress(address);

            GroupsType groups = new GroupsType();
            GroupType group = new GroupType();
            group.setName("user");
            groups.getGroups().add(group);

            user.setGroups(groups);
            users.getUser().add(user);

            response.setUsers(users);
        }

        return response;
    }

    private void validateRequest(GetUsersByNameRequest request) throws EndpointException {
        if (!StringUtils.hasText(request.getName())) {
            UserNameInvalidExceptionType userNameInvalidExceptionType = new UserNameInvalidExceptionType();
            userNameInvalidExceptionType.setMessage("Name or part of the name of the user is required");

            ExceptionsType exceptionsType = new ExceptionsType();
            exceptionsType.setUserNameInvalidException(userNameInvalidExceptionType);

            GetUsersByNameFault fault = new GetUsersByNameFault();
            fault.setExceptions(exceptionsType);

            throw new EndpointException("Name or part of the name of the user is required", fault);
        }
    }
} 

8. Exception resolving (setting fault detail)

This paragraph demontrates the weird way of how you need to add the failt detail XML. 

We use an ExceptionResolver to handle the configured exception. This exception holds the exception XSD generated class, which in turn can be used to generate the XML from using JAXB marshalling.

The 2 classes below are required to make this work. First the EndpointException holding the XSD classes, and the resolver handling this EndpointException.

(EndpointException.java) 

package nl.test.ws.commons.exception;

import org.springframework.util.Assert;

/**
 * Exception containing an ExceptionsType, specifically used for providing WSDL Fault detail data from XSD generated
 * xxxFault elements. This xxxFault class would in this case be the holder. The specific @Endpoint based service method
 * will throw this exception, so the {@link DetailSoapFaultMappingExceptionResolver} can catch it and use the
 * ExceptionsType holder (xxFault class) to transform it into a fault detail.
 * 
 * @see DetailSoapFaultMappingExceptionResolver
 * 
 * @author M de Koster
 */
public class EndpointException extends Exception {
    private static final long serialVersionUID = 1L;

    private Object exceptionsTypeHolder;

    /**
     * Create instance with message, cause and exceptionsTypeHolder.
     * 
     * @param message
     *            Exception message describing what is wrong
     * @param cause
     *            The actual exception that occured
     * @param exceptionsTypeHolder
     *            The holder containing an ExceptionsType with detailed information to make part of the fault detail
     *            section.
     */
    public EndpointException(String message, Throwable cause, Object exceptionsTypeHolder) {
        super(message, cause);
        Assert.notNull(exceptionsTypeHolder, "ExceptionsType holder may not be null");
        this.exceptionsTypeHolder = exceptionsTypeHolder;
    }

    /**
     * Create instance with message and exceptionsTypeHolder.
     * 
     * @param message
     *            Exception message describing what is wrong
     * @param exceptionsTypeHolder
     *            The holder containing an ExceptionsType with detailed information to make part of the fault detail
     *            section.
     */
    public EndpointException(String message, Object exceptionsTypeHolder) {
        super(message);
        Assert.notNull(exceptionsTypeHolder, "ExceptionsType holder may not be null");
        this.exceptionsTypeHolder = exceptionsTypeHolder;
    }

    public Object getExceptionsTypeHolder() {
        return exceptionsTypeHolder;
    }
} 

 (DetailSoapFaultMappingExceptionResolver.java)

package nl.test.ws.commons.exception;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.transform.Result;

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

/**
 * SoapFaultMappingExceptionResolver for transforming an exception into fault details
 * 
 * @see EndpointException
 * @author M de Koster
 * 
 */
public class DetailSoapFaultMappingExceptionResolver extends SoapFaultMappingExceptionResolver {

    /**
     * Method creates fault details when the Exception is an EndpointException. It will marshall the contained
     * ExceptionsType holder, but could be any JAXB generated object which is part of the wsdl:fault information for the
     * service method that throw this exception.
     * 
     * @param endpoint
     *            the executed endpoint, or <code>null</code> if none chosen at the time of the exception
     * @param ex
     *            the exception to be handled. Only EndpointException is handled specifically.
     * @param fault
     *            the created fault
     */
    @Override
    protected void customizeFault(Object endpoint, Exception ex, SoapFault fault) {
        SoapFaultDetail detail = fault.addFaultDetail();
        Result result = detail.getResult();

        if (ex instanceof EndpointException) {
            EndpointException endpoindEx = (EndpointException) ex;
            if (endpoindEx.getExceptionsTypeHolder() == null) {
                System.err.println("No ExceptionTypeHolder defined, so unable to provide extra Fault details");
            } else {
                try {
                    marshallException(endpoindEx.getExceptionsTypeHolder(), result);
                } catch (JAXBException e) {
                    System.err.println("Unable to marshal the ExceptionsType holder");
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * Does the actual JAXB marshalling on the given exceptionsTypeHolder
     * 
     * @param exceptionsTypeHolder
     *            JAXB generated object to be transformed into XML
     * @param result
     *            Result class from javax.xml.transform package
     * @throws JAXBException
     *             Thrown when marshalling fails
     */
    private static void marshallException(Object exceptionsTypeHolder, Result result) throws JAXBException {
        JAXBContext context = JAXBContext.newInstance(exceptionsTypeHolder.getClass());
        Marshaller marshaller = context.createMarshaller();
        marshaller.marshal(exceptionsTypeHolder, result);
    }
} 

As you can see in paragraph 6 the exception resolver has already been defined in the Spring configuration.

When starting this Spring-WS webapp in tomcat or jetty the url of the WSDL would be: http://localhost:8080/user-ws-war/userService/user.wsdl

You could now add the WebService to soapUI, to start testing on it. 

8. Handling multiple exceptions via multiple wsdl:fault elements 

This paragraph will be described in part 3 of this post. I hope people can appreciate the information I share here and hope they will enjoy developing WebServices using Spring-WS with included exception handling. 

Comments:

Post a Comment:
Comments are closed for this entry.

Hire us
Archives
Tags
Links
Stats
Referrers