Package edu.nrao.sss.validation

Object validation support.

See:
          Description

Interface Summary
Validator A validator of objects.
 

Class Summary
AbstractValidator<T> Partial implementation of a Validator.
DataNotEnteredValidation<T> A validation for empty or unitialized properties.
Validation<T> An individual test run by a Validator.
ValidationFailure Information about the failure of an attempt to validate an object.
ValidationManager The entry point for validating model objects.
 

Enum Summary
FailureSeverity An indication of how severe a ValidationFailure is.
ValidationPurpose The reason why an object is to be validated.
 

Exception Summary
ValidationException Thrown when one or more validation failures are detected.
 

Package edu.nrao.sss.validation Description

Object validation support.

Intended Use
This validation package is meant to be used mainly for the kind of validation that cannot be performed on each and every mutator method of a target object. It is not intended for use in every validation situation. For example, the Distance class has a set method that takes a String argument that is parsed into a quantity and units. That method will throw an exception immediately if the incoming text would not result in a valid Distance object. Furthermore, the Distance object will never allow itself to be in an illegal state, so there is no need to use it as a target of this validation package. An exception to this statement would be if a particular application wanted to restrict the allowable range of distance values. Using this package to do such a validation would be appropriate.

A more complex object, such as a Source, has many, many properties. When property A is in state A1, it may be illegal for property B to be in state B7. It may not be appropriate, though, to complain as soon as B is put into state B7, because the user could be in the process doing a lot of tasks with the source, one of which is to change the state of A. In this situation triggering a validation on the whole object at some later point in time is a better solution. That is the main purpose of this package.

(The ValidationException is available, though, for mutator methods to throw, if that seems reasonable for the methods of a particular class.)

Typical Usage Pattern
Most clients will interact with the ValidationManager and not directly with the Validators themselves. The ValidationManager's main method is validate(target, purpose), which returns true if the target passed all validations for the given purpose. If this method returns false, the getFailures method will return a list containing the one or more individual Validations that failed. Example:

   ValidationManager myMgr = new ValidationManager();
   
   if (!myMgr.validate(newCar, ValidationPurpose.CERTIFY_READY_TO_USE))
     communicate(myMgr.getFailures());

How to Write a Validator
The easiest way to write a new validator class is to subclass the AbstractValidator class. This class is parameterized to operate on validation targets of a specific class and its subclasses. What follows are code snippets from an actual implementation of a validator and explanatory text about those snippets.

Class Documentation
This, of course, is not mandatory, but it is highly recommended that the validator class let clients know something about the validations performed. Here is an example:

A validator of source look-up tables.

Validations Performed for All Purposes

  1. Table has at least one source.
  2. Table's earliest date is not in the future.

Extra Validations Performed for Purpose CERTIFY_READY_TO_USE
  1. Validation of contained sources.1
1The particular validations performed depend on the validator used by this one to operate on those contained sources.

Declaration and Construction

   public class SourceLookupTableValidator
     extends AbstractValidator<SourceLookupTable>
   {
     private Validator sourceValidator;

     /** Creates a new instance. */
     public SourceLookupTableValidator()
     {
       super(SourceLookupTableValidator.class.getName(), SourceLookupTable.class);
     }
     ...

The class extends the AbstractValidator and declares that it is a validator of SourceLookupTable via the use of java's generics feature. It next declares an internal validator; this is completely optional, but occurs frequently when the target class (SourceLookupTable) contains other classes that need validation (in this case, class Source). Finally, the constructor of this validator calls the constructor of its super class and tells it the name of this validator (often, but not necessarily, the name of the validator's class) and the class of the objects to be validate. This last item is important because the Validator interface does not use generics and its validate method takes an argument of type Object. The validator will inspect the target of the validation to ensure it is of this class, or one of its subclasses or implementations.

Abstract Method makeValidationList
The job of this abstract method of the super class is to furnish a list of individual validations to be performed on the validation target. Each validation is encapsulated in a Validation class (more to follow on this class, below). It is permissable for this method to return an empty list, but not null. Why would the list of validations be empty? This occurs when the only validation performed on a container object is validation of the container's components. The validation of the components is typically left to component validators, as was seen above. In the example below, two validations are provided:

   ...
   protected List<Validation<SourceLookupTable>>
     makeValidationList(ValidationPurpose purpose)
   {
     List<Validation<SourceLookupTable>> validations =
       new ArrayList<Validation<SourceLookupTable>>();
     
     validations.add(new NotEmptyValidation (this, purpose));
     validations.add(new FirstDateValidation(this, purpose));
     
     return validations; 
   }
   ...

Method validate
The abstract super class provides a concrete implementation of this method that calls the individual validations described above. If your specific validator uses no component validators, you probably will not override this method. If it does, though, you will override it, call super.validate(), and call your component validators, as shown below:

   ...
   protected void validate()
   {
     super.validate();
     
     //Validate the entries
     if (purpose == ValidationPurpose.CERTIFY_READY_TO_USE)
     {
       Validator sourceVal = getSourceValidator();
       
       for (Date key : target.getKeySet())
         failures.addAll(sourceVal.validate(target.get(key), purpose));
     }
   }
   ...

Handling Component Validators
If a validator uses one or more component validators, it is a good idea to allow, but not force, clients to set these validators themselves. Look at this implementation (the methods' comments are not shown):

   ...
   public void setSourceValidator(Validator newValidator)
   {
     sourceValidator = newValidator;
   }
   ...
   public Validator getSourceValidator()
   {
     Validator validator = sourceValidator;
     
     if (validator == null)
     {
       if (manager != null)
         validator = manager.getValidator(Source.class);
     
       if (validator == null)
         validator = new SourceValidator();
     }
     
     return validator;
   }
   ...

The set method just holds a reference to the incoming validator (this should be explained in the method's comments), even if that value is null. The get method will preferentially use a client-supplied validator. However, if one is not available, it will see if it is working on behalf of a ValidationManager. If it is, it will ask the manager for a validator for the component target class. If there is no manager, or if the manager cannot supply a validator, a default is created and returned. Note that we do not save the manager's validator or the default in the instance variable. One reason this is done is that this validator could wind up working for a different validation manager, and we should not treat a validator fetched from the old manager the same way we would treat one specifically set by a client.

How to Write a Validation
Remember from above that a Validation class encapsulates a single test on an object that may require many different tests. Custom validation classes must extend the abstract class Validation. What follows are code snippets from an actual implementation of a validator and explanatory text about those snippets.

Declaration and Construction

   class NotEmptyValidation extends Validation<SourceLookupTable>
   {
     protected NotEmptyValidation(
                 AbstractValidator<SourceLookupTable> validationContainer,
                 ValidationPurpose                    reasonForValidation)
     {
       super(validationContainer, reasonForValidation);
       
       severity = 
         (reasonForValidation == ValidationPurpose.CERTIFY_READY_TO_USE) ?
           FailureSeverity.ERROR : FailureSeverity.WARNING;
     }
     ...

The class extends Validation and declares that it is a validation that operations on objects of type SourceLookupTable. It calls the super class's constructor, and the super class maintains references to the two parameters. These variables are protected and, thus, available to this subclass. The constructor then sets the severity level of this validation, which in this case is valued differently, depending on the purpose of the validation.

The Test
The abstract method passesTest is the place for the logic that determines whether or not the target object passes or fails this particular validation. Our example uses a very simple test, namely whether or not the target table is empty:

     ...
     protected boolean passesTest()
     {
       return target.size() > 0;
     }
     ...

Note that the logic in the test above makes use of the target instance variable held in the super class.

The Debug Message
The validation class holds two kinds of messages: one for communication with an external user, and one for internal programming staff to use for debugging purposes. These two messages could, of course, be the same. Typically, though, the display message is much more verbose, and less technical, than the debug message.

     ...
     protected String debugMessage()
     {
       StringBuilder buff =
         new StringBuilder("No sources contained in table ");
       
       buff.append(target.getName());
       
       return buff.toString();
     }
     ...

Again, the logic in the method above makes use of the target instance variable.

The Display Message

     ...
     protected String displayMessage()
     {
       StringBuilder buff = new StringBuilder("The source lookup table '");
       
       buff.append(target.getName()).append("' contains no entries.");
 
       if (purpose == ValidationPurpose.CERTIFY_READY_TO_USE)
       {
         buff.append("  You may not use this table until after you have ");
         buff.append("populated it with at least one source.");
       }
       else
       {
         buff.append("  While you may leave the table in this state for ");
         buff.append("now, you will need to add at least one source to it ");
         buff.append("before attempting to use it.");
       }
 
       return buff.toString();
     }
     ...

You can see that this message is wordier than the debug message. Notice that it also changes the message a little, depending on the purpose of the validation.

It might be nice to externalize these messages. If we do so, we'll need a mechanism for using replaceable parameters. It would also be nice to have a mechanism for conditional logic, as seen above.

Possible Future Enhancement
The classes in this package have been written in a way that will allow us to move to externally specified tests and messages without an enormous amount of pain. (There will still, though, be work to do.) If we think it is useful, a validator could be configured from an XML file that looked something (but not exactly!) like this:

   <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
   <validator name="edu.nrao.sss.model.project.ProjectValidator">
   
     <validations purpose="CERTIFY_READY_TO_USE">
       <!-- Validations are run in the order entered below. -->
       <validation name="...">
         <targetClass>edu.nrao.sss.model.project.Project</targetClass>
         <severity>ERROR</severity>
         <!-- We'd need a to be able to take $...$ and turn into method call. -->
         <displayMsg>Blah blah $project.abc.xyz$ blah blah.</displayMsg>
         <debugMsg>...</debugMsg>
         <test>Some kind of syntax here.  Actual java code?</test>
       </validation>
       
       <validation name="...">
         ...
       </validation>
     </validations>
     
     <validations purpose="SAVE_TO_REPOSITORY">
       ...
     </validations>
     <componentValidators purpose="CERTIFY_READY_TO_USE">
       <validator name="edu.nrao.sss.model.project.ProgramBlockValidator"/>
       <validator .../>
       <!-- Would / could we fully define component validators here? -->
     </componentValidators>
     
     <componentValidators purpose="SAVE_TO_REPOSITORY">
       ...
     </componentValidators>
   </validator>

A file such as the above could dynamically configure an existing class, dynamically create a class, or be converted to java code (though why not just write the code?). We could scale down the above and have only some of the features, such as the messages, externally specifiable.

Class Diagrams

  1. Validation Package
  2. Validation Failure

Since:
2007-02-05
Author:
David M. Harland


Copyright © 2009. All Rights Reserved.