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/StrutsManualActionClasses ------------------------------------------------------------------------------ == Action Classes == - The goal of an Action class is to process a request and return an ActionForward object. Action class can either implement a ''stateless service'', or can manage a logical ''web resource''. !ActionForward object identifies where control should be transferred to provide the appropriate response, and usually designate either another Action (see [:ActionChaining:action chaining]) or a presentation page. Struts is agnostic to presentation technology, so response can be generated using JSP file, Tile definition, Velocity template, XSLT stylesheet or other source. + The goal of an Action class is to process a request and return an ActionForward object. Action class can either implement a ''stateless service'', or can manage a logical ''web resource''. + + !ActionForward object identifies where control should be transferred to provide the appropriate response, and usually designates either another Action (see [:ActionChaining:action chaining]) or a presentation page. Struts is agnostic to presentation technology, so response can be generated using JSP file, Tile definition, Velocity template, XSLT stylesheet or using other means. !ActionForward object represents a logical outcome of processing a request. By not defining a specific menu choice or a page URL it is possible to separate state of a resource from its visual representation. - The following picture illustrates the difference between Struts and ASP.NET using a simple "render page" use case. In ASP.NET the request means "Display a page", in Struts request means "Display a view that corresponds to current status of an Action". + The following picture illustrates the difference between Struts and ASP.NET using a simple "render page" use case. In Struts this means something like "Process request and transfer control to appropriate location, like a JSP page that corresponds to current status of the Action". In ASP.NET this simply means "Display that page". inline:basic_action_asp.gif '''Example''' - Consider the Action that performs a search. The author of this code should not care about how the search results are presented, his only job is to say "what happened" when the search took place. + Consider the Action that performs a search. The author of this code should not bother about specifics of presentation of the search results. His only job is to say "what happened" after the search took place. Logically, there are three interesting outcomes: * No results were found => outcome "none". @@ -31, +33 @@ == Action As Service == - In its simplest form Action class handles all incoming requests with one callback method, {{{execute()}}}. Two overloaded versions of this method are available. Choosing one or another depends on your servlet environment: + Base Action class handles all incoming requests with one callback method, {{{execute()}}}. Two overloaded versions of this method are available. Choosing one or another depends on your servlet environment: {{{public ActionForward execute(ActionMapping mapping, ActionForm form, @@ -54, +56 @@ * On ''render phase'' (''output phase'', ''render response phase'') the browser requests a web resource to render itself. * On ''input phase'' (''submit phase'', ''event phase'', ''apply request values phase'') the browser sends input data to a web resource, usually by submitting an HTML form. - These two phases correspond closely to GET and POST methods of HTTP request. HTTP specification recommends to use POST for non-idempotent requests, and to use GET for requests that can be repeated several times with the same outcome. In simpler terms, POST requests should be generally used to modify application state, while GET requests should be used to render a view. + In Struts a web resource is represented with Action or Action/!ActionForm combination. + These two phases correspond closely to HTTP GET and POST methods. HTTP specification recommends to use POST method for non-idempotent requests, like those that modify application state. GET method should be used for requests that can be safely repeated several times, like rendering a web page. - In Struts a logical web resource is represented with Action or Action/!ActionForm combination, and two-phase request/response approach is traditionally implemented with setup/submit pattern: - * ''setup action'' (''pre-action'', ''output action'', ''render action'') prepares output data before displaying; - * ''submit action'' (''post-action'', ''input action'', ''accept action'', ''event action'') accepts and handles user input. - This pattern became a de-facto standard with Struts. Action class having only one {{{execute()}}} method instead of Servlet's {{{doGet()}}} and {{{doPost()}}} was another factor that accelerated adoption of this pattern. + Two-phase request/response approach is traditionally implemented with setup/submit pattern: + * ''setup action'' (''pre-action'', ''output action'', ''render action'') prepares output data for display. It loads data from database and queues it into one or more arbitrary objects located in the request or session scope. + * ''submit action'' (''post-action'', ''input action'', ''accept action'', ''event action'') processes input data and redisplays the same data entry form if errors has been found in the input. If input does not contain errors, submit action updates application state and forwards to a success page. + + A significant factor to adopting this pattern was the fact that Action class has only one {{{execute()}}} method instead of separate {{{doGet()}}} and {{{doPost()}}} methods that Servlet has. inline:setup_submit.gif @@ -73, +77 @@ A typical Submit Action will often implement the following logic in its {{{execute}}} method: * Validate the form bean properties as needed. If a problem is found, store the appropriate error message keys as a request (or session) attribute, and forward (or redirect) control to the input form so that the errors can be corrected. - * Perform the processing required to deal with this request (such as saving a row into a database). This can be done by logic code embedded within the Action class itself, but should generally be performed by calling an appropriate method of a business logic bean. + * If input data is valid, perform the business task such as saving a row into a database. This can be done by logic code embedded within the Action class itself, but should generally be performed by calling an appropriate method of a business logic bean. - * Update the server-side objects that will be used to create the next page of the user interface (typically request scope or session scope beans, depending on how long you need to keep these items + * Update the server-side objects that will be used to create the next page of the user interface. These objects would typically be request scope or session scope beans, depending on how long you need to keep these items. * Return an appropriate !ActionForward object that identifies the presentation page to be used to generate this response, based on the newly updated beans. Typically, you will acquire a reference to such an object by calling findForward on either the ActionMapping object you received (if you are using a logical name local to this mapping), or on the controller servlet itself (if you are using a logical name global to the application). + The flip side of Struts flexibility is greater complexity of a most common request/response cycle comparing to similar ASP.NET request/response cycle: - It is common to have several actions of either kind for one logical business object. For example, if you deal with {{{Customer}}} object, you are likely to define two setup actions: {{{viewCustomer.do}}} and {{{editCustomer.do}}} and three submit actions: {{{addCustomer.do}}}, {{{updateCustomer.do}}} and {{{deleteCustomer.do}}}. - - Setup action loads data from database and queues it into one or more arbitrary objects located in the request or session scope. Submit action processes input data and redisplays the same data entry form if errors has been found in the input. If input does not contain errors, submit action forwards to a success page. - - This setup/submit pattern looks far more complex than similar ASP.NET request/response cycle: inline:asp_render_submit.gif - Besides the complexity of implementation, Struts setup/submit pattern has other design and usability implications: + Besides the sheer complexity, original Struts setup/submit pattern has other design and usability implications: + * Most developers program actions as simple services, not as part of a logical web resource. Thus one Action often deals with only one message, like {{{updateCustomer.do}}} handles "Update Customer" event, {{{deleteCustomer.do}}} handles "Delete Customer" event. - * The focus is a page, not a web resource in general. - * One Action deals with only one message, like {{{updateCustomer.do}}} deals with "Update Customer" event, {{{deleteCustomer.do}}} deals with "Delete Customer" event. - * One logical web resource is defined with several action mappings in the {{{struts-config.xml}}} file as well as with several Java classes. * Output data is often scattered in an uncontrolled manner throughout request and session scope. - * In case of error the data entry form is redisplayed by a submit action; that opens a whole can of worms: - * If input data is invalid and autovalidation is turned on, a submit action class is never get called and cannot affect the workflow. + * In case of error the data entry form is redisplayed by a submit action. This means that submit action duplicates setup functionality of setup action. + * This pattern often relies to automatic validation. The downside of this approach is that in case of error the submit action class is never get called and cannot affect the workflow. - * One page is represented with two different URLs in the browser. + * One page is represented with two different URLs in the browser. - * An attempt to refresh a page after it has been redisplayed causes double submit. + * An attempt to refresh a page after it has been redisplayed causes double submit. - * Success page often corresponds to a logically different resource, this leads to a spaghetti code both in Java code as well as in {{{struts-config.xml}}} file. + * Success page where submit action forwards to, often corresponds to a logically different resource. It is impossible to tell which logical resource is managed by which Action classes. The M:M relationship between actions and pages leads to a spaghetti code both in Java code as well as in {{{struts-config.xml}}} file. == Improving Setup/Submit Pattern == - TODO + * Use !ActionForm object to store both input and output data. Use the same !ActionForm object in both setup and submit actions. Declaratively (via {{{struts-config.xml}}}) associate the action form with submit action only, do not associate the action form with setup action. + * On setup phase, prepare the action form with output data and forward to a view. When a user submits HTML form, Struts will populate the action form that is associated with submit action. This is the same form that was used to render a page. + * If input data is not valid, do not redisplay the page from submit action. Instead, update application state if needed, generate error messages, and forward control to setup action. It is the job of setup action to prepare and display views; the job of submit action is to handle the input and to update application state. + * If action form was associated with setup action, it would be repopulated by Struts. Remember, that action form has not been associated with setup action, so it will not be repopulated when control is forwarded to setup action from submit action. Setup action will render the page along with error messages that have been generated by submit action. + * Instead of forwarding to success '''page''', forward to '''setup action''' of success web resource. + + The above steps will allow to cleanly separate concerns between actions, as well as to keep input/output data in one place. == Action As Event Dispatcher ==