Announcement Announcement Module
Collapse
No announcement yet.
Need help passing validated form data to business layer Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Need help passing validated form data to business layer

    I'm very new to Spring an SWF, so please bear with me. I'm trying to understand how to access form data after the bindAndValidate step has completed so that I may use it in my business layer to write data to the database.

    I have looked at all the examples and tried to integrate portions of them, but they are so specific and barely commented that I have no idea why certain code pieces are used there and not in other samples.

    I have tried the following, which is nowhere near as clean or simple as <spring:bind>:

    Code:
    context.getRequestScope&#40;&#41;.getAttribute&#40;"query.email"&#41;
    context.getRequestScope&#40;&#41;.getAttribute&#40;"email"&#41;
    
    context.getFlowScope&#40;&#41;.getAttribute&#40;"query.email"&#41;
    context.getFlowScope&#40;&#41;.getAttribute&#40;"email"&#41;
    
    context.getSourceEvent&#40;&#41;.getAttribute&#40;"query.email"&#41;
    context.getSourceEvent&#40;&#41;.getAttribute&#40;"email"&#41;
    Where 'query' is a FormObject I am using in validation. Isn't there a simpler way to access this data? I can't really see any other purpose to SWF than to receive and process data input, so why isn't this simpler to use?

    This is my flow:
    order-flow.xml:
    Code:
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE webflow PUBLIC "-//SPRING//DTD WEBFLOW//EN"
    	"http&#58;//www.springframework.org/dtd/spring-webflow.dtd">
    
    <webflow id="orderProcess" start-state="orderLogin">
    
        <view-state id="orderLogin" view="orderLoginForm">
    		<transition on="submit" to="userLookupOrInsert">
    			<action bean="orderActions" method="bindAndValidate">
    				<property name="validatorMethod" value="validateOrderLogin"/>
    			</action>
    		</transition>
        </view-state>
    
    	<action-state id="userLookupOrInsert">
    		<action bean="userActions" method="userLookupOrInsert"/>
    		<transition on="success" to="orderPayment"/>
    		<transition on="error" to="orderLogin"/>
    	</action-state>
    
        <view-state id="orderPayment" view="orderPaymentForm">
    		<transition on="submit" to="userPaymentLookupOrInsert">
    			<action bean="orderActions" method="bindAndValidate">
    				<property name="validatorMethod" value="validateOrderPayment"/>
    			</action>
    		</transition>
        </view-state>
    
    	<action-state id="userPaymentLookupOrInsert">
    		<action bean="userActions"/>
    		<transition on="success" to="orderPaymentDetail"/>
    		<transition on="error" to="orderPayment"/>
    	</action-state>
    
    	<view-state id="orderPaymentDetail" view="orderPaymentDetailForm">
    		<transition on="submit" to="userPaymentDetailLookupOrInsert">
    			<action bean="orderActions" method="bindAndValidate">
    				<property name="validatorMethod" value="validateOrderPaymentDetail"/>
    			</action>
    		</transition>
        </view-state>
    
    	<action-state id="userPaymentDetailLookupOrInsert">
    		<action bean="userActions"/>
    		<transition on="success" to="orderConfirm"/>
    		<transition on="error" to="orderPaymentDetail"/>
    	</action-state>
    
    	<view-state id="orderConfirm" view="orderConfirmForm">
    		<transition on="submit" to="userOrderInsert">
    			<action bean="orderActions" method="bindAndValidate">
    				<property name="validatorMethod" value="validateOrderConfirm"/>
    			</action>
    		</transition>
        </view-state>
    
    	<action-state id="userOrderInsert">
    		<action bean="userActions"/>
    		<transition on="success" to="showTicket"/>
    		<transition on="error" to="orderConfirm"/>
    	</action-state>
    
    	<end-state id="showTicket" view="showTicketView"/>
    
    </webflow>
    And this is my business logic:
    userActions.java
    Code:
    package com.blah.bus.logic;
    
    import java.lang.Exception;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    
    import org.springframework.web.flow.Event;
    import org.springframework.web.flow.RequestContext;
    import org.springframework.web.flow.action.MultiAction;
    
    import com.blah.bus.entity.User;
    import com.blah.db.UserDao;
    
    public class userActions extends MultiAction &#123;
    
    	protected final Log logger = LogFactory.getLog&#40;getClass&#40;&#41;&#41;;
    
    	private User user;
    	private UserDao userDao;
    /*	private Address address;
    	private AddressDao addressDao;
    	private TicketDao ticketDao;
    	*/
    
    	public void setUser&#40;User user&#41; &#123;
    		this.user = user;
    	&#125;
    
    	public User getUser&#40;&#41; &#123;
    		return user;
    	&#125;
    
    	public Event userLookupOrInsert&#40;RequestContext context&#41; throws Exception &#123;
    		logger.info&#40;"UserActions - Entered userLookupOrInsert Event!"&#41;;
    		return success&#40;&#41;;
    	&#125;
    
    	public Event userPaymentLookupOrInsert&#40;RequestContext context&#41; throws Exception &#123;
    		logger.info&#40;"UserActions - Entered userPaymentLookupOrInsert Event!"&#41;;
    		return success&#40;&#41;;
    	&#125;
    
    	public Event userPaymentDetailLookupOrInsert&#40;RequestContext context&#41; throws Exception &#123;
    		logger.info&#40;"UserActions - Entered userPaymentDetailLookupOrInsert Event!"&#41;;
    		return success&#40;&#41;;
    	&#125;
    
    	public Event userOrderInsert&#40;RequestContext context&#41; throws Exception &#123;
    		logger.info&#40;"UserActions - Entered userOrderInsert Event!"&#41;;
    		//user = &#40;User&#41;context.getRequestScope&#40;&#41;.getAttribute&#40;"user"&#41;;
    		user.setEmail&#40;&#40;java.lang.String&#41;context.getSourceEvent&#40;&#41;.getAttribute&#40;"email"&#41;&#41;;
    		user.setPassword&#40;&#40;java.lang.String&#41;context.getSourceEvent&#40;&#41;.getAttribute&#40;"password"&#41;&#41;;
    		/*user.setId&#40;null&#41;;
    		user.setCartId&#40;null&#41;;
    		user.setTypeId&#40;new Long&#40;1&#41;&#41;;
    		user.setLevelId&#40;new Long&#40;1&#41;&#41;;
    		user.setStatusId&#40;new Long&#40;1&#41;&#41;;
    		user.setFirstName&#40;null&#41;;
    		user.setLastName&#40;null&#41;;
    		user.setLastAccess&#40;null&#41;;
    		user.setSecondLastAccess&#40;null&#41;;*/
    		//user.setEmail&#40;&#40;java.lang.String&#41;getStringParameter&#40;&#40;HttpServletRequest&#41;request,"email",""&#41;&#41;;
    		//user.setEmail&#40;&#40;java.lang.String&#41;context.getRequestScope&#40;&#41;.getAttribute&#40;"email"&#41;&#41;;
    		//user.setPassword&#40;&#40;java.lang.String&#41;context.getRequestScope&#40;&#41;.getAttribute&#40;"password"&#41;&#41;;
    		logger.info&#40;"OrderActions - Inserting User via iBATIS"&#41;;
    		userDao.insertUser&#40;user&#41;;
    		//context.getRequestScope&#40;&#41;.setAttribute&#40;"user", user&#41;;
    		return success&#40;&#41;;
    	&#125;
    /*
    	protected Event doExecute&#40;RequestContext context&#41; throws Exception &#123;
    		PhoneBookQuery query = &#40;PhoneBookQuery&#41;context.getFlowScope&#40;&#41;.getRequiredAttribute&#40;"query",
    				PhoneBookQuery.class&#41;;
    		context.getRequestScope&#40;&#41;.setAttribute&#40;"persons", phoneBook.query&#40;query&#41;&#41;;
    		return success&#40;&#41;;
    	&#125;
    	*/
    &#125;
    Any help that anyone can provide would be really appreciated!

  • #2
    Yikes, you're doing manual binding! Don't do that!

    Simply leverage the FormAction's bindAndValidate method by extending FormAction (also a MultiAction.). FormAction uses Spring's data binder to automatically bind, convert, and validate input in the source event to a backing form object -- similiar to what Spring MVC does with its FormControllers. The form object can be put in request scope or flow scope for you as well, for convenient retrieval by another action method for persistence via a service layer invocation.

    See the Phonebook sample for the most concrete example of how to do this. It uses FormAction to auto bind and validate input in the event to a PhoneBookQuery object and then another action to actually execute the query.

    SWF and spring:bind tags are completely orthognal. I typically use spring bind tags in views participating in flows, just like those working with traditional controllers. That's the beauty of SWF - with the exception of having to track the _flowExecutionId client side, views participating in flows can look just like views that work with standard Spring MVC controllers.

    SWF is not about being a marshal for form data--it just delegates to Spring's data binding infrastructure for that as a convenience. SWF IS about a controller that makes dynamic page navigation decisions and orchestrates a business process that takes over a series of steps (that involes input from the user.)

    Keith

    Comment


    • #3
      Ok... I'm recoding your actions (the code scared me) :-)

      Code:
      // inherits "bindAndValidate" and "setupForm" capabilities from superclass
      public class UserActions extends FormAction &#123; 
      
         private UserDao userDao; 
      
         public UserActions&#40;UserDao userDao&#41; &#123;
             setFormObjectName&#40;"user"&#41;;
             setFormObjectClass&#40;User.class&#41;;
             setFormObjectScope&#40;ScopeType.FLOW&#41;;
             this.userDao = userDao;
         &#125;
      
         public Event userOrderInsert&#40;RequestContext context&#41; throws Exception &#123; 
            User user = &#40;User&#41;context.getFlowScope&#40;&#41;.getAttribute&#40;"user"&#41;;
            userDao.insertUser&#40;user&#41;; 
            return success&#40;&#41;; 
         &#125; 
      &#125;
      If you have to code much more than that, something is wrong :-)

      Comment


      • #4
        Thanks for the quick reply Keith! I think you might have misunderstood where I was trying to use the business logic. I had already set up a form object named "query" inside a bean named "OrderActions":

        Code:
        	<bean id="orderActions" class="org.springframework.web.flow.action.FormAction">
        		<property name="formObjectName" value="query"/>
        	 	<property name="formObjectClass" value="com.blah.bus.entity.Testtest"/>
        		<property name="formObjectScopeAsString" value="flow"/>
        		<property name="validator">
        			<bean class="com.blah.web.OrderValidator"/>
        		</property>
        	</bean>
        This sets up the form object and then validates it in the first step (a view-state with built-in transition action) of my flow. From there I was flowing to an action state and using that validated data to perform some business logic. Or did I misunderstand you and I need to set up another FormObject (which seems odd)?

        From what I understood reading various documents, I am supposed to keep all of my business logic separated from Actions, so that is why I was trying to keep them out of the orderActions bean, and instead put them in a userActions one.

        I think I'm wholy confused, isn't the following also manual binding?
        Code:
        User user = &#40;User&#41;context.getFlowScope&#40;&#41;.getAttribute&#40;"user"&#41;;
        Thanks Keith, I really appreciate the feedback, I feel overwhelmed.

        Comment


        • #5
          The following you're referring to is not manual data binding -- I guess it is a kind of manual service-layer method binding. We could try to get fancy there and automate that, too, I guess, but that gets trickier and is a big magical--Spring MVC doesn't try to provide a solution there for example, either.

          I'm confused about what you're trying to do in UserActions!??? If all you're trying to do is log the user in, just have the order login view state submit to a UserFormAction that binds the user input to a User domain object. It's not clear to me why you're doing some stuff in Order actions and other stuff in user actions.

          Yes, Busines logic belongs in the service layer, not in webflow actions (generally anyway, as then it's not very reusable without SWF in the picture.)

          Comment


          • #6
            So if I have a complicated multi-page form that will capture addresses, order details, and user details, I will need to create an 'Order' javabean that will contain 'Address', 'User', 'Order', beans, if you get what I mean?

            Also, is my desire to separate business operations from form validating/controlling operations logical, or should I just place my business operations in the main FormAction bean, 'OrderActions'?

            Eventually I would like to use multiple client types, not just web clients, to be able to use the business operations.

            Would a custom controller be better suited in that case than SWF?

            Comment


            • #7
              Well, you can do it however you want. Do you have a toplevel persistable Order domain object that relates everything else and is mapped using a ORM? You could use it as a single form object and bind using nested properties.

              You could also have multiple form objects managed in flow scope easily, and bind to them independently using the FormAction. You could commit them all to the DB at the end of the flow. You'd need different FormAction classes per form object at this point, though (we hope to do something about that soon -- I don't like that.)

              In SWF you have full control over what the controller does, so it'll give you more flexibilty than a hard-coded controller but you have understand its usage patterns.

              As far as validation, it depends what kind of logic you're talking about. Put validation logic in validators -- thats nice and decoupled from SWF. Or put the logic in the domain objects themselves. Put use-case (service layer) validation in your business service objects. Web flow actions (commands) should just figure out a logical result of what they execute so the flow can respond to it.

              Comment


              • #8
                I'm using iBATIS for my Order object, so I suppose that's what I will use to bind.

                I think for now I'll just stick with one FormAction instance, and keep all the operations in there. I'll set it up like you suggest instead of using the XML, and then step to those specific business operations in a step after validation (as I have it set up currently in the XML flow).

                I think I'm trying to learn too much at once. Java + Spring + SWF + iBATIS I come from a C++ background, so some of the concepts in Java are suprising me.

                Anyway, thanks for your input, I'll let you know when I figure it out!

                Any other comments you might have would definitely be appreciated. Thanks!

                Comment


                • #9
                  I simplified everything and am able to pass data between all the layers successfully. Thanks Keith!

                  Comment

                  Working...
                  X