Announcement Announcement Module
Collapse
No announcement yet.
Handling many to one relationships using a Form Controller Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Handling many to one relationships using a Form Controller

    Hello,

    I am currently working on a web form to edit a User object from my domain model. The User can be a member of zero or more Organisations and have zero or more Capabilities. I have decided to "spike" a Form Controller that extends the very handy looking SimpleFormController.

    Here is what I have so far..

    Code:
    public class UserFormController extends SimpleFormController {
    
        private UserDirectoryService uds;
        private OrganisationService os;
        private AuditService as;
    
        private static final char USER_FORM_JOIN_ORGANISATION='j';
        private static final char USER_FORM_LEAVE_ORGANISATION='l';
        private static final char USER_FORM_CHANGE_PASSWORD='p';
        private static final char USER_FORM_ADD_CAPABILITY='c';
        private static final char USER_FORM_DROP_CAPABILITY='d';
        private static final char USER_FORM_COMMAND_SUBMIT='s';
        
        public UserFormController() {
            setSessionForm(true);
            setValidateOnBinding(false);
            setCommandName("userForm");
            setFormView("editUserForm");
            setSuccessView("editUserForm");
        }
    
        public void setUserDirectoryService(UserDirectoryService uds) {
            this.uds = uds;
        }
        public void setOrganisationService(OrganisationService os) {
            this.os = os;
        }
        public void setAuditService(AuditService as) {
            this.as = as;
        }
    
        protected Object formBackingObject(HttpServletRequest request)
                throws Exception {
            String sId = request.getParameter("id");
            Long id = new Long(sId);
            if (id != null) {
                User u = uds.getUser(id);
                return new UserForm(uds.getUser(id), os.getAllOrgCodesAndDescriptions());
            }
            return new UserForm(os.getAllOrgCodesAndDescriptions());
        }
         
        protected ModelAndView onSubmit(HttpServletRequest request,
                HttpServletResponse response, Object command, BindException errors)
                throws Exception {
            
            UserForm uf = (UserForm) command;
            
            switch(request.getParameter("command").charAt(0)) {
            	case USER_FORM_JOIN_ORGANISATION:
            	    uf.getUser().addOrganisation(os.getOrganisation(uf.getAddedOrgCode()));
            	    break;
            	case USER_FORM_LEAVE_ORGANISATION:
            	    //uf.getUser().removeOrganisation(os.getOrganisation(uf.getAddedOrgCode()));
            	    break;
            	case USER_FORM_CHANGE_PASSWORD:
            	    uf.setChangePassword(true);
            	    break;
            	case USER_FORM_ADD_CAPABILITY:
            	    if (uf.isRoleOnlyCapability()) {
            	        uf.getUser().addCapability(uf.getAddedCapRole());
            	    }
            	    else {
            	        uf.getUser().addCapability(uf.getAddedCapRole(),
            	                os.getOrganisation(uf.getAddedCapOrgCode()),
            	                as.getMetaAudit(uf.getAddedCapAuditRef()));
            	    }
            	    break;
            	case USER_FORM_DROP_CAPABILITY:
            	    break;
            	case USER_FORM_COMMAND_SUBMIT:
            	    if (uf.isNewUser()) {
                        uds.saveUser(uf.getUser());
                    }
                    else {
                        uds.updateUser(uf.getUser());
                    }
            	    break;
            }
            return super.onSubmit(request,response,command,errors);
        }
    }
    The behavior I am after is to allow the administrator to manipulate the user but not to commit any changes until the form is submitted. As you can see, the form has multiple submit buttons for adding and dropping capabilities and organisations. I rather like the idea of the setSessionForm attribute although it will not work in this case as I believe that the simpleFormController (and even AbstractFormController) only expects a single submit button (and therefore success or failiure).

    Has anybody managed to find an elegant solution/example to this...

    Cheers,


    James

    PS please ignore the obvious ommisions (binding, validation, etc).
    PPS using velocity.

  • #2
    I think you would be better off using an extension of AbstractWizardFormController and a sessionForm=true of course.

    You could have a main page which binds the form elements to the main attributes of the user object with seperate pages for adding capabilities/organisations. This will allow you to use the
    showPage() methods to create a new Organisation object, after submission you can then validate the object in validatePage() and add it to the Set of your organisations in the original form object.

    By using submit buttons with names "_targetX" where X is the number of the page to go to you can navigate to the addOrganisation addCapability screens

    ie,
    page 1 = main
    page 2 = addOrganisation
    page 3 = removeOrganisation
    page 4 = addCapability
    page 5 = removeCapability

    on the main screen there will be a submit button "_finish" which will allow you to use processFinish() method to persist the model.

    I havn't tried this myself so can't be sure if it'll work/elegant efficent solution but it may give u ideas.

    Comment


    • #3
      I have a number of forms doing what you suggest, with mutiple submit buttons, and different behaviour depending on the button clicked.

      I think you may be limiting your flexibility by terminating with
      Code:
      return super.onSubmit(request,response,command,errors);
      If, instead, you finish with
      Code:
      return new ModelAndView(view, model);
      you can construct a model, and return to a view, which relates to the button pressed.
      If you want more than just the formView and successView which SimpleFormController gives you, you can define them as bean properties in your controller class and configure them in the Spring configuration of the controller.

      Comment


      • #4
        Hi Stu,

        Firstly, well done zeroing in on the problem, I do indeed want to take advantage of Spring's binding utilities as well as maintaining the form object in memory until it is time to save the User.

        I have a sneaking suspicion that the AbstractWizardForm Controller is the tidiest solution in terms of functionality. However, the user experience I am trying to get at involves having a one-stop form where administrators can easily manipulate this User object.

        Here is the velocity template:
        Code:
        <html>
        <body>
        	<h3>Edit User</h3>
        	<form action="editUser.do" method="POST">
        		Name&#58; #springFormInput&#40;"userForm.user.userName" ""&#41;<br>
        		email&#58; #springFormInput&#40;"userForm.user.email" ""&#41;<br>
        		#if &#40;$&#123;userForm.changePassword&#125;&#41;
        			Password&#58; #springBind&#40;"userForm.user.password"&#41;
        				<input type="password" name="$&#123;status.expression&#125;" value="$!&#123;status.value&#125;" /><br>
        			Confirm&#58; #springBind&#40;"userForm.repeatedPassword"&#41;
        				<input type="password" name="$&#123;status.expression&#125;" value="$!&#123;status.value&#125;" /><br>
        		#else
        			<button name="command" value="p">Change Password</button>
        		#end
        		<h4>Organisations</h4>
        		<p>This user is a member of the following organisations</p>
        		#if&#40; $&#123;userForm.user.organisations.size&#40;&#41;&#125; > 0 &#41;
        			#set&#40;$dropCode = "___org" &#41;
        			<table border="1" summary="organisation membership">
        				<thead>
        					<tr>
        					<th>Code</th><th>Description</th><th></th>
        					</tr>
        				</thead>
        				<tbody>
        					#foreach &#40;$org in $&#123;userForm.user.organisations&#125;&#41; 
        						<tr>
        							<td>$&#123;org.code&#125;</td><td>$&#123;org.description&#125;</td><td><input type="checkbox" name="$dropCode$&#123;org.code&#125;"/></td>
        						</tr>
        					#end 
        				</tbody>
        			</table>
        			<button name="command" value="l">Drop selected organisations</button>
        		#else
        			<p>No memberships</p>
        		#end
        		
        		Join an organisation&#58; #springFormSingleSelect&#40;"userForm.addedOrgCode" $&#123;userForm.orgList&#125; ""&#41;<button name="command" value="j">Join</button>
        		
        		<h4>Capabilities</h4>
        		<p>This user enjoys the following capabilities</p>
        		#if&#40; $&#123;userForm.user.capabilities.size&#40;&#41;&#125; > 0 &#41;
        			#set&#40;$dropCode = "___cap" &#41;
        			<table border="1" summary="capabilities">
        				<thead>
        					<tr>
        					<th>Role</th><th>Organisation<i>&#40;optional&#41;</i></th><th>Audit<i>&#40;optional&#41;</i></th>
        					</tr>
        				</thead>
        				<tbody>
        					#foreach &#40;$cap in $&#123;userForm.user.capabilities&#125;&#41; 
        						<tr>
        							<td>$&#123;cap.role&#125;</td><td>$!&#123;cap.org.description&#125;</td>
        							<td>$!&#123;cap.audit.auditRef&#125;</td><td><input type="checkbox" name="$dropCode$&#123;cap.id&#125;"/></td>
        						</tr>
        					#end 
        				</tbody>
        			</table>
        			<button name="command" value="d">Drop selected capablilities</button>
        		#else
        			<p>No capabilities</p>
        		#end
        	</form>
        </body>
        </html>
        As far as I can tell, I'm going to either:

        a). Write a new controller (extending the BaseCommandController as we can not override the getCommand()) and pop the backing form object into the session.
        b). Extend the SimpleFormController, set sessionForm = false and use hidden fields to manage the many to one lists (yuck - how messy)
        c). Extend the SimpleFormController, set sessionForm = false and then ask the formBackingObject to use WebUtils.getOrCreateSessionAttribute().
        d). Abuse the AbstractWizardFormController by setting all the pages to return the main one.
        e). In the case statements, for the cases where we are updating the userForm but not writing the User away, push the userform back into the session.

        It's a shame because the SimpleFormController almost works - it just keeps removing the session object as soon as it's retrieved it (which is great for simple forms - I know).

        Sod's law says I'm probably going to do none of these, but I will post back my eventual solution.

        In the meantime any advice is most welcome.


        Cheers,


        James

        Comment


        • #5
          Hi Chris,

          Thanks for you comment, I shall concentrate more on what is coming out of the onSubmit method.

          Cheers,

          James

          Comment


          • #6
            Originally posted by cmgharris
            I have a number of forms doing what you suggest, with mutiple submit buttons, and different behaviour depending on the button clicked....
            If, instead, you finish with
            Code:
            return new ModelAndView&#40;view, model&#41;;
            you can construct a model, and return to a view, which relates to the button pressed....
            I hate to threadjack your thread James, but I'm trying to do a similar thing and this suggestion really interests me. Chris, if you could give a bit more info on what's involved in this?

            Thanks,
            Pete

            Comment


            • #7
              No worries Pete, jack away.

              btw, am now making some progress (by using option e) with this as I can now add and remove organisations to a user without persisting it. I shall keep this thread 'posted' when the tests are written.

              Comment


              • #8
                if you could give a bit more info on what's involved in this?
                Well, if you exit your onSubmit method with
                Code:
                return super.onSubmit&#40;request,response,command,errors&#41;;
                then as far as I can see, you'll get the default ModelAndView consisting of the successView as the view, and errors.getModel() as the model (i.e. your populated form backing object, and any errors).

                If you create your own ModelAndView, you have complete control over the model and the view returned. You'll often want to use getSuccessView() for the view, but you don't have to. And if you use the same view for formView and successView (as I often do), you'll probably want to initialise your model with
                Code:
                Map model = errors.getModel&#40;&#41;;
                to make sure you've got the errors instance with the form backing object in the model.
                Then you can add additional model elements as your view requires.

                Hope that helps

                Comment


                • #9
                  I would suggest using the MultiActionController which does have support for binding request params to Command objects if the delegate methods have a command object parameter (this appears to be an undocumented feature but a nice one!)

                  You would have to create a new resolver to deal with the multi-submit buttons. i would suggest this:

                  Code:
                  public class SubmitParameterPropertiesMethodNameResolver implements
                  		MethodNameResolver &#123;
                  
                  	private Properties mappings;
                  
                  	/**
                  	 * Set URL to method name mappings from a Properties object.
                  	 * @param mappings properties with URL as key and method name as value
                  	 */
                  	public void setMappings&#40;Properties mappings&#41; &#123;
                  		this.mappings = mappings;
                  	&#125;
                  	
                  	public void afterPropertiesSet&#40;&#41; &#123;
                  		if &#40;this.mappings == null || this.mappings.isEmpty&#40;&#41;&#41; &#123;
                  			throw new IllegalArgumentException&#40;"'mappings' property is required"&#41;;
                  		&#125;
                  	&#125;
                  
                  	public String getHandlerMethodName&#40;HttpServletRequest request&#41;
                  			throws NoSuchRequestHandlingMethodException &#123;
                  	
                  		for &#40;Iterator it = this.mappings.keySet&#40;&#41;.iterator&#40;&#41;; it.hasNext&#40;&#41;;&#41; &#123;
                  			String submitParamter = &#40;String&#41; it.next&#40;&#41;;
                  			if &#40;WebUtils.hasSubmitParameter&#40;request, submitParamter&#41;&#41; &#123;
                  				return &#40;String&#41; this.mappings.get&#40;submitParamter&#41;;
                  			&#125;
                  		&#125;
                  		return null;
                  	&#125;
                  which would map from a properties file of _addOrganisation=methodName
                  where the button name="_addOrganisation" (or "_addOrganisation.x) for image buttons.

                  But the multi-action does binds but doesnt do errors natively.
                  whats really needed is a MultiActionFormController

                  which at its simplest could be a little like this
                  Code:
                  public abstract class SimpleMultiActionFormController extends SimpleFormController &#123;
                  	
                  	private MethodNameResolver methodNameResolver = new InternalPathMethodNameResolver&#40;&#41;;
                  	
                  	public final void setMethodNameResolver&#40;MethodNameResolver methodNameResolver&#41; &#123;
                  		this.methodNameResolver = methodNameResolver;
                  	&#125;
                  	
                  	public final MethodNameResolver getMethodNameResolver&#40;&#41; &#123;
                  		return this.methodNameResolver;
                  	&#125;
                  	
                  	protected ModelAndView processFormSubmission&#40;HttpServletRequest request,
                  			HttpServletResponse response, Object command, BindException errors&#41;
                  			throws Exception &#123;
                  		
                  		if &#40;errors.hasErrors&#40;&#41; || isFormChangeRequest&#40;request&#41;&#41; &#123;
                  			if &#40;logger.isDebugEnabled&#40;&#41;&#41; &#123;
                  				logger.debug&#40;"Data binding errors&#58; " + errors.getErrorCount&#40;&#41;&#41;;
                  			&#125;
                  			return showForm&#40;request, response, errors&#41;;
                  		&#125;
                  		else &#123;
                  			String methodName = this.methodNameResolver.getHandlerMethodName&#40;request&#41;;
                  			
                  			Method m = &#40;Method&#41; this.getClass&#40;&#41;.getMethod&#40;methodName,
                  					new Class&#91;&#93; &#123;Object.class,
                  								 BindException.class&#125;&#41;;
                  			if &#40;m == null&#41; &#123;
                  				throw new NoSuchRequestHandlingMethodException&#40;methodName, getClass&#40;&#41;&#41;;
                  			&#125;
                  
                  			
                  			List params = new ArrayList&#40;2&#41;;
                  			params.add&#40;command&#41;;
                  			params.add&#40;errors&#41;;
                  			
                  			return &#40;ModelAndView&#41; m.invoke&#40;this, params.toArray&#40;new Object&#91;params.size&#40;&#41;&#93;&#41;&#41;;
                  		&#125;
                  		
                  	&#125;
                  
                  &#125;
                  which using the above methodname resolver would resolve to subclass methods such as
                  addOrganisation(Object command, BindException errors) throws Exception
                  addCapability(Object command, BindException errors) throws Exception
                  finalSubmit(Object command, BindException errors) throws Exception

                  hope this helps

                  DISCLAIMER: no idea if this code works as i havnt tested it. -Stuart

                  Comment


                  • #10
                    Stuart,

                    Thanks, what an elegant solution, we (pair programming) do now have a solution but I'm going to refactor it to extend your SimpleMultiActionFormController. The main issue for us was that the backing form kept getting dropped out of the session and the references were not regenerated. We solved this by whacking the session back in:
                    Code:
                        // with the following line, we are just being consistent with the 
                        // AbstractFormCOntroller.showForm method. Sending the command object
                        // would be just as good.
                        request.getSession&#40;&#41;.setAttribute&#40;getFormSessionAttributeName&#40;&#41;,errors.getTarget&#40;&#41;&#41;;
                    And by regenerating the references:
                    Code:
                        // errors.getModel returns a duplicate each time and
                        // therefore should only be called once.
                        Map model = errors.getModel&#40;&#41;;
                        model.putAll&#40;getReferenceData&#40;&#41;&#41;;
                    For those instances where we were not submitting the form.

                    I'll post it all when I'm done.

                    Comment


                    • #11
                      not that elegant. just a quick one.

                      you'd need to have a config like

                      Code:
                      	<bean id="submitActionParamResolver" class="org.springframework.web.servlet.mvc.multiaction.SubmitParameterPropertiesMethodNameResolver">
                      	    <property name="mappings">
                      	    	<props>
                      				<prop key="_addOrganisation">addOrganisationSubmit</prop>
                      				<prop key="_addCapability">addCapabilitySubmit</prop>
                      				<prop key="_finish">finalSubmit</prop>
                      			</props>
                      	    </property>
                      	    <property name="defaultMethodName"><value>finalSubmit</value></property>
                      	</bean>
                      
                      	<bean id="userFormController" class="example.ExtendsSimpleMultiActionFormController">
                      		<property name="methodNameResolver"><ref bean="submitActionParamResolver"/></property>
                      		<property name="sessionForm"><value>true</value></property>
                      		<property name="successView"><value></value></property>
                      		<property name="commandName"><value></value></property>
                      		<property name="formView"><value></value></property>
                      	</bean>
                      i can see the problem with a non POST request the formbacking object gets called again.....

                      guess your workaround should do it though.

                      good luck

                      Comment


                      • #12
                        MultiActionController does not have validations, which might be a problem since we are dealing with the forms. The real stuff is either use MultiActionController and add validations, or Extend BaseCommand controller and add multiaction capabilities to it, the latter is actually quite simple. Code for it might look somthing like this:

                        Code:
                        public class MultiActionFormController extends BaseCommandController  &#123;
                        
                            private Map delegates = new HashMap &#40;&#41;;
                            private MethodNameResolver methodNameResolver = new InternalPathMethodNameResolver &#40;&#41;;
                            private String formView;
                            private String defaultDelegateName;
                            
                            protected ModelAndView handleRequestInternal&#40;
                            		HttpServletRequest request, 
                        			HttpServletResponse response&#41; throws Exception &#123;
                                Object rv = executeAction &#40;request,response&#41;;
                                while &#40;rv != null&#41; &#123;
                                    if &#40;rv instanceof ModelAndView&#41; &#123;
                                        break;   
                                    &#125; else if &#40;rv instanceof String&#41; &#123;
                                        rv = executeAction &#40;&#40;String&#41;rv,request,response&#41;;
                                    &#125; else &#123;
                                        throw new ServletException &#40;"Unsupported return value for execute method of the delegate action"&#41;;   
                                    &#125;
                                &#125;
                                return &#40;ModelAndView&#41; rv;
                            &#125;
                            
                            private Object executeAction &#40;String actionName,
                                                          HttpServletRequest request, 
                                                          HttpServletResponse response&#41; throws Exception &#123;
                                if &#40;!delegates.isEmpty &#40;&#41;&#41; &#123;
                                    MultiActionDelegateCommand c = &#40;MultiActionDelegateCommand&#41; delegates.get &#40;actionName&#41;;
                                    DataBinder binder = null;
                                    if &#40;c != null&#41; &#123;
                                        Object fco = c.formBackingObject &#40;request&#41;;
                                        if &#40;fco == null&#41; fco = createCommand &#40;&#41;;
                                        
                                        if &#40;c.validateOnBind &#40;&#41;&#41; &#123;
                                            binder = bindAndValidate &#40;request, fco&#41;;
                                        &#125; else &#123;
                                            binder = createBinder &#40;request, fco&#41;;   
                                        &#125;
                                        return c.execute &#40;request, response, fco, binder.getErrors &#40;&#41;&#41;;
                                    &#125; else &#123;
                                        throw new ServletException &#40;"There are no delegate actions set for this controller"&#41;;
                                    &#125;
                                &#125; else &#123;    
                                    throw new ServletException &#40;"There are no delegate actions set for this controller"&#41;;
                                &#125;
                            &#125;
                            
                            private Object executeAction &#40;HttpServletRequest request, 
                                                          HttpServletResponse response&#41; throws Exception &#123;
                                if &#40;delegates.isEmpty &#40;&#41;&#41; throw new ServletException &#40;"There are no delegate actions set for this controller"&#41;;
                                String actionName = methodNameResolver.getHandlerMethodName &#40;request&#41;;
                                if &#40;actionName == null || "".equals &#40;actionName.trim &#40;&#41;&#41;&#41; &#123;
                                    actionName = getDefaultDelegateName &#40;&#41;;   
                                &#125;
                                return executeAction &#40;actionName, request,response&#41;;
                            &#125;
                            
                        	/**
                        	 * @return Returns the delegates.
                        	 */
                        	public Map getDelegates&#40;&#41; &#123;
                        		return delegates;
                        	&#125;
                        	/**
                        	 * @param delegates The delegates to set.
                        	 */
                        	public void setDelegates&#40;Map delegates&#41; &#123;
                        		this.delegates = delegates;
                        	&#125;
                        	/**
                        	 * @return Returns the methodNameResolver.
                        	 */
                        	public MethodNameResolver getMethodNameResolver&#40;&#41; &#123;
                        		return methodNameResolver;
                        	&#125;
                        	/**
                        	 * @param methodNameResolver The methodNameResolver to set.
                        	 */
                        	public void setMethodNameResolver&#40;MethodNameResolver methodNameResolver&#41; &#123;
                        		this.methodNameResolver = methodNameResolver;
                        	&#125;
                        	/**
                        	 * @return Returns the defaultDelegateName.
                        	 */
                        	public String getDefaultDelegateName&#40;&#41; &#123;
                        		return defaultDelegateName;
                        	&#125;
                        	/**
                        	 * @param defaultDelegateName The defaultDelegateName to set.
                        	 */
                        	public void setDefaultDelegateName&#40;String defaultDelegateName&#41; &#123;
                        		this.defaultDelegateName = defaultDelegateName;
                        	&#125;
                        	/**
                        	 * @return Returns the formView.
                        	 */
                        	public String getFormView&#40;&#41; &#123;
                        		return formView;
                        	&#125;
                        	/**
                        	 * @param formView The formView to set.
                        	 */
                        	public void setFormView&#40;String formView&#41; &#123;
                        		this.formView = formView;
                        	&#125;
                        &#125;
                        and interface for the command object might be:

                        Code:
                        public interface MultiActionDelegateCommand &#123;
                            
                            public ModelAndView execute &#40;HttpServletRequest request, HttpServletResponse response, Object command, BindException errors&#41; throws Exception;
                            public Object formBackingObject &#40;HttpServletRequest request&#41;;
                            public boolean validateOnBind &#40;&#41;;
                        
                        &#125;
                        this way you can get validate thru spring and provide formBackingObject method for each of the commands separately, since they might be very different depending on the command being executed. Note if you need to store retrieve the stuff to.from your session you can.

                        HTH

                        Comment


                        • #13
                          Hi,

                          I completely forgot to upload the SimpleMultiActionFormController based on stueccles so here we go (NOT properly tested - use at your own risk etc.):

                          The SimpleMultiActionFormController -
                          Code:
                          /**
                           * Based on a post by <b>stueccles</b> on forum.springframework.org.uk.
                           * 
                           * This class can be extended to provide support for multiple submit buttons
                           * on a simple form. Particularly useful for many-to-one relationships with the 
                           * object being edited.
                           */
                          public abstract class SimpleMultiActionFormController extends AbstractFormController &#123;
                              private String formView;
                              private String successView;
                              private SubmitMethodNameResolver methodNameResolver; 
                              
                              // getters and setters &#40;for spring wiring&#41;
                              public final String getFormView&#40;&#41; &#123;
                                  return formView;
                              &#125;
                          
                              public final void setFormView&#40;String formView&#41; &#123;
                                  this.formView = formView;
                              &#125;
                              
                              public final void setMethodNameResolver&#40;
                                      SubmitMethodNameResolver methodNameResolver&#41; &#123;
                                  this.methodNameResolver = methodNameResolver;
                              &#125;
                          
                              public final String getSuccessView&#40;&#41; &#123;
                                  return successView;
                              &#125;
                          
                              public final void setSuccessView&#40;String successView&#41; &#123;
                                  this.successView = successView;
                              &#125;
                          	    
                              /**
                               * This method looks for validators/binders for the action by appending the
                               * word Validator onto the method name. 
                               * 
                               * e.g. If the properties file defined a method foo for submit button this 
                               * routine will look for the method&#58;
                               *
                               *       public void fooValidator&#40;HttpServletRequest request,
                               *                Object command, BindException errors&#41;;
                               */
                              protected final void onBindAndValidate&#40;HttpServletRequest request,
                                      Object command, BindException errors&#41; throws Exception &#123;
                          
                                  defaultBindAndValidate&#40;request,command,errors&#41;;
                                  
                                  String methodName = this.methodNameResolver.getHandlerMethodName&#40;request&#41; + "Validator";
                                  
                                  Method m;
                                  try &#123;
                                      m = &#40;Method&#41; this.getClass&#40;&#41;.getMethod&#40;methodName,
                                            new Class&#91;&#93; &#123;
                                      				HttpServletRequest.class,
                                      				Object.class,
                                                      BindException.class&#125;&#41;;
                                  &#125; catch &#40;SecurityException e&#41; &#123;
                                      throw new ServletException&#40;e&#41;;
                                  &#125; catch &#40;NoSuchMethodException e&#41; &#123;
                                      // fine 
                                      return;
                                  &#125;
                                  List<Object> params = new ArrayList<Object>&#40;3&#41;;
                                  params.add&#40;request&#41;;
                                  params.add&#40;command&#41;;
                                  params.add&#40;errors&#41;;
                                  try &#123;
                                      m.invoke&#40;this, params.toArray&#40;new Object&#91;params.size&#40;&#41;&#93;&#41;&#41;;
                                  &#125; catch &#40;IllegalArgumentException e&#41; &#123;
                                      throw new ServletException&#40;"The method&#58; " + methodName + "Validator"
                                              + " should be in form 'public void fooValidator&#40;HttpServletRequest req, Object command, BindException errors&#41;'",e&#41;;
                                  &#125; catch &#40;IllegalAccessException e&#41; &#123;
                                      throw new ServletException&#40;e&#41;;
                                  &#125; catch &#40;InvocationTargetException e&#41; &#123;
                                  	throw new ServletException&#40;"The method " + methodName 
                                      		+ " blew up with&#58; " + e.getCause&#40;&#41;.getMessage&#40;&#41;, e.getCause&#40;&#41;&#41;;
                                  &#125;
                              &#125;
                              
                              /**
                               * Subclasses can override this to provide cross action/method validation.
                               * @param request
                               * @param command
                               * @param errors
                               */
                              protected void defaultBindAndValidate&#40;HttpServletRequest request, Object command, BindException errors&#41; &#123;
                                  // do nothing
                              &#125;
                              
                              /**
                               * Subclasses must implement the methods setup to respond the the
                               * various submit buttons.
                               * 
                               * Consider the following config file&#58;
                               * 
                               *  <bean id="submitActionParamResolver"
                               *     class="com.foo.SubmitMethodNameResolver"> 
                               *    <property name="mappings”>
                               *      <props> 
                               *        <prop key="_addOrganisation">addOrganisationSubmit</prop> 
                               *        <prop key="_addCapability">addCapabilitySubmit</prop> 
                               *        <prop key="_finish">finalSubmit</prop> 
                               *      </props> 
                               *    </property> 
                               *    <property name="formSubmitMethod''>
                               *      <value>finalSubmit</value>
                               *    </property> 
                               * </bean>  
                               * 
                               * The subclass would need to contain the methods&#58;
                               *    public void addOrganisationSubmit&#40;Object command, BindException errors&#41;
                               *    public void addCapabilitySubmit&#40;Object command, BindException errors&#41;
                               * 
                               * The one method that actually submits the form &#40;the formSubmittedMethod&#41; calls the  
                               * onSubmit method as per the SimpleFormController.
                               */ 
                              protected final ModelAndView processFormSubmission&#40;HttpServletRequest request,
                                      HttpServletResponse response, Object command, BindException errors&#41;
                                      throws Exception &#123;
                                  if &#40;errors.hasErrors&#40;&#41;&#41; &#123;
                                      if &#40;logger.isDebugEnabled&#40;&#41;&#41; &#123;
                                         logger.debug&#40;"Data binding errors&#58; " + errors.getErrorCount&#40;&#41;&#41;;
                                      &#125;
                                   &#125;
                                   else &#123;
                                      String methodName = this.methodNameResolver.getHandlerMethodName&#40;request&#41;;
                                      if &#40;this.methodNameResolver.isFormSubmitMethod&#40;methodName&#41;&#41; &#123;
                                      	return onSubmit&#40;request,response,command,errors&#41;;
                                      &#125;
                                      Method m;
                                      try &#123;
                                          m = &#40;Method&#41; this.getClass&#40;&#41;.getMethod&#40;methodName,
                                                new Class&#91;&#93; &#123;Object.class,
                                                          BindException.class&#125;&#41;;
                                      &#125; catch &#40;SecurityException e&#41; &#123;
                                          throw new ServletException&#40;e&#41;;
                                      &#125; catch &#40;NoSuchMethodException e&#41; &#123;
                                          throw new ServletException&#40;"Please implement the method " + methodName&#41;;
                                      &#125;
                                      List<Object> params = new ArrayList<Object>&#40;2&#41;;
                                      params.add&#40;command&#41;;
                                      params.add&#40;errors&#41;;
                                      try &#123;
                                          m.invoke&#40;this, params.toArray&#40;new Object&#91;params.size&#40;&#41;&#93;&#41;&#41;;
                                      &#125; catch &#40;IllegalArgumentException e&#41; &#123;
                                          throw new ServletException&#40;"The method&#58; " + methodName 
                                                  + " should be in form 'public void foo&#40;Object command, BindException errors&#41;'",e&#41;;
                                      &#125; catch &#40;IllegalAccessException e&#41; &#123;
                                          throw new ServletException&#40;e&#41;;
                                      &#125; catch &#40;InvocationTargetException e&#41; &#123;
                                          throw new ServletException&#40;"The method " + methodName 
                                          		+ " blew up with&#58; " + e.getCause&#40;&#41;.getMessage&#40;&#41;, e.getCause&#40;&#41;&#41;;
                                      &#125;
                                   &#125; 
                                  return showForm&#40;request,response,errors&#41;;
                              &#125;
                          
                              protected ModelAndView showForm&#40;HttpServletRequest request, HttpServletResponse response, BindException errors&#41; throws Exception &#123;
                              	return showForm&#40;request, errors, getFormView&#40;&#41;&#41;;
                              &#125;
                          
                              abstract protected ModelAndView onSubmit&#40;
                          			HttpServletRequest request,	HttpServletResponse response, Object command,	BindException errors&#41;
                          			throws Exception;
                          &#125;
                          I've managed to implement a complex user controller that extends this, I found it quite straight forward as I could use things like the reference data, the form backing object etc.

                          Comment


                          • #14
                            Thanks James

                            I have the code allready in my SpringXMLDB framework and have used it very successfully (https://sourceforge.net/projects/springxmldb/) but i like what you have done with the validations so i ill update it (minus the Java1.5 stuff).

                            Stuart Eccles

                            Comment


                            • #15
                              Hey guys,

                              It's amazing, I was looking for this same support in my application. I tried to search the net for "MultiActionFormController" with no avail. So this helps me a great deal.
                              I'm trying to use your code, James, but am wondering what your latest code looked like for your SubmitMethodNameResolver. I can't seem to find the isFormSubmitMethod() method that you use in your SimpleMultiActionFormController.

                              Great work!

                              -James W.

                              Comment

                              Working...
                              X