You are viewing a plain text version of this content. The canonical link for it is here.
Posted to user@struts.apache.org by Wendel Schultz <ws...@allegromedical.com> on 2006/02/16 19:28:55 UTC

WebWork's OGNL form handling - very RoR in approach. Possible in Struts 1.x?

One very nice thing about WebWork and presumably Struts 2.x is the OGNL
form data scraping.  RoR takes a similar approach.  Though I realize
many will push back at the idea, I wrote a fairly straightforward way to
accomplish putting the form information conveniently in the Action class
using XWork's Ognl capabilities.  I also realize that, at the moment,
this doesn't provide any form validation which means it isn't for every
situation.

For complex and dynamic forms, you don't get any sort of ActionForm
validation/handling anyway.  If a product can have any number of
descriptions keyed off a string, there is no good way to scrape that
data from your form so you can then validate it - maybe with complicated
indirecting using map-backed forms.  I feel that the complication you
incur outweighs the "gain" of validation you achieve by using them and
therefore not worth it.

OGNL  is a very powerful, expressive way of traversing object graphs and
setting data.  XWork has some good things going for it here: if an
element is null as you traverse it, you can optionally plug in a
NullHandler.  They also provide a InstatiationNullHandler which has a
reflective ObjectFactory to create an instance using the default
constructor.

The solution consists of an Annotation, a common Action base class (we
had one anyway), and 2 or 3 lines of OGNL context/runtime configuration.
Though we don't use it, the configuration and stuff can easily done in
Spring, Pico or whatever IoC container you like.  We have our own
configuration object hierarchy because we were oblivious to IoC
containers at the time we started and "we are going to refactor that
part."  The context is just a Map of properties.  One, really.  The
property that says to instantiate null-ness if you'd like.  The runtime
is simply declaring which objects you want the OGNL processor to handle.
We registered our common base Action class, so that all Action classes
annotated are OGNL-enabled.

My action class is annotated with OgnlForm:

@Retention( RetentionPolicy.RUNTIME )
@Target( ElementType.TYPE )
public @interface OgnlForm {}

My action class can define:

...  private Product product;
public get/setProduct()...

Then my form can say:
<textarea name="product.descriptions['LONG'].text">This is my long
description.</textarea> <textarea name="product.descriptions['my
favorite'].text">I had to say a few words about this product.</textarea>

Our base class implements execute and then calls work() on its
subclasses.  execute() just says:
if( this.getClass().isAnnotationPresent( OgnlForm.class ) )
  {
    OgnlUtil.setProperties( request.getParameterMap(), this,
this.webappConfiguration.getOgnlContext( this ) );
  }

  return work();

OgnlForm is our annotation.  OgnlUtils takes a map of name/value pairs,
the object to populate and an optional context (auto nullness).  The
names are valid OGNL expressions, the object to populate is this - the
action with product locally defined and the webappConfiguration is our
IoC configuration garbage accessed from the session, etc...

By the time work() is called on my action, I have a product instance
locally to my action populated with all the necessary data - in this
case at least two descriptions.  You can see using a freemarker (or
velocity or whatever) template to generate the form based on the
contents of the Product, its Skus, descriptions, pricing, inventory,
whatever.

The InstantiationNullHandler can handle Collections, Arrays, Maps as
well as any object with a default (empty) constructor.  For Collections
and Lists, it uses ArrayList and for Maps it uses HashMap.

An alternative is to have input elements that tell me which descriptions
I need to go after and iteratively scraping the request myself looking
for meaningful data.  This is complicated and annoying to me.

Another alternative is to call OgnlUtils.setProperties() on your Struts
ActionForm.  You then have two frameworks manipulating one form and we
opted to dodge the complexity.  You could also process any value object
you want, really.  We just decided that for our webapp considerations,
the Action is as good as any candidate.  Whatever you feel best about.

We are considering reworking the Annotation to take a parameter, such
that if the Action is annotated with OgnlForm we'd put the action itself
in the request, turning it into our value object for display purposes.
Again, I realize that there are many who will violenty oppose this
(please don't flame), but very respectable frameworks such as WebWork
(which IS Struts 2.x), Ruby On Rails (RoR) and others find it
appropriate too, so you'll have a large body of very smart men and women
to contend with.  I will say that having your form data, action, and
view value object all rolled into one is very succinct.
Struts-config.xml slims down considerably.

We are also tossing around the idea that one of the parameters on our
OgnlForm would me method or command or something, so that once our
work() (execute()) is called, we can switch on the command.  We could
then map many actions (*.do) to one Action(? extends OurAction).  A
single action could provide a multitude of related functionality in one
class.

We spent a couple hours digging through XWork's and OGNL's source code,
both written/maintained by OpenSymphony, a VERY good bunch of folks.
Integrating wasn't too bad at all.  WebWork/XWork is very well layered
and factored out so we could use exactly what we wanted/needed.  In
fact, we aren't using any WebWork anything.  It is all XWork - the
underbelly of WebWork.

For complicated and/or dynamic forms, I really like the WebWork/OGNL
approach.  It is interesting to evaluate what other frameworks and
technologies do as well as why they do them.  For us, we can't abandon
the investment we've made in Struts, as I'm sure many out there can't.
We can, however empower our framework innovateively to maximize our ROI
- both on Struts and our developers.

If any are interested, we could put together a small patch jar that
could be committed or posted or something.  If you aren't using java
1.5, I suppose you could do something similar with empty interfaces to
achieve the same net result.