Friday 11 September 2015

Spring web service - provider (server)

Web service server (provider) with Authentication and authorization.

In this article we will build a web service provider which would take in a name and say hello. This would be a contract first web service where in we would write the WSDL first and that can be consumed from a client. We will deploy this service on apache tomcat and we would be using SOAP UI to test our web service.

We would need the following tools to build our web service.
  1. Spring tool suite: Get it from here
  2. Apache Maven: Get it from here
  3. Apache tomcat: Get it from here 
If you are new to maven you can go through the following tutorials. Maven tutorials.

Following are the steps to build a web service server. You can find the code that I have written here.Github SayHelloService

1. Writing the WSDL: For hosting our web service we would need to write our WSDL which we would expose and can be consumed by the clients.  
The WSDL has an Operation called say hello and user can send his name in the request an would get a greeting in the response.

SayHello.wsdl

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://com.karthik.spring.learning/SayHello/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="SayHello" targetNamespace="http://com.karthik.spring.learning/SayHello/">
  <wsdl:types>
    <xsd:schema targetNamespace="http://com.karthik.spring.learning/SayHello/">
      <xsd:element name="SayHelloOperationRequest">
        <xsd:complexType>
          <xsd:sequence>
            <xsd:element name="Name" type="xsd:string"/>
          </xsd:sequence>
        </xsd:complexType>
      </xsd:element>
      <xsd:element name="SayHelloOperationResponse">
        <xsd:complexType>
          <xsd:sequence>
            <xsd:element name="HelloMessage" type="xsd:string"/>
          </xsd:sequence>
        </xsd:complexType>
      </xsd:element>
    </xsd:schema>
  </wsdl:types>
  <wsdl:message name="SayHelloOperationRequest">
    <wsdl:part element="tns:SayHelloOperationRequest" name="parameters"/>
  </wsdl:message>
  <wsdl:message name="SayHelloOperationResponse">
    <wsdl:part element="tns:SayHelloOperationResponse" name="parameters"/>
  </wsdl:message>
  <wsdl:portType name="SayHello">
    <wsdl:operation name="SayHelloOperation">
      <wsdl:input message="tns:SayHelloOperationRequest"/>
      <wsdl:output message="tns:SayHelloOperationResponse"/>
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="SayHelloSOAP" type="tns:SayHello">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="SayHelloOperation">
      <soap:operation soapAction="http://com.karthik.spring.learning/SayHello/SayHelloOperation"/>
      <wsdl:input>
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output>
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="SayHello">
    <wsdl:port binding="tns:SayHelloSOAP" name="SayHelloSOAP">
      <soap:address location="http://localhost:8080/SayHelloService"/>
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

POM files for Main project,Core,war and XML modules.

Main Project POM:

In this Configuration file it has dependency to get Spring WS and Spring ws-security WSS4J (for security) from maven central repository. The main project contains 3 modules.
1. XML - WSDL and related code.
2. CORE- Java implementation code.
3. WAR - Web service configuration and packaging.
<?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/xsd/maven-4.0.0.xsd">

<!-- Main POM for SayHelloService
 Karthik Arun (karthikarun@outlook.com) -->
 
 <modelVersion>4.0.0</modelVersion>
 <artifactId>SayHelloService-pom</artifactId>
 <groupId>com.karthik.spring.learning.sayhelloservice</groupId>
 <packaging>pom</packaging>
 <version>1.0-SNAPSHOT</version>
 <modules>
  <module>xml</module>
  <module>core</module>
  <module>war</module>
 </modules>
 <dependencies>
  <dependency>
        <groupId>org.springframework.ws</groupId>
        <artifactId>spring-ws-core</artifactId>
        <version>2.1.4.RELEASE</version>
     </dependency>
  <dependency>
     <groupId>org.springframework.ws</groupId>
     <artifactId>spring-ws-security</artifactId>
     <version>2.1.4.RELEASE</version>
  </dependency> 
  <dependency>
  <groupId>com.sun.xml.stream</groupId>
   <artifactId>sjsxp</artifactId>
   <version>1.0.2</version>
  </dependency>  
  <dependency>
   <groupId>org.apache.ws.security</groupId>
   <artifactId>wss4j</artifactId>
   <version>1.6.5</version>
  </dependency>
  <dependency> 
       <groupId>org.springframework</groupId> 
       <artifactId>spring-aspects</artifactId> 
       <version>3.1.2.RELEASE</version> 
  </dependency> 
  <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>1.6.12</version>
  </dependency>     
 </dependencies>
</project>

Core module POM:

In this configuration file we have dependency on XML module to get the generated classes so that an endpoint can be written to listen to web service requests.
<?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">
 <!-- Core POM for SayHelloService
 Karthik Arun (karthikarun@outlook.com) -->
 <parent>
  <groupId>com.karthik.spring.learning.sayhelloservice</groupId>
  <artifactId>SayHelloService-pom</artifactId>
  <version>1.0-SNAPSHOT</version>
 </parent>
 <modelVersion>4.0.0</modelVersion>
 <artifactId>SayHelloService-core</artifactId>
 <packaging>jar</packaging>
 <build>
  <plugins>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
     <target>1.7</target>
     <source>1.7</source>
    </configuration>
   </plugin>
  </plugins>
 </build>
 <dependencies>
  <dependency>
   <groupId>jdom</groupId>
   <artifactId>jdom</artifactId>
   <version>1.0</version>
  </dependency>
  <dependency>
   <groupId>org.apache.ws.commons.schema</groupId>
   <artifactId>XmlSchema</artifactId>
   <version>1.4.3</version>
  </dependency>
  <dependency>
   <groupId>${project.groupId}</groupId>
   <artifactId>SayHelloService-xml</artifactId>
   <version>${project.version}</version>
  </dependency>
 </dependencies>
</project>


WAR module POM:

In this configuration file we define dependency to get all modules and package them and war when hosted would be having a context root /SayHelloService

<?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">
 <!-- WAR POM for SayHelloService
 Karthik Arun (karthikarun@outlook.com) -->
 <parent>
  <groupId>com.karthik.spring.learning.sayhelloservice</groupId>
  <artifactId>SayHelloService-pom</artifactId>
  <version>1.0-SNAPSHOT</version>
 </parent>
 <modelVersion>4.0.0</modelVersion>
 <artifactId>SayHelloService-war</artifactId>
 <packaging>war</packaging>
 <dependencies>
   <dependency>
    <groupId>${project.groupId}</groupId>
    <artifactId>SayHelloService-core</artifactId>
    <version>${project.version}</version>
   </dependency>
 </dependencies>
 <build>
  <plugins>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
     <target>1.7</target>
     <source>1.7</source>
    </configuration>
   </plugin>
   <plugin>
    <artifactId>maven-war-plugin</artifactId>
    <version>2.1</version>
    <configuration>
     <!-- <packagingExcludes>WEB-INF/lib/*.jar</packagingExcludes> -->
    <!--  <modules>
               <webModule>
                 <contextRoot>/SayHelloService</contextRoot>
               </webModule>
             </modules> -->
    </configuration>
   </plugin>
  </plugins>
  <finalName>SayHelloService</finalName>
 </build>
</project>


XML module POM:

In this configuration file we would be using the Jaxb2-maven-plugin to compile the WSDL and generate resources from the WSDL.
<?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">
 <!-- XML POM for SayHelloService
 Karthik Arun (karthikarun@outlook.com) -->
 <parent>
  <groupId>com.karthik.spring.learning.sayhelloservice</groupId>
  <artifactId>SayHelloService-pom</artifactId>
  <version>1.0-SNAPSHOT</version>
 </parent>
 <modelVersion>4.0.0</modelVersion>
 <artifactId>SayHelloService-xml</artifactId>
 <packaging>jar</packaging>
 <build>
  <resources>
   <resource>
    <directory>src/main/resources</directory>
    <includes>
     <include>**/*.properties</include>
    </includes>
    <filtering>true</filtering>
   </resource>
   <resource>
    <directory>src/main/resources</directory>
    <excludes>
     <exclude>**/*.properties</exclude>
    </excludes>
   </resource>
  </resources>
  <plugins>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
     <source>1.7</source>
     <target>1.7</target>
    </configuration>
   </plugin>
   <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>jaxb2-maven-plugin</artifactId>
    <version>1.3.1</version>
    <executions>
     <execution>
      <id>xjc</id>
      <goals>
       <goal>xjc</goal>
      </goals>
     </execution>
    </executions>
    <configuration>
                 <outputDirectory>src/main/generated</outputDirectory>
     <wsdl>true</wsdl>
     <xmlschema>false</xmlschema>
                    <schemaDirectory>src/main/resources</schemaDirectory>
     <schemaFiles>SayHello.wsdl</schemaFiles>
     <keep>true</keep>
                </configuration> 
    <dependencies>
     <dependency>
      <groupId>com.sun.xml.bind</groupId>
      <artifactId>jaxb-impl</artifactId>
      <version>2.2.4</version>
     </dependency>
     <dependency>
      <groupId>javax.xml.bind</groupId>
      <artifactId>jaxb-api</artifactId>
      <version>2.2.4</version>
     </dependency>
    </dependencies>
   </plugin>
   <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>build-helper-maven-plugin</artifactId>
    <executions>
     <execution>
      <id>add-source</id>
      <phase>generate-sources</phase>
      <goals>
       <goal>add-source</goal>
      </goals>
      <configuration>
       <sources>
        <source>src/main/generated</source>
       </sources>
      </configuration>
     </execution>
    </executions>
   </plugin>
   <plugin>
    <artifactId>maven-clean-plugin</artifactId>
    <configuration>
     <filesets>
      <fileset>
       <directory>src/main/generated</directory>
      </fileset>
     </filesets>
    </configuration>
   </plugin>
  </plugins>
  <pluginManagement>
   <plugins>
    <plugin>
     <groupId>org.eclipse.m2e</groupId>
     <artifactId>lifecycle-mapping</artifactId>
     <version>1.0.0</version>
     <configuration>
      <lifecycleMappingMetadata>
       <pluginExecutions>
        <pluginExecution>
         <pluginExecutionFilter> 
          <groupId> 
           org.codehaus.mojo 
          </groupId> 
          <artifactId> 
           jaxb2-maven-plugin 
          </artifactId> 
          <versionRange> 
           [1.1.1,) 
          </versionRange> 
          <goals> 
           <goal>xjc</goal> 
          </goals> 
         </pluginExecutionFilter>
         <action>
          <ignore></ignore>
         </action>
        </pluginExecution>
       </pluginExecutions>
      </lifecycleMappingMetadata>
     </configuration>
    </plugin>
   </plugins>
  </pluginManagement>
 </build>
</project>


Deployment descriptor (web.xml):

In the deployment descriptor we make use of spring's MessageDispatcherServlet to route the requests to the program that would be handling incoming soap requests.
<web-app version="3.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemalocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

 <listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener
  </listener-class>
 </listener>
 
 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value></param-value>
 </context-param>

 <servlet>
  <servlet-name>native-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>
  <load-on-startup>1</load-on-startup>
 </servlet>

 <servlet-mapping>
  <servlet-name>native-ws</servlet-name>
  <url-pattern>/*</url-pattern>
 </servlet-mapping>

 <mime-mapping>
  <extension>xsd</extension>
  <mime-type>text/xml</mime-type>
 </mime-mapping>
</web-app>


Spring configuration for the web service:  (native-ws-servlet.xml)

In this configuration file we are defining the marshallers for JAXB to understand.
we also are making use of WSS4J in conjunction with WSSecurityInterceptor to add authentication and authorization to the web service.

<?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:tx="http://www.springframework.org/schema/tx"
 xsi:schemaLocation="
     http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-3.1.xsd">


 <context:annotation-config/>
 <context:spring-configured/>
 <context:component-scan base-package="com.karthik.spring.learning.sayhelloservice"/>
 
 <bean class="org.springframework.ws.server.endpoint.adapter.GenericMarshallingMethodEndpointAdapter">
  <constructor-arg ref="nativeMarshaller"/>
 </bean>
 <bean id="nativeMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
  <property name="contextPaths">
   <list>
    <value>learning.spring.karthik.com.sayhello</value>
   </list>
  </property>
 </bean>

 <!-- <bean id="nativeValidatingInterceptor" class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
  <property name="schemas">
   <list>
    <value>/WEB-INF/wsdls/SayHello.wsdl</value>
   </list>
  </property>
  <property name="validateRequest" value="true" />
  <property name="validateResponse" value="true" />
 </bean> -->

 <bean id="nativeEndpointMapping" class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
  <property name="interceptors">
   <list>
    <!-- <ref bean="nativeValidatingInterceptor"/> -->
    <ref bean="wsSecurityInterceptor" />
   </list>
  </property>
 </bean>

 <bean id="SayHello" class="org.springframework.ws.wsdl.wsdl11.SimpleWsdl11Definition">
  <constructor-arg value="/WEB-INF/wsdls/SayHello.wsdl"/>
 </bean>

 <bean id="nativeMessageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

 <bean id="nativeMessageReceiver" class="org.springframework.ws.soap.server.SoapMessageDispatcher"/>
   
 
 <bean id="callbackHandler" class="org.springframework.ws.soap.security.wss4j.callback.SimplePasswordValidationCallbackHandler">
  <property name="users">
   <props>
    <prop key="karthik">karthik123</prop>
   </props>
  </property>
 </bean>

 <bean id="wsSecurityInterceptor" class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
  <property name="validationActions" value="UsernameToken" />
  <property name="validationCallbackHandler" ref="callbackHandler" />
 </bean>
 
 <!--  WS Security Ends -->
</beans> 

Java program to handle requests. 

A java program to handle the requests is as follows. We annotate the class with @Endpoint so that this class gets picked up during class path scanning or component scan and also this class would be listening to incoming requests. Also the method processing the request would be annotated with @PayloadRoot with request name and namespace so that the request can land on this method for processing.
package com.karthik.spring.learning.sayhelloservice;

import learning.spring.karthik.com.sayhello.SayHelloOperationRequest;
import learning.spring.karthik.com.sayhello.SayHelloOperationResponse;

import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;

/**
 * &ltp>
 * WS Endpoint for Say hello service
 * &lt/p>
 * 
 * @author Karthik Arun (karthikarun@outlook.com)
 * 
 */
@Endpoint
public class SayHelloEndpoint {

 @PayloadRoot(localPart = "SayHelloOperationRequest", namespace = "http://com.karthik.spring.learning/SayHello/")
 public SayHelloOperationResponse sayHello(SayHelloOperationRequest request) {
  SayHelloOperationResponse response = new SayHelloOperationResponse();
  if (request != null) {
   response.setHelloMessage("Hello " + request.getName() + ", good to know you!");
  }
  return response;
 }
}

Deploying the code:

 We can deploy the war generated inside the WAR/target on apache tomcat 7.x.

Testing the code: 

We would be using SOAP UI to test the code we just wrote to see if it works.

Sample Test request:


<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:say="http://com.karthik.spring.learning/SayHello/">
   <soapenv:Header>
      <wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
         <wsse:UsernameToken wsu:Id="UsernameToken-2">
            <wsse:Username>karthik</wsse:Username>
            <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">karthik123</wsse:Password>
         </wsse:UsernameToken>
      </wsse:Security>
   </soapenv:Header>
   <soapenv:Body>
      <say:SayHelloOperationRequest>
         <Name>Karthik</Name>
      </say:SayHelloOperationRequest>
   </soapenv:Body>
</soapenv:Envelope>

Sample Test response:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP-ENV:Header/>
   <SOAP-ENV:Body>
      <ns3:SayHelloOperationResponse xmlns:ns3="http://com.karthik.spring.learning/SayHello/">
         <HelloMessage>Hello Karthik, good to know you!</HelloMessage>
      </ns3:SayHelloOperationResponse>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>  

SOAP UI test sample:


Additional reading
Spring documentation

Please post your comments or suggestions if any in the comments section.

http://www.karthikarun.in 

No comments:

Post a Comment