Wednesday, April 6, 2011

Spring Web Service - Part III ( Creating Web Services by java bean both for SOAP 1.1 & SOAP 1.2)

In the Last part of the servies, we will learn how to Develop web services that will support both SOAP 1.1 and SOAP 1.2 protocol, (Spring WS does not provide out of box support for this and we need to tweak the things little bit).
There are many ways through which you can generate the Web services, like
  • Manually creating the WSDL file and place it in WEB-INF directory of your web application.
  • Creating the Request XML XSD and let the framework to create the WSDL
The Second one is preferred approach as compared to the first one, as you does not have to code the WSDL directly either with tool or yourself. 
In order to create the following tutorial, i had taken help from a number of blogs, following are the their links, however I could not find a complete one, so summarizing all this in a single post.
Listed here are the steps:
  1. First we will create a xsd which represents the schema of the input xml, below is a snipped of it,  It only contains three fields that need to be passed to the Server, ( ID, Name, Email) and in response two things are being returned to the client ( CODE, DESCRIPTION)
    
    
        
            
                
                    
                    
                    
                
            
        
    
     
            
                
                    
                    
                    
                
            
        
    
        
            
                
                 
                 
                
            
        
    
        
            
                
                     
                     
                
            
        
    
        
            
                
                
            
        
    
        
      
       
        
        
        
       
      
        
    
        
         
       
        
        
       
      
        
    
    
    
    it Clearly Shows that the contents of Subscription Request and Subscription Response, as described above.
  2. Now we need to create the Java Objects corresponds to both Request and Response.
    SubscriptionRequest.java

    package com.test;
    
    import javax.xml.bind.annotation.XmlElement;
    import javax.xml.bind.annotation.XmlRootElement;
    
    /**
     *
     * @author PankajB
     */
    @XmlRootElement(namespace="http://krams915.blogspot.com/ws/schema/oss",name="subscriptionRequest")
    public class SubscriptionRequest {
    
     private String id;
     private String name;
     private String email;
    
       @XmlElement(name="email",namespace="http://krams915.blogspot.com/ws/schema/oss")
        public String getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    
       @XmlElement(name="id",namespace="http://krams915.blogspot.com/ws/schema/oss")
        public String getId() {
            return id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    
        @XmlElement(name="name",namespace="http://krams915.blogspot.com/ws/schema/oss")
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
    
    }
    
     SubscriptionResponse.java
    package com.test;
    
    import javax.xml.bind.annotation.XmlElement;
    import javax.xml.bind.annotation.XmlRootElement;
    
    /**
     *
     * @author PankajB
     */
    @XmlRootElement(namespace = "http://krams915.blogspot.com/ws/schema/oss", name = "subscriptionResponse")
    public class SubscriptionResponse {
    
        private String code;
        private String description;
    
        @XmlElement(name = "code", namespace = "http://krams915.blogspot.com/ws/schema/oss")
        public String getCode() {
            return code;
        }
    
        public void setCode(String code) {
            this.code = code;
        }
    
        @XmlElement(name = "description", namespace = "http://krams915.blogspot.com/ws/schema/oss")
        public String getDescription() {
            return description;
        }
    
        public void setDescription(String description) {
            this.description = description;
        }
    }
    
    

  3. Now we need to write our main POJO which which handle the request and generate a response. It pretty simply class, annotated with some special annotations.
    package com.test;
    
    import javax.xml.ws.soap.SOAPBinding;
    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 PankajB
     */
    @Endpoint(value = SOAPBinding.SOAP12HTTP_BINDING)
    public class SubscriptionService {
    
        @PayloadRoot(localPart = "subscriptionRequest", namespace = "http://krams915.blogspot.com/ws/schema/oss")
        @ResponsePayload
        public SubscriptionResponse processSubscription(@RequestPayload SubscriptionRequest subscriptionRequest) {
    
    
            SubscriptionResponse response = new SubscriptionResponse();
            System.out.println("Coming Here.........  " + subscriptionRequest.getName());
            response.setCode("234");
            response.setCode("Successfully Executed the application.");
    
            return response;
    
        }
    }
    
    
    here it simply, receive the Payload ( what is being sent by the client in the XML and converted to our SubscriptionRequest Object and generates an SubscriptionResponse type of object, which will again be automatically going to be converted by the Spring Jaxb marshaller in the XML format.
    This Ends the first part of the story, now let's move to the configuration.
  4. First of all in the configuration files, let's visit web.xml
    
        
            spring-ws
            org.springframework.ws.transport.http.MessageDispatcherServlet
            
                transformWsdlLocationstrue
            1
        
        
            spring-ws
            /service/*
        
        
            
                30
            
        
        
            index.jsp
        
    
    
    
    This Simply tells the container that this servlet is going to receive any of the call that matches with the URI /service/*

  5. Now we have the spring-ws-servlet.xml (spring-ws comes from servlet name above in web.xml) which will define all the components related to Spring Web Service.
    
        
        
        
       
            
         
        
        
        
        
        
            
      
        
            
                
                    com.test.SubscriptionRequest
                    com.test.SubscriptionResponse
    
                
            
            
                
                    
                
            
        
    
        
            
        
    
     
        
    
     
        
            
            
        
    
     
        
                
        
       
    
    

    Most of the Things has been commenting in the XML itself, for better understanding.

  6. Since we need to support both SOAP 1.1 & SOAP 1.2 we need to write out own factory, that is going to be an implementation of SoapMessageFactory.
    /*
     *
     * This All has been done as per the things present at this link.
     * http://forum.springsource.org/showthread.php?t=56560
     *
     */
    
    package com.test.soap;
    
    import java.io.IOException;
    import java.io.InputStream;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.web.context.request.RequestAttributes;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.ws.WebServiceMessage;
    import org.springframework.ws.soap.SoapMessage;
    import org.springframework.ws.soap.SoapMessageFactory;
    import org.springframework.ws.soap.SoapVersion;
    import org.springframework.ws.soap.saaj.SaajSoapMessageFactory;
    import org.springframework.ws.transport.TransportInputStream;
    
    public class MyFactory implements SoapMessageFactory, InitializingBean {
            // This is the Request Context Attribute.
     private static final String REQUEST_CONTEXT_ATTRIBUTE = "MyFactory";
    
     private static final Log logger = LogFactory.getLog(MyFactory.class);
    
            // Two message factories for processing two differnet types of protocols.
     private SaajSoapMessageFactory soap11MessageFactory = new SaajSoapMessageFactory();
     private SaajSoapMessageFactory soap12MessageFactory = new SaajSoapMessageFactory();
    
            // This Object, will be responsible for choosing the Protocol on Runtime, it can be application/xml or text/xml (SOAP 1.2 & SOAP 1.1)
     private SoapProtocolChooser soapProtocolChooser = new MySoapProtocolChooser();
    
     private void setMessageFactoryForRequestContext(SaajSoapMessageFactory mf) {
      RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
      attrs.setAttribute(REQUEST_CONTEXT_ATTRIBUTE, mf, RequestAttributes.SCOPE_REQUEST);
     }
    
     private SaajSoapMessageFactory getMessageFactoryForRequestContext() {
      RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
      SaajSoapMessageFactory mf = (SaajSoapMessageFactory) attrs.getAttribute(REQUEST_CONTEXT_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
      return mf;
     }
    
            // Function called, when we are settign the SOPA Version
     public void setSoapVersion(SoapVersion version) {
                System.out.println("setSoapVersion called with: " + version + " -- ignoring");
     }
    
            // This Function, will set teh SOAP Proptocl chooser
     public void setSoapProtocolChooser(SoapProtocolChooser soapProtocolChooser) {
                System.out.println("Setting out the SOAP Protocol Chooser");
      this.soapProtocolChooser = soapProtocolChooser;
     }
    
            // Function will be invoked, when Spring will create the Bean.
     public void afterPropertiesSet() throws Exception {
      soap11MessageFactory.setSoapVersion(SoapVersion.SOAP_11);
      soap11MessageFactory.afterPropertiesSet();
      soap12MessageFactory.setSoapVersion(SoapVersion.SOAP_12);
      soap12MessageFactory.afterPropertiesSet();
                    System.out.println("Setting both the SOAP Version to 1.1 and 1.2");
     }
    
    
        // Function for creating the Web Service Message.
        public SoapMessage createWebServiceMessage() {
            return getMessageFactoryForRequestContext().createWebServiceMessage();
        }
    
        // Function for creating the Web Service Message from inputStream. 
        public SoapMessage createWebServiceMessage(InputStream inputStream) throws IOException {
            setMessageFactoryForRequestContext(soap12MessageFactory);
      if (inputStream instanceof TransportInputStream) {
                TransportInputStream transportInputStream = (TransportInputStream) inputStream;
             if (soapProtocolChooser.useSoap11(transportInputStream)) {
              setMessageFactoryForRequestContext(soap11MessageFactory);
             }
            }
      SaajSoapMessageFactory mf = getMessageFactoryForRequestContext();
      if (mf == soap11MessageFactory) {
       System.out.println("Final soapMessageFactory? " + soap11MessageFactory);
      } else {
       System.out.println("Final soapMessageFactory? " + soap12MessageFactory);
      }
      return mf.createWebServiceMessage(inputStream);
        }
    
        
    }
    
    This class is not alone itself, and works with the help of some associating classes & interfaces described below.
    SoapProtocolChooser.java Interface: This is the Interface that is being defined for Choosing a SOAP Protocol chooser class at run time.
    package com.test.soap;
    
    import java.io.IOException;
    
    import org.springframework.ws.transport.TransportInputStream;
    
    public interface SoapProtocolChooser {
     public boolean useSoap11(TransportInputStream transportInputStream) throws IOException;
    }
    
    MySoapProtocolChooser.java Implementation of Above Defined Interface:
    /**
     * To change this template, choose Tools | Templates
     * and open the template in the editor.
     */
    package com.test.soap;
    
    /**
     *
     * @author PankajB
     */
    import java.io.IOException;
    import java.util.Iterator;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.springframework.ws.transport.TransportInputStream;
    
    public class MySoapProtocolChooser implements SoapProtocolChooser {
    
        private static final Log logger = LogFactory.getLog(MySoapProtocolChooser.class);
        private static final Pattern userAgentPattern = Pattern.compile("html", Pattern.CASE_INSENSITIVE);
    
        public boolean useSoap11(TransportInputStream transportInputStream) throws IOException {
            for (Iterator headerNames = transportInputStream.getHeaderNames(); headerNames.hasNext();) {
                String headerName = (String) headerNames.next();
                logger.debug("found headerName: " + headerName);
                for (Iterator headerValues = transportInputStream.getHeaders(headerName); headerValues.hasNext();) {
                    String headerValue = (String) headerValues.next();
                    logger.debug("     headerValue? " + headerValue);
                    // Something weird with case names
                  /*  if (headerName.toLowerCase().contains("user-agent")) {
                    System.out.println("UserAgent  - " + headerValue);
                    Matcher m = userAgentPattern.matcher(headerValue);
                    if (m.find()) {
                    logger.debug("Found AXIS in header.  Using SOAP 1.1");
                    return true;
                    }
                    }*/
                    // This is the code written in order to support multiple Endpints by selection of SOAP
                    if (headerName.toLowerCase().contains("content-type")) {
                        logger.debug("Content Type  - " + headerValue);
    
                        if (headerValue.trim().toLowerCase().contains("text/xml")) {
                            logger.debug("Found text/xml in header.  Using SOAP 1.1");
                            return true;
                        }
    
                    }
                }
            }
            return false;
        }
    }
    
    
    The Above code is pretty self explanatory.
    This Class is being invoked by MyFactory class in method createWebServiceMessage(InputStream) to determine which SOAP Message Factory is going to be used, either 1.1 or 1.2
That's all we required, now we can simply deploy our application to the web container after creating a WAR file.
Now simply call the HTTP URI: http://localhost:8080/SpringWSGen/service/service/SubService/subscription.wsdl
( Note: service has been writted two times because:
      1. we have service/* servlet mapping, so first one belongs to it.
      2. we have service/SubService in the dynamic WSDL generation tag in sprng-ws-servlet.xml, so second one refers to it. Obviously one can go ahead with any mappings he want )
you will see the following WSDL Generated. now it can be invoked in any tool example SOAP UI to get to know which one is being called. As the WSDL File has the bindings for both the SOAP 1.1 & SOAP 1.2

  So that's all for the time beings. here can you create Spring Web Services through this. I will have some interesting URL's thta just add to the above functionaltiy, that i will add later on.
Thanks Spring, for creating such a light weight framework.
Please let me know, in case you want the complete source code. I will host it somewhere.

9 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. If you create the xsd first (as spring-ws logic is contract first), then use the jaxb xjc tool to generate all beans (request/response and pojo). Do i still need to create the Jaxb marshaller configuration and mapping?

    ReplyDelete
  3. Also is it necessary to define the endpointMapping and soap11EndpointMapping? I understand you did it to enable your WS with SOAP 1.1/1.2
    Can i make the configuration simpler, just defining what is inside the soap11EndpointMapping?

    I m trying to make an example I found but based on spring-ws 1.5 to work with brand new way of the 2.0.2 release. I have SoapUI trying to send request soap message, but My WS cannot find any mapping for the endpoint. So I m just thinking reading your example. Is it because of my bean definition and the marshalling missing? Or is it because I have to enable SOAP 1.1 and 1.2 as Spring-WS is lightweight but won't do everything for you...

    Thanks in advance for your answers

    ReplyDelete
  4. 1. YES, you need to create the JAXMarshaller and define the mapping. The reason is because by this way you are informing spring, that this class will be used to map either the request/response xml.
    2. SOAP 1.1 or 1.2 is just a choice, based upon how the service is being created, or how you want it to be, because of the difference in the MIME type of both the types.
    3. Yes, i think you can make your configuration simpler by defining only soap11Endpointmapping
    4. If you want to dive in little details, here is the link. http://krams915.blogspot.com/

    ReplyDelete
  5. I already read this blog. But it was not using jaxb. It uses Castor as OXM API

    My problem is, may be it is just eclipse playing with me, if I define the same spring-ws-servlet as you did. I got validation problem. Any chance I can download the full project.

    Can you provide the jars dependency you have to run your WebService application. May be I m just missing something.

    Fairly new to SoapUI, so I just located the WSDL, and make it create the simple request from that. But when running it, My WebService application keeps on not finding the endpoint and just gave him a 404 http error.

    Any advice is welcome.

    ReplyDelete
  6. Okay, my bad, I just did not understand the namespace power: referencing in my spring config a spring-beans 3.0 whereas I m using Spring 3.1 can hurt you badly.

    You definitely help me.

    ReplyDelete
  7. Thanks very much, this was REALLY helpful!

    ReplyDelete
  8. Thanks for this, I was able to set up Spring Integration with Spring Web Services to use both SOAP 1.1 and SOAP 1.2. I did not need to use the marshaling, or auto set up the WSDL, the one I already had worked. In your WSDL file, what are the binding differences between SOAP 1.1 and 1.2?

    ReplyDelete
  9. I have come to the same problem.
    Thanks a lot for the factory implemenation, it works well.

    ReplyDelete