How to use Spring Web Services and REST support in conjunction with JAXB 2 annotations

Update
Updated example application to use annotations for WS endpoint resolution

Spring Webservices encourages a contract first, message oriented approach to creating Webservices. The underlying details are completely under developer control starting from the contract to the marshalling/unmarshalling details to the endpoint handling the request. The same holds for the REST support that was just recently added to Spring MVC (Spring 3.0).

Traditionally, contract first service design has involved creating XSDs defining the messages, however using the Schema Generator (SchemaGen) found in java 6, contracts can be defined in JAXB instead.

Let us start by an example of a simple service to expose as a WS and REST web service – call it the MemberService. MemberService exposes one operation “get member details” which returns the details of a member/person, given an identifier.

We start by defining the contract using standard JAXB 2 annotations on the request:

// MemberDetailsRequest.java
@XmlRootElement(name = "MemberDetailsRequest", namespace = MemberDetailsNamespace.NAMESPACE)
@XmlType(name = "MemberDetailsRequest", namespace = MemberDetailsNamespace.NAMESPACE)
@XmlAccessorType(XmlAccessType.FIELD)
public class MemberDetailsRequest {
 
	public MemberDetailsRequest() {
	}
 
	public MemberDetailsRequest(String id) {
		this.id = id;
	}
 
	@XmlElement(required = true)
	private String id;
 
	public String getId() {
		return id;
	}
 
	public void setId(String id) {
		this.id = id;
	}
 
}

The response is defined in a similar fashion.

Based on the messages, an XSD is generated using the SchemaGen Ant task defined in the Maven pom.xml:

<taskdef name="schemagen" classname="com.sun.tools.jxc.SchemaGenTask" />
<schemagen srcdir="src/main/java" destdir="src/main/resources" 
                     includes="**/*Request.java,**/*Response.java">
   <schema namespace="http://open.bekk.no/memberservice" file="memberservice-common.xsd" />
</schemagen>

The generated memberservice-common.xsd looks like this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" 
targetNamespace="http://open.bekk.no/memberservice" 
xmlns:tns="http://open.bekk.no/memberservice" 
xmlns:xs="http://www.w3.org/2001/XMLSchema">
 
  <xs:element name="MemberDetailsRequest" type="tns:MemberDetailsRequest"/>
 
  <xs:element name="MemberDetailsResponse" type="tns:MemberDetailsResponse"/>
 
  <xs:complexType name="MemberDetailsRequest">
    <xs:sequence>
      <xs:element name="id" type="xs:string"/>
    </xs:sequence>
  </xs:complexType>
 
  <xs:complexType name="MemberDetailsResponse">
    <xs:sequence>
      <xs:element name="memberdetail" type="tns:MemberDetailType" form="qualified"/>
    </xs:sequence>
  </xs:complexType>
 
  <xs:complexType name="MemberDetailType">
    <xs:sequence>
      <xs:element name="name" type="xs:string" form="qualified"/>
      <xs:element name="phone" type="xs:string" form="qualified"/>
      <xs:element name="city" type="xs:string" form="qualified"/>
      <xs:element name="state" type="xs:string" form="qualified"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

Next, we define a web service endpoint, that will handle all WS requests. This endpoint is annotated using Spring’s @Endpoint annotation:

// MemberDetailsEndpoint.java
@Endpoint
public class MemberDetailsEndpoint {
 
	private MemberManager memberManager;
 
	@PayloadRoot(localPart = "MemberDetailsRequest", namespace = MemberDetailsNamespace.NAMESPACE)
	public MemberDetailsResponse memberDetails(MemberDetailsRequest request) {
		MemberDetail memberDetail = memberManager.getMemberDetails(request
				.getId());
		MemberDetailsResponse response = new MemberDetailsResponse(memberDetail);
		return response;
	}
 
	public void setMemberManager(MemberManager memberManager) {
		this.memberManager = memberManager;
	}
 
}

Next, we define a controller that will handle the REST requests. This class too requires a JAXB marshaller to be available, since it uses the @ResponseBody annotation and is returning a JAXB annotated class:

// MemberDetailsController.java
@Controller
public class MemberDetailsController {
 
	private MemberManager memberManager;
 
	@RequestMapping(value = "/member_details/{id}", method = RequestMethod.GET)
	@ResponseBody
	public MemberDetailsResponse memberDetails(
			@PathVariable(value = "id") String id) {
		MemberDetail detail = memberManager.getMemberDetails(id);
		MemberDetailsResponse response = new MemberDetailsResponse(detail);
		return response;
	}
 
	public void setMemberManager(MemberManager memberManager) {
		this.memberManager = memberManager;
	}
 
}

The last thing to do is to wire it all together in Spring configuration files and web.xml. Let’s start by defining the latter:

	<servlet>
		<servlet-name>ws</servlet-name>
		<servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
 
	<servlet>
		<servlet-name>rest</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
 
	<servlet-mapping>
		<servlet-name>ws</servlet-name>
		<url-pattern>/ws/*</url-pattern>
	</servlet-mapping>
 
	<servlet-mapping>
		<servlet-name>ws</servlet-name>
		<url-pattern>*.wsdl</url-pattern>
	</servlet-mapping>
 
	<servlet-mapping>
		<servlet-name>rest</servlet-name>
		<url-pattern>/rest/*</url-pattern>
	</servlet-mapping>

All request to *.wsdl and /ws/* will now be handled by the MessageDsipatcherServlet provided with Spring WS. Requests for /rest/* are handled by a standard Spring MVC DispatcherServlet.

Next, let’s look at the Spring configuration files. Common elements like the MemberManager and Jaxb2Marshaller required by both the WS web service and the REST web service go in the application-context.xml file. Config that is only intended for the WS web service, is found in the ws-servlet.xml file; similarly config intended for the REST web service, is found in the rest-servlet.xml file. Both file names are determined by Spring at runtime based on the value of the tag found in web.xml.

Let’s first have a look at ws-servlet.xml:

 
	<bean class="no.bekk.open.memberservice.endpoint.MemberDetailsEndpoint">
		<property name="memberManager" ref="memberManager" />
	</bean>
 
	<bean
		class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
		<description>An endpoint mapping strategy that looks for @Endpoint and
			@PayloadRoot annotations.</description>
	</bean>
 
	<bean
		class="org.springframework.ws.server.endpoint.adapter.MarshallingMethodEndpointAdapter">
		<description>Enables the MessageDispatchServlet to invoke methods
			requiring OXM marshalling.</description>
		<property name="marshaller" ref="jaxb2Marshaller" />
		<property name="unmarshaller" ref="jaxb2Marshaller" />
	</bean>
 
	<bean id="MemberDetails"
		class="org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition">
		<property name="schema">
			<bean class="org.springframework.xml.xsd.SimpleXsdSchema">
				<property name="xsd" value="classpath:/memberservice-common.xsd" />
			</bean>
		</property>
		<property name="portTypeName" value="memberservice" />
		<property name="serviceName" value="memberservice" />
		<property name="locationUri"
			value="http://localhost:8080/memberservice/ws/MemberDetailsRequest" />
		<property name="soapActions">
			<map>
				<entry key="MemberDetails" value="http://open.bekk.no/memberservice/MemberDetails" />
			</map>
		</property>
	</bean>

The MemberDetails bean automatically generates a WSDL based on conventions, and after you start the jetty server using mvn jetty:run, the wsdl can be accessed at: http://localhost:8080/memberservice/MemberDetails.wsdl. This is great news if you dislike writing XSDs and WSDLs as much as I do! Now in addition, you can actually run your WS web service by importing the WSDL in a tool like SOAP UI – the SOAP address location defined in the WSDL should work just fine.

As you can imagine, the enpoint mapping is defined by the PayloadRootAnnotationMethodEndpointMapping bean, which will make sure all MemberDetailsRequests are handled by our memberDetailsEndpoint bean. And, as you can see, the MarshallingMethodEndpointAdapter uses the previously defined JAXB marshaller to handle (un)marshalling of the messages.

Now, let’s move over to the REST web service, and its config that is found in rest-servlet.xml:

	<bean
		class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
		<property name="messageConverters">
			<list>
				<ref bean="marshallingHttpMessageConverter" />
			</list>
		</property>
	</bean>
 
	<bean id="marshallingHttpMessageConverter"
		class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
		<property name="marshaller" ref="jaxb2Marshaller" />
		<property name="unmarshaller" ref="jaxb2Marshaller" />
	</bean>

Since we’re using the @ResponseBody annotation and are returning a JAXB annotated class, we need to define a AnnotationMethodHandlerAdapter that will take care of converting the messages using the marshallingHttpMessageConverter that uses the previously defined jaxb2Marshaller for marshalling messages. That’s about it. Point you browser at http://localhost:8080/memberservice/rest/member_details/1 to call the REST based member service.

As you can see, we created a truly contract first WS and REST web service without writing a single line of XSD or WSDL! In addition, we ended up exposing the very same service using two different interfaces (WS and REST) – this indeed is DRY.

Final note
The source code for this application can be downloaded from: http://github.com/landro/memberservice
I’d also like to thank Biju Kunjummen for writing and excellent article on How to use Spring Web Services and letting me base my sample application on his code.

  • Anonym

    How can you call this “truly contract first”? You generate xsd from code – to me this looks like the opposite of contract first!? And the result is a bad xsd without proper restrictions and validation, and possibly interoperability problems.

  • Kaare Nilsen

    Well.. First I would second the Anonymous poster. I do not find this very contract first. Also, it kind a does not fully capture the true advantages of Spring Webservices.

    I find using oxm tools in conjunction with Spring Webservices to be somewhat wrong. If it is so that we can use Jaxb then I would opt for code first approaches. Using spring webservices with xpath or such feels more like a natural match to me. Will see if I actually can create a longer post describing my views later.

    But for now, I just would like to add that the approach in this post I would not consider a good practice for Spring WS

    [Editors note: Unspammed the comment, sorry for the delay Kaare]

  • Stefan Landrø

    Hi there,

    I expected someone would ask about that. You define the XML data format using JAXB2 annotations instead of using XSD (or relax ng or dtd for that matter). The way I see it, defining your XML data format using JAXB2 annotations is equivalent to defining it using an XSD schema – please let me know of any shortcommings in the JAXB2 specification if there are any.
    That said, it will always make sense to look through the XSD and WSDL to make sure it interoperates well.

  • Anonym

    How can you achieve this using annotations?

    Or this?

  • Anonym

    Ah.. WordPress removed my XML… Let me try again.

    Stefan: Updated your comment using

    <pre lang="xml">

    How can you achieve this using annotations?

    <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
     
    	<xs:element name="age">
    		<xs:simpleType>
    			<xs:restriction base="xs:integer">
    				<xs:minInclusive value="0"/>
    				<xs:maxInclusive value="120"/>
    			</xs:restriction>
    		</xs:simpleType>
    	</xs:element>
     
    	<xs:element name="password">
    		<xs:simpleType>
    			<xs:restriction base="xs:string">
    				<xs:pattern value="[a-zA-Z0-9]{8}"/>
    			</xs:restriction>
    		</xs:simpleType>
    	</xs:element>
     
    </xs:schema>
  • Stefan Landrø

    Running xjc in jdk 6 on your xsd, generates this:

    @XmlRegistry
    public class ObjectFactory {
     
        private final static QName _Age_QNAME = new QName("", "age");
        private final static QName _Password_QNAME = new QName("", "password");
     
        /**
         * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: no.bekk.open.jaxb
         * 
         */
        public ObjectFactory() {
        }
     
        /**
         * Create an instance of {@link JAXBElement }{@code <}{@link Integer }{@code >}}
         * 
         */
        @XmlElementDecl(namespace = "", name = "age")
        public JAXBElement<Integer> createAge(Integer value) {
            return new JAXBElement<Integer>(_Age_QNAME, Integer.class, null, value);
        }
     
        /**
         * Create an instance of {@link JAXBElement }{@code <}{@link String }{@code >}}
         * 
         */
        @XmlElementDecl(namespace = "", name = "password")
        public JAXBElement<String> createPassword(String value) {
            return new JAXBElement<String>(_Password_QNAME, String.class, null, value);
        }
     
    }

    As one can see, the restrictions on values are lost – I wasn’t aware of that. Also, there is no way of declaring restrictions using JAXB annotations – i.e. you can’t generate an XSD containing restrictions from JAXB annotated code. In addition, according to http://www.java-tips.org/java-ee-tips/java-architecture-for-xml-binding/what-is-new-in-jaxb-2.0.html validation is part of JAXP.

    Let’s say a service provided me with the XSD from the example, I generate JAXB code from it, implement the client using “1234567890″ as a password, I’d have to actually use the validation in JAXP to make sure the message looks as expected. I know for sure that a lot of programmers would never do that, since JAXP offers such a bad API. In addition, there are lots of situations where a value cannot (as far as I know) be restricted properly using XSD anyways – e.g. prime numbers. What would you do then? I know there is some fancy new bean validation coming in JEE 6 that might help validating input, but you wouldn’t get a corresponding schema.

    Is there anything a part from restrictions that cannot be represented using JAXB annotations?

  • Stefan Landrø

    Great news – seems like you can set a schema on org.springframework.oxm.jaxb.Jaxb2Marshaller. Using JAXP for validation should then not be necessary. However, how would you enforce stuff like prime numbers?

  • Anonym

    I know you can’t use restrictions for everything, but I think it is still a great tool – not only to be able to validate the input a service gets (or output the client sends), but also to *communicate* the true contract of the service from the service-provider to the clients. I’ve seen and used too many generated WSDLs that give me no clues whatsoever as to what input they expect..

    The WSDL is the contract, and you should try to make it as good as possible! If you have code in your service that validate a the input and return an error when it doesn’t match your criteria, but the criteria is not explicitly written in the WSDL, then the WSDL doesn’t really specify the contract, the code does. (Ooo.. der var det mange innskutte bisetninger.. ;-) But you don’t publish your code, so how is the client supposed to know what data is valid?

    I agree that using annotations and generating a WSDL is a nice way to work, and it may save you a lot of time. But I wouldn’t call it contract-first..

    Regarding Bean Validation – I agree with Cyrille in this thread (http://relation.to/12855.lace); I hope we can get a tool that generates the proper JSR-303 annotations based on restrictions written in the WSDL/XSD.

  • Anonym
  • Pingback: How to use Spring Web Services and REST support in conjunction with JAXB 2 annotations (part 2) – BEKK Open

  • Shameer

    Another post on Building a web service with Spring-WS using JAXB Marshaller can be found at http://justcompiled.blogspot.com/2010/09/building-web-service-with-spring-ws.html

  • vamsy krishna

     
     
     
     
     
     
     
     
     

     
     
       
       
     

     
     
      
       
        <!–
       
        –>
        
        
       
       
       
        <!–
       
        –>           
       
        <!–
       
        –>

       
       

       

        <!– You should set jvmRoute to support load-balancing via AJP ie :
                
        –> 
       

         
          <!–
         
          –>        

         
          <!–
         
          –>

         
         

         
         

           
            <!–
           
            –>

           
            <!–
           
            –>

         
       
     

    In this explain me about context tag    this one…

    docBase means what we will take here and  how to set the path