Programming Thoughts
Struts 2 - Annotation-based Validation
Evaluation
Actually reducing Struts 2's arcane configuration
Struts 2 is a popular MVC framework for Java-based web applications but its annotation-based validation doesn't properly work. So, an alternative was created. It should be evaluated against its design objectives.
Design Objectives
The Alternate Annotations page stated the objectives of the alternate design. Each will be evaluated.
Objective | Notes | |
---|---|---|
Interceptor based | ✓ | A new interceptor is declared in struts.xml and a new entry for any interceptor
stack for Actions that receive forms. |
Interceptor should be extensible | ✓ | This can only be judged by future extension attempts but the core algorithm calls many, protected functions. |
Actual adjustment, validation and conversion should be separate classes | ✓ | |
Annotation should use defaults where possible | ✓ | The following is a working example. Perhaps the repeated message is the only excess.
@Required(message= "Limit must be number between 10 and 500") @IntegerConversion(message= "Limit must be number between 10 and 500") @IntegerRange(min=10, max=500, message= "Limit must be number between 10 and 500") |
Policies don't apply if the form field value isn't set. | ✓ | |
Receiving form fields are character strings | ✓ | Pages display the original form fields whilst Actions access the converted values. |
Converters should have a reverse conversion function for form formatting | ✓ | These are used in form formatting. |
Form formatting should be automatic | ✕ | This is incompatible with existing code and the following code must be used.
formatForms(); |
Compatible with manual conversion and validation | ✓ | Form fields without annotations are ignored by the interceptor and the Action's
validate function has not gone away. |
Recognise ModelDriven | ✓ | As per standard behaviour, if the Action implements ModelDriven , the interceptor
uses its model as the form, otherwise the Action is the form. |
Annotation can configure that messages write to field errors, action errors or even action messages | ✓ | All annotations have a messageType parameter for this. |
Abandon reliance on Struts UI tags | ✓ | |
Configuration only in the form field annotations | ✓ | struts.xml for declaring the new interceptor and message resource files, if
annotations use message keys, are the only separate files. |
Custom conversion and validator annotations should be able to use code in the same form | ✓ | Custom annotations use class references to obtain custom validators and converters, which can even be inner classes. It would be better if lambda expressions could be used but annotations can't use them. |
A block of code in converters and validators should be able to report one of many error messages | ✓ |
Implementation Notes
- Hardcoded policies - As described in Alternate Interceptor, the set of recognised annotations is hardcoded. Although custom policies are easily written, client supplied annotations would still be better. This will be the subject of a future article.
Other Designs
Implementation of this idea grants better understanding of Struts 2 and consideration of other design ideas. First, a restatement of this idea, followed by other ideas.
- String form fields - request parameters are received by string fields instead of those of the expected data type and a custom interceptor converts them in another set of fields.
- Replacement data tags - use existing annotation-based conversion and validation but use custom tags
instead of data tags that trip over rejected field values, such as
s:date
- Replacement parameters interceptor - whereas the standard parameters interceptor sets string form fields with no attempt at conversion, it does not ignore non-string form fields the alternate validator will convert, creating a conflicting set of conversion errors.
- Custom tag checks form field annotations - use replacement annotation-based conversion and validation but use a custom tag to read form field values. If the value is a string, it's an expected string, or a rejected user input, otherwise the form field's annotation are read to use its converter to format.
- Interceptor formats values to Value Stack - use replacement annotation-based conversion and validation but a form formatting interceptor places a formatted version of each form on top of the Value Stack, hiding the unformatted form.
These can be compared, along with manual validation. Existing code includes unannotated forms using formatted/unformatted field pairs, and view helpers reading formatted form field values.
Feature | Manual |
String fields |
Custom data tags |
Replacement params |
Custom tag uses converter |
Formatted on Value Stack |
---|---|---|---|---|---|---|
Won't break existing code | ✓ | ✓1 | ✕ | ✕ | ✕ | ✕ |
Avoids formatted/unformatted field pairs | ✕ | ✕ | ✓ | ✓ | ✓ | ✓ |
Allows manual conversion and validation | ✓ | ✓ | ✕ | ✕ | ✕ | ✕ |
Standard/custom form field annotations | ✕ | C | S | C | C | C |
Compatible with ModelDriven | ✓ | ✓ | ✕ | ✓ | ✓ | ✓ |
Can write to action errors | ✓ | ✓ | ✕ | ✓ | ✓ | ✓ |
Avoids custom annotation reading interceptor | ✓ | ✕ | ✓ | ✕ | ✕ | ✕ |
Avoids replacement parameter interceptor | ✓ | ✓ | ✓ | ✕ | ✕ | ✕ |
Avoids custom tags | ✓ | ✓ | ✕ | ✓ | ✕ | ✓ |
Automated form formatting | ✕ | ✕2 | ✶3 | ✶3 | ✶3 | ✓ |
1 Automated form formatting cannot be used.
2 Manual call to formatForms()
.
3 Some data types, particularly dates, will need tag parameters.
Redesigning forms to not use formatted/unformatted field pairs and place formatted form fields on the Value Stack is clearly the best option but breaking existing code and incompatibility with manual validation is a deal breaker. This will be the subject of a future article.
Next part
Continued in Example.