[hibernate-commits] Hibernate SVN: r15980 - in validator/trunk/hibernate-validator/src/main/docbook/en-US: modules and 1 other directory.
hibernate-commits at lists.jboss.org
hibernate-commits at lists.jboss.org
Tue Feb 17 01:20:23 EST 2009
Author: hardy.ferentschik
Date: 2009-02-17 01:20:23 -0500 (Tue, 17 Feb 2009)
New Revision: 15980
Modified:
validator/trunk/hibernate-validator/src/main/docbook/en-US/master.xml
validator/trunk/hibernate-validator/src/main/docbook/en-US/modules/customconstraints.xml
validator/trunk/hibernate-validator/src/main/docbook/en-US/modules/gettingstarted.xml
Log:
Added Gunnar's updates to 'Creating custom constraints' chapter
Modified: validator/trunk/hibernate-validator/src/main/docbook/en-US/master.xml
===================================================================
--- validator/trunk/hibernate-validator/src/main/docbook/en-US/master.xml 2009-02-16 16:54:15 UTC (rev 15979)
+++ validator/trunk/hibernate-validator/src/main/docbook/en-US/master.xml 2009-02-17 06:20:23 UTC (rev 15980)
@@ -133,12 +133,13 @@
<!--xi:include href="modules/introduction.xml" xmlns:xi="http://www.w3.org/2001/XInclude" /-->
- <xi:include href="modules/gettingstarted.xml"
- xmlns:xi="http://www.w3.org/2001/XInclude" />
+ <xi:include href="modules/gettingstarted.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
<!--
<xi:include href="modules/usingvalidator.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
+-->
<xi:include href="modules/customconstraints.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
+<!--
<xi:include href="modules/xmlconfiguration.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
<xi:include href="modules/integration.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
<xi:include href="modules/extendedri.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
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-02-16 16:54:15 UTC (rev 15979)
+++ validator/trunk/hibernate-validator/src/main/docbook/en-US/modules/customconstraints.xml 2009-02-17 06:20:23 UTC (rev 15980)
@@ -27,31 +27,377 @@
<chapter id="validator-customconstraints">
<title>Creating custom constraints</title>
- <para>The Bean Validation API doesn't restrict you to the constraints
- specified by the API itself, but rather allows you to create your own custom
- constraints in a simple and timely manner.</para>
+ <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>
+ <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>
+
+ <itemizedlist>
+ <listitem>
+ <para>Create a constraint annotation</para>
+ </listitem>
+
+ <listitem>
+ <para>Implement a validator, that's able to evaluate that
+ annotation</para>
+ </listitem>
+
+ <listitem>
+ <para>Define a default error message</para>
+ </listitem>
+ </itemizedlist>
+
<section id="validator-customconstraints-constraintannotation" revision="1">
- <title>Create a constraint annotation</title>
+ <title>Creating a constraint annotation</title>
- <para></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>
+
+ <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,
+
+ LOWER;
+
+}</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>
+
+ <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;
+
+ at Target( { METHOD, FIELD, ANNOTATION_TYPE })
+ at Retention(RUNTIME)
+ at Constraint(validatedBy = CheckCaseValidator.class)
+ at Documented
+public @interface CheckCase {
+
+ String message() default "{validator.checkcase}";
+
+ Class<?>[] groups() default {};
+
+ CaseMode value();
+
+}</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>
+
+ <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>
+
+ <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>
+
+ <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>@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>
<section id="validator-customconstraints-validator" revision="1">
- <title>Implement the constraint validator</title>
+ <title>Implementing the constraint validator</title>
- <para></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;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {
+
+ private CaseMode caseMode;
+
+ public void initialize(CheckCase constraintAnnotation) {
+ this.caseMode = constraintAnnotation.value();
+ }
+
+ public boolean isValid(String object, ConstraintValidatorContext constraintContext) {
+
+ if (object == null)
+ return true;
+
+ if (caseMode == CaseMode.UPPER)
+ return object.equals(object.toUpperCase());
+ else
+ return object.equals(object.toLowerCase());
+ }
+
+}</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>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>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>
<section id="validator-customconstraints-errormessage" revision="1">
- <title>Define the error message</title>
+ <title>Defining the error message</title>
- <para></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>
+
+ <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>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;
+
+public class Car {
+
+ @NotNull
+ private String manufacturer;
+
+ @NotNull
+ @Size(min = 2, max = 14)
+ @CheckCase(CaseMode.UPPER)
+ private String licensePlate;
+
+ @Min(2)
+ private int seatCount;
+
+ public Car(String manufacturer, String licencePlate, int seatCount) {
+
+ this.manufacturer = manufacturer;
+ this.licensePlate = licencePlate;
+ this.seatCount = seatCount;
+ }
+
+ //getters and setters ...
+
+}</programlisting>
+
+ <para>Finally let's demonstrate in a little test that the @CheckCase
+ constraint is properly validated:</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 testLicensePlateNotUpperCase() {
+
+ Car car = new Car("Morris", "dd-ab-123", 4);
+
+ Set<ConstraintViolation<Car>> constraintViolations =
+ validator.validate(car);
+ assertEquals(1, constraintViolations.size());
+ assertEquals(
+ "Case mode must be UPPER.",
+ constraintViolations.iterator().next().getInterpolatedMessage());
+ }
+
+ @Test
+ public void carIsValid() {
+
+ Car car = new Car("Morris", "DD-AB-123", 4);
+
+ Set<ConstraintViolation<Car>> constraintViolations =
+ validator.validate(car);
+
+ assertEquals(0, constraintViolations.size());
+ }
+}</programlisting>
</section>
<section id="validator-customconstraints-compound" revision="1">
- <title>Compound constraints</title>
+ <title>Constraint composition</title>
- <para></para>
+ <para>Looking at the licensePlate field of the Car class, we see three
+ constraint annotations already. In complexer scenarios, where even more
+ constraints could be applied to one element, this might become a bit
+ confusing easily. Furthermore, if we had a licensePlate field in another
+ class, we would have to copy all constraint declarations to the other
+ class as well, violating the DRY principle.</para>
+
+ <para>This problem can be tackled using compound constraints. In the
+ following we create a new constraint annotation @ValidLicensePlate, that
+ comprises the constraints @NotNull, @Size and @CheckCase:</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;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+
+ at NotNull
+ at Size(min = 2, max = 14)
+ at CheckCase(CaseMode.UPPER)
+ at Target( { METHOD, FIELD, ANNOTATION_TYPE })
+ at Retention(RUNTIME)
+ at Constraint(validatedBy = {})
+ at Documented
+public @interface ValidLicensePlate {
+
+ String message() default "{validator.validlicenseplate}";
+
+ Class<?>[] groups() default {};
+
+}</programlisting>
+
+ <para>To do so, we just have to annotate the constraint declaration with
+ its comprising constraints (btw. that's exactly why we allowed annotation
+ types as target for the @CheckCase annotation). As no additional
+ validation is required for the @ValidLicensePlate annotation itself, we
+ don't declare a validator within the @Constraint meta annotation.</para>
+
+ <para>TODO GM: Specifying no validator does not yet work.</para>
+
+ <para>Using the new compound constraint at the licensePlate field now is
+ fully equivalent to the previous version, where we declared the three
+ constraints directly at the field itself:</para>
+
+ <programlisting>package com.mycompany;
+
+public class Car {
+
+ @ValidLicensePlate
+ private String licensePlate;
+
+ //...
+
+}</programlisting>
+
+ <para>The set of ConstraintViolations retrieved when validating a Car
+ instance will contain an entry for each violated composing constraint of
+ the @ValidLicensePlate constraint. If you rather prefer a single
+ ConstraintViolation in case any of the composing constraints is violated,
+ the @ReportAsSingleViolation meta constraint can be used as
+ follows:</para>
+
+ <programlisting>//...
+ at ReportAsSingleViolation
+public @interface ValidLicensePlate {
+
+ String message() default "{validator.validlicenseplate}";
+
+ Class<?>[] groups() default {};
+
+}</programlisting>
</section>
</chapter>
Modified: validator/trunk/hibernate-validator/src/main/docbook/en-US/modules/gettingstarted.xml
===================================================================
--- validator/trunk/hibernate-validator/src/main/docbook/en-US/modules/gettingstarted.xml 2009-02-16 16:54:15 UTC (rev 15979)
+++ validator/trunk/hibernate-validator/src/main/docbook/en-US/modules/gettingstarted.xml 2009-02-17 06:20:23 UTC (rev 15980)
@@ -26,7 +26,7 @@
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<chapter id="validator-gettingstarted">
- <title>Getting started in 5 minutes</title>
+ <title id="getting-started">Getting started in 5 minutes</title>
<para>This chapter will show you how to quickly get started with the
reference implementation (RI) of the Bean Validation API as specified by JSR
@@ -104,17 +104,24 @@
<para>Finally confirm all entered values and change into the newly created
project directory. All properties of a Maven project (such as its
dependencies to other libraries, the steps to be performed during build
- etc.) are described in a file contained pom.xml (project object model),
+ etc.) are described in a file called pom.xml (project object model),
located in the project's root directory. Now open the project's pom.xml
and perform the following changes:</para>
<itemizedlist>
<listitem>
- <para>Add the dependencies to the Bean Validation API and the
- reference implementation</para>
+ <para>Add the dependency to hibernate-validator</para>
</listitem>
<listitem>
+ <para>Add an SLF4J binding. As Hibernate Validator uses the logging
+ facade <ulink url="http://www.slf4j.org/">SLF4J</ulink>, it can make
+ use of your preferred logging API. In the following the binding to
+ <ulink url="http://logging.apache.org/log4j/">log4j</ulink> is
+ used.</para>
+ </listitem>
+
+ <listitem>
<para>Set the compiler level to 1.5 (as we want to use
annotations)</para>
</listitem>
More information about the hibernate-commits
mailing list