2010-02-06

REST with Spring 3.0, Spring MVC and Dojo. Part 3 - POST and JSR-303 validation

In the third part of the series we will take a look at handling POST requests from REST in Spring MVC, and also at validation. And - not just any validation but JSR-303 validation! There's a trick to it though, I have to be honest. I will explain this later.
We will also look at sending POST requests with Dojo.

You need Java 6 for this and I am also using JBoss 6.0.0.M1.

POST requests are the C in CRUD. What it means is you use it to create new items. Please see this little table.

Operation REST method Sample URI
Return item(s) GET /books (all books)
/books/ (all books)
/books/12 (one book with ID = 12)
Create a new item POST (you pass the ID in the object itself)
/books
/books/
Update an item PUT (you pass the ID in the object itself)
/books
/books/
Delete an item DELETE /books/12 (book with ID = 12)

As you can see, in the POST or PUT case we are not passing the ID. Instead, we will pass the entire object! Spring MVC makes it possible for us to retrieve the object straight from REST and does all the binding for us.

Also, some of the URIs are identical across the methods. That's perfectly correct - it's the method (POST, PUT) that makes the distinction. You don't have to do any additional handling in your controller methods (actions).

Let us take a look at the controller then. The code should be familiar by now.

@RequestMapping(value = "/books", method = RequestMethod.POST)
    public BookTO handleBookSave(HttpServletResponse response,
            @RequestBody @Valid BookTO bookTO) {
        BooksBean booksBean = Factory.getBooksBean();
        return booksBean.saveBook(bookTO);
    }

First, notice the RequestMapping annotation and the URI that it handles (/books). Second, notice the method - it's POST which means it's supposed to handle the create case.

It's a controller method which means it's an entry point to handle REST requests and - as promised - it already retrieves an object! How does it happen? Well, that's thanks to the RequestBody annotation. It does auto-binding for us and also does type validation.

What is the Valid annotation for? It tells Spring to do JSR-303 on that object when binding. It uses Hibernate constraints, to be precise (the previously mentioned type checking is independent of this and happens with or without the Valid annotation). Please take a look at this TO object that we validate and note the validation annotations needed for JSR-303.

import javax.validation.constraints.NotNull;
import org.hibernate.validator.constraints.Length;

public class BookTO {
    // ...
    @NotNull @Length(min = 20)
    public String getName() {
        return name;
    }
}

See? It's pretty easy. But... Yeah, there's a problem with it - it doesn't work. That's right, it doesn't work. What's the problem with it? Take a look at the controller code. We have two annotations on the method argument (@Valid and @RequestBody) and when combined, Spring ignores the Valid annotation. It's a bug and it has already been submitted to Spring JIRA. Let's hope this get resolved when Spring 3.1 comes out; I'm keeping my eye on it.

Let's also look at the Dojo part of the story.

function saveBook(bookName) {
    var newBook = {
            "name":bookName
    };
    var actualContent = dojo.toJson(newBook);
    
    //Save.
    var deferred = this._request("rawXhrPost", {
        url: "http://localhost:8080/restsample-0.0.1-SNAPSHOT/rest/books.json",
        handleAs: "json",
        postData: actualContent,
        headers: { "Content-Type": "application/json"}
    });
    deferred.addCallback(this, function(value) {
        alert("Saved! " + value.bookTO.name);
    });
    deferred.addErrback(this, function(value) {                
          alert("Error: "  + value);
    });    
}

We first create an object to pass - the fields have to match the TO object fields (the names have to be the same).

Then we use dojo.toJson to convert this object to a JSON string.

Next, we call our RESTful service. Notice the method - it's rawXhrPost as we need it raw for binding to work. We also pass the object (well, it's a string at this point) as postData. For PUT requests we will use, you guessed it, putData.

And that's all! Download the sample code for this article if you wish.

  1. REST with Spring 3.0, Spring MVC and Dojo. Part 1 - GET
  2. REST with Spring 3.0, Spring MVC and Dojo. Part 2 - GET from Dojo perspective
  3. REST with Spring 3.0, Spring MVC and Dojo. Part 3 - POST and JSR-303 validation
  4. REST with Spring 3.0, Spring MVC and Dojo. Part 4 - PUT (updating objects)
  5. REST with Spring 3.0, Spring MVC and Dojo. Part 5 - DELETE

18 comments:

  1. Hi I just downloaded the source and when I tried to start the Jetty server I'm getting these errors:

    What am I missing? Thank you.

    SEVERE: Context initialization failed
    org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter#0' defined in ServletContext resource [/WEB-INF/rest-servlet.xml]: Cannot create inner bean 'org.springframework.web.bind.support.ConfigurableWebBindingInitializer#1f243d1' of type [org.springframework.web.bind.support.ConfigurableWebBindingInitializer] while setting bean property 'webBindingInitializer'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.bind.support.ConfigurableWebBindingInitializer#1f243d1' defined in ServletContext resource [/WEB-INF/rest-servlet.xml]: Cannot resolve reference to bean 'validator' while setting bean property 'validator'; nested exception is org.springframework.beans.factory.CannotLoadBeanClassException: Error loading class [org.springframework.validation.beanvalidation.LocalValidatorFactoryBean] for bean with name 'validator' defined in ServletContext resource [/WEB-INF/rest-servlet.xml]: problem with class file or dependent class; nested exception is java.lang.NoClassDefFoundError: javax/validation/ValidatorFactory
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveInnerBean(BeanDefinitionValueResolver.java:281)

    ReplyDelete
  2. Hi, well the root cause is "nested exception is java.lang.NoClassDefFoundError: javax/validation/ValidatorFactory" so I would assume that while JBoss (I ran it on JBoss) has the proper class, Jetty doesn't.

    ReplyDelete
  3. Hi,
    I got Jetty to run, the app is working except for saving a book. I'm getting java.lang.NoSuchMethodError: org.codehaus.jackson.map.type.TypeFactory.type(Ljava/lang/reflect/Type;)Lorg/codehaus/jackson/type/JavaType;

    I have tried springframework 3.0.3 and 3.0.5 with org.codehaus.jackson 1.4.2+ but still getting the same error. Do you have any idea?

    Thank you.

    ReplyDelete
  4. Hi, are you sure you are using Jackson 1.4.2? I'm looking at this: https://jira.springsource.org/browse/SPR-7063

    ReplyDelete
  5. Yes, I have read the same post and I'm using


    org.codehaus.jackson
    jackson-mapper-asl
    1.4.2


    org.codehaus.jackson
    com.springsource.org.codehaus.jackson.mapper
    1.4.2

    ReplyDelete
  6. Now that I'm using 1.6.2, it's working for me.

    ReplyDelete
  7. Excellent. :) Good luck with it and thanks for visiting the blog!

    ReplyDelete
  8. I have data in Oracle in HTML form. I need to send this data in XML or JSON format. What is the best way to send html data in XML or JSON format?

    ReplyDelete
  9. Hi kanneganti75,
    I would expect it to be escaped automatically so you shouldn't worry.

    ReplyDelete
  10. Hi Michal,
    Thank you. But I need to put this html in CDATA. How can I do that? Could you please suggest me the best approach?

    ReplyDelete
  11. Sorry to answer question with a question - why do you need to put it in CDATA?
    Maybe you don't?

    ReplyDelete
  12. May be I don't.That was the suggestion from my tech lead.I am sorry to bother you. I have one more query related to xml output. If data is empty jaxbMarshaller is not generating xml tag. How can I generate empty xml tags if data null

    ReplyDelete
  13. You're not bothering me at all. :)

    To answer your question - from my experience marshallers often behave counter intuitive. I doubt you can alter its behaviour - your code should rather accommodate it. If you feel this is a bug - you should report it. Alternatively, you may want to try a different marshaller.
    Cheers

    ReplyDelete
  14. Hi Michal,
    I have one more question.I have more than one domain classes and relation is one to many. I need to generate xml in the following format.


    Here I have two domain objects 1. emp 2. address
    relationship one to many
    Using JAXB how to marshall the object to the xml

    Alternatively, I thought of creating a database view and map the view to the domain object.In this case I need to generate XML wrapper elements addressInfo
    address type="home".

    Please suggest.

    ReplyDelete
  15. Hi, I have one more quick question. How to handle images and videos in Spring REST. Images are stored in db. Also can I use MOXy JAXB with JPA and Hibernate 3.6?

    ReplyDelete
  16. Hi, I have one more quick question. How to handle images and videos in Spring REST. Images are stored in db. Also can I use MOXy JAXB with JPA and Hibernate 3.6?

    ReplyDelete
  17. Hi Srinivas, I have never done this, I can't answer your question. I can only suggest this discussion: http://codemonkeyism.com/how-to-put-binary-data-with-rest/

    ReplyDelete