Programming Thoughts
Struts 2 - Annotation-based Validation
Interceptor Redesign

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.

Multiple modes

The alternate design only supported two modes of conversion, described below.

  • No conversion - string fields with no converter annotations, though adjusters and non-conversion validators may apply. Such fields can also be half of manual converted string/non-string pairs.
  • Pair conversion - string/non-string pairs with converter annotation on the string field

The design objective of eliminating the need for string/non-string pairs creates more modes.

  • Auto conversion - non-string fields with converter annotations.
  • Default conversion - non-string fields with no converter annotations. A default converter for the field type will be used, logged as an error if none applies.
  • Manual parameter conversion - invokes a function on the form to validate and convert from request parameters not used for other modes. Always applies where the @ManualParameterConversion annotation is set.

Taking over the role of the Parameters interceptor means multiple request parameters using the same name must be considered. Everything is designed around single value parameters as multiple value parameters are rarely used. Nonetheless, multiple parameters should be received in string arrays or string collections for use in manual conversion, creating the set only mode. Also, it must handle File parameters, leading to the last mode.

  • Set only - string array fields or string collection fields with no converter annotation. Any annotations on these are ignored and manual code is expected to handle conversion and validation.
  • File - single, array, or collection fields of File type. These are set from a special kind parameter uploaded by FileUploadInterceptor. Any annotations on these are ignored and manual code is expected to handle validation.

Except where a @ManualParameterConversion annotation is used, how each mode applies is summarised below.

Field type Converter annotation Mode Notes
String No No conversion
String Yes Pair conversion
Non-string No Default conversion
Non-string Yes Auto conversion
String array   Set only Converters don't apply to arrays
String collection No Set only
String collection Yes Auto conversion
Non-string array   Manual parameter conversion Converters don't apply to arrays
Non-string collection No Default conversion Few default converters for collection types exist
Non-string collection Yes Auto conversion
File   File Set from a special parameter created by FileUploadInterceptor
File array   File Set from a special parameter created by FileUploadInterceptor
File collection   File Set from a special parameter created by FileUploadInterceptor

Note this differs from the standard Parameters interceptor for treatment of non-string arrays and collections as it applies the converter for each request parameter. The alternate redesign does not do this for design simplicity and to avoid confusion with collection converters, which parse a single value.

Viewer Actions

The alternate design considered only form processing Actions as standard practice expects those to read data into session and viewer Actions merely display data in session. Where viewer Actions read URL parameters, these are read and converted manually, which is the kind of boilerplate code to be reduced.

The redesigned interceptor could also set member fields of viewer Actions to receive parameters but viewer Actions have many member fields for displaying data and all these would be processed as form fields. The viewer Action would usually overwrite them but it's still a waste of time. Processing could be restricted to existing parameters but there's still the security risk of processing fake parameters interfering with display logic. The model of form processing Actions define the only parameters to receive, avoiding the risk, but viewer Actions usually aren't model driven. Making them model driven creates more boilerplate code, which should be avoided.

Thus, for Struts Commons 1.7.1, the AnnotationValidationInterceptor2 interceptor has a parameter to process only member fields that are annotated with policy annotations. This creates an obvious distinction between parameter receiving fields and display fields. As URL parameters are generated by page code, not entered by the user, validators and adjusters aren't needed, leaving only converters. Using default converters means no annotation and string fields don't have converters, so a new @FormField annotation is needed. For example, the refresh and recordNo fields receive URL parameters whilst the rest are display fields.

@FormField private boolean refresh; @FormField private Integer recordNo; private PrimeMinisterForm form; private int listSize; private LocaleSelectBoxDisplay localeSelectBoxDisplay; private PartySelectBoxDisplay partySelectBoxDisplay;

Alas, even URL parameters can be buggy and fail to convert. There's no point writing error messages to Action errors as the user can't edit the parameters, so the message should be logged at warning level instead. Normally, that should be the interceptor's own logger but Struts design is built around Actions, so if a page is failing, you consider switching on the logging of the Action, not the interceptors. That is, interceptors logging an Action's URL parameters should use the Action's logging category. That needs the Action to present an interface stating its logger. The interface is below.

import org.apache.logging.log4j.Logger; public interface LoggingAware { public Logger getLogger(); }

This creates a dichotomy between form processing Actions and viewer Actions for default message target where the former writes error messages and the latter logs warnings. The solution is interceptor configuration defines what the default target is. The full list of message types is below with converter and validator annotations defaulting to DEFAULT.

public enum MessageType {DEFAULT, ERROR, FIELD, IGNORE, LOG_DEBUG, LOG_ERROR, LOG_INFO, LOG_TRACE, LOG_WARN, MESSAGE, WARNING}

The standard struts.xml configuration for viewer Actions is.

<!-- Stack for actions that display data --> <interceptor-stack name="ViewStack"> ... <interceptor-ref name="annotationValidation2"> <param name="defaultMessageType">LOG_WARN</param> <param name="excludeMethods">input,back,cancel,browse</param> <param name="ignoreNonAnnotatedFields">true</param> </interceptor-ref> ... </interceptor-ref> </interceptor-stack>

Next part

Continued in Manual Parameter Conversion and Validation.