Friday, 26 July 2013

SOAP WebService

SOAP is a common type of webservice.  Spring has a lot of support for creating SOAP webservices.  Below is an example configuration.

Maven Dependencies

There are a number of dependencies required for wiring up spring webservices.  The two spring ones here are obvious.  The org.apache.ws.xmlschema allows multiple schemas to be used and spring wraps some of the functionality of this dependency.  You may not need it as a compile dependency but certainly as a runtime.  The wsdl4j dependency allows a dynamic wsdl to be created.


        <dependency>
            <groupId>org.springframework.ws</groupId>
            <artifactId>spring-ws</artifactId>
            <version>${org.springframework.ws.version}</version>
            <scope>compile</scope>
            <classifier>all</classifier>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${org.springframework.version}</version>
            <scope>compile</scope>
        </dependency>
        
        <dependency>
            <groupId>org.apache.ws.xmlschema</groupId>
            <artifactId>xmlschema-core</artifactId>
            <version>2.0.1</version>
            <scope>runtime</scope>
        </dependency>
        
        <dependency>
            <groupId>wsdl4j</groupId>
            <artifactId>wsdl4j</artifactId>
            <version>1.6.3</version>
            <scope>compile</scope>
        </dependency>

server.xml Configuration

The server.xml configuration is very straightforward.  Firstly though a webservice in spring is usually wired with the org.springframework.ws.transport.http.MessageDispatcherServlet rather than the more common org.springframework.web.servlet.DispatcherServlet.  It means that the server.xml in WEB-INF is as follows,

    <display-name>My web service</display-name>

    <!-- This servlet is the actual web service -->
    <servlet>
<servlet-name>my-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>
<load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
<servlet-name>my-ws</servlet-name>
<url-pattern>/ws/*</url-pattern>
    </servlet-mapping>


The transformWsdlLocations parameter means that spring will automatically adjust the location of the service, published in the wsdl.

Spring xml configuration

The spring configuration has a number of options.

Standard Spring 
There are a few standard tags for spring ws.  These are below

    <context:component-scan base-package="com.me.web.ws" />
    
    <!-- Enable @Required -->
    <context:annotation-config />
    
    <!-- Enable @Endpoint, @PayloadRoot, etc -->
    <sws:annotation-driven />


Defining the Schemas
The schemas can be defined using a schema collection.  The advantages are that multiple schemas can be used and the spring bean that holds them all can be reused.  The schemaCollection is the reason for the apache pom entry above.  The schemas can be defined using,

    <bean id="schemas" class="org.springframework.xml.xsd.commons.CommonsXsdSchemaCollection">
        <property name="xsds">
            <list>
                <value>classpath:/schema_1.xsd</value>
                <value>classpath:/schema_2.xsd</value>
            </list>
        </property>
        <property name="inline" value="true" />
    </bean>


Defining the WSDL
The WSDL can be defined automatically by spring from the schemas.  If you want to create a static wsdl yourself then spring has plenty of support for publishing this too.  However, in most cases it is easier this way,

    <bean id="webservice" class="org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition">
        <property name="schemaCollection" ref="schemas"/>
        <property name="portTypeName" value="MyWsPortType"/>
        <property name="locationUri" value="/ws/mywebservice"/>
    </bean>

Here the location defines the link to the wsdl.  In this case it would be

  http://<server>:<port>/<context><locationUri><id>.wsdl

  http://<server>:<port>/<context>/ws/mywebservice/webservice.wsdl

Defining the wsdl this way also allows us to reuse the schemaCollection that was defined above.  There is an optional property for 'requestSuffix' and 'responseSuffix'.  These are the suffixes to define the entry points for the webservice.  By default they are 'Request' and 'Response'.  If you have not objects in your schema which end with 'Request' or 'Response' then there will be no obvious entry points for your webservice.

There is a shorthand way of defining the wsdl with springs sws tags. However, the schemaCollection cannot be used for this.

     <sws:dynamic-wsdl  id="possession" portTypeName="PossessionPortType" locationUri="/ws/possession">
         <sws:xsd location="classpath:/schema_1.xsd"/>
         <sws:xsd location="classpath:/schema_2.xsd"/>
     </sws:dynamic-wsdl>
    

Interceptors
An interceptor is something which happens before the request is passed to the webservice or after the webservice has finished with the request.  There are two particular interceptors which are really useful.  The logging interceptor will log all the requests which are made of the service.  The validating interceptor will validate that the request and response payloads are legitimate against the defined schemas.   

    <bean id="validatingInterceptor" class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
        <property name="xsdSchemaCollection" ref="schemas" />
        <property name="validateRequest" value="true"/>
        <property name="validateResponse" value="true"/>
    </bean>

    <!-- Logging interceptor -->
    <bean id="loggingInterceptor" class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor">
        <description>
            This interceptor logs the message payload.
        </description>
    </bean>

    <sws:interceptors>
        <ref bean="validatingInterceptor" />
        <ref bean="loggingInterceptor" />
    </sws:interceptors>

Endpoint

The Endpoint is the class which actually receives the request.  It should be annotated with @Endpoint.  The actual method which is called is defined using the @PayloadRoot annotation.  The localPart is the name of the xml root element in the request.  The namespace is defined in the schema for that element. Note that if a reference to the namespace is used like this then the namespace value isn't quoted!


    public static final String WEBSERVICE_NAMESPACE = "http://www.me.com/my/webservice";

    @PayloadRoot(localPart = "ReadRequest", namespace = WEBSERVICE_NAMESPACE)                 
    @ResponsePayload
    public ReadResponse getPossessions(@RequestPayload final ReadRequest readRequest, final SoapHeader header) 
    {  
        ...
    }

If you don't want to use the @PayloadRoot annotation then an xml configuration is available

    <bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
        <property name="mappings">
            <props>
                <prop key="{http://www.me.com/my/webservice}ReadRequest">readEndpoint</prop>
            </props>
        </property>
        <property name="interceptors">
            <bean class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor" />
        </property>
    </bean>

where 'readEndpoint' is a bean defined in the context.



No comments:

Post a comment