Wednesday, October 23, 2013

Building Webservices using Spring

In this post, I'm going to explain how to write a contract first webservice using Spring.

We are going to create a service for creating a person record. We are going to call the service as 'CreatePersonService'. It will accept first name, middle name and last name as parameters and return a id parameter back as a response.

We need to first define the XSD representing the person record. Also, we need an XSD to represent the request to create the Person record. 

Let us first start with the maven dependencies required:

pom.xml:

We will be using jaxb for generating classes from xsd.

<dependency> 
    <groupId>org.springframework.ws</groupId>
    <artifactId>spring-ws-core</artifactId>
    <version>2.1.3.RELEASE</version>
    <type>jar</type>
    <scope>compile</scope>
</dependency>
      
<dependency>
    <groupId>org.dom4j</groupId>  
    <artifactId>dom4j</artifactId>  
    <version>1.0</version>
    <type>jar</type>
    <scope>provided</scope>  
</dependency>   

<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>3.0</version>
      <type>jar</type> 
      <scope>compile</scope>
</dependency>
<dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.5</version>
      <type>jar</type> 
      <scope>compile</scope>
</dependency>
<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>${spring.version}</version>
</dependency>
<!-- Spring 3 dependencies -->
<dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring.version}</version>
</dependency>
 
<dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>${spring.version}</version>
</dependency>
 
<dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>${spring.version}</version>
</dependency>
<dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
</dependency>

<dependency>
        <groupId>org.apache.ws.xmlschema</groupId>
        <artifactId>xmlschema-core</artifactId>
        <version>2.0.3</version>
</dependency>

Also in order to configure jaxb, you will need the following in the plugin section of pom.xml:
<plugin>  
        <groupId>org.codehaus.mojo</groupId>  
        <artifactId>jaxb2-maven-plugin</artifactId>  
        <version>1.4</version>  
        <executions>  
              <execution>  
                 <goals>  
                      <goal>xjc</goal>  
                 </goals>  
                 <phase>generate-sources</phase>  
              </execution>  
        </executions>  
        <configuration>  
            <clearOutputDir>false</clearOutputDir>  
            <outputDirectory>src/main/java</outputDirectory>  
            <schemaDirectory>src/main/webapp/schemas</schemaDirectory>  
            <includeSchema>**/*.xsd</includeSchema>             
            <enableIntrospection>false</enableIntrospection>  
        </configuration>  
</plugin>  


Service Interface and Implementation:

Create the following Interface and impl class:

public interface PersonService {
      public String createPerson(String firstName,String middleName,String lastName);
}

@Service
public class PersonServiceImpl implements PersonService{
       public String createPerson(String firstName,String middleName,String lastName){
                String userId = "";
                //Create Person
  
                return userId;
       }
}

Person XSD:

Create a xsd 'Person.xsd' in webapp/schemas folder to represent Person as below. Note the use of namespace attributes. I have used a namespace "http://schemas.myproduct.myorg.com". It will create package structure of the form "com.myorg.myproduct.schemas" for this namespace usage.


<?xml version="1.0" encoding="UTF-8"?>  
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
      xmlns="http://schemas.myproduct.myorg.com" 
      targetNamespace="http://schemas.myproduct.myorg.com" 
      elementFormDefault="qualified" attributeFormDefault="unqualified">  

      <xs:element name="Person" type="Person"/>  
      <xs:complexType name="Person">  
           <xs:sequence>  
                <xs:element name="FirstName" type="xs:string"/>  
                <xs:element name="MiddleName" type="xs:string"/>  
                <xs:element name="LastName" type="xs:string"/>  
           </xs:sequence>  
      </xs:complexType>  
</xs:schema> 

CreatePersonOperation XSD:

Create a xsd 'CreatePersonOperation.xsd' in webapp/schemas folder to represent the request operation for our webservice as below.

<?xml version="1.0" encoding="UTF-8"?>  
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
      xmlns="http://com/myorg/myproduct/webservices/createpersonservice" 
      targetNamespace="http://com/myorg/myproduct/webservices/createpersonservice" 
      elementFormDefault="qualified" attributeFormDefault="unqualified">
  
  
    <xs:element name="CreatePersonRequest">  
           <xs:complexType>  
                <xs:sequence>  
                      <xs:element name="FirstName" type="xs:string"/>
                        <xs:element name="MiddleName" type="xs:string"/>  
                 <xs:element name="LastName" type="xs:string"/>  
                </xs:sequence>  
           </xs:complexType>  
     </xs:element>  
     <xs:element name="CreatePersonResponse">  
           <xs:complexType>  
                <xs:sequence>  
                    <xs:element name="response" type="xs:string"/>  
                </xs:sequence>  
           </xs:complexType>  
      </xs:element>  
  
</xs:schema>

Changes to Spring config xml:

Add the following namespace attributes to the config xml. Also make sure the annotation config is there to allow spring container to create the necessary beans.

<beans xmlns="http://www.springframework.org/schema/beans"  
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns:sws="http://www.springframework.org/schema/web-services"
        xmlns:mvc="http://www.springframework.org/schema/mvc"  
        xmlns:context="http://www.springframework.org/schema/context"  
        xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd 
        http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-2.0.xsd">  
        
     
       <context:component-scan base-package="com.myorg" />
       <mvc:annotation-driven/>
       <sws:annotation-driven/>
       
       <bean id="CreatePersonService" class="org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition" lazy-init="true">  
         <property name="schemaCollection">  
         <bean class="org.springframework.xml.xsd.commons.CommonsXsdSchemaCollection">  
                <property name="inline" value="true" />  
                <property name="xsds">  
                        <list>  
                        <value>schemas/CreatePersonOperation.xsd</value>  
                        </list>  
                </property>  
                </bean>  
                </property>  
                <property name="portTypeName" value="CreatePersonService"/>  
                <property name="serviceName" value="CreatePersonService" />  
                <property name="locationUri" value="/endpoints"/>  
      </bean>      

</beans>

Changes to web.xml:

The webservice request will go through as a http request and is handled by the container. So we need to configure this in web.xml as below:

<servlet>  
        <servlet-name>webservices</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>  
            <init-param>  
                <param-name>contextConfigLocation</param-name>  
                 <param-value></param-value>  
            </init-param>  
            <load-on-startup>1</load-on-startup>  
       </servlet>  
       <servlet-mapping>  
            <servlet-name>webservices</servlet-name>  
            <url-pattern>*.wsdl</url-pattern>  
       </servlet-mapping>  
       <servlet-mapping>  
            <servlet-name>webservices</servlet-name>  
            <url-pattern>/endpoints/*</url-pattern>  
       </servlet-mapping>
</servlet>       

Endpoint Class:

Create a class which represents the webservice endpoint as below. Note that this class needs to be created after you compile the project once and there by generating the JAXB mapping classes from the xsd. I have also provided the import statements for this class for clarity.

package com.myorg.service.endpoint;

import org.springframework.beans.factory.annotation.Autowired;

import com.myorg.myproduct.webservices.createpersonservice.CreatePersonRequest;
import com.myorg.myproduct.webservices.createpersonservice.CreatePersonResponse;
import com.myorg.service.PersonService;
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;


@Endpoint
public class PersonServiceEndPoint {
private static final String TARGET_NAMESPACE = "http://com/myorg/myproduct/webservices/createpersonservice";
 
        @Autowired
        private PersonService personService;

  
        public void setPersonService(PersonService personService) {
                this.personService = personService;
        }




        @PayloadRoot(localPart = "CreatePersonRequest", namespace = TARGET_NAMESPACE)  
        public @ResponsePayload CreatePersonResponse createPerson(@RequestPayload CreatePersonRequest request) {
                CreatePersonResponse response = new CreatePersonResponse();
  
                String userId = personService.createPerson(request.getFirstName(), request.getMiddleName(), request.getLastName());
                response.setResponse(userId);
                return response;
        }
}


After compiling the project, you will notice that additional classes would have been created under com.myorg.myproduct.webservices.createpersonservice and com.myorg.myproduct.schemas  packages. Notice that a WSDL is not generated as a static resource file. Instead, the WSDL file is generated dynamically on the fly by the Spring container.

To test you can run the following in the browser to obtain the wsdl:
http://localhost:8080/testbed/endpoints/CreatePersonService.wsdl

No comments:

Post a Comment