A lot of what I do is web-applications, and the framework for serving up these web-applications that I've chosen has been tomcat and struts: tomcat serves up the pages; struts organizes and controls the flow of the web-app. This essay is about struts, and how to avoid some of the pitfalls that a novice can fall into.
Okay, I'll be honest: avoiding the pitfalls that I fell into.
I'm not going to go into the whole strut's ouevre, there are hundreds of sites out there that desrcibe the whole ins and outs of the frame work, what I am going to do is give you some advice. This is really most useful if you're about to start, or have only just started, to implement your web-app. If you've been hacking away at it for a while now, then it'll be less useful, if only because the changes that you'd to have to make to follow this guideline are liable to be HUGE, and perhaps not worth it immediately, but even still, there are a few ideas here that will probably appeal to you, if for no ther reason than they centralise code and you've probably spent ages fixing the same bug all over the place.
I'm new to this whole essay-writing thing, so I'd be delighted to receive any comments, additions or clarifications you have on any of this. I live at daniel@braindelay.com.
I've written some more of these: thay can be found at danielbray.com.
Struts comes with a base action, org.apache.struts.action.Action
that
you can extend to define all the actions you need in your web-application. What
I'm advising is that the first thing you do when you're writing your web-app is
to extend from this Action
your own base action and then extend
all of your web-app's actions from this. Even if initially you have
nothing to generalize you should do it.
I can hear you thinking: Whaaaat? Are you seriously suggesting that I extend a class for no reason? This is the problem with you object-oriented guys: indirection all over the place. I know that's what it looks like, but you are going to find both business and navigation functionality that will need to be centralised at some time. The business code can, of course, be generalized as you see fit, but the code concerning navigation is system-wide and has to be put into one place, and when, not if, you discover that you have some kind of system-wide navigation bug to fix you're not going to want to retro-fit a hundred or so actions to make them extend your new web-app action.
I know I didn't, but I had to, and trust me: it's painful. Since we've never met, I'm going to assume that you don't trust me and still need some convincing, so here's an example, and it has to do with something that you'd think struts would do, but doesn't.
Software users aren't like coders: they aren't particularly willing to accept that computers can be slow; and this really shows itself in web-apps. When a user hits some button on their browsers and the page is taking a bit too long to load, a user can almost never resist the temptation to keep hitting the button, to "speed it up," in much the same way that you'll see someone at a pedestrian crossing hitting the traffic light button repeatedly in a vain effort to affect the traffic system.
Usually what happens when a user submits a form twice in a browser all hell breaks loose, and all manner of faults, from wasted server activity to broken database constraint, come to the surface... and usually come to the surface in the form of a rather ugly stack trace.
Now, struts' Action
class does have functionality to implement this
protection, but it has to be defined programatically in your web-apps
actions, and can't be defined in the struts-config. Again, you're probably
thinking: So what? I still don't see a need for a single base action class
for my web-app: all I need to do is define some class that implements this
protection and extend the actions that need it from this base. Well, patience
grasshopper... it will all become clear soon enough. First I'll say how this
protection functionality in org.apache.struts.action.Action
works.
It's split up into two parts, one on the client, and one on the server:
Strut's Action
class contains the following operations to do this:
setToken(HttpServletRequest)
<html:form>
tag is used.validateToken(HttpServletRequest)
... and that's it. It's nice and simple, all you need to do is call
setToken()
in the action that renders your "menu" page, and call
validateToken()
in the action that executes the event that you want
to protect; piece of cake, really. The problem isn't how to protect single actions
'though; the problem is how to protect single actions throughout the entire
web-app. The web-app I'm working on right now has over a hundred and fifty
actions, so you can bet your bottom dollar that I'm not writing this into every
action.
So you can see a need now for generalising code like this into a single base class, I hope. In case you're still thinking that you can get away with only extending actions that need this protection from this base, consider that in the real world, because requirements tend to change on a whim, the implementation of a piece of software needs to be completely malleable. When your product manager decides that he wants some new functionality to be added to the application and that, as a result, a big chunk of layout of the site-map needs to be changed, you're not going to want to root through your code and change where your actions exist within the class hierarchy, just because you want to switch on, or off, some navigation functionality... do you? Now, this is where the real beauty of having a single application action comes into play.
Like I said, you'd think that a requirement as common as multiple form submission protection would be an integral part of the declarative parts of struts, and not something that needs to be coded programatically, but it's not. Well, not yet anyway, and until it is, you're going to have to cope with it as best you can, and the best way to do it is to implement this code programatically once in a base web-app action of your own, and have the execution of this code be optional on the presence of something that can be defined declaratively.
Words like "declaratively" shouldn't be bandied about too lightly, so I think another example is needed. Back to our multiple form submission protection problem. What you'd want to do is define a base action like this:
abstract public class WebAppAction extends Action { /** Here's the magic, what this operation's method does is take a peek at this action's action mapping to see if there's a forward called CONTROL_isViewAction, and returning true if there is. This action mapping is defined by the struts-config, and so whether this action is a "view" action or not, can be defined declaratively, the struts-config. I've put the big CONTROL at the beginning of the forward so that nobody will confuse it with other forwards that actually manage the navigation of the web-app. */ private boolean isViewAction(ActionMapping mapping) { return null != mapping.findForward("CONTROL_isViewAction"); } /** This is the same as isViewAction() */ private boolean isEventAction(ActionMapping mapping) { return null != mapping.findForward("CONTROL_isEventAction"); } /** What this guy does is tests whether or not this is an "view" action, and if it is, it sets the token in the session so that when the following "Event" action is executed it won't fail. */ public ActionForward doPostCondition(ActionMapping mapping, ActionForm form, HttpServletRequest request) { if (isViewAction(mapping)) { setToken(request) } return ...; // the "successful" action forward } /** What this guy does is tests whether or not this is an "event" action, and if it is, it checks to see if it has been executed already. If it has it will return an appropriately alarming ActionForward. */ public ActionForward doPreCondition(ActionMapping mapping, ActionForm form, HttpServletRequest request) { if (isEventAction(mapping)) { if (false == validateToken(request)) { return new ActionForward("INVALID_TOKEN"); } resetToken(); // subsequent calls musn't find the token in the session } return ...; // the "successful" action forward } /** This class is an example of a "Template" design pattern. What you'd do when you extend it is you'd implement this operation with the method that you'd normally have used for the action's "execute" operation. See, Design patterns: Elements of reusable object-oriented softwarew, by Erich Gamma et al. Addison Wesley */ abstract public ActionForward doTask(ActionMapping mapping, ActionForm form, HttpServletRequest request); /** And this is your execute operation. As you can see, it implements the multiple form submission protection that we needed and that this protection can be switched on, or off, for very specific actions, without the need to recompile, or redefine the code. */ public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request) { ActionForward success = ...; try { ActionForward conditionResult = doPreCondition(mapping, form, request); if (false == success.equals(conditionResult)) { return conditionResult; } return doTask(mapping, form, request); } finally { ActionForward conditionResult = doPostCondition(mapping, form, request); if (false == success.equals(conditionResult)) { return conditionResult; } } } }
I've you've skipped over that code sample, you'll probably want to go back and skoot through it, since all the explanation is in it, if you've read it and you're still not convinced then there's nothing else I can do for you, except suggest that you buy a very comfortable seat and one of those ergonomic keyboards 'cause you're going to be doing an awful lot of needless typing.
The fact of the natter is that after a very short time there'll end up being a ton of functionality that will be I everywhere in your web-app: stuff that's going to be almost everywhere, like breadcrumbs, certain logging and database handling, the handling of business design contracts, etc., that you're going to want to be in one place; also there'll be stuff like this multiple form submission protection that you're going to want to be easily switched on and off.
Either way, you're not going to be able to do this without extending your actions from a single base, or at least, a single hierarchy. You can try if you want to but you'll only be making it hard for yourself.
Finally, in case you thought I was just going to toss that "at least, a single hierarchy" into that last paragraph and ignore it: I'm not denying that there'll be sections of the code where there'll be functionality that only makes sense in that function area, and in this case you'd create a separate subclass to generalize this behaviour; but that subclass will have to be a subclass of your web-app action... unless you fancy rewriting all of this code all over again.
If I haven't persuaded you to extend your actions from a single base action of your own making, I'm not going to convince you that you need to do this for your forms as well; but you do.
It's less of a stroke-inducing mistake to extend all of your forms directly from
org.apache.struts.form.ActionForm
, but you're only making work for
yourself, and unless you're in some kind of trade union that's usually not a
good thing.
If you create your own web-app form then you can do things like:
The mechanics of struts is founded on HTML forms being submitted, although it's possible, and I have to admit that it's so very much easier, to link directly to the action mappings you really shouldn't do this.
I'm sorry if I shouted there, but I usen't to mind so much, but now I'm starting to get militant about this. Everything that makes struts good is founded on its correct use, and, like I said, its correct use is form submissions. This needs an example, so, again, I'll go back to my multiple form submission protection problem.
If you remember, this works by placing a token in the session so that when a form is rendered by the struts taglb tag <html:form>, it places that token into a hidden form parameter. When this form is submitted the action can look at the form parameter and if the token is there and it matches the session's token then all is well.
If I sounded a bit testy, I'm sorry, but I really want this to sink in, so I'll say it again: this works by placing a token in the session so that when a form is rendered by the struts taglb tag <html:form>, it places that token into a hidden form parameter.
There are two things to take note of here:
I know what you're thinking, you're thinking: nice try, but a HTML form can only have one submit button in it, and my GUI has dozens of pages that have two or three buttons; some even have tables with two or three buttons per row: how can I make them all submit the form? Settle down, boys: I wouldn't have said you should do something unless I knew you could actually do it; and here's how:
This is doable, and it's pretty easy, it can just get a little finicky the first time you try. It's a solution in three parts:
This is a bit like a struts DispatchAction
, only a little bit
easier. It only needs to be written one, you'll be glad to know and it can be
reused for every action "menu." It also has the added bonus that it can be used
to refactor old code that uses HTML links to directly call actions without the
need to rewrite those actions at all.
It works simple enough by looking for a given parameter and forwarding to the action forward it named, the code looks like this:
public class RelayAction extends Action { /** Very simple really, it looks for an "operation" parameter and uses it to find a forward in the mapping and then uses it. */ public static ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServlerResponse response) { String operation = request.getParameter("operation"); if (false == isDefined(operation) { return new ActionForward("error"); } String forward = mapping.findForward(operation); if (false == isDefined(forward) { return new ActionForward("error"); } return new ActionForward(forward); } }
Now, to make this rather generalized RelayAction
useful. This is
done in the struts-config. This example would be the menu action for the following
sample JSP.
<action-mapping path = "/webapp/subapp/monkeyActionMenu" type = "com.braindelay.struts.action.RelayAction" forwards> <forwards> <forward name = "create" path = "/webapp/subapp/createMonkeyAction" /> <forward name = "delete" path = "/webapp/subapp/deleteMonkeyAction" /> </forwards> </action-mapping>
There's nothing too complicated here:
RelayAction
, this will
be the same for every action mapping that uses it; it will never need to change
since it's as generalized s it needs to be: this is code-reuse at its purest... nice!
This is best explained with an example. Here there are two buttons, neither
of which are "natural" submit buttons, nut which both define an onsubmit
action; the functions that they call set any parameters that need setting and
then call a submitFrom
operation with an operation
parameter: this parameter is the key.
If you look at the form, a hidden operation
parameter is defined
at the end; now compare this with the implemetation of the RelayAction
:
it is this operation
parameter that the RelayAction
is looking for as a key to determine whcih forward to go to. So all this
submitForm
is doing is setting this hidden parameter to the
appropriate value and submitting the form: easy as pie.
<%-- suppose this action is using a form called monkeyForm --%> <html:form path = "/webapp/subapp/monkeyActionMenu"> <script type = "text/javascript"> <!-- // this is the internet explorer version, but do something like this // for the browsers you're supporting function submitForm(operation) { document.monkeyForm.element['operation'].value = operation; document.monkeyForm.submit; } function createMonkey() { // set the parameters for creating the monkey - these will // also be hidden setCreateParameters(); submitForm('create'); } function deleteMonkey() { // set the parameters for deleting the monkey - these will // also be hidden setDeleteParameters(); submitForm('delete'); } --> </script> <form:hidden name = "operation" value = "error" /> <form:button styleId = "Create" onclick = "createMonkey()" /> <form:button styleId = "Delete" onclick = "deleteMonkey()" /> </html:form>
If you absolutely, positively, must have links in your page, but you still want this multiple form submission lark that we mentioned before; you can set the token in the request (in JSPs only in one of the following two ways:
<html:link transaction = "true" src = ..... />
<a org.apache.struts.action.request.TOKEN =
"<%request.getSession().getAttribute("org.apache.struts.action.request.TOKEN");%>"
href = .... />
One last thing: DOM already places a create
operation on some of
its elements, so you can't have a javascript function in your JSP called
create
'cause it simply won't be called. This hasn't anything to
do with always using forms, it's just a problem that took me ages to
find and I figured I'd warn you about it.
Well, I won't lie to you: compared to just linking directly, going through a form submission is, but it's a minor inconvenience when you consider how much control over your web-app you can leverage from struts if you always work with its protocols rather than slide past them.
Another real bonus of always navigating through the GUI with form submissions is that your entire site map and navigation trail will be defined in the struts-config, and not, as will otherwise be the case, partly in the struts-config and mostly ('cause we're all that lazy) defined in a completely unstructured and invisible way in a hundred or so JSPs... try figuring all that out. It's just so much nicer to be able to fire a XSL stylesheet at your struts-config to see what kind of mess you've gotten yourself into.
This is less a struts problem, and more of a GUI problem in general: that of defining a GUI that merely mimics the model; but this is its struts manifestation, and if you see it happening in your web-app then it's time to start screaming ICEBERG... but like the Titanic, you're probably too late to save yourself.
The problem here is that if you've defined a form that's a row in your database then it's almost certain that you haven't defined your form as matching what the user thinks the underlying concept is. Let's be honest, GUI coders are seen as pastry chefs, and it's rare indeed that they've given an API for the model that isn't just a set of SQL queries disguised as operations... sometimes they're even put into things called beans, that aren't beans, just to lull you into a false sense of security.
Jeez, that was bitter...
Anyway, when your forms are rows in the database, then, whenever your users have to play about with any kind of even slightly complex GUI element, chances are they're going to be forced to submit a load of different pages just to edit one thing, and they're not going to understand, or care, why.
It's a sad fact in web-apps that if an exception is thrown up as far as the presentation layer, then there usually isn't very much that you can do about it. Usually, you'll see code all over the place that catches all manner of exceptions that you cannot possibly fix, just for the case of logging them and setting some flag so that a presentable error page is shown to the user.
You know, this sort of thing:
public class DoItAction extends Action { private ActionForward doItToIt() throws IOException, SQLException, ApplicationException {...}; public ActionForward execute(...) throws Exception { try { // whatever needs doing try { return doItToIt(); } catch (FixableException hmm) { return trySomethingElse(); } return new ActionForward("ERROR"); } catch (IOException badIO) { errors.add("exception.io", new ExceptionActionError(badSpecificTask)); return new ActionForward("ERROR"); } catch (SQLException badSQL) { errors.add("exception.sql", new ExceptionActionError(badSQL)); return new ActionForward("ERROR"); } catch (ApplicationException badSpecificTask) { errors.add("exception.application", new ExceptionActionError(badSpecificTask)); return new ActionForward("ERROR"); } finally { closeDatabaseConnections(); clearOutSession(); closeLogger(); // Report any errors we have discovered back to the original form if (!errors.empty()) { saveErrors(request, errors); } } } }
The problem here is that we're in an action, and up here these isn't really much we can do with these exceptions except tell the user that something's gone wrong; and the user is never going to want to know that there's been some SQL error, so ten times out of ten the error page is only going to say something like
Something broke: talk to your sysadmin.
This code is fairly awkward, and it does tend to clutter up what's going on in
the action. You may be thinking that we could just catch one
java.lang.Exception
and be done with it in one place, but that kind
of catch all can be fairly heartbreaking as it tends to swallow up an awful
lot of problems that would normally be caught in development unit tests.
Thankfully struts has a fairly natty solution, and it all comes down to that
Exception
that is permitted to be thrown by the
org.apache.struts.action.Action.execute(..)
operation, and it's a
solution that comes in three parts.
As an example, we'll take the action above, and remove all the exception that we weren't doing anything with.
public class DoItAction extends Action { private ActionForward doItToIt() throws IOException, SQLException, ApplicationException {...}; public ActionForward execute(...) throws Exception { try { // whatever needs doing try { return doItToIt(); } catch (FixableException hmm) { return trySomethingElse(); } return new ActionForward("ERROR"); } finally { closeDatabaseConnections(); clearOutSession(); closeLogger(); } } }
Much simpler, no? Then we define out ExceptionHandler
. This can be
viewed as a special kind of action that gets called whenever an exception is
thrown by an action; it will do whatever cleaning up or logging it needs to and
will then forward to an appropriate page.
ExceptionHandler
might look like this:
public class ExceptionHandler extends org.apache.struts.action.ExceptionHandler { private String getFailedRequest(HttpServletRequest request) { return "Error executing action : " + request.getRequestURI() +"?"+ request.getQueryString() } private String getInterestingStackTrace(Exception exception) { return // filter out the bits we don't want } /** * @see org.apache.struts.action.ExceptionHandler */ public ActionForward execute( Exception exception, ExceptionConfig config, ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws ServletException { Logger logger = LoggerFactory.createLogger(request); String userMessage = null; // if we can log this error somehow, then create a unique reference code // for it, and onclude it in the error message so that the user's problem // can be solved quicker. if (logger.isLoggable(Level.SEVERE)) { logger.log(Level.SEVERE, referenceCode, getFailedRequest(request)); logger.log(Level.SEVERE, referenceCode, getInterestingStackTrace(exception)); // this UID will be unique on the server String referenceCode = new java.rmi.server.UID().toString(); userMessage = "An internal error has occured:" + " please inform the system administrator" + " (with this reference:"+referenceCode+")"; } else { userMessage = "An internal error has occured:" +" please inform the system administrator."; } // get any existing errors from the session, if there are any. This // long string can be found in the struts "Action" class ActionErrors errors = null; if (null == request.setAttribute("org.apache.struts.action.ERROR")) { errors = new ActionErrors(); } else { errors = (ActionErrors)request.getAttribute("org.apache.struts.action.ERROR"); } // add the new error, and resubmit the errors list to the session. errors.add(exception.getClass().getName(), new ActionError(userMessage)); request.setAttribute("org.apache.struts.action.ERROR", errors); // forward to the apprpriare errors page return mapping.findForward(Constants.FORWARD_ERROR); } }
<global-exceptions> <exception type="java.lang.Exception" key="errors.heading" handler="com.braindelay.ExceptionHandler" path="/errors.jsp" scope="request" /> </global-exceptions>
This is a quick one, and if I were being honest I'd have to admit that it's more of a personal preference, so I'll pass this off ad advice, and not a warning.
With something like xdoclets and a preprocessor you can define the bits of your struts-config that are defining action-mappings for a specific action in the comments of that action. Architects and project managers love this kind of thing, 'cause at first glance it looks like a good thing: it might make coders actually update the rest of the comments if there are some comments that they have to maintain; also it will automatically remove action-mappings that are no longer in use - to be honest, these do tend to hang around for ages after they've been removed from the web-app. Note that I said at first glance, because that's all architects and project managers will do with the code, give it one glance: they don't have to work with the struts-config and these actions on a daily basis, so even if they know how sthey work, they sure as hell don't know how they're used.
The basic problem, in my opinion, with defining the struts-config piecemeal, is this: action-mappings are almost never edited on its own. Normally they're edited in relation to the other actions in its funtional area and it's going to drive you insane if you have to keep flicking back and forth between class files to see if a dozen or so actions are working in synch.
![]() |