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.