Programming Thoughts
Struts 2 - Annotation-based Validation
Existing Code

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.

Compatibility with existing view helpers

The view helpers help format SELECT HTML tags and are a holdover from Struts 1 code. Though only applicable to the author, they're described here as a case study in compatibility. Such helpers are derived from a base, Template Method class using a generic type and the key methods of this are below.

public abstract class SingleSelectBoxDisplay<T> { protected abstract String getText(T item); protected abstract String getValue(T item); public void setModel(Collection<T> model) { SelectBoxItemDisplay<T> formattedItem; String value, text; try { formattedModel.clear(); if (model != null) { for (T item: model) { if (isAllowed(item)) { value = getValue(item); text = getText(item); formattedItem = new SelectBoxItemDisplay<T>(value, text, item); formattedModel.add(formattedItem); } } } Collections.sort(formattedModel, getSortComparator()); addItems(formattedModel); if (hasBlankValue()) { formattedModel.add(0, new SelectBoxItemDisplay<T>("", getBlankText(), null)); } } catch (Exception e) { LOG.error("Setup of JSP view helper failed", e); } } public void setSelectedValue(String value) { SelectBoxItemDisplay<T> item; ListIterator<SelectBoxItemDisplay<T>> iter; boolean found; // Deselect existing, selected item. if (selectedIndex > -1) { item = formattedModel.get(selectedIndex); item.setSelected(false); } selectedIndex = -1; if (value != null && value.length() > 0) { // Find item and set it as selected iter = formattedModel.listIterator(); found = false; while (iter.hasNext() && !found) { item = iter.next(); if (item.getValue().equals(value)) { selectedIndex = iter.previousIndex(); item.setSelected(true); found = true; } } } } }

This refers to a POJO class, which describes particular attribute values of OPTION HTML tags and its member fields shown below.

public class SelectBoxItemDisplay<T> { private String value; private String text; private T data; private boolean selected;

Whereas a helper expects a collection of records to be formatted and selection based on a formatted, string field value, the new versions select on an unformatted field value. Obviously, a new version is required that uses an additional generic type. Starting with the item class.

public class SelectBoxItemDisplay2<K,T> { private String value; private String text; private K key; private T data; private boolean selected;

The setSelectedValue function of the helper is changed to accept the use the unformatted field value.

public abstract class SingleSelectBoxDisplay2<K,T> { protected abstract K getKey(T item); protected abstract String getText(T item); protected abstract String getValue(T itemK key); public void setModel(Collection<T> model) { SelectBoxItemDisplay2<K,T> formattedItem; K key; String value, text; try { formattedModel.clear(); if (model != null) { for (T item: model) { if (isAllowed(item)) { key = getKey(item); value = getValue(itemkey); text = getText(item); formattedItem = new SelectBoxItemDisplay2<T>(value, text, key, item); formattedModel.add(formattedItem); } } } Collections.sort(formattedModel, getSortComparator()); addItems(formattedModel); if (hasBlankValue()) { formattedModel.add(0, new SelectBoxItemDisplay2<K,T>("", getBlankText(), null, null)); } } catch (Exception e) { LOG.error("Setup of JSP view helper failed", e); } } public void setSelectedValue(String valueK key) { SelectBoxItemDisplay2<K,T> item; ListIterator<SelectBoxItemDisplay2<K,T>> iter; boolean found; // Deselect existing, selected item. if (selectedIndex > -1) { item = formattedModel.get(selectedIndex); item.setSelected(false); } selectedIndex = -1; if (value != null && value.length() > 0key != null) { // Find item and set it as selected iter = formattedModel.listIterator(); found = false; while (iter.hasNext() && !found) { item = iter.next(); if (item.getValue().equals(value)key.equals(item.getValue())) { selectedIndex = iter.previousIndex(); item.setSelected(true); found = true; } } } } }

Compatibility with existing forms

This is easy to solve. As existing forms must be rewritten to set unformatted field and not write formatted fields themselves, they can also extend a new interface, FormattableForm, and the form formatter interceptor will ignore any form that does not. Manual calls to formatForms() will still work.

This could be used for the existing form formatter interceptor but it doesn't fix the other flaw. The new interceptor will be described in a later article.

Next part

Continued in Workflow Redesign.