Details
Description
Allowing lists to be validated helps a great deal but the code that is supplied
with the commons-validator 1.0 could be simplified and enhanced. There are two
features that it would be nice to have:
1) When the IndexedListProperty is used it would be nice if all the fields in
the list were validated, i.e. the validation did not stop at the first error.
This would need a different loop mechanism and the key to be set to the fully
qualified path, e.g list[0].value and not list[].value.
2) It would be very nice if the IndexedListProperty could be nested, e.g. the
size for all the doors on all the rooms must be greater than n. I am not
sure of the syntax in the configuration file that makes sense but offer
list1.list2.list3. as a suggestion. The PropertyUtils could then be called
on the bean using the "list1" property to get the first list and then called
on each of the returned objects using the "list2" property, etc. So, if you
had two rooms, the first with one door, the second with two and the
validation.xml Field looked like:
<field
property="size"
indexedListProperty="rooms.doors"
depends="min">
<arg0 key="error.door.size"/>
<var>
<var-name>min</var-name>
<var-value>${n}</var-value>
</var>
</field>
The validator waould in effect be called for:
rooms[0].doors[0].size
rooms[1].doors[0].size
rooms[1].doors[1].size
The code below provides an example of how this could be done. It uses the
IndexedProperty to pass information down a reentrant stack because I did not
want to change the parameters to the Validator.validate method but that would
obviously be safer. Also, by putting this code snipit in place, all the other
isIndexed code could be removed so you don't have to loop twice (as it does in
version 1.0).
Thanks...Peter
//////////////////////////////////////////////////////////////////////////////
// These snippits are out of the Validator class. There is a one line
// change to validate, one new method and one tidied up method.
// The code has been tested and works but you may not like the way it
// changes the BEAN_KEY and uses the IndexProperty.
//////////////////////////////////////////////////////////////////////////////
/**
- Performs validations based on the configured resources.
- Needed as we cannot access (override) the private methods.
* - @return The <code>Map</code> returned uses the property
- of the <code>Field</code> for the key and the value
- is the number of error the field had.
*/
public ValidatorResults validate() throws ValidatorException
{
ValidatorResults results = new ValidatorResults();
Locale locale = null;
if (hResources.containsKey(LOCALE_KEY))
{ locale = (Locale)hResources.get(LOCALE_KEY); }hResources.put(VALIDATOR_KEY, this);
if (locale == null)
{ locale = Locale.getDefault(); } Form form = null;
if (resources == null)
if ((form = resources.get(locale, formName)) != null)
{
for (Iterator i = form.getFields().iterator(); i.hasNext(); )
{
Field field = (Field)i.next();
if (field.getPage() <= page)
}
}
return results;
}
/**
- Validate field nested. This method handles nested list validation
- of the form IndexedListProperty = list1.list2.list3. It gets all the
- instances in list1 off the BEAN_KEY (root bean) using the PropertyUtils
- then gets all the list2 entries of all the list1 objects, then all the
- list3 objects of the list2 objects, etc. The result is that the
- validateField method is called on all the list3 objects with the property
- as it was but the BEAN_KEY set to the current list3 object and the
- Field key set to the fully qualified key (e.g. list1[0].list2[0].list3
[0]).
* - @param field See Validator.validateField
- @param allResults See Validator.validateField
*/
private void validateFieldNested (Field field, ValidatorResults allResults)
throws ValidatorException
{
// Does it have an IndexedList property?
String indexedList = field.getIndexedListProperty();
if (null != indexedList && 0 < indexedList.length())
{
// Is it nested?
String nestName = indexedList;
String restName = null;
int nestOffset = indexedList.indexOf(".");
if (-1 != nestOffset) { nestName = indexedList.substring(0, nestOffset); restName = indexedList.substring(nestOffset + 1); }
// Build the field object based on the nesting.
Field indexedField = (Field)field.clone();
indexedField.setIndexedListProperty(restName);
// The keyBase is a local copy of the IndexedProperty so that
// the appropriate instance ([n]) can be added inside the
// loop. The IndexedProperty is not currently used by the
// validator framework and so we use it to pass the revised
// nesting level through the reenterant code.
String keyBase = field.getIndexedProperty();
if (null == keyBase)
else
{ keyBase += "." + nestName; } // Save the current BEAN_KEY, the object that the validator
// is working on, so we can reset it as we pop out of the
// reenterant loop.
Object oldBean = hResources.get(BEAN_KEY);
// Call for each of the objects at this level
Object[] list = getIndexedList(oldBean, nestName);
for (int i = 0; i < list.length; i++)
// Put the old BEAN_KEY back as we pop the reentrancy stack.
hResources.put(BEAN_KEY, oldBean);
return;
}
// If it is not nested (actually IndexedList) use the normal method.
validateField(field, allResults);
}
/**
- Get an array of instances for the supplied property, whether it is a
- collection or an array. This is just a tidy up of an existing method
- in the Validator class.
* - @param bean The bean that contains the list or array.
- @param property The name of the property that will return a list
- or array on the supplied bean.
- @return Object[] An array of objects that were retrieved from the
- supplied property.
*/
private Object[] getIndexedList(Object bean, String property)
{
Object oIndexed;
try { oIndexed = PropertyUtils.getProperty(bean, property); }catch (Exception e)
{ log.error("in validateFieldNested", e); return null; }
Object indexedList[] = new Object[0];
if (oIndexed instanceof Collection)
{ indexedList = ((Collection)oIndexed).toArray(); }else if (oIndexed.getClass().isArray())
{ indexedList = (Object[]) oIndexed; }return indexedList;
}