Sunday, October 28, 2007

Struts 2 Dynamic Validators

We had a need for a dynamic, DB driven form with simple validation at work. The site was to be used by multiple clients and they would all have the same basic functionality except that each client would have a form they could customize and specify validation on any of the fields if necessary. Spec wise the validation was to be required or not. I figured that as time would move on they'd want other validations as well so I figured we could use the existing Struts2/XWork validators as to not recreate the wheel.

The plan was simple. Have a table that represented the form's fields with a client foreign key and a validator foreign key. The validator key linked to the validator table that would provide all the information needed by a Struts2/XWork field validator. This would actually be two tables, the first would be the "parent" storing the validator class name and the the second would be a mapping of parameter names and values. Basically everything that's in the validation xml but in the database.

When the action was gotten we would loop over the fields and render each one based on the type. When the form was posted we would then loop over the list of validators, instantiate each one, populate it's parameters and call .validate() on them.

Creating the form and validators with correctly populated parameters was easy. The problem came when it tried to validate. See the validation framework validates the action class, as it should. So when it came time to validate a field, a getFieldName() method would be called on the action class. Since the form is dynamic that method won't exist and validation will fail.

I tried implementing the class as a commons DynaBean. That didn't work (didn't expect it to really, but just trying anything).
I tried implementing the class in Groovy and using MetaExpandoClass to handle the calls to the missing methods, but that type of magic only works within Groovy and the XWork classes don't know about that.
I tried the java.lang.reflection.Proxy class and a ImplementationHandler but those aren't for handling dynamic methods.
The only think I didn't try is doing it like the old struts 1 map backed forms. Where the field name would be something like myfields[name] and the validating on the map. But I don't think that will work either. I don't really like that solution either because it forces you to be aware of the implementation and when entering validators in the db you'd have to be aware of that. Or if you used an ExpressionValidator with OGNL.
I'd hate to have to create an implementation of the XWork OGNL parser that looks for DynaBeans and treat them appropriately. But I think that might be the only solution.
Something I'd look into if work really need this functionality but for now I've spent way too much time on it.