Chariot Training Classes

Training Courses

I run Chariot's training and mentoring services. We provide training in AngularJS, HTML5, Spring, Hibernate, Maven, Scala, and more.

Chariot Education Services

Technology

Chariot Emerging Tech

Learn about upcoming technologies and trends from my colleagues at Chariot Solutions.

Resources

Chariot Conferences

Podcasts

« Spring Roo In Action MEAP Now Available | Main | Kickin' Butt with Spring Roo at the command line with Maven shade plugin »
Thursday
Jul012010

RESTing with Roo - adding Content Negotiation and REST in two easy steps (well, kinda)

Note: I don't know if I'm approaching this right... At least on Firefox, it seems to work. But on Safari, it fails. I'll revisit this soon.

Ok, here's another stream-of-consciousness post on REST support from a Roo application. I'm working on various research for our [PLUG] SpringSource Core Spring training classes, always attempting to get better examples so I can do more than just show the slides and labs, and I decided to use Roo to build myself a REST server.

Easy, I thought... Roo has RESTful URLs, and so I just have to set up the right configuration.

Kind of.

The basic steps to REST-enable a web app in Spring MVC using the built-in REST support are:

  1. Install the proper JARs for your Marshallers
  2. Install the ContentNegotiatingViewResolver

I had a bit of a hiccup figuring this out. Thanks to a great pair of threads on SpringSource's forums, here and here, I realized I had configured a few things wrong.

Setting up the example

Here's my Spring Roo log file, which you can cut and paste into a text file and then do a "roo script filename" command to set up for you.

project --topLevelPackage rest.demo --projectName spring-mvc-rest-demo
persistence setup --provider HIBERNATE --database HYPERSONIC_PERSISTENT 
entity --class ~.db.Customer
field string firstname
field string lastname
field date --fieldName dob --type java.util.Date
field number --fieldName discount --type java.math.BigDecimal --decimalMin 0 --decimalMax 1.0
controller scaffold --entity rest.demo.db.Customer --class rest.demo.web.CustomerController

Once you do that, now you're ready for some fun...

The additional dependencies for your Maven pom.xml file (you'll need to add these to the main dependencies tag section at the end...) (for XML conversion, we're using Castor, which auto-generates XML based on the reflection of JavaBean properties on your bean, and for JSON we're using the Jaskson JSON library. For this to work, you have to install spring-oxm, which isn't installed in Roo by default - I SMELL PLUGIN... RACE YA!).

<dependency>
  <groupId>org.codehaus.jackson</groupId>
  <artifactId>jackson-mapper-asl</artifactId>
  <version>1.5.3</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-oxm</artifactId>
  <version>${spring.version}</version>
</dependency>
<dependency>
  <groupId>org.codehaus.castor</groupId>
  <artifactId>castor-xml</artifactId>
  <version>1.3.1</version>
</dependency>

Now, the Spring webmvc-config.xml file changes. This file is located in src/main/webapp, under the WEB-INF/spring directory, of course. Add this to the end of the file before the closing beans tag...

<bean id="htmlMediaType" class="org.springframework.http.MediaType">
  <constructor-arg value="text" />
  <constructor-arg value="html" />
</bean>
<bean
class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
  <property name="order" value="0" />
  <property name="defaultContentType">
    <ref bean="htmlMediaType" />
  </property>
  <property name="mediaTypes">
    <map>
      <entry key="json" value="application/json" />
      <entry key="xml" value="application/xml" />
    </map>
  </property>
  <property name="defaultViews">
    <list>
      <bean
     class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
      <bean class="org.springframework.web.servlet.view.xml.MarshallingView">
        <property name="marshaller">
          <bean class="org.springframework.oxm.castor.CastorMarshaller" />
        </property>
      </bean>
    </list>
  </property>

  <property name="viewResolvers">
    <ref bean="tilesViewResolver" />
  </property>
</bean>

Next, in the same file, add the order property, setting it to 1, to the instance of the AjaxUrlBasedViewResolver so that it looks roughly like this :

<bean class="org.springframework.js.ajax.AjaxUrlBasedViewResolver"
  id="tilesViewResolver">
  <property name="order" value="1" />
  <property name="viewClass"
    value="org.springframework.web.servlet.view.tiles2.TilesView" />
</bean>

What you're seeing at work here in this setup is the configuration of a default media type of text/html, and a configuration of a first-level view resolver that does content negotiation first. You'll see the order setting was made to make the AjaxBasedViewResolver go first, which threw me, but that's an area for more research as I start to dig deeper into the interplay between the various view resolvers... Generally you want the JSP file-based resource view resolver to go last, as it is greedy, but Tiles changes that for us a bit. I am not 100% sure everything works as advertised - for example I need to do some AJAX testing to make sure those calls work. But for getting REST against a webapp, well, there you have it.

Testing it!

Well, that wasn't so much configuration, was it? I'm experimenting with REST using the Groovy RESTClient library at the moment (I'm going to use Spring REST Template in JUnit4 for the class, but also wanted something I could script up at a moment's notice and show them how REST works without waiting for those compile cycles...) BTW, I'm on Groovy 1.7.2 (haven't yet upgraded to 1.7.3) and the @Grab line uses Groovy's GRAPE artifact manager to install a grape from the ole' vine (puts it in ~/.groovy/grape/grapes or something)... WICKED COOL. Yes, I used my blog to say WICKED COOL. You can't stop me!

@Grab(group='org.codehaus.groovy.modules.http-builder', module='http-builder', version='0.5.0' )
import groovyx.net.http.RESTClient

def customers = new RESTClient( 'http://localhost:8008/demo/')
def results = customers.get (path : 'customers',
   headers: [Accept : 'application/json', "Accept-Encoding" : 'gzip,deflate'])

println results.data

Switch from JSON to XML, using application/xml as the Accept header, and you'll see it automatically respond with the appropriate datatype. Great! Now, if you use XML, change from that println statement to this:

results.data.customer.each { c ->
  println "customer:  $c.id, $c.firstname $c.lastname"
}

And if you want to test it on the browser, and see it do content negotation by file extension instead, just hit: http://localhost:8080/spring-mvc-rest-demo/customers.xml or http://localhost:8080/spring-mvc-rest-demo/customers.json to see the REST data, and http://localhost:8080/spring-mvc-rest-demo/customers to see the regular tiles views.

Enjoy...

Oh, and while I'm shilling for my training group at Chariot, please visit our course calendar if you are interested in our fall lineup - Hibernate with Spring, Spring Enterprise Integration, Core Spring, or Maven Intro and Advanced training. Thanks, and I'll refrain from using a strange chamois in any of my in-line advertisements...

PrintView Printer Friendly Version

EmailEmail Article to Friend

Reader Comments (9)

Nice post. However, the order (correctly) specifies that CNVR goes first. ViewResolvers are invoked in order (lowest to highest) until one returns a non-null View. Generally you always want CNVR to have the highest precedence.

July 7, 2010 | Unregistered CommenterDavid Turanski

Thanks for the clarification!

July 8, 2010 | Registered CommenterKen Rimple

Hi Ken,

Nice post! Can Roo handle (out of the box) an Accept header like --
Accept: application/xml;q=1.0, application/json;q=0.6
In which client makes it clear that it can accept XML and JSON media-types, however it prefers XML over JSON (indicating by its q parameter value).

July 30, 2010 | Unregistered CommenterSurya Suravarapu

Surya - I think it tries these one by one until it hits the first match. I'm not sure it takes weights into account.

August 29, 2010 | Registered CommenterKen Rimple

Works like a charm. Needed an example of using Spring WS with tiles and this was perfect. Thanks!

October 7, 2010 | Unregistered CommenterKurt

I am trying this out with Roo 1.1.1 and there is a class not found issue with
org.springframework.beans.factory.CannotLoadBeanClassException: Cannot find class [org.springframework.js.ajax.AjaxUrlBasedViewResolver] for bean with name 'tilesViewResolver' defined in ServletContext resource [/WEB-INF/spring/webmvc-confi


at org.codehaus.classworlds.Launcher.main(Launcher.java:375)
Caused by: java.lang.ClassNotFoundException: org.springframework.js.ajax.AjaxUrlBasedViewResolver

Feb 19, 2011 5:22:36 PM org.apache.catalina.core.StandardContext loadOnStartup
SEVERE: Servlet /spring-mvc-rest-demo threw load() exception
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named '
applicationConversionService' is defined
at org.springframework.beans.factory.support.DefaultListableBeanFactory.

February 19, 2011 | Unregistered CommenterDavid Harris

Very comprehensible post. Thank you. There are many examples of how to get this working, but none as clear.

I ran into one showstopper though. My data has one-to-many bidirectional links. The resultant code generates infinite recursion problems as it tries to jump back and forth descending into the parent-child links forever.

I know this is fixable for hand written code: http://wiki.fasterxml.com/JacksonFeatureBiDirReferences
but for roo code, it defines the point at which you have to quit scripting roo and start "normal" work as one needs to write getters for all set/reference members, or use the above annotations.

In my app, the perfect solution is to return a rest reference in place of attempting to marshal a referenced object. Maybe some day when roo is more json friendly.

October 19, 2011 | Unregistered CommenterBruce

Ken,

I tried to create the Groovy script with v1.8.5. I had to modify the script's Grab config as below:


import groovyx.net.http.RESTClient

@Grab(group='org.codehaus.groovy.modules.http-builder', module='http-builder', version='0.5.2')
@GrabExclude('xml-apis:xml-apis')

def job = new RESTClient( 'http://localhost:8080/JobDemo/')
...

January 26, 2012 | Unregistered CommenterGordon Dickens

Ken

can you please try the same example in the latest Spring Roo 1.2.1
also instead of

controller scaffold

can you try
controller all

and please see if that works.

I am really trying to build BackBone.JS as the front end and Roo with REST as backend.

-thanks much
Vamsi

June 14, 2012 | Unregistered CommenterVam

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>