Monday, January 24, 2011

Accessing ROO with JSON and Prototype.js

I decided to play around a little this week-end with JSON and Roo. The main reason is that I wanted to look into contributing to a solution for ROO-207, and it seems to me that JSON will probably factor greatly into it. Initially, I was testing against the PetClinic example (clinic.roo), but when I started to see the errors I am describing here, I decided to go for a more basic example. I decided to use prototype.js to facilitate the ajax calls from within JavaScript since I will be building a test UI completely from scratch without anything from ROO.

Setup
Create a directory called jsonlist. The following is the contents of my roo.log.
// Spring Roo 1.1.1.RELEASE [rev 156ccd6]
project --topLevelPackage ca.digitalface.jsonlist
persistence setup --database HYPERSONIC_IN_MEMORY --provider HIBERNATE
entity --class ~.Item --testAutomatically
field string --fieldName name --notNull
controller all --package ~.web
json all
Create a basic html page (I called mine testJSONPrototype.html) to test the JavaScript access.
Put it in src/main/webapp.
<html>
<head>
<title>Accessing ROO with JSON and Prototype.js</title>
<script type="text/javascript" src="./prototype.js"></script>
<script type="text/javascript">
  document.observe('dom:loaded', function() {
    var url = 'http://localhost:8080/jsonlist/items';
      new Ajax.Request(url, {
        method: 'POST', method: 'GET',
        contentType: 'application/json',
        requestHeaders: ["Accept","application/json"],
        onLoaded: function(){alert("sent");},
        onSuccess: function(transport) {
          var val = transport.responseText;
          var target = $('target');
          if (transport != null)
            target.update('Response: '+val);
          else
            target.update('Transport is null!');
        }
    });
  });
</script>
</head>
<body>
<h1>Accessing ROO with JSON and Prototype.js</h1>
<div id="target">List of Items.</div>
</body>
</html>
Download the prototype.js file from www.prototypejs.org and put it in src/main/webapp.


Execute
Run the application with mvn tomcat:run and then open http://localhost:8080/jsonlist in your browser.
Add some items to the list.

Here's where the confusion comes in.
If you use curl at the command line, you get the JSON text as you would expect,
curl -i -H "Accept: application/json" http://localhost:8080/jsonlist/items

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json;charset=UTF-8
Content-Length: 92
Date: Mon, 24 Jan 2011 12:57:32 GMT

[{"id":1,"name":"aaaaaaaaaaa","version":0},{"id":2,"name":"bbbbbbbbbbbbbbbbbb","version":0}]
however if you go to the test page (localhost:8080/jsonlist/testJSONPrototype.html) we created above it fails with the following error and stack trace.
Stepping back two steps is not supported
flexjson.JSONTokener.back(JSONTokener.java:83)
flexjson.JSONTokener.nextValue(JSONTokener.java:378)
flexjson.JSONDeserializer.deserialize(JSONDeserializer.java:150)
ca.digitalface.jsonlist.Item_Roo_Json.fromJsonToItem_aroundBody0(Item_Roo_Json.aj:21)
ca.digitalface.jsonlist.Item_Roo_Json.ajc$interMethod$ca_digitalface_jsonlist_Item_Roo_Json$ca_digitalface_jsonlist_Item$fromJsonToItem(Item_Roo_Json.aj:1)
ca.digitalface.jsonlist.Item.fromJsonToItem(Item.java:1)
ca.digitalface.jsonlist.Item_Roo_Json.ajc$interMethodDispatch1$ca_digitalface_jsonlist_Item_Roo_Json$ca_digitalface_jsonlist_Item$fromJsonToItem(Item_Roo_Json.aj)
ca.digitalface.jsonlist.web.ItemController_Roo_Controller.ajc$interMethod$ca_digitalface_jsonlist_web_ItemController_Roo_Controller$ca_digitalface_jsonlist_web_ItemController$createFromJson(ItemController_Roo_Controller.aj:107)
ca.digitalface.jsonlist.web.ItemController.createFromJson(ItemController.java:1)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
java.lang.reflect.Method.invoke(Method.java:597)
org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:176)
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:426)
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:414)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:790)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:719)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:644)
org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:560)
javax.servlet.http.HttpServlet.service(HttpServlet.java:637)
javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter.doFilterInternal(OpenEntityManagerInViewFilter.java:113)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:857)
org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588)
org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
java.lang.Thread.run(Thread.java:662)

Here are the headers that were captured by the "Live HTTP Headers" plugin for Firefox.
http://localhost:8080/jsonlist/items

POST /jsonlist/items HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.04 (lucid) Firefox/3.6.13
Accept: application/json
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
X-Requested-With: XMLHttpRequest
X-Prototype-Version: 1.7
Content-Type: application/json; charset=UTF-8
Referer: http://localhost:8080/jsonlist/testJSONPrototype.html
Content-Length: 0
Cookie: JSESSIONID=2F82F1236EA5EFDC0C0464A87F64EB67
Pragma: no-cache
Cache-Control: no-cache

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=1BE83EB4104405124F75B91A652197D5; Path=/jsonlist
Content-Type: text/html;charset=UTF-8
Content-Language: en-US
Content-Length: 7304
Date: Mon, 24 Jan 2011 14:21:51 GMT
I have not yet figured out the solution to this, but I thought I'd open a JIRA Ticket and see what comes of it. Watch this space for updates.

Update
Well, thanks to Stefan's comment the solution was easy.  8^)
I only had to change the method from POST to GET, and everything went as planned. I was sure that I had tried that, but I guess not.

There is no denying the value of a second set of eyes!

1 comment:

  1. So to understand the intention of your test application - you want to send a HTTP POST message to the JSON enabled controller? If so you should make sure that you actually send the JSON document along in the POST message. Currently you are not sending any data which is visible from your headers: Content-Length: 0

    I would suggest you start off by doing a HTTP GET first to retrieve and display JSON data coming from your controller.

    -Stefan

    ReplyDelete