Thursday, October 31, 2013

Configure Web, REST and Web Service from a single application using Spring

Now, you have your web application ready and you have deployed it on the cloud. Your website has some viewers. Now comes Ms. Sharma to tell you, "Hey the look and feel of your web site is not good at all. Why don't you put some pink flowery image background and your site will attract more lady customers."
Now, its a cool approach you are going to implement, now again Mr. Kundu has some views on your application and he sends a mail, "Hey can you put some corporate looks in your website. It would be good to attract corporate customers as well'."

Now what, are you going Ms. Sharma way and put some images in your web site background or you are going with Mr. Kundu with some BlueSky theme ?

Now, I have my own way, I would have given them WS and REST exposures and ask them to create their own interfaces or I myself would have created different versions using the same business layer.

If you are not aware of them, you can read them. There are plenty of resources explaining them in minute detail.

Or may be your business requirement is something where you should have a web app, an android app and also an exposure for other clients (Like Windows Phone, ASP.NET  etc). What you are going to do in this situation ?
Mostly you can do, create different applications one for web, another for Web Service and another for REST. But that has a problem too, if you make a change in business, you have to change in all the 3 areas, code re-usability is in question. Other way around could be, install the business logic as separate jar and use it all the 3 applications. But some Cloud account has restrictions of limited application hosting (like Openshift has a restriction of only 3 for a free account). Of course you can buy some space on cloud but for most of the development or simple use case scenarios, I think you are not going to invest a lot.

So, what ?
What about exposing all your applications in a single build.

Yeah you can do that with help of Spring. There may be other way around leaving Spring too. But this one worked for me and it was easy to setup.

Its all about working with 3 different servlets in web.xml and configuring 3 Spring applications. So, the implementation part I am not going into detail, just going through the configuration.

Classpath Dependencies:
I am using JSF as web app and Spring WS and Spring RS. So, I got 31 dependencies in my system. You may have some more or less and if you are reading this section, you are advanced enough to determine which one to keep and which one to remove. Here are my dependencies,

antlr-2.7.7.jar
commons-logging-1.1.1 (1).jar
dom4j-1.6.1.jar
el-impl-2.2.1-b05.jar
hibernate-commons-annotations-4.0.5.Final.jar
hibernate-core-4.3.7.Final.jar
hibernate-jpa-2.1-api-1.0.0.Final.jar
hsqldb.jar
jandex-1.1.0.Final.jar
javassist-3.18.1-GA.jar
jboss-logging-3.1.3.GA.jar
jboss-logging-annotations-1.2.0.Beta1.jar
jboss-transaction-api_1.2_spec-1.0.0.Final.jar
jsf-api-2.2.8-02.jar
jsf-impl-2.2.8-02.jar
jstl-1.2.jar
primefaces-4.0.jar
spring-aop-4.1.3.RELEASE.jar
spring-beans-4.1.3.RELEASE.jar
spring-context-4.1.3.RELEASE.jar
spring-context-support-4.1.3.RELEASE.jar
spring-core-4.1.3.RELEASE.jar
spring-expression-4.1.3.RELEASE.jar
spring-jdbc-4.1.3.RELEASE.jar
spring-orm-4.1.3.RELEASE.jar
spring-tx-4.1.3.RELEASE.jar
spring-web-4.1.3.RELEASE.jar
spring-webmvc-4.1.3.RELEASE.jar
jdom-2.0.2.jar
spring-ws-core-2.2.0.RELEASE.jar
wsdl4j-1.6.2.jar

Please choose accordingly and carefully, otherwise you will end up having ClassNotFoundException.

Now with that, we are moving forward with our web.xml changes.

Configure different exposure in web.xml
In Spring, Simple web app and REST both are handled by DispatcherServlet only while Web Services are handled by MessageDispatcherServlet. So, you don't have to worry about Web Service and REST/Web App.

But you will be in problem while configuring REST and Web App. It is the most important part.
You can try different url pattern mapping with the same servlet and handle them in controller. But, I got errors in this way too and its really painful to handle REST and Web App mapping in controllers. Cause, they behave in the same way.

So, what's the way out ?
Just extend DispatcherServlet and create a dummy servlet that does nothing except making itself a DispatcherServlet. Here is a sample.

 /**  
  *   
  */  
 package servlet;  
 import org.springframework.web.servlet.DispatcherServlet;  
 /**  
  * @author palash.k  
  *   
  */  
 public class RestDispatcher extends DispatcherServlet{  
      /**  
       *   
       */  
      private static final long serialVersionUID = 5153026121972664690L;  
 }  

And you put this in web.xml for your REST controllers.
 <?xml version="1.0" encoding="UTF-8"?>  
 <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
      xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"  
      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee   
      http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"  
      id="WebApp_ID" version="2.5">  
      <display-name>PrimeFaces Web Application</display-name>  
      <context-param>  
     <param-name>contextConfigLocation</param-name>  
     <param-value>  
       /WEB-INF/resources/context.xml  
     </param-value>  
   </context-param>  
      <listener>  
     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
   </listener>  
   <listener>  
     <listener-class>  
       org.springframework.web.context.request.RequestContextListener  
     </listener-class>  
   </listener>  
      <!-- WEB Application -->  
       <servlet>  
     <servlet-name>web</servlet-name>  
     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
     <load-on-startup>1</load-on-startup>  
   </servlet>  
   <servlet-mapping>  
     <servlet-name>web</servlet-name>  
     <url-pattern>/web/*</url-pattern>  
   </servlet-mapping>  
   <servlet-mapping>  
     <servlet-name>web</servlet-name>  
     <url-pattern>*.jsp</url-pattern>  
   </servlet-mapping>

<!-- REST Service --> <servlet> <servlet-name>rest</servlet-name> <servlet-class>servlet.RestDispatcher</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>rest</servlet-name> <url-pattern>/rest/*</url-pattern> </servlet-mapping> <!-- Web Service --> <servlet> <servlet-name>webService</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>webService</servlet-name> <url-pattern>/service/*</url-pattern> </servlet-mapping> </web-app>

Configure 3 different Spring applications:

web-servlet.xml
 <beans xmlns="http://www.springframework.org/schema/beans"  
      xmlns:context="http://www.springframework.org/schema/context"  
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
      xsi:schemaLocation="  
     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">  
      <import resource="resources/context.xml" />  
      <context:component-scan base-package="controllers" />  
      <bean  
           class="org.springframework.web.servlet.view.InternalResourceViewResolver">  
           <property name="prefix">  
                <value>/WEB-INF/jsp/</value>  
           </property>  
           <property name="suffix">  
                <value>.jsp</value>  
           </property>  
      </bean>  
 </beans>  

rest-servlet.xml

 <beans xmlns="http://www.springframework.org/schema/beans"  
      xmlns:context="http://www.springframework.org/schema/context"  
      xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
      xsi:schemaLocation=" 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/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">  
      <context:component-scan base-package="rest" />  
      <mvc:annotation-driven />  
 </beans>  

webService-servlet.xml

 <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="ws" />  
      <sws:annotation-driven />  
      <sws:dynamic-wsdl id="person" portTypeName="Person"  
           locationUri="/service/Person/" targetNamespace="urn:assistant:1.0:person"  
           requestSuffix="Request" responseSuffix="Response">  
           <sws:xsd location="classpath:xsd/person.xsd" />  
      </sws:dynamic-wsdl>  
 </beans>  



Now, that's it. You now have configured your web app, Spring WS and Spring RS in a single app.

Palash Kanti Kundu