Pages

Friday, September 28, 2012

Static WSDL with imported XML Schema in Spring Web Service

Spring Web Service provides a contract-first way (which is an industry preferred way) to develop web services using java and the spring framework. Basically you need to design the contract first for making the web service call . Such contract can be defined in XML Schemas (xsd files). The web service description language (WSDL) file is used to carry these XML schemas.

Dynamic WSDL generation or Static WSDL 

Spring Web Service has a very handy feature to generate a dynamic WSDL file based on a set of defined xsd files. Here's an example on how to use this handy feature to create a spring web service.

However, in some cases, we already have a WSDL file created and this WSDL file sometimes does not contain the whole defined XML schema in it. Instead, it imports other separate xsd files (maybe for the reason of modular design).

Exposing imported XSD files in the static WSDL file, Unanswered?

Spring Web Service does support static WSDL file. However, if the WSDL file, as stated before, has imported xsd files, the developed web service by default does not work. Some people already complained this problem. For example, the links below lists the related problem.

http://forum.springsource.org/showthread.php?102269-XSD-import-in-WSDL-using-relative-path

http://forum.springsource.org/showthread.php?50496-Using-static-WSDL-that-imports-an-XSD-must-be-able-to-resolve-schemaLocation-URI

http://stackoverflow.com/questions/2378829/spring-map-a-file-to-a-url-uri


The weird thing is there seems no clear answer to the question. 

I then have to search on and found this bug (https://jira.springsource.org/browse/SWS-281) of the spring web service, which is raised exactly for this problem. This bug has been fixed since  spring-ws1.5.1. This means there should be a way to resolve the problem of expose imported XSD files in the static WSDL file. But the description of the bug fixing failed to explain clearly how to do it.

Therefore, I have to delve in the very detail (reading the spring source code, turning on all the debugging logs and etc) to find the solution for myself. 

The Simple Solution and the Illustrated Example

The solution turns out to be very easy actually. For example, lets say we have a WSDL file (based on the previous example of spring web service) and the imported XSD file as follows.

WSDL file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
    xmlns:sch="http://robin.mytechtip.com/springws2example/temperature/schemas"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:tns="http://robin.mytechtip.com/springws2example/temperature/schemas"
    targetNamespace="http://robin.mytechtip.com/springws2example/temperature/schemas">
    <wsdl:types>
        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
            attributeFormDefault="unqualified" 
            elementFormDefault="qualified">
            <xs:import
                namespace="http://robin.mytechtip.com/springws2example/temperature/schemas"
                schemaLocation="temperature-schema.xsd"></xs:import>
        </xs:schema>
    </wsdl:types>
    <wsdl:message name="GetTemperaturesResponse">
        <wsdl:part 
            element="tns:GetTemperaturesResponse" 
            name="GetTemperaturesResponse">
        </wsdl:part>
    </wsdl:message>
    <wsdl:message name="GetTemperaturesRequest">
        <wsdl:part 
            element="tns:GetTemperaturesRequest" 
            name="GetTemperaturesRequest">
        </wsdl:part>
    </wsdl:message>
    <wsdl:portType name="TempeatureService">
        <wsdl:operation name="GetTemperatures">
            <wsdl:input 
                message="tns:GetTemperaturesRequest" 
                name="GetTemperaturesRequest">
            </wsdl:input>
            <wsdl:output 
                message="tns:GetTemperaturesResponse" 
                name="GetTemperaturesResponse">
            </wsdl:output>
        </wsdl:operation>
    </wsdl:portType>
    <wsdl:binding 
        name="TempeatureServiceSoap11" 
        type="tns:TempeatureService">
        <soap:binding 
            style="document"
            transport="http://schemas.xmlsoap.org/soap/http" />
        <wsdl:operation name="GetTemperatures">
            <soap:operation soapAction="" />
            <wsdl:input name="GetTemperaturesRequest">
                <soap:body use="literal" />
            </wsdl:input>
            <wsdl:output name="GetTemperaturesResponse">
                <soap:body use="literal" />
            </wsdl:output>
        </wsdl:operation>
    </wsdl:binding>
    <wsdl:service name="TempeatureServiceService">
        <wsdl:port 
            binding="tns:TempeatureServiceSoap11" 
            name="TempeatureServiceSoap11">
            <soap:address 
                location="http://localhost:8080/TestWS2/services/temperature" />
        </wsdl:port>
    </wsdl:service>
</wsdl:definitions>

XSD file
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    attributeFormDefault="unqualified" elementFormDefault="qualified"
    targetNamespace="http://robin.mytechtip.com/springws2example/temperature/schemas">

    <xs:element name="GetTemperaturesRequest">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="city" type="xs:string" />
                <xs:element 
                    maxOccurs="5" minOccurs="1" 
                    name="date" type="xs:date" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:element name="GetTemperaturesResponse">
        <xs:complexType>
            <xs:sequence>
                <xs:element 
                    maxOccurs="5" minOccurs="1" 
                    name="TemperatureInfo">
                    <xs:complexType>
                        <xs:sequence>
                            <xs:element name="min" type="xs:float" />
                            <xs:element name="max" type="xs:float" />
                            <xs:element name="average" type="xs:float" />
                        </xs:sequence>
                        <xs:attribute 
                            name="city" 
                            type="xs:string" 
                            use="optional" />
                        <xs:attribute 
                            name="date" 
                            type="xs:date" 
                            use="optional" />
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>

By default, the spring web service servlet beans definition file (with the static WSDL definition) looks like the following (the one that will NOT work):

<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"
    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">

    <context:component-scan 
        base-package="com.mytechtip.robin.springws2example" />

    <sws:annotation-driven />
    
    <bean 
        class="com.mytechtip.robin.springws2example.TemperatureServiceImpl">
    </bean>

    <bean
        class="org.springframework.ws.server.endpoint.adapter.GenericMarshallingMethodEndpointAdapter">
        <property name="marshaller" ref="marshaller" />
        <property name="unmarshaller" ref="marshaller" />
    </bean>

    <bean id="marshaller" 
        class="org.springframework.oxm.xmlbeans.XmlBeansMarshaller">
    </bean>

    <sws:static-wsdl 
        id="temperature" 
        location="/WEB-INF/wsdl/temperature.wsdl"/>
    
    <!--
    <sws:dynamic-wsdl id="temperature" portTypeName="TempeatureService"
        locationUri="/services/temperature" >
        <sws:xsd location="/WEB-INF/temperature.xsd" />
    </sws:dynamic-wsdl>
     -->

</beans>

Using the above servlet bean file, you will get the following error when accessing the xsd file:

To fix the problem, all you need to do is to add the following bean definition (in bold font) in the beans definition file. Please make sure the id of that bean should be the exact XSD file name (without the .xsd suffix) you import in the WSDL file.
<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"
    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">

    <context:component-scan 
        base-package="com.mytechtip.robin.springws2example" />

    <sws:annotation-driven />
    
    <bean 
        class="com.mytechtip.robin.springws2example.TemperatureServiceImpl">
    </bean>

    <bean
        class="org.springframework.ws.server.endpoint.adapter.GenericMarshallingMethodEndpointAdapter">
        <property name="marshaller" ref="marshaller" />
        <property name="unmarshaller" ref="marshaller" />
    </bean>

    <bean id="marshaller" 
        class="org.springframework.oxm.xmlbeans.XmlBeansMarshaller">
    </bean>

    <bean 
        id="temperature-schema" 
        class="org.springframework.xml.xsd.SimpleXsdSchema">
        <property 
            name="xsd" 
            value="/WEB-INF/wsdl/temperature-schema.xsd">
        </property>
    </bean>
    
    <sws:static-wsdl 
        id="temperature" 
        location="/WEB-INF/wsdl/temperature.wsdl"/>
    
    <!-- 
    <sws:dynamic-wsdl id="temperature" portTypeName="TempeatureService"
        locationUri="/services/temperature" >
        <sws:xsd location="/WEB-INF/temperature.xsd" />
    </sws:dynamic-wsdl>
     -->

</beans>

How does the internal work? if you want, you may look at the the code of MessageDispatcherServlet and its related code

3 comments:

  1. Thanks, you bold line works for me.

    ReplyDelete
  2. I assume you didn't try yet with xsd files located in sub-folders around the wsdl?
    Would you have a solution for this scenario also?

    ReplyDelete