IntroductionIn the previous post, I began introducing the concepts defined in the JSR 303 (Bean validation) specification. The first concept we dealt with was the concept of constraint. The constraint represent the validation rule you want to apply to the target of your validation process, be it a field, a method or an entire class. This post is an overview of the validation API and a brief description of the validation process you can trigger on a JavaBean of your own. The understanding of the validation routine is necessary to avoid common pitfalls when defining and applying constraint to a class or a class' fields.
A word of warning: JSR 303 API is not yet committed and might change in a future revision of the specification. At the moment, the JSR 303 is in the public draft review state.
The validation process (in a nutshell)
The validation process is always started on a bean instance and may be instructed to recursively validate the objects in the object graph starting from such bean. Although the validator API also provides methods to validate only a given field or property of the target bean (for fine grained validation process, such as it might be the case for a partially populated bean) the validation routine described here will be partially honored for such methods, too.
The validation routine performs the following tasks, for the applying groups to be validated:
- perform all field-level constraints' validations
- perform all method-level constraints' validations
- perform class-level constraints' validations
- perform cascade validations
The validation routine is a deterministic routine which also checks the following:
- the validation routine will avoid infinite loops, keeping track of the validated object instances in the current validation process
- the validation routine won't execute the same validation more than once for the same validation constraint, keeping track if it matched on a previous group match.
The constraint validations matching the current groups being validated are run with no deterministic order.
Constraints semantics and constraint validator visibility
The algorithm implemented by the validation routine is pretty intuitive: it starts from inside the bean and goes to the outside. First, fields are validated, then properties, then the entire bean. The first thing to grasp is what a constraint really represent, that is something you must be aware of when designing your constraints and your validation strategy.
The constraint may be thought of as a validation rule applied to an object. The context in which the constraint is applied (the root object's type) is part of the validation rules' semantics:
- If you apply a constraint to an object's field or property, you're applying a validation rule to that field (as part of a type), but the validation routine visibility is limited to only that object. You cannot access values of other fields of your object during the validation of the fields' constraints.
- If you apply a constraint to a type, the validation routine visibility will be able to apply class' invariants (if you've got any) and any other validation rule whose parameters may be whichever part (be it field or property) that make up your type.
Basically, you'll use field-level (resp: property-level) constraints when you want to apply the validation rule to that field (resp.: property) as part of a type. For example, a String id field may be length-bounded when part of a type, such as a Passport (A String isn't length-bounded by definition).
You'll use class-level constraints when you want to apply a validation rule to the state of a type as a whole. For example, if you define a Passport type you would apply a class level constraint to check that the id-field is coherent with the Country field.
That's why the validation routine starts from inside the bean and proceeds outwards: you would not apply validation rules to an entire class instance state if some of the class' fields were invalid.
Constraint design principles and a constraint domain
The validation routine definition and the constraints semantics outlined in the previous section have got an effect, which is visible in the way these concepts have been defined. As we saw, whether to apply a constraint to a class or to a field depends on the semantics of the constraint you defined. But a class' fields are class' instances themselves. Let's suppose we're validation a bean of class X which defines a field of type Y.
public class X {
private Y y;
[...]
}
Moreover, let's suppose you designed class Y as an helper class for X and that Y is seldom visible, or visible at all, in your API. When validating such type you might be tempted to apply validation rules to Y instances as field level validations in class X. The constraint validation would indeed be passed Y instance and you could get access to all of Y's fields and properties, if you needed them. Furthermore, you could argue that this way you could validate both X and Y instances in just one validation pass. Yes, but that could be a huge design flaw if the constraints you're applying in the "X context" were in reality validation rules of class Y whose scope is not restricted to the "Y as a field of X" use case.
The question you've got to answer, during the definition of every constraint you design, is the following:
What is this constraint's domain?
If you establish an analogy with a constraint validators and a set of mathematical operators, you should be asking yourself:
What is the domain of this operator?
The answer to this question will suggest you the correct way to define and apply your constraint. If the answer is "The domain of the constraint is the entire type Y" (and you own type Y), you would define constraints accordingly and apply them to type Y (and its fields, if necessary). If the answer is "The domain of the constraint is field y of type X" you would define the constraints accordingly and you'll apply them to X.id.
Obviously reality might be more complicated than this and you might, for example:
- discover that you need both a set of constraints for the Y domain and another set for X.id domain.
- need constraints for the Y type but it's not yours.
In the first case you would act accordingly to achieve the correct isolation of the two distinct domains while in the second case you could extend, if possible, type Y or wrap it in another type of yours.
This leads us directly to why the validation routine must execute cascading validations.
Cascading validations
Cascading validations are the means you need to cleanly implement constraints such as:
Type X is subject to the following validation rules [...] and its field Y y is valid in the Y domain.
To model such situation you would apply the @Valid annotation to the X.y field:
public class X {
@Valid
private Y y;
[...]
}
When encountering such annotation during the validation of an X instance, the validation routine will also apply the constraint validators applied to Y in the Y domain. In other words, the validation routine would go down the object graph recursively reapplying itself to everyone of such fields (resp: properties).
What's next
In the next post I'll introduce the least (but not last) few missing concepts and then we'll start building some examples.