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]>