DO NOT REPLY TO THIS EMAIL, BUT PLEASE POST YOUR BUG 
RELATED COMMENTS THROUGH THE WEB INTERFACE AVAILABLE AT
<http://nagoya.apache.org/bugzilla/show_bug.cgi?id=6686>.
ANY REPLY MADE TO THIS MESSAGE WILL NOT BE COLLECTED AND 
INSERTED IN THE BUG DATABASE.

http://nagoya.apache.org/bugzilla/show_bug.cgi?id=6686

make "action" attribute of html:form tag optional w/default = URL launching it





------- Additional Comments From [EMAIL PROTECTED]  2002-02-27 21:37 -------
The mechanism for preserving the original formvars depends upon what Struts 
does with the ActionForm bean after it finishes handling a request.

I've gotten the impression that Struts permits a single bean to be used to tie 
together the values from a multi-page form, with already-defined bean values 
being left alone -- the bean itself stored in session context -- as long as no 
formvar on the later-submitted page shares the same name (in which case the 
newly-submitted value(s) would replace the existing value(s)). If that's 
correct, nothing further needs to be done to preserve the original formvars. 
For all intents and purposes, the intercepting login form is nothing more than 
an ad-hoc multipage form. As long as the developer doesn't cause a namespace 
conflict between the login formvars and the rest of the formvars, everything 
should work as expected.

Assuming I'm not totally off-base and the form/bean mechanism works like that, 
making the "action" parameter of the html:form tag optionally inheritable is 
basically a matter of noting its absence and retrieving the proper value from 
HttpServletRequest.getRequestURI() to use as the action parameter.

On the other hand, I guess it's possible that Struts might discard form beans 
after it's finished processing the request. I don't know enough at this point 
to conclusively say one way or the other. If I'm wrong about the way form beans 
and multipage forms work, there will be a bit more work to do... But before 
proceeding to the book I wrote regarding that scenario (*grin*), I'll address 
the other workflow issues.

The main rationale for going with passively-intercepted logins is to enable web 
visitors to freely roam around until they want to do something that needs to be 
restricted or has consequences. Why? Simply put, the more you force web users 
to do up front, the more likely they are to simply go elsewhere instead. On the 
other hand, NOTHING is more annoying than initiating an action on a web site, 
being forced to log in, then finding yourself back at square one. This solution 
conveniently solves both problems for  users AND developers.

Insofar as not permitting users to enter form data whose submittal might 
require authentication, the same goal can be achieved by simply ensuring that 
the template displaying the form is only accessible via a mapping returned from 
the hypothetical performLoggedInAction() method. 

If I'm right about the way ActionForm beans are handled and you understand what 
I'm trying to achieve, you can stop reading here. 

OK, getting back to the issue of saving formvars... assuming I'm totally wrong 
about the way form beans work and some mechanism needs to be explicitly created 
to preserve them between requests, here is a scheme that might work quite well:

First, we'll need to create a new object. For the sake of illustration, let's 
call it an InterceptedFormvars object. It's basically a container for a HashMap 
of String[] ("savedFormvars") and a String used to store a unique key 
("interceptionId"). We need to use an array of Strings, because it's possible 
to have multiple formvars with the same name, but different values in a single 
submission. No big deal... singleton formvars will just have one element in the 
array.

Once Struts realizes that it needs to inherit the "action" for the html:form 
it's about to render, it creates an InterceptedFormvars object, and populates 
the InterceptedFormvars's "savedFormvars" HashMap with the names and values[] 
of the just-submitted formvars within it. 

Struts proceeds to generate some random string, and stores it in the 
InterceptedActionForm's "interceptionId" variable. It then renders the 
html:form. Somewhere inside the newly-rendered form, Struts adds a hidden 
formvar with a name that's used consistently by Struts, but unlikely to ever be 
used by a real form (say, Struts_PreservedFormVarUniqueId_ip.address.of.server) 
whose value is the same as that stored in the 
InterceptedFormvars's "interceptionId" value. Finally, the InterceptedFormvars 
is stored in session context, and Struts continues as it always has.

Now, getting to the other end of the equation, when a form submission is made 
and BEFORE the ActionForm's subclass is  populated with the submitted formvars, 
Struts notices that the user has an InterceptedFormvars object stored in 
session context. It retrieves it, stores it within the ActionForm object 
itself, and deletes it from session context. The ActionForm checks to see 
whether the formvars just submitted contain a value keyed 
to "Struts_PreservedFormVarUniqueId_ip.address.of.server". If they don't, it 
nulls out the InterceptedFormvars object and pretends it never existed. If the 
hidden formvar DOES exist, but its value doesn't match the 
InterceptedFormvars's interceptionId, it does the same thing (unceremoniously 
nulls it out). However, if the InterceptedFormvars object's interceptionId 
matches that of the "Struts_PreservedFormVarUniqueId_ip.address.of.server" 
formvar, it proceeds to repopulate the bean with the values from the 
InterceptedFormvars. 

Why bother with the interceptionId? To ensure that users who open multiple 
browser windows, back-arrow around, and/or go to bookmarks that might have URL-
encoded parameters won't get a rude surprise by mistake.

Having repopulated the bean, the ActionForm processes the newly submitted 
formvars. First, it wipes out any values stored in the bean whose names 
conflict with the new formvars. It then populates the bean with the values from 
the new formvars, leaving the non-conflicting restored values intact, and 
proceeds as normal.

By now, you might be wondering why we saved the InterceptedFormvars, or how we 
plan to handle the consequences of namespace conflicts between the preserved 
formvars and the login formvars.

By default, the html:form tag's object should throw an exception whenever it 
detects a namespace conflict while rendering a form with an inherited action -- 
in other words, if any formvars in the new (intercepting) form that have the 
same name as formvars from the previously-submitted (intercepted) form. This is 
to prevent ACCIDENTAL namespace collisions. HOWEVER, the programmer should be 
able to specify an additional parameter (silent="true"?) with the html:form tag 
that will suppress the exception if he really, truly wants to do it and 
understands the consequences. 

Likewise, when there's a namespace collision, the formvars from the most 
recently-subbmited form take precedence over those saved from the previous form 
and are the values present when perform() gets called. 99.9% of the time, this 
is fine. For the remaining .01%, the ActionForm class should have another 
method -- restoreFormvars() -- that can be explicitly called once the tasks 
related to intercepting the request have been completed to wipe the FormBean's 
values clean and restore them to the EXACT condition they were in the first 
time around. Once again, keeping things easy and convenient for the majority of 
use cases, but keeping the door open a crack for the rare instance when 
somebody MUST have the alternate behavior.

************

For the sake of clarification, here's an example use case I came up with. I 
originally had this at the beginning, but decided to move it to the bottom 
since it's quite long, probably unnecessary to most of the people reading this, 
and I didn't want to lose everyone's attention before I even got to the main 
point :-)

Suppose a user wants to launch an action that will culminate in a SMS message 
being sent to his phone. Obviously, the user needs to have logged in first. The 
user has not yet logged in, because he's browsing around the site looking for 
something to send.

He finds something interesting and clicks a submit button. 

A HTTP POST is made to "sendSomething.do" along with a few formvars -- say, a 
radio button named "id" with an integer value and two hidden form fields 
named "hidden1" and "hidden2". 

The path "sendSomething" maps to a subclass (SendSomethingAction.class) of an 
abstract subclass (AbstractLoggedInUserAction.class) of Struts' own Action 
class and has a form bean (SendSomethingFormBean.class) with get/set methods 
for three variables (id, hidden1, and hidden2) that is a subclass of an 
abstract FormBean class used to handle logins (AbstractLoginFormBean.class) 
that itself is a subclass of Struts' ActionForm. The AbstractLoginFormBean 
class has get/set methods for three variables too... username, password, and 
doLogin.

SendSomethingAction has only a single method -- performLoggedInAction -- that 
takes as input parameters an ActionMapping, an ActionForm, a 
HttpServletRequest, a HttpServletResponse, a User object, and an errList object 
(User encapsulates things like the user_id and phone number, errList is a 
subclass of ActionErrors).

AbstractLoggedInUserAction has only a single method -- perform(), that takes 
the usual 4 parameters (ActionMapping, ActionForm, HttpServletRequest, 
HttpServletResponse). It retrieves the User object from session context, or 
instantiates a new one if necessary. 

It casts the ActionForm into an AbstractLoginFormBean and checks to see whether 
the bean's doLogin value is null. If it's not, it calls the User object's login
() method, passing the bean's username and password values as parameters.

if User.isLoggedIn(), AbstractLoggedInUserAction returns the ActionForward 
returned by performLoggedInAction(*,*,*,*,*,*) (remember, we're actually using 
SendSomethingAction... the perform() method is in SendSomethingAction's 
superclass). Otherwise, it returns mapping.findForward("login"), which is 
globally forwarded to "login.jsp".

So far, so good. Now's when we get to find out how well I actually understand 
how Struts works ;-)

At this point, Struts' normal routines take over, and display the login form. 
The login form takes advantage of Struts' newest feature (*grin*) whereby the 
form inherits the action that called it if no "action" attribute is explicitly 
specified in the html:form tag. In this case, it's "sendSomething.do".

The user enters his username and password (doLogin is a hidden formvar set to 
any non-null value) and hits submit.

Once again, the Action servlet calls SendSomethingAction's perform() method 
(inherited from AbstractLoggedInUserAction). Since the user hasn't logged in 
yet, there's no User object stored in session context yet, so it instantiates 
another one, then calls its login() method after discovering that the bean's 
doLogin value is non-null.

Success. The user is logged in. User.isLoggedIn() is true, so we clear the 
username, password, and doLogin values in the bean, and performLoggedInAction
(*,*,*,*,*,*) gets called to do its thing.

What about the three formvars that were submitted in the first place before we 
had to take the side trip to log in? Well, login.jsp inherited its action -- 
sendSomething.do. The formBean mapped to sendSomething is still a 
SendSomethingFormBean. We just happened to repurpose it a bit by using its 
latent skills as a login form bean to communicate the username, password, and 
doLogin command to the perform() class. 

The mechanism for their preservation was addressed earlier...

--
To unsubscribe, e-mail:   <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>

Reply via email to