Author: hardy.ferentschik
Date: 2009-03-27 06:11:19 -0400 (Fri, 27 Mar 2009)
New Revision: 16224
Modified:
validator/trunk/hibernate-validator/src/main/docbook/en-US/modules/customconstraints.xml
Log:
Applied Gunnar's last doc updates
Modified:
validator/trunk/hibernate-validator/src/main/docbook/en-US/modules/customconstraints.xml
===================================================================
---
validator/trunk/hibernate-validator/src/main/docbook/en-US/modules/customconstraints.xml 2009-03-26
22:27:12 UTC (rev 16223)
+++
validator/trunk/hibernate-validator/src/main/docbook/en-US/modules/customconstraints.xml 2009-03-27
10:11:19 UTC (rev 16224)
@@ -30,44 +30,47 @@
<para>Though the Bean Validation API defines a whole bunch of standard
constraint annotations such as @NotNull, @Size, @Min or @AssertTrue, one can
easily think of situations, for which these standard annotations won't
- suffice.</para>
+ suffice. But as the specification was designed with extensibility in mind,
+ you are able to create custom constraints tailored to your specific
+ validation requirements in a simple and timely manner.</para>
- <para>But as the specification was designed with extensibility in mind, you
- are able to create custom constraints tailored to your specific validation
- requirements in a simple and timely manner. To create a custom constraint,
- the following three steps are required, which will be explained in the
- following:</para>
+ <section id="validator-customconstraints-simple"
revision="1">
+ <title>Creating a simple constraint</title>
- <itemizedlist>
- <listitem>
- <para>Create a constraint annotation</para>
- </listitem>
+ <para>To create a custom constraint, the following three steps are
+ required, which will be explained in the following:</para>
- <listitem>
- <para>Implement a validator, that's able to evaluate that
- annotation</para>
- </listitem>
+ <itemizedlist>
+ <listitem>
+ <para>Create a constraint annotation</para>
+ </listitem>
- <listitem>
- <para>Define a default error message</para>
- </listitem>
- </itemizedlist>
+ <listitem>
+ <para>Implement a validator, that's able to evaluate that
+ annotation</para>
+ </listitem>
- <section id="validator-customconstraints-constraintannotation"
revision="1">
- <title>Creating a constraint annotation</title>
+ <listitem>
+ <para>Define a default error message</para>
+ </listitem>
+ </itemizedlist>
- <para>Let's write a constraint annotation, that can be used to express
- that a given String shall either be upper case or lower case. We'll apply
- it later on to ensure, that the licensePlate field of the Car class from
- the <link linkend="validator-gettingstarted">Getting
started</link>
- chapter is always an upper-case String.</para>
+ <section id="validator-customconstraints-constraintannotation"
+ revision="1">
+ <title>Creating a constraint annotation</title>
- <para>First we need a way to express the two case modes. We might use
- String constants, but a better way to go is to use a Java 5 enum for that
- purpose:</para>
+ <para>Let's write a constraint annotation, that can be used to express
+ that a given String shall either be upper case or lower case. We'll
+ apply it later on to ensure, that the licensePlate field of the Car
+ class from the <link linkend="validator-gettingstarted">Getting
+ started</link> chapter is always an upper-case String.</para>
- <programlisting>package com.mycompany;
+ <para>First we need a way to express the two case modes. We might use
+ String constants, but a better way to go is to use a Java 5 enum for
+ that purpose:</para>
+ <programlisting>package com.mycompany;
+
public enum CaseMode {
UPPER,
@@ -76,11 +79,11 @@
}</programlisting>
- <para>Now we can define the actual constraint annotation. If you've never
- designed an annotation before, this may look a bit scary, but actually
- it's not that hard:</para>
+ <para>Now we can define the actual constraint annotation. If you've
+ never designed an annotation before, this may look a bit scary, but
+ actually it's not that hard:</para>
- <programlisting>package com.mycompany;
+ <programlisting>package com.mycompany;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
@@ -105,66 +108,67 @@
}</programlisting>
- <para>An annotation type is defined using the @interface keyword. All
- attributes of an annotation type are declared in a method-like manner. The
- specification of the Bean Validation API demands, that any constraint
- annotation defines</para>
+ <para>An annotation type is defined using the @interface keyword. All
+ attributes of an annotation type are declared in a method-like manner.
+ The specification of the Bean Validation API demands, that any
+ constraint annotation defines</para>
- <itemizedlist>
- <listitem>
- <para>an attribute "message" that returns the default key for
creating
- error messages in case the constraint is violated</para>
- </listitem>
+ <itemizedlist>
+ <listitem>
+ <para>an attribute "message" that returns the default key for
+ creating error messages in case the constraint is violated</para>
+ </listitem>
- <listitem>
- <para>an attribute "groups" that allows the specification of
- validation groups, to which this constraint belongs (see section
- "Using validation groups" for further details, TODO GM: link). This
- must default to an empty array of type Class<?>.</para>
- </listitem>
- </itemizedlist>
+ <listitem>
+ <para>an attribute "groups" that allows the specification of
+ validation groups, to which this constraint belongs (see section
+ "Using validation groups" for further details, TODO GM: link). This
+ must default to an empty array of type Class<?>.</para>
+ </listitem>
+ </itemizedlist>
- <para>Besides those two mandatory attributes we add another one allowing
- for the required case mode to be specified. The name "value" is a special
- one, which can be omitted upon using the annotation, if it is the only
- attribute specified, as e.g. in @CheckCase(CaseMode.UPPER).</para>
+ <para>Besides those two mandatory attributes we add another one allowing
+ for the required case mode to be specified. The name "value" is a
+ special one, which can be omitted upon using the annotation, if it is
+ the only attribute specified, as e.g. in
+ @CheckCase(CaseMode.UPPER).</para>
- <para>In addition we annotate the annotation type with a couple of
- so-called meta annotations:</para>
+ <para>In addition we annotate the annotation type with a couple of
+ so-called meta annotations:</para>
- <itemizedlist>
- <listitem>
- <para>@Target({ METHOD, FIELD, ANNOTATION_TYPE }): Says, that methods,
- fields and annotation declarations may be annotated with @CheckCase
- (but not type declarations e.g.)</para>
- </listitem>
+ <itemizedlist>
+ <listitem>
+ <para>@Target({ METHOD, FIELD, ANNOTATION_TYPE }): Says, that
+ methods, fields and annotation declarations may be annotated with
+ @CheckCase (but not type declarations e.g.)</para>
+ </listitem>
- <listitem>
- <para>@Retention(RUNTIME): Specifies, that annotations of this type
- will be available at runtime by the means of reflection</para>
- </listitem>
+ <listitem>
+ <para>@Retention(RUNTIME): Specifies, that annotations of this type
+ will be available at runtime by the means of reflection</para>
+ </listitem>
- <listitem>
- <para>@Constraint(validatedBy = CheckCaseValidator.class): Specifies
- the validator to be used to validate elements annotated with
- @CheckCase</para>
- </listitem>
+ <listitem>
+ <para>@Constraint(validatedBy = CheckCaseValidator.class): Specifies
+ the validator to be used to validate elements annotated with
+ @CheckCase</para>
+ </listitem>
- <listitem>
- <para>@Documented: Says, that the use of @CheckCase will be contained
- in the JavaDoc of elements annotated with it</para>
- </listitem>
- </itemizedlist>
- </section>
+ <listitem>
+ <para>@Documented: Says, that the use of @CheckCase will be
+ contained in the JavaDoc of elements annotated with it</para>
+ </listitem>
+ </itemizedlist>
+ </section>
- <section id="validator-customconstraints-validator"
revision="1">
- <title>Implementing the constraint validator</title>
+ <section id="validator-customconstraints-validator"
revision="1">
+ <title>Implementing the constraint validator</title>
- <para>Next, we need to implement a constraint validator, that's able to
- validate elements with a @CheckCase annotation. To do so, we implement the
- interface ConstraintValidator as shown below:</para>
+ <para>Next, we need to implement a constraint validator, that's able to
+ validate elements with a @CheckCase annotation. To do so, we implement
+ the interface ConstraintValidator as shown below:</para>
- <programlisting>package com.mycompany;
+ <programlisting>package com.mycompany;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
@@ -190,53 +194,61 @@
}</programlisting>
- <para>The ConstraintValidator interface defines two type parameters, which
- we set in our implementation. The first one specifies the annotation type
- to be validated (in our example CheckCase), the second one the type of
- elements, which the validator can handle (here String).</para>
+ <para>The ConstraintValidator interface defines two type parameters,
+ which we set in our implementation. The first one specifies the
+ annotation type to be validated (in our example CheckCase), the second
+ one the type of elements, which the validator can handle (here
+ String).</para>
- <para>In case a constraint annotation is allowed at elements of different
- types, a ConstraintValidator for each allowed type has to be implemented
- and registered at the constraint annotation as shown above.</para>
+ <para>In case a constraint annotation is allowed at elements of
+ different types, a ConstraintValidator for each allowed type has to be
+ implemented and registered at the constraint annotation as shown
+ above.</para>
- <para>The implementation of the validator is straightforward. The
- initialize() method gives us access to the attribute values of the
- annotation to be validated. In the example we store the CaseMode in a
- field of the validator for further usage.</para>
+ <para>The implementation of the validator is straightforward. The
+ initialize() method gives us access to the attribute values of the
+ annotation to be validated. In the example we store the CaseMode in a
+ field of the validator for further usage.</para>
- <para>In the isValid() method we implement the logic, that determines,
- whether a String is valid according to a given @CheckCase annotation or
- not. This decision depends on the case mode retrieved in initialize(). As
- the Bean Validation specification recommends, we consider null values as
- being valid. If null is not a valid value for an element, it should be
- annotated with @NotNull explicitely.</para>
+ <para>In the isValid() method we implement the logic, that determines,
+ whether a String is valid according to a given @CheckCase annotation or
+ not. This decision depends on the case mode retrieved in initialize().
+ As the Bean Validation specification recommends, we consider null values
+ as being valid. If null is not a valid value for an element, it should
+ be annotated with @NotNull explicitely.</para>
- <para>The passed-in ConstraintValidatorContext could be used to raise any
- custom validation errors, but as we are fine with the default behavior, we
- can ignore that parameter for now (TODO GM: example for usage).</para>
- </section>
+ <para>The passed-in ConstraintValidatorContext could be used to raise
+ any custom validation errors, but as we are fine with the default
+ behavior, we can ignore that parameter for now (TODO GM: example for
+ usage).</para>
+ </section>
- <section id="validator-customconstraints-errormessage"
revision="1">
- <title>Defining the error message</title>
+ <section id="validator-customconstraints-errormessage"
revision="1">
+ <title>Defining the error message</title>
- <para>Finally we need to specify the error message, that shall be used, in
- case a @CheckCase constraint is violated. To do so, we create a file named
- ValidationMessages.properties under src/main/resources with the following
- content:</para>
+ <para>Finally we need to specify the error message, that shall be used,
+ in case a @CheckCase constraint is violated. To do so, we create a file
+ named ValidationMessages.properties under src/main/resources with the
+ following content:</para>
- <programlisting>validator.checkcase=Case mode must be
{value}.</programlisting>
+ <programlisting>validator.checkcase=Case mode must be
{value}.</programlisting>
- <para>If a validation error occurs, the validation runtime will use the
- default value, that we specified for the message attribute of the
- @CheckCase annotation to look up the error message in this file.</para>
+ <para>If a validation error occurs, the validation runtime will use the
+ default value, that we specified for the message attribute of the
+ @CheckCase annotation to look up the error message in this file.</para>
+ </section>
- <para>Now that our first custom constraint is completed, we can use it in
- the Car class from the <link
linkend="validator-gettingstarted">Getting
- started</link> chapter to specify that the licensePlate field shall only
- contain upper-case Strings:</para>
+ <section id="validator-customconstraints-using"
revision="1">
+ <title>Using the constraint</title>
- <programlisting>package com.mycompany;
+ <para>Now that our first custom constraint is completed, we can use it
+ in the Car class from the <link
+ linkend="validator-gettingstarted">Getting started</link>
chapter to
+ specify that the licensePlate field shall only contain upper-case
+ Strings:</para>
+ <programlisting>package com.mycompany;
+
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@@ -265,10 +277,10 @@
}</programlisting>
- <para>Finally let's demonstrate in a little test that the @CheckCase
- constraint is properly validated:</para>
+ <para>Finally let's demonstrate in a little test that the @CheckCase
+ constraint is properly validated:</para>
- <programlisting>package com.mycompany;
+ <programlisting>package com.mycompany;
import static org.junit.Assert.*;
@@ -316,6 +328,7 @@
assertEquals(0, constraintViolations.size());
}
}</programlisting>
+ </section>
</section>
<section id="validator-customconstraints-compound"
revision="1">
@@ -400,4 +413,158 @@
}</programlisting>
</section>
+
+ <section id="validator-customconstraints-classlevel"
revision="1">
+ <title>Class-level constraints</title>
+
+ <para>A common requirement with constraints is that wether the value of a
+ given property is valid or not can not be determined by solely inspecting
+ the property itself but depends on the value of annother property of the
+ object owning the property.</para>
+
+ <para>This is where class-level constraints come into play. They are
+ annotated not at the fields of a class but at the class definition itself,
+ allowing for the accompanying validators having access to all properties
+ of the object to be validated.</para>
+
+ <para>As example let's create a constraint, that ensures, that no more
+ passengers sit in a car than its seat count allows. The constraint
+ annotation looks as follows:</para>
+
+ <programlisting>package com.mycompany;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.*;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.validation.Constraint;
+
+@Target( { TYPE, ANNOTATION_TYPE })
+@Retention(RUNTIME)
+@Constraint(validatedBy = {ValidPassengerCountValidator.class})
+@Documented
+public @interface ValidPassengerCount {
+
+ String message() default "{validator.validpassengercount}";
+
+ Class<?>[] groups() default {};
+
+}</programlisting>
+
+ <para>Note, that within the @Target annotation TYPE instead of METHOD and
+ FIELD is specified as possible target for the annotation (besides
+ ANNOTATION_TYPE, which allows for @ValidPassengerCount to be used in
+ constraint composition). The validator implementation is fairly
+ easy:</para>
+
+ <programlisting>package com.mycompany;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+public class ValidPassengerCountValidator implements
ConstraintValidator<ValidPassengerCount, Car> {
+
+ public void initialize(ValidPassengerCount constraintAnnotation) {
+ }
+
+ public boolean isValid(Car object,
+ ConstraintValidatorContext constraintContext) {
+
+ return object.getPassengers().size() <= object.getSeatCount();
+ }
+
+}</programlisting>
+
+ <para>As with the first constraint an error text in
+ ValidationMessages.properties is required:</para>
+
+ <programlisting>validator.validpassengercount=Passenger count must be less than
or equal to seat count.</programlisting>
+
+ <para>The @ValidPassengerCount annotation can now be used at the car
+ class:</para>
+
+ <programlisting>package com.mycompany;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+
+@ValidPassengerCount
+public class Car {
+
+ @Min(2)
+ private int seatCount;
+
+ @NotNull
+ List<String> passengers;
+
+ public Car(int seatCount, String... passengers) {
+
+ this.seatCount = seatCount;
+ this.passengers = new ArrayList<String>();
+
+ for (String onePassenger : passengers) {
+ this.passengers.add(onePassenger);
+ }
+ }
+
+ //getters and setters ...
+}</programlisting>
+
+ <para>Finally let's try our new class-level annotation in a
test:</para>
+
+ <programlisting>package com.mycompany;
+
+import static org.junit.Assert.*;
+
+import java.util.Set;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.Validation;
+import javax.validation.Validator;
+import javax.validation.ValidatorFactory;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class CarTest {
+
+ private static Validator validator;
+
+ @BeforeClass
+ public static void setUp() {
+ ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+ validator = factory.getValidator();
+ }
+
+ @Test
+ public void tooManyPassengers() {
+
+ Car car = new Car(2, "Jupiter", "Pete", "Bob");
+
+ Set<ConstraintViolation<Car3>> constraintViolations
=
+ validator.validate(car);
+ assertEquals(1, constraintViolations.size());
+ assertEquals(
+ "Passenger count must be less than or equal to seat count.",
+ constraintViolations.iterator().next().getInterpolatedMessage());
+ }
+
+ @Test
+ public void carIsValid() {
+
+ Car car = new Car(2, "Jupiter", "Pete");
+
+ Set<ConstraintViolation<Car3>> constraintViolations
=
+ validator.validate(car);
+
+ assertEquals(0, constraintViolations.size());
+ }
+}</programlisting>
+ </section>
</chapter>