Programming Thoughts
Struts 2 - Annotation-based Validation
Alternate Validators (Collections)

Actually reducing Struts 2's arcane configuration

Struts 2 is a popular MVC framework for Java-based web applications but can suffer from tedious configuration. The annotation Java language feature can reduce this but the out-of-the-box annotation-based validation conflicts with making redirect after post work and falls apart with page designs the Struts UI tags can't create. Annotation-based validation must be redesigned.

Data Type Checking for Collections

Typically, form fields are single values, such as strings or numbers, but a few are parsed into multiple values, such as comma separated integers. This is a problem for recipient field type checking as type erasure removes the generic type from, for example, Collection<Integer>. Type checking can know converter expects the recipient field to be a collection but not specifically a collection of integers.

Thus, another converter and post-conversion validator interface is needed and the getRecipientClass defines the expected type of collection entries whereas the recipientFieldClass parameter of the convert function is the type of collection.

public interface CollectionConverter<A extends Annotation,T> extends Validator<A> { public ConversionResult<T> convert(String formValue, Class<?> recipientFieldClass, Class<T> recipientClass); public String format(Collection<T> unformattedValues); public Class<T> getRecipientClass(); public String getRecipientFieldName(); public boolean getProcessNoValue(); } public interface CollectionPostConversionValidator<A extends Annotation> extends Validator<A> { public Class<T> getRecipientClass(); public boolean getShortCircuit(); public boolean getProcessNoValue(); public boolean validate(Collection<T> formValue); }

It may seem there's still type erasure of the recipient field but it's actually stored and available from Field.getGenericType(). Code to check the recipient field's type is more complicated and shown below.

public static boolean checkCollectionRecipientDataType(Field recipientField, Class<T> recipientClass) { Class<T> entryClass; entryClass = getTypeFromCollectionField(recipientField); return checkFieldClass(recipientClass, entryClass); } public static <T> Class<T> getTypeFromCollectionField(Field recipientField) { Class<?> recipientFieldClass; Type[] recipientEntryTypes; recipientFieldClass = recipientField.getType(); if (!Collection.class.isAssignableFrom(recipientFieldClass)) { return null; } recipientEntryTypes = ((ParameterizedType)recipientField.getGenericType()).getActualTypeArguments(); if (recipientEntryTypes.length != 1) { return null; } return (Class<T>)recipientEntryTypes[0]; }

Validator Bases Classes

The base classes are similar to that for single value.

public abstract class AbstractCollectionConverterSupport<A extends Annotation,T> extends AbstractValidatorSupport<A> implements CollectionConverter<A,T> { protected <T> Collection<T> makeCollectionForRecipient(Class<?> recipientClass) { return ValidatorLibrary.makeCollectionForRecipient(recipientClass); } @Override public String format(Object unformattedValue) { return null; } } public abstract class AbstractCollectionPostConversionValidatorSupport<A extends Annotation,T> extends AbstractValidatorSupport<A> implements CollectionPostConversionValidator<A,T> { }

Different Collection Types

The makeCollectionForRecipient creates an empty collection, depending on the recipient field, to receive the converted values.

public static <T> Collection<T> makeCollectionForRecipient(Class<?> recipientClass) { if (BlockingDeque.class.isAssignableFrom(recipientClass)) { return new LinkedBlockingDeque<>(); } else if (BlockingQueue.class.isAssignableFrom(recipientClass)) { return new LinkedBlockingQueue<>(); } else if (Deque.class.isAssignableFrom(recipientClass)) { return new LinkedList<>(); } else if (List.class.isAssignableFrom(recipientClass)) { return new ArrayList<>(); } else if (NavigableSet.class.isAssignableFrom(recipientClass)) { return new TreeSet<>(); } else if (Queue.class.isAssignableFrom(recipientClass)) { return new LinkedList<>(); } else if (SortedSet.class.isAssignableFrom(recipientClass)) { return new TreeSet<>(); } else if (Set.class.isAssignableFrom(recipientClass)) { return new HashSet<>(); } else if (TransferQueue.class.isAssignableFrom(recipientClass)) { return new LinkedTransferQueue<>(); } else { return new ArrayList<>(); } }

Next part

Continued in Form Formatting.