[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&lt;?&gt;[] 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&lt;?&gt;.</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&lt;CheckCase, String&gt; {
+
+    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&lt;ConstraintViolation&lt;Car&gt;&gt; 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&lt;ConstraintViolation&lt;Car&gt;&gt; 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&lt;?&gt;[] 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&lt;?&gt;[] 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