Results 1 to 8 of 8

Thread: Simulate sessionForm with @SessionAttributes

Hybrid View

  1. #1

    Default Simulate sessionForm with @SessionAttributes

    In the old AbstractFormController you had the option to keep the form object in session between subsequent POST requests but to be cleared (transparently) at the first GET request (using setSessionForm(true)).
    The current @SessionAttributes annotation implies keeping the specified model attributes until you call SessionStatus.setComplete() and I find it difficult to simulate the old functionality.

    You can not call setComplete() in the last POST request because you don't know if it's going to be the last, neither can you call it in GET requests because you want it cleared before your GET handler so the method that creates the model is called.

    I can think of two workarounds but neither is a clean way of implementing this.

    1: Call model attribute creator method yourself in GET:
    Code:
    @SessionAttributes("myCommand")
    @RequestMapping("...")
    public class MyController {
    
      @ModelAttribute("myCommand")
      public MyCommand createCommand(@RequestParameter("...") someParam) {
         //create and return a new MyCommand object using someParam
      }
    
      @RequestMapping(method = RequestMethod.GET)
      public String show(ModelMap modelMap, @RequestParameter("...") someParam) {
        //just ignore the one in session, don't even add it as a @ModelAttribute parameter
        MyCommand myCommand = createCommand(someParam);
        modelMap.put("myCommand", myCommand);
        //use myCommand
        return "someView";
      }
    
    }
    The main problems with this approach are that you have to add every parameter createCommand needs to every GET method and you have to assume the implementation uses your myCommand (from modelMap) and not the one in session.

    2. complete the session and do a forward
    Code:
    @SessionAttributes("myCommand")
    @RequestMapping("...")
    public class MyController {
    
      @ModelAttribute("myCommand")
      public MyCommand createCommand(@RequestParameter("...") someParam, HttpSessionRequest request) {
         request.addAttribute("createCommandWasCalled", true);
         //create and return a new MyCommand object using someParam
      }
    
      @RequestMapping(method = RequestMethod.GET)
      public String show(@ModelAttribute("myCommand") MyCommand myCommand, SessionStatus sessionStatus, HttpSessionRequest request) {
        if (request.getAttribute("createCommandWasCalled") == null) {
          sessionStatus.setComplete();
          return "forward:...";
        }
        //use myCommand
        return "someView";
      }
    
    }
    The problems with this approach are that you have to add HttpSessionRequest to all GET methods and createCommand method and you have to add an attribute to the request like "createCommandWasCalled" in order to avoid recursion (you only forward the first time, when createCommand was not called because myCommand was in session).

    Both approaches add code that is not straightforward. Is there a better way of doing this ?

  2. #2

    Default

    I find it odd no one bumped into this problem, sessionForm was a powerful feature.
    Anyway there were two other solutions I considered :

    3. Have an interceptor that removes the model from session before the handler is executed. The problem wit this is that it would be executed for every request and it has to scan the annotations, parse @SessionAttributes value and somehow call remove from the SessionAttributeStore. And you should add another annotation that cancels this so you also have the default meaning of @SessionAttributes.

    4. This is the method I'm probably going to use. Just forget about @SessionAttributes, don't try to adapt it for some custom behavior (even if it was standard in the old MVC). Handle your own session:
    Code:
    @RequestMapping("...")
    public class MyController {
    
      private static final String MODEL_SESSION_KEY = "...";
    
      @ModelAttribute("myCommand")
      public MyCommand createCommand(@RequestParameter("...") someParam, @HttpServletRequest request) {
         if ("GET".equals(request.getMethod() || request.getSession.getAttribute(MODEL_SESSION_KEY) == null) {
           //create and return a new MyCommand object using someParam
         } else {
            return (MyCommand)request.getSession.getAttribute(MODEL_SESSION_KEY);
         }
      }
    
      @RequestMapping(...)
      public String show(@ModelAttribute("myCommand") MyCommand myCommand) {
        //use myCommand
        return "someView";
      }
    
    }

  3. #3

    Default Optional session attribute?

    It's a pity the documentation on annotated controllers is so inadequate. There ought to be documentation on migrating from pre-annotation Spring MVC explaining how to accomplish functionality like this that was easy before.

    On a related note, I've got a form that's backed by a command object stored in the session, but if it's not found in the session, I want to create a new one. An optional session attribute in other words. Problem is I get an error ("Session attribute 'search' required - not found in session") if there's no cmd object in the session.

    Code:
    @SessionAttributes(types=Search.class)
    public class SearchController {
    ...
        @RequestMapping(value = "/search", method = RequestMethod.GET)
        public String showForm(@ModelAttribute Search search, 
        		@RequestParam(value = "reset", required = false) Boolean reset,
        		ModelMap modelMap) {
        	if (search == null || reset)
        		modelMap.addAttribute("search", new Search());
    I don't seem to be allowed to map 2 methods to the same URL either, one with ModelAttribute and one without. It looks like I would have to map them to different URLs, or manage the session storage manually and avoid the @SessionAttributes annotation altogether. Any ideas anyone?

    Thanks.

  4. #4

    Default

    Any handler method that needs a model attribute which is specified in @SessionAttributes will fail if there is no method that creates the model attribute.
    This is another difference from the previous version of MVC. Spring will not create a model for you if it can not find the model in session, however it will call your @ModelAttribute methods.
    So this should work for you :
    Code:
    @SessionAttributes(types=Search.class)
    public class SearchController {
    ...
        @ModelAttribute
        public Search createSearch() {
            return new Search();
        }
    ...
        @RequestMapping(value = "/search", method = RequestMethod.GET)
        public String showForm(@ModelAttribute Search search, 
        		@RequestParam(value = "reset", required = false) Boolean reset,
        		ModelMap modelMap) {
        	//Search will allways exist here
    It all has to do with the implementation of the handler. Previously we had a detailed life-cycle of SimpleFormController or AbstractFormController, now there is no documentation for that.

    Looking through the sources one can see the general algorithm :

    Code:
    - determine all model attributes that match @SessionAttributes, for each do :
      - get the value from session
      - if it was not found in session call the appropriate creator method and store the result in the model map
    - ..... binding and other stuff ......
    - determine what attributes are needed by the handler method, for each do :
       - if the model is found in the model map use that value
       - else if the model matches @SessionAttributes then:
           - get the value from session
           - if it was not found in the session throw an exception
    All of this happens before your handler method is called, so in your case the path leads to the exception you get (because the model is not in the session nor does it have a creator method).

    LE: by "creator method" I mean those methods annotated with @ModelAttribute. This annotation is a bit misleading because it can be applied to both methods and arguments with different meanings (for a method it means it creates the model and for an argument it means injecting that model)
    Last edited by senoctar; May 18th, 2010 at 08:34 AM.

  5. #5

    Default

    Thank you very much for that helpful explanation. This is just the sort of useful information that ought to be documented in the Spring docs.

  6. #6
    Join Date
    Oct 2005
    Location
    Mobile, AL
    Posts
    345

    Default My Solution

    Senoctar,

    I ran into the same issue and I basically replace the current sessionAttribute in my method that does the initial "get" request and I don't call the .setComplete().

    For instance, my get method does the following:

    Code:
    // re-initialize the SearchCriteria to be the default.
    SearchCriteria searchCriteria = new searchCriteria();
    modelMap.addAttribute("searchCriteria", searchCriteria);
    So basically the initial get method creates a new instance and replaces the old instance if it already exists and subsequent post methods use the existing session bean.

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •