There are a number of facets to validation.
Let's begin by describing one aspect of the GUI. In the user interface,
when an object is displayed, it can be edited and saved (updated).
The user interface code that lays out the form conveniently inserts
a number of panels called validation panels. These panels are normally
empty and thus not apparent in the user interface.
For an object with three atomic fields, say a Speaker with a name
(StringEO), a title (StringEO), and a biography (TextEO),
four validation panels will be setup: one for each field, and one
for the instance.
Each atomic type asserts its own validation requirements. Let's take
a look at USZipCode for a moment:
-
private static String omit = "- ";
private static String valid = "0123456789";
public int validate()
{
String value = SimpleParser.parseValue(omit, valid, _value);
if (value == null - - value.length() != 5 && value.length() != 9)
return invalid();
return 0;
}
private int invalid()
{
fireValidationException("Invalid zip code: "+_value);
return 1;
}
Here we see that USZipCode implements the validate()
method, which the framework will invoke at the proper time. The return
value, an int, specifies the number of "errors"
encountered. So a return value of 0 implies that validation
passed.
There's a publish/subscribe pattern here, where the model object does
not have a reference to its corresponding view (or editor) object.
Yet, somehow the UI must display the validation error message. So
the model object publishes the message *Invalid zip code...* when
a validation error is encountered. The validation panel that corresponds
to a field of this type in the user interface is a listener, and receives
the messages, and properly displays the error:
Figure 9.1:
Zip Code Validation
The way you should view this form is that each field has its own validation
panel embedded in the form, just above it. Most of the time these
panels are dormant. When a validation message is published, it will
appear in the appropriate place. Each panel listens to the value of
its corresponding field.
The method validate() will be called on atomic objects when
a user tabs out of the editor (when the field loses focus) and again
when the user attempts to save the object.
So validation on atomic types such as such as USZipCode, SSN,
and USPhone is implemented in this way.
Some validation logic is really more an artifact of how the atomic
type's editor chose to allow you to enter the information, and so
is implemented directly on the editor. Let's take a look at PasswordEditor's
bind() method, which is called by the framework to bind the
value entered in the editor back to the model object:
-
public int bind(AtomicEObject value)
{
Password eo = (Password) value;
String pwd1 = new String(_pf1.getPassword());
String pwd2 = new String(_pf2.getPassword());
if (pwd1.length() < Password.MINLENGTH)
{
eo.fireValidationException("Password must be at least "
+ Password.MINLENGTH+" characters long");
return 1;
}
if (! pwd1.equals(pwd2) )
{
eo.fireValidationException("Password and repeated password do not match.");
return 1;
}
eo.parseValue(pwd1);
return 0;
}
This editor displays two password fields. The entered value must match
each time. Also there's a check to ensure that the specified password
meets certain criteria, such as a minimum length, or exceeding a certain
minimum password strength, based on a specific algorithm. Again, we
see here how the editor, through its reference to the model object,
fire a validation signal, causing the proper validation error message
to display.
In addition, baked into the framework is a simple mechanism for marking
fields as required. The framework will automatically check that required
fields left empty will veto the ability to save an object, and will
also display a proper error message.
The way to mark required fields in JMatter is to specify them in the
properties file model-metadata.properties. Here's a sample
file, taken from an application having to do with aircraft that I
recently implemented:
-
Airport.airportID.required=true
Airport.name.required=true
Airport.lat.required=true
Airport.lon.required=true
Airport.city.required=true
Airport.stateCode.required=true
Trip.aircraft.default=from Aircraft a where name='CitationJet'
TripSegment.from.required=true
TripSegment.to.required=true
Notice that this metadata file is also used to specify default values
for fields. Note how a default value is specified for an association
by actually looking it up in the database. The value specified is
valid hql (hibernate query language). Let's get back to discussing
validation.
Any type you define in your object model can define its own validation
as well, again, simply by implementing validate(). Here's how
it was implemented for the type "TripSegment" in
an application I developed:
-
public int validate()
{
if (_from == null - - _to == null)
{
String msg = "From and To fields cannot be empty";
fireValidationException(msg);
return 1;
}
if (_from.equals(_to))
{
fireValidationException("From and To fields cannot be the same");
return 1;
}
if (_trip != null && _trip.getAircraft() != null)
{
Aircraft aircraft = _trip.getAircraft();
if (aircraft.hasRangeFor(_numPassengers.intValue()))
{
int range = aircraft.rangeFor(_numPassengers.intValue());
if (range < distance())
{
String msg = String.format("Segment distance exceeds aircraft " +
"range (%d) for specified number of passengers", range);
fireValidationException(msg);
return 1;
}
}
}
return 0;
}
So there's a simple business rule in this case that a trip segment's
distance cannot exceed the specified aircraft's range for the specified
number of passengers.
So here you have it, in a nutshell, an overview of validation in JMatter,
how to implement it in your applications, and how things work under
the hood.