Programming Thoughts
Struts 2 - Annotation-based Validation
Custom Validation is Too Arcane
Simple workflow and pages or don't bother
Struts 2 is a popular MVC framework for Java-based web applications. Every organisation has its own methods and rules, so customisation and extension of the Struts 2 framework is inevitable. Allowing useful customisations that aren't complicated or difficult to understand is a fine art but Struts 2 is terrible for customised type conversion and validation.
Painful Custom Validators
At time of writing, the official documentation for validation is at the Core Developers Guide Validation page and the section for 'Writing custom validators' consists of only the following.
Writing custom validators
If you want to write custom validator use on of these classes as a starting point:
com.opensymphony.xwork2.validator.validators.ValidatorSupport
com.opensymphony.xwork2.validator.validators.FieldValidatorSupport
com.opensymphony.xwork2.validator.validators.RangeValidatorSupport
com.opensymphony.xwork2.validator.validators.RepopulateConversionErrorFieldValidatorSupport
The javadoc for ValidatorSupport
is
/** * Abstract implementation of the Validator interface suitable for subclassing. * * @author Jason Carreira * @author tm_jee * @author Martin Gilday */
Gee! Thanks a bunch!
Looking at a simple validator, RequiredStringValidator
, the code is mostly accepting annotation
parameters and the following.
public void validate(Object object) throws ValidationException { Object fieldValue = this.getFieldValue(getFieldName(), object); if (fieldValue == null) { addFieldError(getFieldName(), object); return; } if (fieldValue.getClass().isArray()) { Object[] values = (Object[]) fieldValue; for (Object value : values) { validateValue(object, value); } } else if (Collection.class.isAssignableFrom(fieldValue.getClass())) { Collection values = (Collection) fieldValue; for (Object value : values) { validateValue(object, value); } } else { validateValue(object, fieldValue); } } protected void validateValue(Object object, Object fieldValue) { try { setCurrentValue(fieldValue); if (fieldValue == null) { addFieldError(getFieldName(), object); return; } if (fieldValue instanceof String) { String stingValue = (String) fieldValue; if (trim) { stingValue = stingValue.trim(); } if (stingValue.length() == 0) { addFieldError(getFieldName(), object); } } else { addFieldError(getFieldName(), object); } } finally { setCurrentValue(null); } }
This is far more complicated than just checking if a string length is greater than zero. As well as checking a single value form field, it also checks if it's an array or collection and checks each entry. Multiple entry form fields are unusual enough they can be ignored by default and left to manual validation. If they should be considered, that should be handled by a base template class.
Further, the form field value must be manually converted to the expected type. It would be better if a validator defined what types it required and a framework converted it or logged if the linked annotation was applied to the wrong type. This also looks like a candidate for type parameters.
Complicated Custom Type Converters
At time of writing, the official documentation for type conversion is at the Core Developers Guide Type Conversion page and the section for 'Creating a Type Converter' starts with the following skeleton example.
public class MyConverter extends StrutsTypeConverter { public Object convertFromString(Map context, String[] values, Class toClass) { ..... } public String convertToString(Map context, Object o) { ..... } }
The first function can handle multiple form fields with the same name, not just one, and can be applied to
various types. If there are multiple values to be converted to an array, the function is called for each
entry instead. For single values, the value
parameter is an array of just one value. Multiple
values converted to a single value is so unusual, it's not worth complicating the normal case for it.
Multiple types processed by the same type converter is also strange and better handled by a type converter
for each type.
Applying a custom type converter using annotations is like the following.
@TypeConversion(converterClass = MyConverter.class)
Conclusion
In short, basic Struts validation works but if unusual forms or page layouts requires abandoning Struts UI tags or using custom type converters, validation design falls apart or becomes painful to code. Also, Annotation-based validation doesn't work correctly with ModelDriven, which is necessary for Post/Redirect/Post. This requires a redesign of annotation-based validation.
Next part
Continued in Alternate Design.