Sunday 17 April 2011

Contract First Web Service with Spring Web Services 2

Recently I've needed a simple SOAP based WebService which will be called from an Android Application. Below is an example of using the power of Spring WebServices 2 to create a contract first WS in a relatively short period of time. I chose to use Contact First due to the benefits and flexibility it brings on all levels, Spring Source also recommend a Contract first approach to reduce the XML and Object mapping infeasibility and inflexibility issues that commonly arise.

Spring-WS website: http://static.springsource.org/spring-ws/site/

Along side spring-ws sits spring-ws-test which makes it very easy for both client and server side testing and reducing alot of boiler plate set-up you may have to make when attempting the test web service out the box.

Before I start you can grab all the source code related to the post from my GitHub account.
Simply navigate to the below and clone or fork the project:


https://github.com/jamesemorgan/LoginDemoWebService

Creating your Contract:


Below is the XSD I created before writing any code which I use as a starting point for the following example. It simply defines the request and response payloads plus any additional complex object types I use. The most complex being a GUID with a matching regular expression to enforce validation and make up. The good thing about spring is it takes care of validating and enforcing incoming request payloads so you don't have to.




 
  
   
    
    
    
    
   
  
 
 
 
  
   
    
    
   
  
 
 

 
  
   
  
 



JAXB Object Creation:

I use the JAXB compiler to interpret and create my Incoming and outgoing request payloads.

This can be seen below:
Navigate to you XSD.
cd .\src\main\resources\webService
Invoke JaxB and specify the XSD location, the package to place created objects and the directory to put them.
xjc incoming-android-login-schema.xsd 
-p "com.morgan.design.demo.login.ws.generated" 
-d "../../java/"

Defining you Spring Endpoint and Jaxb Marshaller:


Below is my spring context configuration file, all I am doing here is defining the actual incoming WS location and applying the needed and marshallers to convert the object to and form my JAXB classes, plus specifying additional request and response payload validation.




 
 

 
  
 
 
 
 
  

 
 
  
   
    com.morgan.design.demo.login.ws.generated
   
  
 

 
 
  
  
  
   
   
   
  
 


Creating your EndPoint:

I've chosen to go down the annotation driven approach simply as this is what I'm familiar with, but as with all Spring products the choice is yours and Endpoint creation can be done by either XML or Annotation configuration. If you choose to go with a annotation driven route, all you need to do is add the following to your spring config and ensure the correct packages are being scanned:


  



Firstly you need to annotate your class with @Endpoint to specify your class is ready for accepting incoming web service requests.
@Endpoint
Secondly create your method and specify the incoming payload with @RequestPayload (in the example below this is a Jaxb generated class).
@RequestPayload
Next annotate your method with the incoming namespace and request type.
@PayloadRoot(namespace = NAMESPACE_URI, localPart = "LoginRequest"
The example method below has two arguments, firstly the marshalled incoming object and plus an additional argument of org.jdom.Element, which will contain the complete incoming request which I later use to extract the raw request.
@RequestPayload final Element element
@Endpoint
public class LoginRequestEndpoint {

 private final Logger log = Logger.getLogger(this.getClass());

 public static String APPLICATION_UUID = "bcf7d786-61b3-11e0-a8cf-643150372d03";

 public static final String NAMESPACE_URI = "http://morgan-design.com/ws/schema/2010";

 @PayloadRoot(namespace = NAMESPACE_URI, localPart = "LoginRequest")
 public @ResponsePayload
 LoginResponse handleLoginRequest(@RequestPayload final LoginRequest loginRequest, @RequestPayload final Element element)
   throws Throwable {

  // Validate Login Application Code -> this could be/should be DB driven in a live environment
  if (!APPLICATION_UUID.equals(loginRequest.getApplicationUUID())) {
   throw LoginRequestInvalidClientIdException.create(loginRequest.getApplicationUUID());
  }

  // Convert element to string for purposes of audit logging etc, this is optional
  final String xml = convertPayloadToString(element);

  // Generate a new UUID, this should be saved to the DB for the current session of the client application
  final String uuid = UUID.randomUUID()
   .toString();

  // Return a login response to the client
  return createLoginResponse(uuid, 1);
 }

 /**
  * @param element the Raw {@link Element} SOAP Element
  * @return the XML in {@link String} form
  * @throws LoginRequestTransformationException
  */
 private String convertPayloadToString(final Element element) throws LoginRequestTransformationException {
  try {
   final XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat());
   return outputter.outputString(element);
  }
  catch (final Throwable e) {
   this.log.error("Error converting Login Request: ", e);
   throw LoginRequestTransformationException.create();
  }
 }

 /**
  * @param authenticationID a generated {@link UUID} code
  * @param responseCode the success, failure response code
  * @return a constructed {@link LoginResponse}
  */
 private LoginResponse createLoginResponse(final String authenticationID, final int responseCode) {
  final LoginResponse loginResponse = new LoginResponse();
  loginResponse.setAuthenticationID(authenticationID);
  loginResponse.setResponseCode(responseCode);
  if (this.log.isDebugEnabled()) {
   this.log.debug(String.format("Creating Login Response, Authentication Code [%s], Response Code [%s] ", authenticationID,
     responseCode));
  }
  return loginResponse;
 }
}

Creating WebService Exceptions:

Below is an example of a Spring Web Service Exception. Simply annotating your Exception with @SoapFault(faultCode = FaultCode.SERVER) and ensuring the containing package is being scanned by spring the exception will be marshalled and sent as a SOAP exception, in this case a Server Fault exception. Other exception types can be used as seen below.
@SoapFault(faultCode = FaultCode.SERVER)
public class LoginRequestException extends Exception {

 /** */
 private static final long serialVersionUID = 4850097068571927700L;

 protected LoginRequestException(final String message) {
  super(message);
 }

 public static LoginRequestException create() {
  return new LoginRequestException("A server fault has occured");
 }
}

All in all, spring web service 2 makes it very simple and quick for you to throw together web services. Once you understand the types of steps to go though when making contract first web services it all becomes alot easier and spring takes out the complex parts such as request and response validation and marshalling.

The above demo simply highlights some of the niceness you get by using SpringWS 2. All the source can be grabbed from my GitHub account. https://github.com/jamesemorgan/LoginDemoWebService

When I get abit more time I will demonstrate how to get Android to consume the above web service.

2 comments:

  1. very nice article....however i am late..

    how to get Android to consume the above web service..
    Please demonstrate..

    ReplyDelete
  2. Hi Satyam, I would look at the Spring Android project, this may provide some spring specific utilities for use with this. http://www.springsource.org/spring-android

    Otherwise I would look at a SOAP specific Android project such as FSOAP2 https://code.google.com/p/ksoap2-android/

    Hope this helps.

    ReplyDelete