Thursday, May 29, 2008

REST 0.7, now with Spring support

REST 0.7 is out and now includes support for the Spring framework. Peter Liu has written a nice example that uses NetBeans 6.1 to create a web application that contains a Spring-aware servlet, a singleton resource, and a request resource. His example is derived from a blog post by Paul Sandoz showing how to manually integrate Spring and Jersey (and predating REST 0.7).

Note that in REST 0.8, Spring support will be enhanced so that the user does not have to create a Spring-aware servlet.

This is Peter's example. It requires NetBeans IDE Web and Java EE distribution 6.1 and REST plugin 0.7.

  1. Create a Web Application project and name it SpringRestWebApp. On the Frameworks page of the project creation wizard, select Spring Web MVC 2.5.
  2. Right-click the SpringRestWebApp node and choose New > RESTful Web Services from Patterns. Select the Singleton pattern and name the resource Singleton (class name will then be SingletonResource). Create the test.servlet package to contain the resource.
  3. Replace the code in SingletonResource with the following. Right-click in the code and select Fix Imports after replacing the code.
    @Path("singleton")
    @Singleton
    public class SingletonResource {
        
        private String name;
        
        private int uses = 0;
        
        private synchronized int getCount() {
            return ++uses;
        }
        
        public SingletonResource() {
            name = "unset";
        }
    
        public String getName() {
            return name;
        }
        
        public void setName(String name) {
            this.name = name;
        }
        
        @GET
        @ProduceMime("text/plain")
        public String getDescription() {
            return "Name: " + getName() + ", Uses: " + Integer.toString(getCount());
        }
    }
  4. Create another RESTful Web Service using the Singleton pattern. Name the resource PerRequest and create it in the same test.servlet package.
  5. Replace the code in PerRequestResource with the following. Right-click in the code and select Fix Imports when you are done.
    @Path("request")
    public class PerRequestResource {
        
        private String name;
        
        private int uses = 0;
        
        private synchronized int getCount() {
            return ++uses;
        }
        
        public PerRequestResource() {
            name = "unset";
        }
    
        public String getName() {
            return name;
        }
        
        public void setName(String name) {
            this.name = name;
        }
        
        @GET
        @ProduceMime("text/plain")
        public String getDescription() {
            return "Name: " + getName() + ", Uses: " + Integer.toString(getCount());
        }
    }
  6. Right-click the SpringRestWebApp node and choose New > Servlet. Name the servlet SpringServlet and create it in the test.servlet package.
  7. Replace the code in SpringServlet with the following. Right-click in the code and select Fix Imports when done.
    public class SpringServlet extends ServletContainer {
        
        private static class SpringComponentProvider implements ComponentProvider {
            private ApplicationContext springContext;
    
            SpringComponentProvider(ApplicationContext springContext) {
                this.springContext = springContext;
            }
            
            private String getBeanName(Class c) {
                String names[] = springContext.getBeanNamesForType(c);
                if (names.length == 0) {
                    return null;
                } else if (names.length > 1) {
                    throw new RuntimeException("Multiple configured beans for " 
                            + c.getName());
                }
                return names[0];            
            }
            
            public Object getInstance(Scope scope, Class c) 
                    throws InstantiationException, IllegalAccessException {            
                String beanName = getBeanName(c);
                if (beanName == null) return null;
                
                if (scope == Scope.WebApplication && 
                        springContext.isSingleton(beanName)) { 
                    return springContext.getBean(beanName, c);
                } else if (scope == Scope.ApplicationDefined &&
                        springContext.isPrototype(beanName) &&
                        !springContext.isSingleton(beanName)) {
                    return springContext.getBean(beanName, c);
                } else {
                    return null;
                }
            }
    
            public Object getInstance(Scope scope, Constructor contructor, 
                    Object[] parameters) 
                    throws InstantiationException, IllegalArgumentException, 
                    IllegalAccessException, InvocationTargetException {
                return null;
            }
    
            public Object getInjectableInstance(Object instance) {
               return instance;
            }
            
            public void inject(Object instance) {
            }        
        };
        
        @Override
        protected void initiate(ResourceConfig rc, WebApplication wa) {
            ApplicationContext springContext = WebApplicationContextUtils.
                    getRequiredWebApplicationContext(getServletContext());
            
            wa.initiate(rc, new SpringComponentProvider(springContext));
        }        
    }

    Paul Sandoz writes "Notice that SpringServlet extends ServletContainer and the initiate method is overridden. This method creates an ApplicationContext and then initiates the WebApplication by passing in an instance of the static inner class SpringComponentProvider. This class implements ComponentProvider and the getInstance method will attempt to obtain a Spring bean that is present and matches the requested scope, if so then the bean instance is returned otherwise null is returned. (Note that the getInstance method with a Constructor type parameter is not implemented, this is because we have not determined how to support constructors with Spring beans)."

  8. Open the project's web.xml file. Replace com.sun.ws.rest.impl.container.servlet.ServletAdaptor with test.servlet.SpringServlet.
  9. Add the following to the project's applicationContext.xml file, to initialize the resources:
    <bean id="bean1" scope="singleton" class="test.servlet.SingletonResource">
         <property name="name" value="Mr. Singleton Bean"/>
    </bean>
    <bean id="bean2" scope="prototype" class="test.servlet.PerRequestResource">
         <property name="name" value="Mr. PerRequest Bean"/>
    </bean>
  10. Right-click the project node and select Test RESTful Web Services.

When you test the Singleton path, the service returns the Mr. Singleton Bean property and the Uses value increments for every request. When you test the PerRequest path, the service returns the Mr. PerRequest Bean property and the Uses value does not increment.

by: Jeffrey Rubinoff

1 comment:

arnold said...

A few weeks ago I have integrated extjs 2.1 and jersey, with spring integration, just for the fun of it. Take a look at http://extjs.com/forum/showthread.php?p=165437
when your interested in retrieving both the sourcecode and the related war file.