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