6

I am in the process of converting an already exisiting Java Web application into a RESTful web application using Spring MVC and Groovy. One of the main features I wanted to achieve was HOT DEPLOYMENT. I chose groovy because I did not want to make changes to the already implemented Business logic(handlers) and also if I had to ever make changes to the groovy code after deployment, I could easily do that without restarting the server(ie. at runtime). This can be done because Spring supports Dynamic reloading of groovy scripts(beans). It reloads classes of dynamic languages if they are changed.

I am using Spring annotations to map request URL's to controller methods and the application is deployed in tomcat 6.0.35.

This is the web.xml file

//web.xml

        <?xml version = "1.0" encoding = "UTF-8"?>
        <web-app xmlns="http://java.sun.com/xml/ns/javaee"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">

         <!-- Spring Dispatcher -->
         <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>rest</servlet-name>
              <url-pattern>/service/*</url-pattern>
         </servlet-mapping>
        <!-- Loads application context files in addition to  ${contextConfigLocation} -->
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>    

            <!-- Set session timeout to 30 minutes -->
        <session-config>
            <session-timeout>30</session-timeout>
        </session-config>
    </web-app>

This groovy file is the controller to which the DispatcherServlet maps the request.

// UserController.groovy

@Controller
class UserController 
 {
    // This is the method to which the HTTP request is submitted to based on the mapping of the 
    // action field of the form ie. /service/user/login/auth.json
    @RequestMapping(value="/user/login/auth.{extension:[a-zA-Z]+}", method=RequestMethod.POST)
    @ResponseBody
    public String authenticate(
    @PathVariable String extension,
    @RequestParam(value="username", required=true) String username,
    @RequestParam(value="password", required=true) String password) 
    {
        // UserResource makes the backend calls, authenticates a user and returns the result.
        def user = new UserResource()
        def result = user.login(name:username, userPassword:password)

        // Output the result of the query. Method makeView makes a JSON response of the result
        // and sends to the client(browser) 
        def builder = makeView(extension) 
        {
            it.login(action:result.action, message:result.message)
        }
    }
  }

The Spring configuration file is as follows where I have used the "lang:groovy" tag which supports dynamic languages. I have also mentioned the refresh time to be 5 seconds, so that any changes made to those groovy files at runtime can be seen every 1 second and the classes are reloaded.

//applicationContext.xml

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

    <context:annotation-config/>
    <context:component-scan base-package="app.controller,app.resource" />

     <lang:groovy id="user" script-source="classpath:controller/UserController.groovy" refresh-check-delay="1000"></lang:groovy>

    <!-- To enable @RequestMapping process on type level and method level -->
    <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />
    <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />

    <!-- Resolves view names to template resources within the directory -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/"/>
        <property name="suffix" value=".html"/>
    </bean>

</beans>

I have configured my Buildpath and groovy compiler accordingly, so that all the groovy scripts directly get copied to the target folder instead of getting compiled to class files.

THE MAIN PROBLEM
When I deploy this project in a tomcat server, it loads all the Spring beans required including the ScriptProcessor. Now, when I go to my browser, load the form, and try to submit the authentication form, I get the following error in Tomcat log:

15:20:09 WARN - No mapping found for HTTP request with URI [/service/user/login/auth.json] in DispatcherServlet with name 'rest'

I have also made changes in $TOMCAT_DIR/conf/context.xml to antilock resources and JARS

<Context antiResourceLocking="true" antiJARLocking="true" reloadable="true" privileged="true">
.
.
.</Context>

However, if I configure my project to compile those groovy scripts into bytecode classes, comment out the "lang:groovy" tag in applicationContext.xml, and then restart the server, the groovy scripts get compiled into class files and the request is serviced perfectly. Authentication takes place.

Also, if I configure the dynamic beans in my applicationContet.xml using the following two lines instead of the tag, my beans DO get created dynamically at runtime and the URLs do get mapped to the respective controller methods because of the annotations.

<bean class="org.springframework.scripting.support.ScriptFactoryPostProcessor" />

<bean id ="User" class="org.springframework.scripting.groovy.GroovyScriptFactory">
   <constructor-arg value="classpath:controller/UserController.groovy" />
</bean>

But I do not know how to create the bean refreshing functionality with this style. So I guess there is an issue with the way the tag processes the groovy scripts.

I would really appreciate some help on this. I have searched all over the internet and read an infinite number of tutorials, and followed the exact procedure mentioned there. But I cant find out whats going wrong.

Please help me solve this problem.

Thank you.

1 Answers1

0

Try creating the controller with Java/Groovy that is compiled and let it get injected the Groovy 'script' as a dependency to do the actual work. I seem to remember doing this before and it might be the annotations or the way Spring loads controllers that makes the 'script' not work for you properly.

Todd W Crone
  • 1,185
  • 8
  • 23