Yet again I have reconsidered because ThreadLocals are sort of clumsy,
and since I need these objects in the freemarker/velocity/jsp
(presentation) phase of the game, I need to clean up the ThreadLocal way
late which seems like a filter's job.  All of this is just getting
obnoxious.

We extended RequestProcessor to populate the form for us if the form has
our annotation.  This retains the possibility to validate, use errors
and all the other good stuff Struts provides.  (It also, however, keeps
all those forms declared in struts-config.xml.  Since they were already
there, it isn't too big a deal I guess.)

Our request processor only overrides processPopulate() like so:

  protected void processPopulate(HttpServletRequest request,
HttpServletResponse response, ActionForm form, ActionMapping mapping)
throws ServletException
  {
    if( form.getClass().isAnnotationPresent( OgnlForm.class ) )
    {
        WebappConfiguration config =
WebappConfiguration.getWebappConfiguration( request );
        OgnlUtil.setProperties( request.getParameterMap(), form,
config.getOgnlContext( form ) );
    }
    else
    {
        super.processPopulate(request, response, form, mapping );
    }
  }

And our struts-config.xml says:
  <controller
processorClass="com.allegrocentral.web.struts.OgnlFormPopulatingRequestP
rocessor" />

We still have the OGNL context/runtime configured the same as before,
but we added ActionForm to it:
    NullHandler nh = new InstantiatingNullHandler();
    OgnlRuntime.setNullHandler( AbstractAction.class, nh );
    OgnlRuntime.setNullHandler( ActionForm.class, nh );

If you don't mind the issues that ThreadLocals introduce, you can still
use the action to do this stuff.  At the end of the day, all this buys
us is OGNL form population, which I think may be used by the Struts
Taglib.  We are a freemarker shop, ergo the interest in OGNL.

FWIW


-----Original Message-----
From: Wendel Schultz [mailto:[EMAIL PROTECTED] 
Sent: Thursday, February 16, 2006 11:56 AM
To: Struts Users Mailing List
Subject: RE: WebWork's OGNL form handling - very RoR in approach.
Possible in Struts 1.x?

Ok.  I meant to declare the private Product as a ThreadLocal<Product>.

So:
  private ThreadLocal<Product> product;
  
  public void setProduct(Product product)
  {
    if ( product  == null )
    {
      this.product = new ThreadLocal<Product>();
    }

    this.product.set( product );
    
    logger.error( "I just got a product for free!!!" );
  }
  
  public Product getProduct()
  {
    return this.product.get();
  }
  
Do similarly for other local Action variables.
-w

-----Original Message-----
From: Wendel Schultz [mailto:[EMAIL PROTECTED]
Sent: Thursday, February 16, 2006 11:29 AM
To: user@struts.apache.org
Subject: 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.




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


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

Reply via email to