REST Web Services with Spring MVC Presentation Outline

  • Creating REST Web Services is "catching on" based on the apperance of a number of REST "How To" sessions at conferences.
  • Google, Yahoo, Amazon, Ebay and others are providing REST Web Services
  • This presentation will explain how a demo REST web service was implemented.
    • The demo will be a school domain with
      • Schools
      • Students
      • Instructors
      • Courses
      • Classes
    • Spring MVC and Java Technology is used for the demo
      • There is a little Groovy code sprinkled about
  • Maven 2.0.7 was used for building the applications
    • What is so great about Maven 2?
      • In corporate environments, Maven is being used to unify and standardize how (Java) projects are being built (including continuous integration, documentation and project health status reporting) and to create modular applications.
    • Automated Builds
    • Manages library dependencies
    • Development Related Reports
    • Generate JavaDoc
    • Generates a development related web site
    • Parent-Child projects
    • Project to Project familiarity by developers via Standard Directory Layout and common build phases
    • handles creating Java POJOs from Hibernate *.hbm.xml files
    • hibernate3 Maven plug-in creates the database ddl schema file from *.hbm.xml files
    • This demo uses Spring Framework 2.5-rc1
      • In order to use Spring Milestones with Maven, you need to add a repository in the pom.xml file. See article http://blog.interface21.com/main/2007/09/18/maven-artifacts-2/
        <repository>
                <id>spring-milestone</id>
                <name>Spring Portfolio Milestone Repository</name>
                <url>http://s3.amazonaws.com/maven.springframework.org/milestone</url>
                <!--
                To browse repo use URL:
                http://s3browse.com/explore/maven.springframework.org/milestone
                -->
        </repository>
        
  • Documentation
    • Maven APT "Almost Plain Text" Format was used to document this demo
  • This demonstration uses three Maven projects plus a parent project
    • Rule of Thumb is one pom per application artifact (jar or war file) and one pom per parent/grouping
    • nbt-projects-parent (the parent)
      • rest-web-service-school-demo; artifact is restschooldemo.war
      • rest-university-web; artifact is restuniversity.war
      • school-demo-domain-model; artifact is school-demo-domain-model-<version>.jar
  • Data lives in the hsqldb database
    • Ant scripts are present to start and stop hsqldb database
    • Ant script to show if there is any data in the database
  • Hibernate 3 (Object to Relational Mapping) (ORM) is used to get and save data to the database
    • domain model java POJOs are generated from the Hibernate *.hbm.xml files
      • School.hbm.xml
      • Student.hbm.xml
      • Instructor.hbm.xml
      • Course.hbm.xml
      • Clazz.hbm.xml
    • Maven plug-in hibernate3 creates the ddl schema
    • Demo uses OpenSessionInViewFilter\

    File: web.xml

    <filter>
            <filter-name>hibernateFilter</filter-name>               
            <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
    </filter>
    
    <!-- attempt to get rid of error
    ERROR [org.hibernate.LazyInitializationException] - <could not initialize proxy - the owning Session was closed>
    -->
    
    <filter-mapping>
            <filter-name>hibernateFilter</filter-name>
            <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    • Spring DAO Helpers
      • Hibernate (HibernateDaoSupport)
  • REST Web Services
    • REST is a set of architectural principles (style)
      • Guidelines, not a standard
    • Based on HTTP Protocol
    • Stateless Request-Reply
    • Lightweight (compared with SOAP and WSDL)
    • Can be Secured
      • ACL
      • Login
      • HTTPS / SSL
    • Can be Cached
    • Can be Scaled
    • Elements of REST
      • Resource
      • URI
      • HTTP Methods (GET, POST, PUT, DELETE, etc.)
      • GET HTTP Method should not modify the resource
        • GET HTTP Method is thus bookmarkable
      • HTTP Return Codes (200 - Success, 404 - Not Found, etc.)
        • Subset of HTTP Response Codes
          SC_OK (200)
          SC_CREATED (201)
          SC_MOVED_PERMANENTLY (301)
          SC_BAD_REQUEST (400) Status code (400) indicating the request sent by the client was syntactically incorrect.
          SC_NOT_FOUND (404)
          SC_GONE (410)
          
          
          SC_UNSUPPORTED_MEDIA_TYPE (415)
          SC_UNAUTHORIZED (401)
          SC_REQUEST_ENTITY_TOO_LARGE (413)
          SC_NO_CONTENT (204)
          SC_METHOD_NOT_ALLOWED (405)
          SC_INTERNAL_SERVER_ERROR (500)
          SC_FORBIDDEN (403)
          
          

    Also see List of HTTP status codes at Wikipedia

    • XML for request and response data where needed
      • Other content formats are allowed (HTML, JSON, PDF, tiff, png, etc.)
    • There is pure REST, and not pure REST
      • Pure REST
        • GET will not alter the resource (data)
          • Thus GET makes Resource URL bookmark-able
        • Use POST, PUT and DELETE to alter the resource (data)
        • Pure REST people are sometimes known as RESTafarians
      • GET-ful REST
        • also called "Popular REST"
        • GET may alter the resource (data)
        • not recommended
    • Each Resource gets its own URL
      • If you have students as your resource than each student gets its own URL
      • If you have 200,000 students, then you will have 200,000 addressable URLs
      • Usually the id for the student is embedded somewhere in the URL path
        • Example URL: To list a single student with Id of 2, view the URL
          http://localhost:8080/restschooldemo/spring/rest/school/v1/student/2
          
      • Each Collection (List) of Resources also gets its own URL!
        • Example URL: To list all students, view the URL
          http://localhost:8080/restschooldemo/spring/rest/school/v1/student
          
      • For a Collection (List) URL, your can limit (query for) the items returned with URL parameters
        • Example URL: To list all students with First Name of "J", view the URL
          http://localhost:8080/restschooldemo/spring/rest/school/v1/student?fn=J&ln=
          
        • Example URL: To list all students with First Name of "Jjennifer" and Last Name of "Vendama", view the URL
          http://localhost:8080/restschooldemo/spring/rest/school/v1/student?fn=Jjennifer&ln=Vendama
          
    • Pros
      • Good for stateless request-response style interaction
      • Each resource is bookmarkable (GET HTTP Method is thus bookmarkable)
      • Because it is closely related to HTTP and the Internet, you can reuse your knowledge about how to secure, cache and scale web pages and apply them to your REST Web Services
      • Simple to understand, straight forward to code and short time to be productive with.
    • Cons
      • Some vendors, bosses, products require SOAP (and WSDL) and don't know about or support REST Web Services
        • Products you want to interact with may not provide a REST Web Service and only provide SOAP Web Service with a WSDL
      • Not a rigid standard, just suggestions
      • May not be suited for building asynchronous, event-driven, call back styles of interaction
      • Web Browsers usually just support GET and HTML forms support only GET and POST
      • No standard way to learn a new REST web service instance other than providing Programmer Documentation. (i.e no WSDL)
      • Different People/Groups may implement REST differently
        • May take shortcuts or violate principals when implementing REST
    • CRUD
      • This demo does CRUD (Create, Read, Update, Delete) actions
        • You can have you own REST web service perform more than just CRUD POJOs
  • REST Controversies
    • Is POST or PUT used to add a new resource?
    • Is POST or PUT used to update a resource?
    • Is it good if the URL path is human readable friendly?
    • Can two different URLs control the same resource?
      • Example:
        • http://localhost:8080/fittoprnt/article
        • http://localhost:8080/fittoprnt/articles
      • Example:
        • http://localhost:8080/school/student/13456
        • http://localhost:8080/school/student/username/paullee
  • The http URL
    • "http:" "//" host [ ":" port ] [ abs_path [ "?" query ]]
  • REST and the the http URL
    • "http:" "//" host [ ":" port ] [ abs_path [ "?" query ]]
    • You can use the URL path for hierarchical organization.
      • Example: http://localhost:8080/blog/2007/10/04/REST-for-Geeks
      • Backing off the directories off the right of the URL path may result in collections of resources
        • Example: All articles in Oct. 2007: http://localhost:8080/blog/2007/10
        • Example: All articles in 2007: http://localhost:8080/blog/2007
      • Use Query Parameters to represent non-hierarchical attributes
        • Example: http://localhost:8080/blog/2007?titlecontains=Geeks&bodycontains=flippin
    • When implementing REST, we will find a need to get parameters out of the URL path
    • Some REST practitioners say, you should embed the version of the Web Service in the URL path
      • Example: http://localhost:8080/restschooldemo/spring/rest/school/v1 /student
      • Example: http://localhost:8080/restschooldemo/spring/rest/school/v44 /student
      • This eases introducing a new version of the web service because two versions can be alive at the same time. People using the old version can be allowed to continue using the old version and be encouraged or forced to eventually start using the new version of the web service.
  • XStream 1.2.2 is used for converting POJO to/from XML

    File: XStreamCustomFactoryImpl.java

    //---------------------------------------------------------
    public static XStream getXStreamInstance() 
    {
    
    XStream xstream = new XStream() {
            protected MapperWrapper wrapMapper(MapperWrapper next)
            {
            return new HibernateMapper(next);
            }
            };
    
    xstream.registerConverter(
            new HibernateProxyConverter(xstream.getMapper(),
                    new PureJavaReflectionProvider()),
                    XStream.PRIORITY_VERY_HIGH);
    
    return xstream;
    }
    
    
    • Using HibernateMapper and HibernateProxyConverter will slow down the REST web service processing time.
    • Suggestion to avoid having to sanitize Hibernate POJOs:
      • use JDBC instead of Hibernate
      • or don't use XStream
      • or create your XML marshalling/unmarchalling by hand.
  • Spring MVC is a web framework provided by Spring Framework that integrates with Spring configuration and philosophy
  • A REST Web Service can be implemented using a Spring MVC Controller
  • The goal for a Spring REST Web Service Controller is to eventually have methods that will look similar to
    ...
    
    //---------------------------------------------------------
    public ModelAndView doGet(HttpServletRequest request, HttpServletResponse response)
    {
    // do something useful here
    
    ModelAndView mav = new ModelAndView(xmlView);
    return mav;
    }
    
    //---------------------------------------------------------
    public ModelAndView doPost(HttpServletRequest request, HttpServletResponse response)
    {
    // do something useful here
    
    ModelAndView mav = new ModelAndView(xmlView);
    return mav;
    }
    
    //---------------------------------------------------------
    public ModelAndView doPut(HttpServletRequest request, HttpServletResponse response)
    {
    // do something useful here
    
    ModelAndView mav = new ModelAndView(xmlView);
    return mav;
    }
    
    //---------------------------------------------------------
    public ModelAndView doDelete(HttpServletRequest request, HttpServletResponse response)
    {
    // do something useful here
    
    ModelAndView mav = new ModelAndView(xmlView);
    return mav;
    }
    
    ...
    
  • Now we need a switcher to call the appropriate method based on the request's HTTP method.

    File: com.nbtconsulting.demo.restws.v2007.web.BasicSpringRestController

    ...
    
    //---------------------------------------------------------
    /**
    Primary &quot;do work&quot; method called when a request is made 
    on the web service. 
    
    */
    public ModelAndView handleRequestInternal(HttpServletRequest request,
            HttpServletResponse response)
    {
    logger.debug("entered");
    logger.debug("supportedMethods: ${this.supportedMethods}");
    
    ModelAndView mav = null;
    
    String strRequestHttpMethod = request.getMethod();
    logger.debug("strRequestHttpMethod: " + strRequestHttpMethod);
    
    if (METHOD_GET.equalsIgnoreCase(strRequestHttpMethod))
       {
       mav = doGet(request, response);
       }
    else if (METHOD_PUT.equalsIgnoreCase(strRequestHttpMethod))
       {
       mav = doPut(request, response);
       }
    else if (METHOD_POST.equalsIgnoreCase(strRequestHttpMethod))
       {
       mav = doPost(request, response);
       }
    else if (METHOD_DELETE.equalsIgnoreCase(strRequestHttpMethod))
       {
       mav = doDelete(request, response);
       }
    else if (METHOD_HEAD.equalsIgnoreCase(strRequestHttpMethod))
       {
       mav = doHead(request, response);
       }
    else if (METHOD_OPTIONS.equalsIgnoreCase(strRequestHttpMethod))
       {
       mav = doOptions(request, response);
       }
    else if (METHOD_TRACE.equalsIgnoreCase(strRequestHttpMethod))
       {
       mav = doTrace(request, response);
       }
    else
       {
       //default:
       //mav = doNothing(request, response);
       }
    
    return mav;
    }
    
    ...
    
  • And configure the supported HTTP methods for the Controller

    File: com.nbtconsulting.demo.restws.v2007.web.BasicSpringRestController

    public class BasicSpringRestController
            extends AbstractController
    {
    
    // following METHOD_* constants are not currently provided by Spring Framework;
    // however, METHOD_GET and METHOD_POST are provided  by Spring Framework
    public static final String METHOD_PUT = "PUT";
    public static final String METHOD_DELETE = "DELETE";
    public static final String METHOD_HEAD = "HEAD";
    public static final String METHOD_OPTIONS = "OPTIONS";
    public static final String METHOD_TRACE = "TRACE";
    
    ...
    
    //---------------------------------------------------------
    public BasicSpringRestController()
    {
    super();
    
    String[] strarraySupportedHttpMethods =
       {
       METHOD_GET,
       METHOD_POST,
       METHOD_PUT,
       METHOD_DELETE,
       
       //METHOD_HEAD,
       //METHOD_OPTIONS,
       //METHOD_TRACE,
       };
    
    setSupportedMethods(strarraySupportedHttpMethods);
    }
    
    ...
    
  • Consider providing custom Views for specific content types received or sent by the REST Web Service, such as
    • XmlView.java ; content type is "application/xml"
    • TextView; content type is "text/plain"
    • HtmlView; content type is "text/html"
    • XhtmlView; content type is "application/xhtml+xml"
    • More as needed: PdfView, PngView, GifView, JpgView, etc...
    • return the correct content type in the method getContentType()
  • For REST, we will have a URL for each resource and for collections of resources
    • For each resource object type, you need to decide if you want one controller to handle individual item URLs and another controller to handle the collection URL or if you want to combine them; so that, one controller handles both collection URL and individual item URLs.
    • For this demo, I have one controller, for each resource object type, handle both collection URL and individual item URLs.
  • The REST Web Service Controller will get mapped to particular URL patterns in the Spring dispatcher servlet xml configuration file.
  • Use a URL mapper to map the REST resource URLs to a Spring MVC Controller implementation.
    • Normally we might use the Spring provided SimpleUrlHandlerMapping
      <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
              <property name="order" value="2"/>
              <property name="mappings">
                      <props>
                              <prop key="/welcome.htm">clinicController</prop>
                              <prop key="/vets.htm">clinicController</prop>
      ...
      
    • However, when doing REST and because we want to get parameter values from the URL path, we will use a third party developed URL mapper carbonfive.spring.web.pathparameter.ParameterizedUrlHandlerMapping .
      <bean id="urlMappingRest" 
              class="carbonfive.spring.web.pathparameter.ParameterizedUrlHandlerMapping">
              <property name="order" value="1"/>
              <property name="alwaysUseFullPath" value="true"/>
              <property name="mappings">
                      <props>
                              <prop key="/spring/rest/school/v1/school">schoolRestController</prop>
      ...
      
  • Common practice in implementing REST is to have an id for a resource be embedded in the URL path

    File: MySpringWebApp-servlet.xml

    <!--
    This URL mapping can pull parameters off of the URL path
    and make them available to the controller.
    Thus it enables a feature of REST web service.
    -->
    <bean id="urlMappingRest" 
            class="carbonfive.spring.web.pathparameter.ParameterizedUrlHandlerMapping">
            <property name="order" value="1"/>
            <property name="alwaysUseFullPath" value="true"/>
            <property name="mappings">
                    <props>
                            <prop key="/spring/rest/school/v1/school">schoolRestController</prop>
                            <prop key="/spring/rest/school/v1/school/(*:id)">schoolRestController</prop>
    
                            <prop key="/spring/rest/school/v1/student">studentRestController</prop>
                            <prop key="/spring/rest/school/v1/student/(*:id)">studentRestController</prop>
    ...
    
    
  • To get the id off of the URL path, you can use code similar to the following.

    File: com.nbtconsulting.demo.restws.v2007.web.BasicSpringRestController

    ...
    
    //---------------------------------------------------------
    /**
    If present, get the &quot;id&quot; paramater from the URL path.
    
    @return null if id parameter is not present on the URL path,
            else returns the id value from the URL path.
    */
    public String getIdFromUrlIfPresent(HttpServletRequest request)
    {
    String strIdReturn = null;
    
    Map mapRestParams = (Map) request.getAttribute(
            "ParameterizedUrlHandlerMapping.path-parameters");
    if (mapRestParams == null)
            {
            return null;
            }
    strIdReturn = (String) mapRestParams.get("id");
    
    return strIdReturn;
    }
    
    ...
    
  • Calling the REST Web Service
    • How would a web application perform CRUD actions interacting with a REST web service instead of a database?
      • Sample web application is named restuniversity
      • Sample web application uses Jakarta Commons HttpClient 3.1 library to programmatically call the REST web service URLs.
      • Example PUT (Update) Client Code
        ...
        
        import org.apache.commons.httpclient.HttpClient;
        import org.apache.commons.httpclient.HttpException;
        import org.apache.commons.httpclient.methods.DeleteMethod;
        import org.apache.commons.httpclient.methods.GetMethod;
        import org.apache.commons.httpclient.methods.PostMethod;
        import org.apache.commons.httpclient.methods.PutMethod;
        import org.apache.commons.httpclient.methods.RequestEntity;
        import org.apache.commons.httpclient.methods.StringRequestEntity;
        
        ...
        
        public class SchoolRepositoryRestImpl
                implements
                        ISchoolRepository,
                        IWebServiceConfigClient
        {
        protected static final String strUrlRootResource = "school";
        protected WebServiceConfig webServiceConfig;
        protected HttpClient httpClient = new HttpClient();
        
        ...
        
        //---------------------------------------------------------
        public void update(School objData) throws Exception
        {
        logger.debug("entered");
        
        try
                {
                String strId = objData.getId().toString();
                logger.debug("strId: " + strId);
                String strRestWebServiceUrl = buildWebServiceUrlForTargetItem(strId);
        
                PutMethod method = new PutMethod(strRestWebServiceUrl);
        
                XStream xstream = XStreamCustomFactoryImpl.getXStreamInstance();
        
                String xmlStr = xstream.toXML(objData);
                logger.debug("xmlStr: " + xmlStr);
        
                RequestEntity requestEntity = new StringRequestEntity(xmlStr, null, null);
        
                method.setRequestEntity(requestEntity);
        
                httpClient.executeMethod(method);
                }
        catch (HttpException ex)
                {
                logger.error(
                        "Exception during accessing REST web service to update the data item.",
                        ex);
                throw ex;
                }
        catch (IOException ex)
                {
                logger.error(
                        "Exception during accessing REST web service to update the data item.",
                        ex);
                throw ex;
                }
        }
        
  • Manually testing REST web services
    • You can try the following to manually test a REST Web Service
      • Web Browser can perform a GET HTTP method on a URL
      • cURL utility can call URLs from the command line and supports GET, POST, PUT, DELETE
      • using a front end web application
  • Use JUnit with Jakarta Commons HttpClient to test REST web services in an automated fashion. (This is not implemented in this demo.)
  • Because you are using Spring MVC you can secure the REST web service with Acegi Spring Security or org.springframework.web.servlet.HandlerInterceptor 's
  • What about WSDL?
    • REST Web Services do not use/have WSDL
    • Who is WSDL for? Is WSDL for the computer or for the programmer?
    • You can have a programmer learn your REST Web Serivce by providing documentation (in HTML) describing your expectations and examples.
  • Lessons Learned
    • Developing REST Web Services with Spring MVC can be acomplished
    • Need to sanitize Hibernate POJOs before XStream will convert them to XML
    • Used third party code to get parameters embedded in the URL path
    • cURL is a neat utility for calling REST web services from a command line
  • Future additions being considered for the Demo
    • add pdf pages
    • add images for students, instructors and schools
    • add a fav icon to restuniversity web application
    • make the restuniversity web application pretty