|
||||||||||
PREV PACKAGE NEXT PACKAGE | FRAMES NO FRAMES |
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. |
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
Extra Validations Performed for Purpose CERTIFY_READY_TO_USE
- Table has at least one source.
- Table's earliest date is not in the future.
1The particular validations performed depend on the validator used by this one to operate on those contained sources.
- Validation of contained sources.1
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.
|
||||||||||
PREV PACKAGE NEXT PACKAGE | FRAMES NO FRAMES |