Tip of the week: Validation
Posted by Eitan Suez Sat, 12 Aug 2006 01:27:00 GMT
Shedding Light on JMatter's Design. Part 2: Validation
In this Blog entry I will describe how validation works in JMatter. 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 (StringOE), 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 everything validated just fine.
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:

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 a 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, can also tap into the validation panels to display the proper validation error message.
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 at the moment 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.
In the next tip, I will be discussing how to write your own editors and renderers for atomic types.


