Programming Thoughts
Struts 2 - Annotation-based Validation
Manual Parameter Conversion and Validation
Improving a fix to a Struts 2 feature
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 but it has design limitations. This article considers a redesign to overcome them.
Need for manual conversion and validation
A simple form, such as below, has multiple fields combine into a single value but annotations only apply to their own field. It's possible to redesign custom converters to read multiple fields but this would make them much more complex to write for a few cases.

Figure 1: Facebook birthday form
Manual parameter conversion
Form fields in the alternate design require an explicit converter annotation to avoid clashing with existing,
manual conversion. Rewriting forms to remove the formatted field of a pair removes almost all such manual
conversion, removing the need for an explicit annotation. Form fields of a recognised type but no conversion
annotation can use a default converter. Where manual conversion of a recognised type is needed, a new
@ManualParameterConversion
annotation can skip automated conversion. Thus manual parameter
conversion is needed for unrecognised data types with no custom converter, and where explicitly marked as
manual.
The definition of a form's manual conversion function becomes the following.
public ManualParameterConversionResults manualParameterConvert(Map<String,String> unprocessedParameters, ValidationAware validationAware, TextProvider textProvider) public class ManualParameterConversionResults implements Serializable { private Set<String> processedParameterNames = new HashSet<>(); private Set<String> failedParameterNames = new HashSet<>(); public Set<String> getProcessedParameterNames() { return processedParameterNames; } public Set<String> getFailedParameterNames() { return failedParameterNames; } }
Failed request parameters are added to conversion errors, so they can be redisplayed.
This appears in the form's class, not the Action's class. This is incongruous with Strut's Action-centric
design, especially as the validate
function applies to Actions, but it should pair with form
formatting functions. Form formatting applies to view Actions, not form processing Actions, and they can
display multiple forms, so manual form formatting functions must be on the form and, thus, the manual
conversion function with it.
View helpers
Abandoning reliance of Struts UI tags means complex HTML tags, such as SELECT, use view helpers in the view Action, which read form field values. They typically only present allowed values, so any conversion failure is a hacked value, not entered by a genuine user. However, manual parameter conversion forms may combine and convert multiple request parameters into a single value, such as seen in figure 1 above. If a combination, such as 31st of February, is rejected, the rejected values exist only as conversion errors, not a form field value.
Action code can access such conversion errors and example code is shown below.
formattedDateComponents = DateOfBirth2ManualParameterConversionForm.formatDate(value); actionContext = ActionContext.getContext(); conversionErrors = actionContext.getConversionErrors(); dayConversionData = conversionErrors.get("dateOfBirth2Form.day"); if (dayConversionData != null) { formattedDateComponents.setDay((String)dayConversionData.getValue()); } … dayOfMonthSelectBoxDisplay = new DayOfMonthSelectBoxDisplay(); dayOfMonthSelectBoxDisplay.setSelectedFormattedValue(formattedDateComponents.getDay());
This needs to be made easier, which can be done by moving conversion error checking code into the view helper, such as below.
public void setSelectedFormattedValueWithConversionError(String value, String conversionErrorName) { ActionContext actionContext; ConversionData conversionData; Map<String, ConversionData> conversionErrors; String rejectedValue; rejectedValue = null; actionContext = ActionContext.getContext(); if (actionContext != null) { conversionErrors = actionContext.getConversionErrors(); conversionData = conversionErrors.get(conversionErrorName); if (conversionData != null) { rejectedValue = (String)conversionData.getValue(); setSelectedFormattedValue(rejectedValue); } } if (rejectedValue == null) { setSelectedFormattedValue(value); } }
This turns client code into the following.
dayOfMonthSelectBoxDisplay = new DayOfMonthSelectBoxDisplay(); dayOfMonthSelectBoxDisplay.setSelectedFormattedValueWithConversionError(dateOfBirth2Components.getDay(), "dateOfBirth2Form.day");
Next part
Continued in Redesign Example.