How do you get around Struts' single inheritence model?
Adrian Lanning sent me an interesting e-mail yesterday. He's looking for a better way to extend Struts. I thought I'd post his message here and see if anyone has ideas.
I am a big struts fan and use it for my projects but I think there is something fundamentally wrong with the design. The real crux of the matter may be that Java is a single-inheritance model and Struts is designed more for multiple-inheritance.
Example 1.
Complexity when trying to use "extensions" to struts that have their own RequestProcessors. Such as my extension for roles (mentioned below) used together with Asqdotcom's ActionPlugin which is a plugin but still uses its own RequestProcessor, plus another hypothetical extension which has it's own RequestProcessor. Basically the only way to use them all is to modify the source of one or two to extend the others which doesn't seem like a good design.
Example 2.
Extending LookupDispatchAction to include per-method validation (set in struts-config.xml). Basically same problem. Need to combine functionality of ValidatorAction and LookupDispatchAction but can't extend from both. Have to modify source. Actually Brandon Goodin wrote this already (I find it very useful). It's called ValidatorLookupDispatchAction.
I know this is a common issue with single-inheritence models/languages. It would be really easy to extend Struts in a multiple-inheritance model.
* THE POINT *
I was just wondering if you knew of a better way to design when dealing with single-inheritance models to avoid problems such as these.
Personally, I don't find the need to extend Struts that much, so this is not much of an issue for me. I think Struts 2.0 will solve many of Struts single-inheritance design by declaring interfaces for all core components. I wonder when 2.0 is roadmapped to ship - sometime in 2006? ;-)
Later: The article, Security in Struts: User Delegation Made Possible, by Werner Ramaeker provides a good example of a Struts extension strategy.
Posted by Robert S. Sfeir on February 25, 2004 at 01:16 PM MST #
Posted by Vic on February 25, 2004 at 02:20 PM MST #
Posted by Lars Hoss on February 25, 2004 at 03:56 PM MST #
With <code>ActionMapping</code>, mostly it's up to the extension to play nice with others. Often the point of offering an <code>ActionMapping</code> extension is to add custom properties to the XML file for each <code>Action</code>. If the extension specifies an interface, and treats the <code>ActionMapping</code> <em>only</em> as a place where it can retrieve the custom propertiesâthen it can put the handling code in a helper class of some kind. This lets each Struts end user create a basic extension of the Struts <code>ActionMapping</code>, and then implement many interfaces that are (typically) simple getters and setters. So it's likely that the use of intefaces in Struts 2.0 will really help this dillema. Until then it's entirely possible for extension writer to "play nice," but it's up to them to do so. An interesting example is Struts-Workflow. It did well on making the <code>RequestProcessor</code> easily combine with others, but could be better at letting its <code>ActionMapping</code> do so as well.
Posted by Tim on February 25, 2004 at 04:46 PM MST #
Posted by Jason Carreira on February 25, 2004 at 05:16 PM MST #
You guys need some serious marketing. ;-)
Posted by Matt Raible on February 25, 2004 at 05:27 PM MST #
Posted by Jason Carreira on February 25, 2004 at 05:48 PM MST #
Posted by Unknown on February 25, 2004 at 07:59 PM MST #
I think it's time I started a new policy. From this point forward, anonymous bashers must leave their name or their comments will be deleted.
Posted by Matt Raible on February 25, 2004 at 08:09 PM MST #
Posted by Jason Carreira on February 25, 2004 at 08:15 PM MST #
Posted by Robert S. Sfeir on February 25, 2004 at 08:31 PM MST #
Posted by Jason Carreira on February 25, 2004 at 08:46 PM MST #
It seems most people think that using Interfaces solves this issue.
I may be showing my ignorance here but can someone point out how using Interfaces solves my 2 example issues? Jason?
Is the "pipeline" concept of chain-able processing blocks (servlet filters, superscalar microprocessor cores, graphics pipelines, MS Windows event hooks) a better way to design for extension?
Thoughts?
Thanks,
Adrian
Posted by Adrian Lanning on February 26, 2004 at 05:05 AM MST #
Posted by Jason Carreira on February 26, 2004 at 05:27 AM MST #
Posted by Jason Carreira on February 26, 2004 at 05:29 AM MST #
Posted by Matthew Payne on February 26, 2004 at 04:53 PM MST #
Posted by Matthew Payne on February 26, 2004 at 05:04 PM MST #
So Interfaces aren't the solution, Interceptors (filters, chain of responsibility, what have you) are. Any number of "extensions" can be used because they are all implemented as "interceptors" (filters, hooks, etc...). Is this correct?
Assuming this is correct, then the design of Struts itself is only flawed in this regard in that there is no method for Interception. For example, many times extension authors must add extra functionality to the RequestProcessor which causes problems such as I illustrated in my original email. Each extension basically nullifies all others because there is no way for multiple RequestProcessors to work together.
Solving the Struts Extension ProblemA permanent solution would be for extension authors to be able to implement RequestProcessor-esque "interceptors" which would each be put into the processing pipeline for each request... Have I reached the heart of the matter?
To solve the extension problem in Struts temporarily we just need to create a new RequestProcessor, ActionMapping, and Action. As Lars posted early on, if we changed the RequestProcessor constructor sig to also accept other RequestProcessors, this would allow developers to declare multiple RequestProcessors in the struts-config.xml file (thereby solving the extension problem). We would also have to make the base RequestProcessor's "important messages" public rather than private as Lars points out above.
Does this solve the problem? Has anyone done this already?
WebWorksJason, it seems that you are saying WebWork uses Interception and therefore solves these issues. It is "extensible" because you can plug any number of interceptors into the request pipeline. Please provide an example of a time when you used multiple "extensions" in WebWork together. It will no doubt shed some light on this.
(PLEASE NOTE: If the any of the functionality I suggest below as examples of "extensions" is "built-in" to WebWork please use a different example. The point here is to study how frameworks can be designed for extension, not to solve any specific problem of mine.)
I'm not familiar with WebWork terminology or structure but say, for example, I want to process a request three times before any Action gets called. Once to validate data on a per-method basis (vs the per request basis that I think is built into WebWork), again to validate user roles, and a final hypothetical time for some other "extension".
Posted by Adrian Lanning on February 26, 2004 at 06:57 PM MST #
Upon further thought on the issue of temporarily modifying Struts to allow for multiple RequestProcessors, what changes are really necessary?
Not knowing the struts internals, could it be as simple as modifying the digester code to take a comma seperated list of RequestProcessors and then changing the code that uses the RequestProcessor to use each in turn?
This strategy might be directly applicable to other projects as well. Such as Matt's excellent Struts-Menu.
For arguments sake assume someone wants to use more than one "PermissionAdapter" when using Struts-Menu. I want to do 'permissions="pa1,pa2'. (I know, I know, PermissionAdapter is so simple that it would be really, really easy to use composition to solve this.) Thoughts?
Posted by Adrian Lanning on February 26, 2004 at 07:51 PM MST #
In WebWork, we use Interceptors for much of the core functionality. I'm not sure what you're talking about, when you say "I want to process a request three times before any Action gets called". If you mean you want to apply 3 Interceptors, then that's very easy, you just configure your Action with those 3 Interceptors applied.
For Interceptors, we have everything from the very easy Logging and Timing, to core functionality like setting the parameters from the request onto your Action (yes, this can be omitted and/or replaced with your own). We also have complex Interceptors like the validation Interceptor (the Interceptor is simple, but the framework behind it does a lot of work). The validation framework is very flexible and provides for different validation sets for different action configurations, even if they're mapped to the same Action class, and provides for inheriting common validations, and even allows you to pass the validation down into your properties to be validated using their own validation rules. We also do things like form token validation, to prevent duplicate posting. There's actually 2 Interceptors to prevent the Action from executing twice... One returns a code which can be mapped to another page, etc. The other actually keeps the previously executed Action in the Session and re-renders it the same as the first time the request was posted...
These are just examples, though. In the Hibernate example app, which is built with WebWork2, they implemented an Interceptor to handle managing the Hibernate Session, creating it when the request enters and closing it when it's done (after the page has been rendered).
Other Interceptor ideas are unlimited... Whatever you need to do. We implemented a lot of the core functionality in Interceptors so they can be plugged in (or not) and only provide the exact functionality required for this Action.
Posted by Jason Carreira on February 26, 2004 at 08:05 PM MST #
Posted by Unknown on February 26, 2004 at 10:56 PM MST #
Jason, thanks for the breakdown. I'm looking forward to trying out WW2.
Any plans to make a Struts 1.1 -> WW2 migration guide? Or flesh out the Struts 1.0 ->WW2 one?
Since Struts is apparently the most used framework, an example app written in Struts and then ported to WW2 (to show the difference/benifit of WW2) might be very successful in encouraging developers to migrate to WW.
Posted by Adrian Lanning on February 28, 2004 at 03:45 AM MST #
Posted by Ashok Agrawal on July 30, 2005 at 07:23 AM MDT #