Announcement Announcement Module
Collapse
No announcement yet.
Automatic convertion of request body to net.sf.json.JSON object Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Automatic convertion of request body to net.sf.json.JSON object

    Hi all,
    I'm building the application based on Ext-JS on client side and Spring on server side. Communication is based on Spring-managed services, which takes input as JSON, and return response also as JSON. (Something REST-like, but not very strictly).
    In other words: the client sends the request, whose body is JSON string (in same cases it may be empty; additionally, there can be be request params too). The server returns response which contains also JSON string.

    At this point, I wan't to have full control over JSON-to-Java convertion, so I do it manually, instead of using some automatic JSON-to-Java marshalling and unmarshalling. Using JSON-lib library makes is really simple. So on the server side, in my controller methods, I want to get net.sf.json.JSON as imput (and optionally some request params too), and be able to return net.sf.json.JSON as output. So from my perspective I would like to be able to write the controller like this:

    Code:
    import net.sf.json.JSON;
    
    @Controller
    public class MyController
    
    @RequestMapping..
    public JSON getEntries(@RequestParam String userId) {
      JSON response = ... //I build JSON response here
      return response;
    }
    
    @RequestMapping..
    public JSON saveEntry(JSON entry) {
      // manually convert entry to Java entity
      // do business
      // return some other JSON as response
      return response;
    }
    
    }

    For converting response, I probably can use JSONView (though I have not tested it yet). But what about automatic conversion of request body to the JSON object? The Spring's binders can bind request params, but no request body, from what I see. Any idea how to implement it?

    At this moment I do it fast-and-dirty, like this:

    Code:
     
      @RequestMapping("post")
       public void saveEntry(Reader in, HttpServletResponse out) throws IOException {
          String inputString = IOUtils.toString(in);
          JSONObject input = JSONObject.fromObject(inputString);
          
          //process input
          
          JSONObject result = // build result
          out.setContentType("application/json; charset=utf-8");
          out.getWriter().write(result.toString());
       }

    But it would be much cleaner to have method like
    Code:
    public JSON saveEntry(JSON input) {...}
    This is a bit similar to how Spring WebServices with POX works, but I need JSON here instead of XML as request/response body.

  • #2
    Hi,


    Return type :
    Rather than return a JSON object you can create a custom JSONView (implements spring View) and define it in your mvc-config.xml as the View Class.

    Method Signature:
    I dont know exactly how is define your JSON object, have you tried to implements a custom binder?

    ...

    Comment


    • #3
      oups NO! custom binder it s not possible, because it is all the command object and not only a field of your command object

      Comment


      • #4
        The problem with binder is that you can only bind request parameters, not request body, AFAIK. I'd like to bind/convert the response body the the JSON object and pass it as method parameter.
        Regarding JSONView: probably I can use it, but in fact it is not semantically correct, and does not make really sens here: views are for server-side web application, because you really return the VIEW (from MVC) to the client. Here it is not like this. I return simply data, it is more web-service for the client.
        This is why I said I need solution similar to how Spring Web Services work: you don't define any views there. You simply take input XML document, and return output XML document in your service methods (or optionally you can register some marshallers/unmarshallers too).

        Comment


        • #5
          In fact the JSonView will write exactly as you made in the response. It is just to make the developpement more "beautifull"
          For the input, I think you have to create your own ServletRequestBinder, and transform the request to JSOn object.

          I Have a question ... for Forms, the Spring binding is very usefull,
          how do you do on the client side to transform your form into JSON and server side your JSON into command object? and how do you proceed for File upload?





          I am currently designing a quite similar application (Spring MVC + Spring Web Flow + (ExtJs or YUI)), for the moment I have a POC working with PrototypeJS+scriptaculous but I will used ExtJS OR YUI.
          Last edited by jujuz; Jan 20th, 2009, 08:16 AM.

          Comment


          • #6
            Originally posted by jujuz View Post
            I Have a question ... for Forms, the Spring binding is very usefull,
            how do you do to transform your form into JSON and server side your JSON into command object? and how do you proceed for File upload?
            In Ext-JS I simply call the method that returns me the data from the form as the JSON object (key-value pairs), and send it. On server side, I instantiate the model object and rewrite manually the fields form JSON to this object, using JSON-lib. There is not much fields to copy, so it's not problematic. So I don't use Spring binding for this purpose.
            This is a bit similar to copying data from model to DTO, but in this case the JSON object is a DTO.
            I don't use file upload, so I can't say. Ext-JS has some component for it, but I don't know how it works.

            Comment


            • #7
              I found the solution of converting incoming request body to the method parameter. The answer is in the Javadoc of AnnotationMethodHandlerAdapter class.
              There is a method setCustomArgumentResolver() taking WebArgumentResolver implementation, which can be used here. So I created the class:

              Code:
              public class JsonArgumentResolver implements WebArgumentResolver {
              
                 public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) throws IOException {
                    if (methodParameter.getParameterType().equals(JSONObject.class)) {
                       ServletRequest req = (ServletRequest)webRequest.getNativeRequest();
                       String inputString = IOUtils.toString(req.getReader());
                       JSONObject json = JSONObject.fromObject(inputString);
                       return json;
                    }
                    return UNRESOLVED;
                  }
              
              }
              and injected it into AnnotationMethodHandlerAdapter in spring context configuration file. Works like a charm. Now I can add JSONObject parameter to methods in my controller and the JSON content is automatically binded.

              This solves the input binding. But output binding still remains. As it was said, I can use JSONView, but I don't like this idea. As I said earlier, I'm constructing really service here, so I return data, not view. It makes no sense of using views in such case. My application is not server-side MVC application. I use only C and to some extend M from MVC here.
              I'm thinking about writing the around aspect, that would be able to take the returned value, convert it and send to output stream, and return null finally. But the problem is, that I need access to ServletResponse object in the aspect. How can I do it? I googled a lot, but I haven't found any solution how to get access to current request/response objects from the aspect.

              Comment


              • #8
                It's strange, but it seems impossible to inject current request or response object into aspect. I can obviously get as join point method argument, but then all my controller methods would have to declare ServletResponse as one of arguments, and this is exactly what I wanted to avoid.
                Unfortunatelly, AnnotationMethodHandlerAdapter does not provide and extension point for this. The code of invokeHandlerMethod of this class contains following section:
                Code:
                Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
                			ModelAndView mav =
                					methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest);
                The point is that I have to plug my extension between those two lines: after abtaining the result form handler method, and before calling getModelAndView. More preceisely, I need to overwrite the getModelAndView() method. Unfortunatelly, this method is called on some private inner class, what prevents from subclassing and overriding it.
                Finally, I copied the code of AnnotationMethodHandlerAdapter and created my own, patched version, which introduces the ModelAndViewResolver interface, which contains getModelAndView() method, and one is able to plug in his own resolver into AnnotationMethodHandlerAdapter via new setCustomModelAndViewResolver() method.
                Code:
                         Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
                         ModelAndViewResolver customModelAndViewResolver = null;
                         if (customModelAndViewResolvers != null) {
                            for (ModelAndViewResolver mavr : customModelAndViewResolvers) {
                               if (mavr.supports(handlerMethod.getReturnType())) {
                                  customModelAndViewResolver = mavr;
                               }
                            }
                         }
                         ModelAndView mav;
                         if (customModelAndViewResolver != null) {
                            mav = customModelAndViewResolver.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest);
                         } else {
                            mav = methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest);
                         }
                I will submit it as RFE to Spring JIRA.

                Comment


                • #9
                  Submitted as http://jira.springframework.org/browse/SPR-5426

                  Comment

                  Working...
                  X