Differences between revisions 12 and 13
Revision 12 as of 2009-09-20 23:12:44
Size: 15143
Editor: localhost
Comment: converted to 1.6 markup
Revision 13 as of 2012-03-08 17:51:49
Size: 14459
Editor: Bill McCarty
Comment:
Deletions are marked like this. Additions are marked like this.
Line 4: Line 4:

Traditionally web applications use to store state information either in in the HTTP request or in the HTTP session. 
Traditionally web applications use to store state information either in in the HTTP request or in the HTTP session.
Line 18: Line 17:
Line 22: Line 20:
Line 27: Line 26:
Line 31: Line 31:
Line 41: Line 40:
Line 45: Line 43:

In a Struts application a rollover scope can be used in action mapping definition just as request and session scopes. To declare a rollover scope for an action form specify {{{scope="rollover"}}} in action mapping definition. Below is example of a typical use case implemented with two action mappings: a Log In component. 
In a Struts application a rollover scope can be used in action mapping definition just as request and session scopes. To declare a rollover scope for an action form specify {{{scope="rollover"}}} in action mapping definition. Below is example of a typical use case implemented with two action mappings: a Log In component.
Line 52: Line 49:
...or "logged in" page.  ...or "logged in" page.
Line 56: Line 53:
A rollover scope is used to store form bean in between requests.  A rollover scope is used to store form bean in between requests.
Line 59: Line 56:
Line 65: Line 61:
        type = "org.apache.struts.samples.login.LoginInputAction"
        name = "loginform"
        scope = "rollover"
        validate = "false"
        parameter = "initEvent=init,loginEvent=login,cancelEvent=cancel,logoutEvent=logout">
Line 71: Line 62:
        <forward name = "render" path = "/loginrenderaction.do" redirect = "true"/>
       
<forward name = "userhome" path = "/userhome.do" redirect = "true"/>
       
<forward name = "cancel" path = "/main.do" redirect = "true"/>
 . type = "org.apache.struts.samples.login.LoginInputAction" name = "loginform" scope = "rollover" validate = "false" parameter = "initEvent=init,loginEvent=login,cancelEvent=cancel,logoutEvent=logout">
<forward name = "render" path = "/loginrenderaction.do" redirect = "true"/> <forward name = "userhome" path = "/userhome.do" redirect = "true"/> <forward name = "cancel" path = "/main.do" redirect = "true"/>
Line 77: Line 68:
        type = "org.apache.struts.samples.login.LoginRenderAction"
        name = "loginform"
        scope = "rollover"
        validate = "false">
Line 82: Line 69:
        <forward name = "notloggedin" path = "/logindialog/logincomponent-login.jsp"/>
        <forward name = "loggedin" path = "/logindialog/logincomponent-logout.jsp"/>
 . type = "org.apache.struts.samples.login.LoginRenderAction" name = "loginform" scope = "rollover" validate = "false">
 <forward name = "notloggedin" path = "/logindialog/logincomponent-login.jsp"/> <forward name = "loggedin" path = "/logindialog/logincomponent-logout.jsp"/>
Line 87: Line 75:
Line 91: Line 78:
        type = "org.apache.struts.samples.login.LoginInputAction"
        name = "loginform"
        scope = "rollover"
        validate = "false"
        parameter = "initEvent=init,loginEvent=login,cancelEvent=cancel,logoutEvent=logout">
Line 97: Line 79:
        <set-property key="rolloverStrategy" value="timeout" />
        <set-property key="rolloverLimit" value="3" />
 . type = "org.apache.struts.samples.login.LoginInputAction" name = "loginform" scope = "rollover" validate = "false" parameter = "initEvent=init,loginEvent=login,cancelEvent=cancel,logoutEvent=logout">
 <set-property key="rolloverStrategy" value="timeout" /> <set-property key="rolloverLimit" value="3" />
 <forward name = "render" path = "/loginrenderaction.do" redirect = "true"/> <forward name = "userhome" path = "/userhome.do" redirect = "true"/> <forward name = "cancel" path = "/main.do" redirect = "true"/>
Line 100: Line 83:
        <forward name = "render" path = "/loginrenderaction.do" redirect = "true"/>
        <forward name = "userhome" path = "/userhome.do" redirect = "true"/>
        <forward name = "cancel" path = "/main.do" redirect = "true"/>
Line 106: Line 86:
        type = "org.apache.struts.samples.login.LoginRenderAction"
        name = "loginform"
        scope = "rollover"
        validate = "false">
Line 111: Line 87:
        <set-property key="rolloverStrategy" value="rcount" />
        <set-property key="rolloverLimit" value="1" />
 . type = "org.apache.struts.samples.login.LoginRenderAction" name = "loginform" scope = "rollover" validate = "false">
 <set-property key="rolloverStrategy" value="rcount" /> <set-property key="rolloverLimit" value="1" />
 <forward name = "notloggedin" path = "/logindialog/logincomponent-login.jsp"/> <forward name = "loggedin" path = "/logindialog/logincomponent-logout.jsp"/>
Line 114: Line 91:
        <forward name = "notloggedin" path = "/logindialog/logincomponent-login.jsp"/>
        <forward name = "loggedin" path = "/logindialog/logincomponent-logout.jsp"/>
Line 119: Line 94:
Line 123: Line 97:
        type = "org.apache.struts.samples.login.LoginInputAction"
        name = "loginform"
        scope = "rollover"
        validate = "false"
        parameter = "initEvent=init,loginEvent=login,cancelEvent=cancel,logoutEvent=logout">
Line 129: Line 98:
        <set-property key="rolloverStrategy" value="rcount" />
        <set-property key="rolloverLimit" value="5" />
        <set-property key="rolloverRelease" value="cancel,userhome" />
 . type = "org.apache.struts.samples.login.LoginInputAction" name = "loginform" scope = "rollover" validate = "false" parameter = "initEvent=init,loginEvent=login,cancelEvent=cancel,logoutEvent=logout">
 <set-property key="rolloverStrategy" value="rcount" /> <set-property key="rolloverLimit" value="5" /> <set-property key="rolloverRelease" value="cancel,userhome" />
 <forward name = "render" path = "/loginrenderaction.do" redirect = "true"/> <forward name = "userhome" path = "/userhome.do" redirect = "true"/> <forward name = "cancel" path = "/main.do" redirect = "true"/>
Line 133: Line 102:
        <forward name = "render" path = "/loginrenderaction.do" redirect = "true"/>
        <forward name = "userhome" path = "/userhome.do" redirect = "true"/>
        <forward name = "cancel" path = "/main.do" redirect = "true"/>
Line 139: Line 105:
        type = "org.apache.struts.samples.login.LoginRenderAction"
        name = "loginform"
        scope = "rollover"
        validate = "false">
Line 144: Line 106:
        <set-property key="rolloverStrategy" value="rcount" />
        <set-property key="rolloverLimit" value="5" />
        <set-property key="rolloverRelease" value="loggedin" />
 . type = "org.apache.struts.samples.login.LoginRenderAction" name = "loginform" scope = "rollover" validate = "false">
 <set-property key="rolloverStrategy" value="rcount" /> <set-property key="rolloverLimit" value="5" /> <set-property key="rolloverRelease" value="loggedin" />
 <forward name = "notloggedin" path = "/logindialog/logincomponent-login.jsp"/> <forward name = "loggedin" path = "/logindialog/logincomponent-logout.jsp"/>
Line 148: Line 110:
        <forward name = "notloggedin" path = "/logindialog/logincomponent-login.jsp"/>
        <forward name = "loggedin" path = "/logindialog/logincomponent-logout.jsp"/>
Line 153: Line 113:
Line 157: Line 116:
        type = "org.apache.struts.samples.login.LoginInputAction"
        name = "loginform"
        scope = "rollover"
        validate = "false"
        parameter = "initEvent=init,loginEvent=login,cancelEvent=cancel,logoutEvent=logout">
Line 163: Line 117:
        <set-property key="rolloverId" value="MyLoginRollover" />  . type = "org.apache.struts.samples.login.LoginInputAction" name = "loginform" scope = "rollover" validate = "false" parameter = "initEvent=init,loginEvent=login,cancelEvent=cancel,logoutEvent=logout">
 <set-property key="rolloverId" value="MyLoginRollover" />
 <forward name = "render" path = "/loginrenderaction.do" redirect = "true"/> <forward name = "userhome" path = "/userhome.do" redirect = "true"/> <forward name = "cancel" path = "/main.do" redirect = "true"/>
Line 165: Line 121:
        <forward name = "render" path = "/loginrenderaction.do" redirect = "true"/>
        <forward name = "userhome" path = "/userhome.do" redirect = "true"/>
        <forward name = "cancel" path = "/main.do" redirect = "true"/>
Line 171: Line 124:
        type = "org.apache.struts.samples.login.LoginRenderAction"
        name = "loginform"
        scope = "rollover"
        validate = "false">
Line 176: Line 125:
        <set-property key="rolloverId" value="MyLoginRollover" />  . type = "org.apache.struts.samples.login.LoginRenderAction" name = "loginform" scope = "rollover" validate = "false">
 <set-property key="rolloverId" value="MyLoginRollover" />
 <forward name = "notloggedin" path = "/logindialog/logincomponent-login.jsp"/> <forward name = "loggedin" path = "/logindialog/logincomponent-logout.jsp"/>
Line 178: Line 129:
        <forward name = "notloggedin" path = "/logindialog/logincomponent-login.jsp"/>
        <forward name = "loggedin" path = "/logindialog/logincomponent-logout.jsp"/>
Line 183: Line 132:
Line 186: Line 134:
All you need to do is to define your !Edit link so that it contained parameter with the same name as value of "rolloverId" parameter. The value of this HTTP parameter will be concatenated with base rollover id to form a complete name. This is easier to show with code sample.  All you need to do is to define your !Edit link so that it contained parameter with the same name as value of "rolloverId" parameter. The value of this HTTP parameter will be concatenated with base rollover id to form a complete name. This is easier to show with code sample.
Line 189: Line 137:
        type = "org.apache.struts.samples.OrderInputAction"
        name = "orderform"
        scope = "rollover"
        validate = "false"
        parameter = "editEvent=edit,viewEvent=view>
Line 195: Line 138:
        <set-property key="rolloverId" value="orderId" />  . type = "org.apache.struts.samples.OrderInputAction" name = "orderform" scope = "rollover" validate = "false" parameter = "editEvent=edit,viewEvent=view>
 <set-property key="rolloverId" value="orderId" />
 <forward name = "render" path = "/orderrenderaction.do" redirect = "true"/> <forward name = "cancel" path = "/orderlist.do" redirect = "true"/>
Line 197: Line 142:
        <forward name = "render" path = "/orderrenderaction.do" redirect = "true"/>
        <forward name = "cancel" path = "/orderlist.do" redirect = "true"/>
Line 202: Line 145:
        type = "org.apache.struts.samples.OrderRenderAction"
        name = "orderform"
        scope = "rollover"
        validate = "false">
Line 207: Line 146:
        <set-property key="rolloverId" value="orderId" />  . type = "org.apache.struts.samples.OrderRenderAction" name = "orderform" scope = "rollover" validate = "false">
 <set-property key="rolloverId" value="orderId" />
 <forward name = "edit" path = "/order/editorder.jsp"/>
Line 209: Line 150:
        <forward name = "edit" path = "/order/editorder.jsp"/>
Line 212: Line 152:
The mapping above is similar to mapping in Example 4. Now the crucial part: the Edit links. They may look like this:  The mapping above is similar to mapping in Example 4. Now the crucial part: the Edit links. They may look like this:
Line 217: Line 157:
Line 226: Line 165:
Instances of this class store rollover-scoped data; the class implements Map. Static methods of this class obtain/create /remove a rollover scope instance.  Instances of this class store rollover-scoped data; the class implements Map. Static methods of this class obtain/create /remove a rollover scope instance.
Line 238: Line 177:
Method {{{getScope}}} now looks up for rollover scope along with standard J2EE scopes.  Method {{{getScope}}} now looks up for rollover scope along with standard J2EE scopes.

Attention! This page describes a feature that has not been implemented yet!

Rollover Scope for Struts 1.x

Traditionally web applications use to store state information either in in the HTTP request or in the HTTP session.

If request object is used, state is usually serialized to HTML page as part of HTML FORM. In this case moving back and forward along page history changes the state. Consider implementing an online store checkout service, using request object to store state. After a customer payed for goods, he can click Back button and pay again. To prevent this kind of error a token or similar facility should be used.

If session object is used to store state, it may open a potential source of memory leak, because application has to explicitly remove user objects from session. Also, opening several windows for same-type object like a product in a traditional CRUD application may not be possible, because session stores state corresponding only to one product.

Because of issues related to using session, many developers abstain from using it, and prefer fighting with double submit problems and POSTDATA messages instead. They rarely split input and render tasks into two actions; when they do, they use in-server forwarding instead of redirection, because request object does not survive between requests. This is unfortunate because Redirect-After-Post pattern is a simple and proven solution for creating user-friendly and error-resistant interfaces.

Starting from Struts 1.4 it will be possible to store a multi-request conversation data in the Rollover Scope.

rollover.gif

Rollover scope in a nutshell

A rollover scope is essentially a map stored within session scope. One session object can store several associated rollover scopes.

Rollover scope can be used in the following ways:

  • Directly from application code by calling methods of RolloverScope class. [not tested]

  • By passing rollover scope to saveXXX() and loadXXX() methods of Action class. [not tested]
  • By specifying rollover scope for an action form in struts-config.xml file. [implemented]

A rollover scope can be configured for automatic garbage collection. Two techniques are possible:

  • Specifying removal strategy (by timeout or by request count) and a limiting value (number of requests or time to live) at scope creation time. Scope will be removed when its lifetime counter exceeds limiting value.
  • Specifying a release property in action mapping; when action forwards to a release target, the rollover scope is destroyed.

Using rollover scope explicitly from application code

(Not all statements of this section are backed up by actual code)

To obtain an instance of a rollover scope use RolloverScope.getInstance static method. If the scope you are accessing does not exist and "create" flag is true, new scope will be created. When a rollover scope is accessed explicitly, its content is not copied to request scope.

To store data in a scope or to read data from a scope use appropriate methods of Map interface. Rollover scope is just an enhanced Map. If "writeThrough" flag was set at scope creation time, then all data written to the rollover scope is duplicated in the request object.

To remove rollover scope from the session object use RolloverScope.remove method. If "writeThrough" flag was set at scope creation time, rollover data is removed from request scope as well.

Using saveXXX methods of Action class

TBD

Using rollover scope to store an action form

In a Struts application a rollover scope can be used in action mapping definition just as request and session scopes. To declare a rollover scope for an action form specify scope="rollover" in action mapping definition. Below is example of a typical use case implemented with two action mappings: a Log In component.

One mapping is used for submitting login and password from the browser, another mapping use used for rendering either "Not logged in"...

login.gif

...or "logged in" page.

logout.gif

A rollover scope is used to store form bean in between requests.

Rollover-scoped action form, example 1

This is the simplest way of configuring the rollover scope: just declaring the scope as "rollover". The input action inherits from EventDispatchAction and is used as event processor. Events are defined in 'parameter' attribute (see EventDispatchAction for details). Notice that 'scope' has 'rollover' value. A removal strategy of with lifetime of one request is defined for rollover scope - perfect for most redirect-after-post use cases. Render action uses login/logout state to render an appropriate view.

By default, removal strategy is by request count, and maximum lifetime is one request. This means, that when you navigate from this action, the "loginform" action form will mature, and on a next request it will be removed from session.

{{{<action path = "/logininputaction"

  • type = "org.apache.struts.samples.login.LoginInputAction" name = "loginform" scope = "rollover" validate = "false" parameter = "initEvent=init,loginEvent=login,cancelEvent=cancel,logoutEvent=logout"> <forward name = "render" path = "/loginrenderaction.do" redirect = "true"/> <forward name = "userhome" path = "/userhome.do" redirect = "true"/> <forward name = "cancel" path = "/main.do" redirect = "true"/>

</action>

<action path = "/loginrenderaction"

  • type = "org.apache.struts.samples.login.LoginRenderAction" name = "loginform" scope = "rollover" validate = "false"> <forward name = "notloggedin" path = "/logindialog/logincomponent-login.jsp"/> <forward name = "loggedin" path = "/logindialog/logincomponent-logout.jsp"/>

</action>}}}

Rollover-scoped action form, example 2

This is more complex example which defines the removal strategy and limit explicitly using action mapping properties. Properly "rolloverStrategy" specifies strategy by timout, and property "rolloverLimit" specifies maximum lifetime of the idle rollover scope - 3 minutes.

{{{<action path = "/logininputaction"

  • type = "org.apache.struts.samples.login.LoginInputAction" name = "loginform" scope = "rollover" validate = "false" parameter = "initEvent=init,loginEvent=login,cancelEvent=cancel,logoutEvent=logout"> <set-property key="rolloverStrategy" value="timeout" /> <set-property key="rolloverLimit" value="3" /> <forward name = "render" path = "/loginrenderaction.do" redirect = "true"/> <forward name = "userhome" path = "/userhome.do" redirect = "true"/> <forward name = "cancel" path = "/main.do" redirect = "true"/>

</action>

<action path = "/loginrenderaction"

  • type = "org.apache.struts.samples.login.LoginRenderAction" name = "loginform" scope = "rollover" validate = "false"> <set-property key="rolloverStrategy" value="rcount" /> <set-property key="rolloverLimit" value="1" /> <forward name = "notloggedin" path = "/logindialog/logincomponent-login.jsp"/> <forward name = "loggedin" path = "/logindialog/logincomponent-logout.jsp"/>

</action>}}}

Rollover-scoped action form, example 3

In addition to explicit removal strategy (by request, maximum lifetime is 5 requests to a session), this configuration specifies conditions for immediate removal of the rollover scope. They are defined with "rolloverRelease" property, which contains action outcomes for which the rollover scope should be deallocated. In the sample below, if the input action chooses 'cancel' or 'userhome' outcomes, then rollover scope is removed when action finishes. If the render action displays user info page a.k.a. logout page, then rollover scope is not needed either.

{{{<action path = "/logininputaction"

  • type = "org.apache.struts.samples.login.LoginInputAction" name = "loginform" scope = "rollover" validate = "false" parameter = "initEvent=init,loginEvent=login,cancelEvent=cancel,logoutEvent=logout"> <set-property key="rolloverStrategy" value="rcount" /> <set-property key="rolloverLimit" value="5" /> <set-property key="rolloverRelease" value="cancel,userhome" /> <forward name = "render" path = "/loginrenderaction.do" redirect = "true"/> <forward name = "userhome" path = "/userhome.do" redirect = "true"/> <forward name = "cancel" path = "/main.do" redirect = "true"/>

</action>

<action path = "/loginrenderaction"

  • type = "org.apache.struts.samples.login.LoginRenderAction" name = "loginform" scope = "rollover" validate = "false"> <set-property key="rolloverStrategy" value="rcount" /> <set-property key="rolloverLimit" value="5" /> <set-property key="rolloverRelease" value="loggedin" /> <forward name = "notloggedin" path = "/logindialog/logincomponent-login.jsp"/> <forward name = "loggedin" path = "/logindialog/logincomponent-logout.jsp"/>

</action>}}}

Rollover-scoped action form, example 4

In previous samples the rollover scope was not given name explicitly, so action form name was used as rollover scope name: "loginform". It is possible to give an explicit name to the rollover scope, using "rolloverId" property, for example:

{{{<action path = "/logininputaction"

  • type = "org.apache.struts.samples.login.LoginInputAction" name = "loginform" scope = "rollover" validate = "false" parameter = "initEvent=init,loginEvent=login,cancelEvent=cancel,logoutEvent=logout"> <set-property key="rolloverId" value="MyLoginRollover" /> <forward name = "render" path = "/loginrenderaction.do" redirect = "true"/> <forward name = "userhome" path = "/userhome.do" redirect = "true"/> <forward name = "cancel" path = "/main.do" redirect = "true"/>

</action>

<action path = "/loginrenderaction"

  • type = "org.apache.struts.samples.login.LoginRenderAction" name = "loginform" scope = "rollover" validate = "false"> <set-property key="rolloverId" value="MyLoginRollover" /> <forward name = "notloggedin" path = "/logindialog/logincomponent-login.jsp"/> <forward name = "loggedin" path = "/logindialog/logincomponent-logout.jsp"/>

</action>}}}

Rollover-scoped action form, example 5

It is possible to have several instances of rollover scope per action. Consider a typical CRUD application, it displays a list of customer orders. Every order line contains !View and !Edit links. If you click on !Edit link, then an order edit form is displayed. To select a specific order, an order id is passed to the server as HTTP parameter. Say, you want to open several order forms in different windows. You would like to right-click the !Edit link and select "Open in separate browser window". This would be hard to do with saving state in the session, but is easy with rollover scope.

All you need to do is to define your !Edit link so that it contained parameter with the same name as value of "rolloverId" parameter. The value of this HTTP parameter will be concatenated with base rollover id to form a complete name. This is easier to show with code sample.

{{{<action path = "/orderinputaction"

  • type = "org.apache.struts.samples.OrderInputAction" name = "orderform" scope = "rollover" validate = "false" parameter = "editEvent=edit,viewEvent=view> <set-property key="rolloverId" value="orderId" /> <forward name = "render" path = "/orderrenderaction.do" redirect = "true"/> <forward name = "cancel" path = "/orderlist.do" redirect = "true"/>

</action>

{{{<action path = "/orderrenderaction"

  • type = "org.apache.struts.samples.OrderRenderAction" name = "orderform" scope = "rollover" validate = "false"> <set-property key="rolloverId" value="orderId" /> <forward name = "edit" path = "/order/editorder.jsp"/>

</action>}}}

The mapping above is similar to mapping in Example 4. Now the crucial part: the Edit links. They may look like this:

orderrenderaction?itemId=1701

When this request is submitted, rollover scope manager will look up "rolloverId" property in the action mapping, it has value "orderId". Then it will look up "orderId" request parameter and read its value, it is "1701". Then it will concatenate parameter name and value, producing "orderId1701", this will be the name of rollover scope for order 1701.

As you see, for this particular use case the natural ID makes a perfect rollover scope name. For other use cases you may need to create artificial IDs.

Changes to Struts core classes

Several core classes have been updated to accommodate usage of rollover scope. Two new commands have been added to default chain.

New: org.apache.struts.scope.RolloverScope

Instances of this class store rollover-scoped data; the class implements Map. Static methods of this class obtain/create /remove a rollover scope instance.

New: org.apache.struts.chain.commands.LoadRolloverData

This command must be specified in chain config file before CreateActionForm command; it matures all rollover scopes corresponding to current session, removes aged scopes, looks up for a rollover scope corresponding to an action mapping and loads rollover data into request scope to ensure that JSP tags perform properly.

New: org.apache.struts.chain.commands.ReleaseRolloverData

This command must be specified in chain config after ExecuteCommand and ExecuteAction commands; it immediately releases current rollover scope if current action is configured so with "rolloverRelease" property.

Updated: org.apache.struts.chain.contexts.ActionContext

Added getRolloverScope method along with "rollover" literal to be used in action mapping definition.

Updated: org.apache.struts.chain.contexts.ActionContextBase

Method getScope now looks up for rollover scope along with standard J2EE scopes.

Updated: org.apache.struts.chain.contexts.WebActionContext

Defines getRolloverScope method, which obtains/creates rollover scope based on current webcontext and request scope. This method is used by ActionContextBase.getScope method.

Updated: struts-config_1_4.dtd

RequestScope enity now allows "rollover" value along with "request" and "session".

Source code and samples

The changes related to rollover scope have not been committed to main Struts 1.x codebase yet. You can try a simple example of rollover scope usage by downloading this sample application: rollover.war . Source code and build script are included.

RolloverScope (last edited 2012-03-08 17:51:49 by Bill McCarty)