Announcement Announcement Module
Collapse
No announcement yet.
AbstractWizardFormController based controller calling handleInvalidSubmit Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • AbstractWizardFormController based controller calling handleInvalidSubmit

    I have a simple 2 page form with a 3rd page being the success/confirmation page. I am extending AbstractWizardFormController. It was working at first, but now, regardless of what is entered into the form on page 1, the submit just redisplays page 1 with the form empty.

    I tracked the problem down to handleInvalidSubmit being called. I overrode this method and added logging. The Wrox Spring book page 472 states that this happens when sessionForm is true (AbstractWizardFormController sets this to true) and Spring cannot find the command object in session.

    AbstractFormController.getCommand is removing my command object from session. I do not really understand why it does this. Its javadoc says "Note that the form object gets removed from the session, but it will be re-added when showing the form for resubmission."

    The source code comments say:

    // Remove form object from HTTP session: we might finish the form workflow
    // in this request. If it turns out that we need to show the form view again,
    // we'll re-bind the form object to the HTTP session.

    When I submit my page 1, whatever process is supposed to rebind my command object so that validation can run and so forth, is not running.

    I hate to paste in a bunch of code and ask people to debug for me. Can someone give me some insight as to what might cause this? I've got 4 spring books, but am dying for the dedicated MVC/WebFlow book.

  • #2
    Go ahead and post your code

    Comment


    • #3
      Code Samples

      Code:
      <bean name="/signup/customer.edit" class="com.viz.web.controller.springmvc.publicsite.signup.CustomerController">
          <property name="lookupService"><ref bean="lookupService"/></property>
          <property name="customerService"><ref bean="customerService"/></property>
          <property name="creditCardService"><ref bean="creditCardService"/></property>
          <property name="pages">
              <list>
                  <value>.signup.customer</value>
                  <value>.signup.creditcard</value>
              </list>
          </property>
          <property name="pageAttribute"><value>currentPage</value></property>
      </bean>
      Code:
      public class CustomerController extends AbstractWizardFormController {
          private static Log log = LogFactory.getLog(CustomerController.class);
      
          private static final int
                  CUSTOMER_PAGE = 0,
                  CREDIT_CARD_PAGE = 1;
      
          private LookupService lookupService;
          private CustomerService customerService;
          private CreditCardService creditCardService;
      
          public CustomerController() {
              super();
              setCommandClass(Customer.class);
              setCommandName("customer");
              //setBindOnNewForm(true);
          }
      
          public void setLookupService(LookupService service) {
              this.lookupService = service;
          }
      
          public void setCustomerService(CustomerService service) {
              this.customerService = service;
          }
      
          public void setCreditCardService(CreditCardService service) {
              this.creditCardService = service;
          }
      
          @Override
          protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) {
              binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
      
              SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy");
              dateFormat.setLenient(false);
              binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
          }
      
          @Override
          protected ModelAndView handleInvalidSubmit(HttpServletRequest request, HttpServletResponse response) throws Exception {
              System.out.println("IN handleInvalidSubmit!!!!!!!!!");
              System.out.println("Request Method = " + request.getMethod());
      
              System.out.println("Session Form = " + isSessionForm());
      
              throw new IllegalStateException("SpringMVC was looking for command object in session and could not find it!");
      
              // AbstractFormController.getCommand is removing command from session.  It says:
      
              /* Note that the form object
               * gets removed from the session, but it will be re-added when showing the
               * form for resubmission.
               */
      
              //return super.handleInvalidSubmit(request, response);
          }
      
          /**
           * This method handles the role of Edit
           */
          @Override
          public Object formBackingObject(HttpServletRequest request) {
              Customer customer = new Customer();
              customer.setName("Sample Name");
              customer.setActiveCreditCard(new CreditCard());
              return customer;
          }
      
      
          /**
           * This method handles the role of Prepare
           */
          @Override
          protected Map referenceData(HttpServletRequest request, int i) throws Exception {
              Map<String, Object> map = new HashMap<String, Object>();
      
              map.put("commandObject", getCommand(request));
      
              if (i == CUSTOMER_PAGE) {
                  map.put("stateCollection", lookupService.getState());
              } else if (i == CREDIT_CARD_PAGE) {
                  map.put("creditCardTypeCollection", lookupService.getCreditCardType());
                  map.put("monthCollection", lookupService.getMonth());
                  map.put("yearCollection", lookupService.getYear());
                  map.put("stateCollection", lookupService.getState());
              }
              return map;
          }
      
          @Override
          protected void validatePage(Object object, Errors errors, int i, boolean isFinish) {
      
              System.out.println("Entering validatePage");
      
              Customer customer = (Customer) object;
              if (customer == null) {
                  throw new IllegalArgumentException("Customer is null!");
              }
      
              log.debug("errors.getNestedPath() = " + errors.getNestedPath());
      
              if (i == CUSTOMER_PAGE) {
                  genericValidation(customer, errors);
              } else if (i == CREDIT_CARD_PAGE) {
                  CreditCard creditCard = customer.getBillingOptions().getCreditCard();
                  if (creditCard == null) {
                      throw new IllegalArgumentException("Credit Card is null!");
                  }
      
                  String creditCardErrorPrefix = "billingOptions.creditCard.";
                  genericValidation(creditCard, errors, creditCardErrorPrefix);
      
                  if (!errors.hasErrors()) {
                      // Perform "expensive" validation only if no simple errors found above.
                      boolean validCard = creditCardService.validateCreditCard(creditCard);
                      if (!validCard) {
                          errors.reject("error.creditcard.invalid"); // Spring is adding a .customer suffix to the error key.  No idea why.
                      }
                  }
              }
          }
      
          private void genericValidation(BaseObject modelObject, Errors errors) {
              genericValidation(modelObject, errors, null, null);
          }
      
          private void genericValidation(BaseObject modelObject, Errors errors, String errorPrefix) {
              genericValidation(modelObject, errors, errorPrefix, null);
          }
      
          private void genericValidation(BaseObject modelObject, Errors errors, Set<String> relevantProperties) {
              genericValidation(modelObject, errors, null, relevantProperties);
          }
      
          private void genericValidation(BaseObject modelObject, Errors errors, String errorPrefix, Set<String> relevantProperties) {
              InvalidValue[] validationMessages = AnnotationValidator.getInvalidValues(modelObject, relevantProperties);
              for (InvalidValue value : validationMessages) {
                  log.debug("getBean() = " + value.getBean()); // Returns top level or nested bean instance cast to Object
                  log.debug("getBeanClass() = " + value.getBeanClass()); // Returns Customer, or nested such as Contact class
                  log.debug("getMessage() = " + value.getMessage()); // Didn't specify resourceBundle, so returns nice String error text
                  log.debug("getPropertyName() = " + value.getPropertyName()); // Name of property as a string, such as "number" from getNumber()
                  Object propertyValue = value.getValue();
      
                  log.debug("getValue() = " + propertyValue); // Gets value from the get method.
      
                  // The following code is needed due to a limitaton in the validator framework.
                  // http://opensource.atlassian.com/projects/hibernate/browse/ANN-125
                  String secondaryErrorPrefix = "";
                  Class resultClass = value.getBeanClass();
                  if (Customer.class.equals(resultClass)) {
                      secondaryErrorPrefix = "";
                  } else if (Contact.class.equals(resultClass)) {
                      secondaryErrorPrefix = "contact.";
                  } else if (Address.class.equals(resultClass)) {
                      secondaryErrorPrefix = "contact.address.";
                  }
      
                  String prefix;
                  if (errorPrefix != null) {
                      prefix = errorPrefix + secondaryErrorPrefix;
                  } else {
                      prefix = secondaryErrorPrefix;
                  }
      
                  if (! "customer".equals(value.getPropertyName()) && !"customerId".equals(value.getPropertyName())) { // Hack to ignore bidirectional relationship for now.
      
                      errors.rejectValue(prefix + value.getPropertyName(), value.getMessage(), value.getMessage());
      
                      log.debug("Rejected value.  prefix = " + prefix + value.getPropertyName());
                      log.debug("Rejected value.  message = " + value.getMessage());
      
                  }
      
                  // The above prepends customer. because that is the command object being used.
                  // Validation is done on credit card, so the returned field is just number.
                  // Need to add in the middle object navigation to connect the command object to the specific field.
              }
      
      
          }
      
      
          @Override
          protected ModelAndView processFinish(HttpServletRequest request, HttpServletResponse response, Object object, BindException bindException) throws Exception {
              // Save the signup after validations are complete
      
              Customer customer = (Customer) object;
      
              customer.setTypeId(Customer.PROFESSIONAL);
      
              RatePlan ratePlan = (RatePlan) request.getSession().getAttribute("ratePlan");
      
              customer.getBillingOptions().setRatePlanId(ratePlan.getId());
      
              int customerId = customerService.signupCustomer(customer);
      
              return new ModelAndView(".signup.confirmation");
          }
      }
      Code:
      <form method="post" action="<c:url value="/signup/customer.edit"/>">
          <input type="hidden" name="_page" value="${currentPage}"/>
          <%@ include file="/includes/edit_customer_info.jsp" %>
          <input type="submit" value="Next" name="_target1"/>
      </form>

      Comment


      • #4
        Can you please print out your relevant debug (i.e. all your printlns) as well (sorry, forgot to ask).

        Comment


        • #5
          Debug log

          This is what displays if I enter any junk (or nothing) into the form and submit.

          Code:
          [viz] DEBUG [resin-tcp-connection-*:8080-3] DispatcherServlet.doService(633) | DispatcherServlet with name 'springmvc' received request for [/signup/customer.edit]
          [viz] DEBUG [resin-tcp-connection-*:8080-3] DispatcherServlet.getHandler(844) | Testing handler map [org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping@bc676b] in DispatcherServlet with name 'springmvc'
          [viz] DEBUG [resin-tcp-connection-*:8080-3] AbstractUrlHandlerMapping.getHandlerInternal(134) | Looking up handler for [/signup/customer.edit]
          [viz] DEBUG [resin-tcp-connection-*:8080-3] DispatcherServlet.getHandlerAdapter(883) | Testing handler adapter [org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter@29bc56]
          IN handleInvalidSubmit!!!!!!!!!
          Request Method = POST
          Session Form = true
          [viz] ERROR [resin-tcp-connection-*:8080-3] FrameworkServlet.processRequest(412) | Could not complete request
          java.lang.IllegalStateException: SpringMVC was looking for command object in session and could not find it!
                  at com.viz.web.controller.springmvc.publicsite.signup.CustomerController.handleInvalidSubmit(CustomerController.java:112)
                  at org.springframework.web.servlet.mvc.AbstractFormController.handleRequestInternal(AbstractFormController.java:251)
                  at org.springframework.web.servlet.mvc.AbstractController.handleRequest(AbstractController.java:139)
                  at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:44)
                  at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:717)
                  at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:658)
                  at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:392)
                  at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:357)
                  at javax.servlet.http.HttpServlet.service(HttpServlet.java:154)
                  at javax.servlet.http.HttpServlet.service(HttpServlet.java:92)
                  at com.caucho.server.dispatch.ServletFilterChain.doFilter(ServletFilterChain.java:106)
                  at com.caucho.server.webapp.WebAppFilterChain.doFilter(WebAppFilterChain.java:178)
                  at com.caucho.server.dispatch.ServletInvocation.service(ServletInvocation.java:229)
                  at com.caucho.server.http.HttpRequest.handleRequest(HttpRequest.java:259)
                  at com.caucho.server.port.TcpConnection.run(TcpConnection.java:386)
                  at com.caucho.util.ThreadPool.runTasks(ThreadPool.java:490)
                  at com.caucho.util.ThreadPool.run(ThreadPool.java:423)
                  at java.lang.Thread.run(Thread.java:613)

          Comment


          • #6
            Any ideas, or other information I need to post?

            Comment


            • #7
              Added some debugging code

              Code:
                 @Override
                  protected ModelAndView handleInvalidSubmit(HttpServletRequest request, HttpServletResponse response) throws Exception {
                      System.out.println("IN handleInvalidSubmit!!!!!!!!!");
                      System.out.println("Request Method = " + request.getMethod());
              
                      System.out.println("Session Form = " + isSessionForm());
              
              
                      if (isSessionForm()) {
                          HttpSession session = request.getSession(false);
                          if (session == null) {
                              throw new IllegalStateException("SpringMVC tried to access the current session and could not find it");
                          } else if (session.getAttribute(getFormSessionAttributeName(request)) == null) {
                              Enumeration sessionAttributes = session.getAttributeNames();
                              while (sessionAttributes.hasMoreElements()) {
                                  Object element = sessionAttributes.nextElement();
                                  System.out.println("Session Element = " + element);
                              }
              
                              System.out.println("Form element name = " + getFormSessionAttributeName(request));
              
                              //ServletRequestDataBinder binder = createBinder(request, new Customer());
                              Customer command = new Customer();
                              ServletRequestDataBinder binder = bindAndValidate(request, command);
                              return processFormSubmission(request, response, command, binder.getErrors());
                      // The above line is where the running code reaches.
                              //throw new IllegalStateException("SpringMVC was looking for command object in session and could not find it!");
                          } else {
                              throw new IllegalStateException("SpringMVC had an unknown problem causing handleInvalidSubmit to be run.");
                          }
                      } else {
              
                          // AbstractFormController.getCommand is removing command from session.  It says:
              
                          /* Note that the form object
                          * gets removed from the session, but it will be re-added when showing the
                          * form for resubmission.
                          */
              
                          return super.handleInvalidSubmit(request, response);
                      }
                  }
              I added a little more code to help me at least get past page 1 and continue working. I am still unsure of what the problem is or exactly how and why spring removes my command object from session and plans to replace it.

              Comment


              • #8
                A workaround for this ....

                I encountered this same issue. I, similarly was calling getCommand in my overriden referenceData method. I'd go to handleInvalidSubmit as well...

                I added the following method to MY wizardController (which extends AbstractWizardFormController) to get the command without the undesired side affect of the session form object (command obj) being toasted. I call this instead of getCommand. (I stole this code from Spring's AbstractFormController and removed the session.removeAttribute stuff....)

                WARNING: THIS IS UGLY BUT IT DID THE TRICK FOR ME.

                /**
                * Method to get the command, but doesn't remove the sessionForm like getCommand does...
                *
                */
                protected Object getCommandNoRemoval(HttpServletRequest request) throws Exception
                {
                // Session-form mode: retrieve form object from HTTP session attribute.
                HttpSession session = request.getSession(false);
                if (session == null) {
                throw new ServletException("Must have session when trying to bind (in session-form mode)");
                }
                String formAttrName = getFormSessionAttributeName(request);
                Object sessionFormObject = session.getAttribute(formAttrName);
                if (sessionFormObject == null) {
                throw new ServletException("Form object not found in session (in session-form mode)");
                }
                return sessionFormObject;
                }

                Comment


                • #9
                  Thanks for the reply. I'd put this part of the project on the backburner and worked on other stuff in hopes the MVC book would be out by now.

                  I was looking into this and I was thinking of calling getCommand and then calling request.getSession().setAttribute to stuff it right back in.

                  I still wish one of the Spring MVC gurus here would explain why it works this way, and if there should be an enhancement request for a method without this side effect in 2.0.

                  Comment

                  Working...
                  X