Uploaded image for project: 'Commons Validator'
  1. Commons Validator
  2. VALIDATOR-186

Enhance the IndexedListProperty to handle nested lists.

    XMLWordPrintableJSON

Details

    • Improvement
    • Status: Resolved
    • Minor
    • Resolution: Abandoned
    • Nightly Builds
    • 2.0
    • Framework
    • None
    • Operating System: All
      Platform: All

    • 16394

    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)

      { throw new ValidatorException("Resources not defined for Validator"); }

      if ((form = resources.get(locale, formName)) != null)
      {
      for (Iterator i = form.getFields().iterator(); i.hasNext(); )
      {
      Field field = (Field)i.next();
      if (field.getPage() <= page)

      { // ************************************************* // This is the only line that changed in this method. // ************************************************* validateFieldNested(field, results); // ************************************************* }

      }
      }
      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)

      { keyBase = nestName; }

      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++)

      { // Set the BEAN_KEY to the current object hResources.put(BEAN_KEY, list[i]); // Use the indexedProperty to pass the current level into // the next level so that it does not have to do a load of // parsing and replacement when it builds the key. The key // is used to make the field error (if any) unique. indexedField.setIndexedProperty(keyBase + "[" + i + "]"); indexedField.setKey(indexedField.getIndexedProperty() + "." + indexedField.getProperty()); // Call ourselves to handle the next (if any) level of // nesting. validateFieldNested(indexedField, allResults); }

      // 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;

      }

      Attachments

        Activity

          People

            Unassigned Unassigned
            peteroldershaw@comcast.net Peter Oldershaw
            Votes:
            1 Vote for this issue
            Watchers:
            1 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved: