Search code examples
springspring-wswsdl2java

How does Jaxb2Marshaller work in a spring-ws service?


I an new to Spring Web Service and I want to know how spring ws works. I have done a simple spring ws according to the spring ws tutorial. But I don't quite understand the internal mechanism, especially the marshaling/unmarshaling part. Unlike the spring tutorial, here I use the JAXB2 instead of JDOM for marshaling.

Generally, my question is How does Jaxb2Marshaller work in a spring-ws service? or Can anyone explain the detail processing logic for the incoming/outgoing message?

Here I had tried to debug the service and I'd like to provide a detail step to build the environment.

Step0: create a spring-ws project with maven:

  mvn archetype:create -DarchetypeGroupId=org.springframework.ws \
  -DarchetypeArtifactId=spring-ws-archetype \
  -DarchetypeVersion=2.1.4.RELEASE \
  -DgroupId=com.jeecourse.hr \
  -DartifactId=holidayServiceV3

Step1: update pom.xml:

<?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>com.jeecourse.hr</groupId>
<artifactId>holidayServiceV3</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>holidayService Spring-WS Application</name>
<url>http://www.springframework.org/spring-ws</url>
<build>
    <finalName>holidayServiceV3</finalName>
    <plugins>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>1.5</source>
                <target>1.5</target>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>tomcat-maven-plugin</artifactId>
            <version>1.1</version>
        </plugin>
    </plugins>
    </build>
    <dependencies>
    <dependency>
        <groupId>org.springframework.ws</groupId>
        <artifactId>spring-ws-core</artifactId>
        <version>2.1.4.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>com.sun.xml.messaging.saaj</groupId>
        <artifactId>saaj-impl</artifactId>
        <version>1.3.23</version>
    </dependency>
    </dependencies>
    </project>

Step2: update web.xml as 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>MyCompany HR Holiday Service</display-name>
<!-- take especial notice of the name of this servlet -->
       <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>

Step3: update the spring-ws-serlvet.xml in WEB-INF/

<?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"
    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.jeecourse.hr" />
    <sws:annotation-driven />

    <bean name="hrSchema-v1.0" class="org.springframework.xml.xsd.SimpleXsdSchema">
        <property name="xsd" value="classpath:com/jeecourse/holidayService/ws/v1/hrSchema-v1.0.xsd" />
    </bean>

    <bean id="hrService-v1.0" class="org.springframework.ws.wsdl.wsdl11.SimpleWsdl11Definition">
        <constructor-arg value="classpath:com/jeecourse/holidayService/ws/v1/hrService-v1.0.wsdl" />
    </bean>

    <bean id="hrMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
        <property name="contextPath" value="com.jeecourse.hr.ws.v1.beans" />
    </bean>

    <bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
    </bean>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory">
        <property name="messageFactory">
            <bean class="com.sun.xml.messaging.saaj.soap.ver1_1.SOAPMessageFactory1_1Impl" />
        </property>
    </bean>
    <bean id="messageReceiver" class="org.springframework.ws.soap.server.SoapMessageDispatcher" />
</beans>

Step4: create hrSchema-v1.0.xsd & hrService-v1.0.wsdl:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:hr="http://www.jeecourse.com/hr/schemas" elementFormDefault="qualified"
targetNamespace="http://www.jeecourse.com/hr/schemas">
<xs:element name="HolidayRequest">
    <xs:complexType>
        <xs:all>
            <xs:element name="Holiday" type="hr:HolidayType" />
            <xs:element name="Employee" type="hr:EmployeeType" />
        </xs:all>
    </xs:complexType>
</xs:element>

<xs:element name="HolidayResponse" type = "xs:boolean" />

<xs:complexType name="HolidayType">
    <xs:sequence>
        <xs:element name="StartDate" type="xs:date" />
        <xs:element name="EndDate" type="xs:date" />
    </xs:sequence>
</xs:complexType>
<xs:complexType name="EmployeeType">
    <xs:sequence>
        <xs:element name="Number" type="xs:integer" />
        <xs:element name="FirstName" type="xs:string" />
        <xs:element name="LastName" type="xs:string" />
    </xs:sequence>
</xs:complexType>

<wsdl:message name="HolidayRequest">
    <wsdl:part element="schema:HolidayRequest" name="HolidayRequest" />
</wsdl:message>

<wsdl:message name="HolidayResponse">
    <wsdl:part element="schema:HolidayResponse" name="HolidayResponse" />
</wsdl:message>

<wsdl:portType name="HumanResource">
    <wsdl:operation name="Holiday">
        <wsdl:input message="tns:HolidayRequest" name="HolidayRequest" />
        <wsdl:output message="tns:HolidayResponse" name="HolidayResponse" />
    </wsdl:operation>

</wsdl:portType>

<wsdl:binding name="HumanResourceBinding" type="tns:HumanResource">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
    <wsdl:operation name="Holiday">
        <soap:operation soapAction="http://www.jeecourse.com/RequestHoliday" />
        <wsdl:input name="HolidayRequest">
            <soap:body use="literal" />
        </wsdl:input>
        <wsdl:output name="HolidayResponse">
            <soap:body use="literal" />
        </wsdl:output>
    </wsdl:operation>
</wsdl:binding>

<wsdl:service name="HumanResourceService">
    <wsdl:port binding="tns:HumanResourceBinding" name="HumanResourcePort">
        <soap:address location="http://localhost:8080/holidayServiceV3/" />
    </wsdl:port>
</wsdl:service>

Step5: wsimport wsdl to generate beans: (please change the path accordingly.)

    C:\Data\Study\holidayServiceV3\src\main\resources\com\jeecourse\holidayService\ws\v1>wsimport -verbose -s C:\Data\Study\holidayServiceV3\src\main\java -p com.jeecourse.hr.ws.v1.beans hrService-v1.0.wsdl
parsing WSDL...

step6: create the service:

package com.jeecourse.hr.service;

import java.util.Date;

public interface HumanResourceService {
    boolean bookHoliday(Date startDate, Date endDate, String name);
}

create the service impl:

    package com.jeecourse.hr.service;

    import java.util.Date;

    import org.springframework.stereotype.Service;

    @Service
    public class StubHumanResourceService implements HumanResourceService {
       public boolean bookHoliday(Date startDate, Date endDate, String name) {
        System.out.println("Booking holiday for [" + startDate + "-" + endDate + "] for [" + name + "] ");
        return true;
    }
}

step7: create endpoint:

   package com.jeecourse.hr.ws.v1;

   import java.util.Date;
   import javax.xml.bind.JAXBElement;
   import org.springframework.beans.factory.annotation.Autowired;
   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;
   import com.jeecourse.hr.service.HumanResourceService;
   import com.jeecourse.hr.ws.v1.beans.HolidayRequest;
   import com.jeecourse.hr.ws.v1.beans.ObjectFactory;

   @Endpoint
   public class HolidayEndpoint {
    private static final String NAMESPACE_URI = "http://www.jeecourse.com/hr/schemas";
    private static final ObjectFactory OBJ_FACTORY = new ObjectFactory();
    private HumanResourceService humanResourceService;

    @Autowired
    public HolidayEndpoint(HumanResourceService humanResourceService) {
    this.humanResourceService = humanResourceService;
    }

    @PayloadRoot(namespace = NAMESPACE_URI, localPart = "HolidayRequest")
    public @ResponsePayload JAXBElement<Boolean> handleHolidayRequest(@RequestPayload JAXBElement<HolidayRequest> holidayRequest) throws Exception {
    HolidayRequest hlRequest = holidayRequest.getValue();

    Date startDate = hlRequest.getHoliday().getStartDate().toGregorianCalendar().getTime();
    Date endDate = hlRequest.getHoliday().getEndDate().toGregorianCalendar().getTime();
    String name = hlRequest.getEmployee().getFirstName() + hlRequest.getEmployee().getLastName();

    boolean result = humanResourceService.bookHoliday(startDate, endDate, name);
    return OBJ_FACTORY.createHolidayResponse(result);
    }
       }

Step8: run in web server(jetty) and start soap ui to make a request:

    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"     xmlns:sch="http://www.jeecourse.com/hr/schemas">
       <soapenv:Header/>
       <soapenv:Body>
          <sch:HolidayRequest>
             <!--You may enter the following 2 items in any order-->
             <sch:Holiday>
                <sch:StartDate>2012-11-05T16:38:30</sch:StartDate>
                <sch:EndDate>2012-11-15T16:38:30</sch:EndDate>
             </sch:Holiday>
             <sch:Employee>
                <sch:Number>1001</sch:Number>
                <sch:FirstName>Tom</sch:FirstName>
                <sch:LastName>Jerry</sch:LastName>
             </sch:Employee>
          </sch:HolidayRequest>
       </soapenv:Body>
    </soapenv:Envelope>

This spring-ws can work. But I want to debug to know the detail of the internal processing logic.

If I put break points in the org.springframework.oxm.jaxb.Jaxb2Marshaller, I can find the Jaxb2Marshaller is initialized and afterPropertiesSet is called with following message in console:

INFO: Creating JAXBContext with context path [com.jeecourse.hr.ws.v1.beans]

But the method marshal/unmarshal is never hit. So who has done the marshaling/unmarshaling? What's the intention of Jaxb2Marshaller here? Thanks.


Solution

  • I don't know the internals of Spring WS, but I looked over the code. First of all, if you enable DEBUG logging you will see that marshalling/unmarshalling is done by another class: JaxbElementPayloadMethodProcessor with logs like this:

    DEBUG [org.springframework.ws.server.endpoint.adapter.method.jaxb.JaxbElementPayloadMethodProcessor] - <Unmarshalled payload request to [javax.xml.bind.JAXBElement@2bd6a41e]>
    ...
    DEBUG [org.springframework.ws.server.endpoint.adapter.method.jaxb.JaxbElementPayloadMethodProcessor] - <Marshalling [javax.xml.bind.JAXBElement@17fa911e] to response payload>
    

    Looking over the source code of Spring WS 2.1.4 I see that an instance of this class is constructed in the class org.springframework.ws.config.AnnotationDrivenBeanDefinitionParser which comes into play when <sws:annotation-driven/> is used.

    So, it may be possible that everything your sample needs to work is <sws:annotation-driven/> which "auto-magically" creates and instantiates the necessary beans.