Dear Wiki user, You have subscribed to a wiki page or wiki category on "Struts Wiki" for change notification.
The following page has been changed by MichaelJouravlev: http://wiki.apache.org/struts/StrutsCatalogInputOutputSeparation ------------------------------------------------------------------------------ - This information is covers almost the same topic, as StrutsMultipleActionForms, but I made it a separate page for clarity. The problem we are trying to solve is how to process input and output data, having only one form bean per action class. + The subject of this page is separation of input from output when using Struts actions. It has two aspects: + * separating input data from output data + * separating input phase from output phase - == Traditional Struts request/response cycle == + This page is relevant to StrutsMultipleActionForms topic, which you may want to look at as well. - First, let us recall the traditional way of processing input and generating output in Struts application. + === Separating input data from output data === - HTML FORM is submitted from the input page, usually using POST request method. Struts populates form bean (marked with [F]) with request data. Then form bean validates input and if something wrong, it generates error messages. If validate() returns errors, Struts does not bother to call action class. Instead, it forwards to location, which is defined in "input" property of <action> element. If, on the other hand, input data is correct, Struts calls execute() method of the action class. It usually performs model update, then fills out form bean with output values, and forwards to JSP page, which displays output data. + As StrutsMultipleActionForms states, "the most common use case is when we go from one page to another page on the browser, sending the server certain request values from the first page and getting from the server certain response values on the second page. Most pages have both input data and output data." The reasonable choice would be to correlate each web page with an action form. In this case "the obvious answer would be to have an Action process data from !FirstPageActionForm and setup data for !SecondPageActionForm. This would allow us to have a page oriented !ActionForm, which makes sense, without having to use page-centric solutions. But, we cannot, because action mapping allows us to have only one !ActionForm per Action." - attachment:actioncombo01.gif + What are the possible choices for storing and accessing input and output data from an action class? + === Not using action forms === - What is wrong with this scheme? Number of things. - * Form bean is used both for input and for output. If input and output page uses same fields, this is OK, but usually this is not the case. So, one form has to combine input and output fields. - * On error action class is not called. This may be useful in some cases, but not always. - * On error control is forwarded to location, defined in "input" property. This property itself is a source of misunderstanding for Struts newbies, and makes clean request->processing->response sequence a little fuzzy. - * By default, "input" property allows only forwarding, but not redirection. Redirection can be enabled, but for the whole application. - * Because in case of error most applications forward to input page instead of redirecting, page is sent back to broswser immediately in response to POST request. This produces POSTDATA effect on reload, which results in double sumbit. - So, as a first step of cleaning up the input/output mess I would suggest getting rid of "input" property and automatic validation. This makes request processing more clear and linear, and allows to have several error pages instead of only one. Also, now you are free to redirect to error page instead of just forwarding to it. + This is the radical choice. Proponents of this choice do not see added value of action forms. They can obtain input data directly from the request object, and they can display information directly from the business object or another bean. They prefer to validate input data by explicitly calling the validator, or they have their own validation techniques. - attachment:actioncombo02.gif + Another reason for not using action forms is using presentation engine different from JSP. Quoting Don Brown: "My primary app uses stxx (http://stxx.sf.net) so without taglibs, there isn't much left to !ActionForms. My forms are XML based, so I use commons-validator with custom XML validators, and JXPath to populate (treats the form element names as xpath expressions). My wizards that use Struts Flow use the same population and validation methods." - Next step is to separate input and output data. This is not an easy task considering that a form bean should handle both. First, let us consider two exotic choices. First, is to use form bean for input only. Output data will be generated manually and pushed into request object field by field on in a non-Struts bean. + attachment:noform.gif - attachment:actioncombo03.gif + This approach is used by 22% of developers, who participated in the quick poll: http://marc.theaimsgroup.com/?l=struts-user&m=111584899632546&w=2 You can still vote either right in the mailing list, or on my web page: http://www.superinterface.com/projects.htm - Another choice is to use form bean for output only. In this case action class processes input data directly from request object and fills out form bean with output data. Oh, right, form bean is called first by Struts. This is not a problem, because Struts uses getters and setters to access form bean. So, if you define fields with package scope, and set them directly, you would not need setter. Thus, Struts would not be able to set their values during populate phase. + === Using action form for input only === - attachment:actioncombo04.gif + Struts populates form bean with input data from the request. After input is processed and domain model is updated, output data is generated manually and pushed into request or session scope, either field by field or packaged into a bean. This approach makes sense if a page is rendered with non-JSP engine, too. - But these approaches are quite exotic. What if we just have two actions? + attachment:forminput.gif - == Two actions and two forms == + Used by 11% of respondents. - In this case we can assign a very own form bean for each action class. We would have one input form bean coupled with input action, and one output form bean coupled with output action. + === Using action form for output only === - attachment:actioncombo05.gif + This is quite an exotic choice. To make this work, action form should not have any setters, since Struts uses setters to populate action form bean. + attachment:formoutput.gif + + No one who responded to the poll, uses this approach. + + === Using same action form for both input and output data === + + This choice turned out to be quite popular, 50% usage out of all respondents. Here is an outline: + + * HTML FORM is submitted from the input page, usually using POST request method. + * Struts populates form bean with request data. + * Form bean validates input and if something wrong, it generates error messages. + * If validate() returns errors, Struts does not bother to call action class. Instead, it forwards to location, which is defined in "input" property of <action> element. + * If, on the other hand, input data is correct, Struts calls execute() method of the action class. + * execute() usually performs model update, then fills out form bean with output values, and forwards to JSP page, which displays output data. + + attachment:forminputoutput.gif + + === Using input form and output form === + + Action mapping does not allow to assign more than one action form to an action class declaratively. If you want to use action form for both input and output, but do not want to keep the data in the single form, then you can use two action classes, each with its own action form. + + attachment:twoforms.gif + - Now, this approach can work. In this sheme each action and form bean performs its own specific task. + In this scheme each action class and form bean performs its own specific task. + * Input form bean is populated with input data. - * Input action class explicitly calls validate() and updates the domain model. + * Input Form bean validates input and if something wrong, it generates error messages. + * If validate() returns errors, Struts forwards to location, which is defined in "input" property of <action> element. + * If input data is correct, Struts calls execute() method of the input action class. - * Then input action class forwards to output action. This is basically a simple action chaining. + * Input action class forwards to output action mapping. This is basically a simple action chaining. - * Struts would want to populate output form bean, but here is the trick: you do not need to define setters for properties in the output form. Also, the field on the output form are usually different from fields on the input form. - * Output form does not define validate() method because there is nothing to validate. - * Struts calls output action class, which fills out output form bean with data. - Now we have clean input/output separation and each class is doing a small but specific task. This scheme is more maintainable. + Struts will try to populate output form bean, but here is the trick: you do not need to define setters for properties in the output form. Also, the field on the output form usually differ from fields on the input form. - But, even with this cleaner model we still have POSTDATA and double submit problem, because all processing is performed during a single request/response cycle. From browser's point of view, server receives POST request and responds with result page. When a user wants to refresh the result page, browser has to resend its address, that is, the POST request. This produces POSTDATA situation. + Now we have clean separation of input and output data, and each class is doing a small but specific task. About 17% of respondents use separate input and output form beans. - The solution is quite simple: to chain actions using redirect instead of forward. + === Separating input phase from output phase === - attachment:actioncombo07.gif + Separating data may be important for a web developer, but it is invisible for a browser. The only thing that browser knows, is that to obtain a certain page it has to issue a certain request. Putting it differently: each page has a unique location. What does it really mean? + When a browser submits data, it sends a request to the server. By default, when browser submits an HTML form, the request has POST type. Server application processes input data, updates domain objects, and responds with result page. From browser's point of view, the result page has an address, which is a combination of URL and browser input data. To reload the result page the browser needs to send to the server exactly the same address. Thus, when a user clicks Reload button, browser resends to the server information that has been already submitted. - In this case, after input data is processed and domain model is updated, input action redirects to output action. What is special about redirection? - * The request has to make a roundrip through browser. - * The request has GET type - * No data from previous request is included in redirected request. - Thus, from server's point of view, it receives completely separate request for output action. Alas, because there is no input data in the request, we do not know what to display. Usually, we do not need much, just a primary key or object ID. We can pass this ID through the session, or we can append it to the redirected request as query parameter. When redirected request comes to the server, Struts would pull object ID out of the request, and load business object from database. Then it would fill output form bean and voila! - Redirection provides freedom of browsing. Now we can reload result page without risking to re-submit input data. And we can go back and forward again without invoking POST requests. + HTTP specification distinguishes between POST and GET request types. GET should be used for requests, which do not produce side effects when repeated. POST should be used primarily for changing server state. Because of that, when the same POST is resent to the server, browser is obliged to ask a user for confirmation. Obviously, if application does not check for duplicate input data, the same server activity can be performed twice, say, a user credit card can be charged once again. - What if we need to use one form? Say, we have a wizard or a dialog, which shares data. No problem, this even easier than to have two forms. Just define the form with session scope, and it will retain all input data between requests. + This situation is known as "postdata situation" or "double submit problem". Struts provides a special object, token, to detect that a particular request is resubmitted. While this helps to fight with the double submit, what if we can avoid double submits altogether? + To accomplish that, we need to ensure that when a user clicks Reload, browser does not send another POST request to the browser. That means that result page must have a different address. That, in turn, means that result page cannot be generated in response to POST request. Instead, it should be generated in response to GET request. This means, that we need to make two requests out of one, and the second response must have GET type. - attachment:actioncombo08.gif - ==== Follows the example from a working CRUD application. ==== - {{{ - <!-- - Create Item. Creates new object with random ID, temporarily - saves it in the session, attaches item ID to redirected URL - and redirects to editing. - Input: none - validation: none - --> - <action path = "/createItem" - type = "com.superinterface.items.CreateItemAction" - validate = "false"> - <forward name="itemCreated" path="/editItem.do" redirect="true"/> - </action> + Combining this information together, we can come up with a logical solution: + * Browser submits input data using POST request. + * Server application processes input data and updates domain model. + * Server redirects to the address of result page. Redirection codes 302 and 303 produce GET request. 302 code is automatically generated with Java !HttpServletResponse.sendRedirect method. + * Browser uses new address to load the result page. - <!-- - Edit Item. Presents new or existing item for editing. - Item is looked up by ID. - Input: item id - validation: item must exist in the item list - --> - <action path = "/editItem" - type = "org.apache.struts.actions.ForwardAction" - name = "itemFormOutput" - input = "itemError" - parameter = "/WEB-INF/items/editItem.jsp"> - <forward name="itemError" path="/WEB-INF/items/error.jsp"/> - </action> + Now, when a user clicks Reload button, browser uses address from GET request to reload a page. Thus, no input data is resubmitted to the server. Also, because this request has GET type, browser does not show warning dialog window. - <!-- - Store Item. Persists item in the storage. If item has "New" status, - it is persisted, if item has "Stored" status, it is updated. - On success redirects to home page, on error returns to editing. - Input: item id, item value - validation: input form fields are validated - --> - <action path = "/storeItem" - type = "com.superinterface.items.StoreItemAction" - name = "itemFormInput"> - <forward name="itemStored" path="/itemList.do" redirect="true"/> - <forward name="storeError" path="/editItem.do" redirect="true"/> - </action> - }}} - Let's check out the output action first, editItem. Notice, that it does not care where it was called from and was it forwarded to or redirected to. All it knows, that it recieves object id in the ID property of its form bean. Well, I cheated a little, using the same form bean for input and for output in this case. + === Remove "input" property and use redirect === - If item is not found, action forward to error page. If a user reloads error page, editItem action would try to locate the item again, which does not change server state, but can improve situation if the item is found. On the other hand, it would be cleaner to use redirection to error page, so that database would not be bothered if error page is reloaded. If item is found, editItem shows it. + Usually, when form bean is used for input, errors are forwarded to location, defined in "input" property. By default, "input" property does not allow redirection. Redirection can be enabled, but for the whole application. Because most applications forward to input page instead of redirecting, page is sent back to browser immediately in response to POST request. This results in POSTDATA situation when a user tries to reload input page afterwards. - Updated item is submitted to storeItem action. It is an input action and uses form bean to collect browser data. If data is incorrect, errors are generated and saved in the session, then control is redirected back to output action, editItem, which redisplays the item along with the errors. If data is correct, item is stored in the database and control is redirected to the home page. Home page can be reloaded, this will not incur item resubmit. + So, as a first step of splitting one request into two I suggest either to get rid of "input" property and automatic validation, or to set controller property, that allows redirection for "input" location. - createItem creates new item. This would be an input action, but it has no input parameters. It does not have output data either, since it redirects to editItem which is output action for createItem. + Then we need to use redirection instead of forwarding between the action mappings, and this is it! + + attachment:twoformsredirect.gif + + Well, we are almost there, but not quite. Because now we need to find a place, where to store intermediate data and error messages between requests. This data can either be passed in the redirected request, or saved in the session. The choice is yours. I prefer to store most of the data in the session, and to pass only object ID as the request parameter. I will discuss the details of redirecting from POST to GET on a separate page. + + === Dialog Action === + + What if we need to use one form? Say, we have a wizard or a dialog, which shares data between pages. No problem, just use one form bean with session scope instead of two, and it will retain all input data between requests. You can use one action class as well. To avoid endless loop, you need to check the request method. It it is POST, then you redirect to the same action mapping again. If it is GET, you display the result page. + + attachment:twoformsdialog.gif + + Separation between input and output phases using redirection provides freedom of browsing. Now we can reload result page without risking to resubmit input data. And we can go back and forward again without invoking POST requests. See also: StrutsMultipleActionForms == Comments == - * [http://www.mail-archive.com/user%40struts.apache.org/msg26454.html Thread on Struts user list] + * [http://www.mail-archive.com/user%40struts.apache.org/msg26454.html Thread on Struts user list] + * This page was heavily updated since after the comments on Struts user list --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]