1. Introduction

The validation plugin enables Java and Groovy friendly DSLs for evaluating property constraints on POJO/POGO classes. The validation DSL is heavily inspired by GORM.

1.1. Declaring Constraints

A validateable POJO/POGO must implement the Validateable interface and define a conventional static property that contains constraints definitions. The following example shows a Credentials POJO with two properties and their constraints

giffon.plugins.validation.Credentials.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package griffon.plugins.validation;

import griffon.plugins.validation.constraints.ConstraintDef;
import org.codehaus.griffon.runtime.validation.AbstractValidateable;

import java.util.List;
import java.util.Map;

import static griffon.plugins.validation.constraints.Constraints.blank;
import static griffon.plugins.validation.constraints.Constraints.list;
import static griffon.plugins.validation.constraints.Constraints.map;
import static griffon.plugins.validation.constraints.Constraints.size;

public class Credentials extends AbstractValidateable {
    private String username;
    private String password;

    public static final Map<String, List<ConstraintDef>> CONSTRAINTS = map()
        .e("username", list(blank(false), size(3, 10)))
        .e("password", list(blank(false), size(8, 10)));

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

The following is a variation that relies on static methods to provide the names of the properties to be validated

giffon.plugins.validation.Credentials2.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package griffon.plugins.validation;

import griffon.plugins.validation.constraints.ConstraintDef;
import griffon.plugins.validation.constraints.PropertyConstraintDef;
import org.codehaus.griffon.runtime.validation.AbstractValidateable;

import javax.annotation.Nonnull;
import java.util.Collection;
import java.util.List;

import static griffon.plugins.validation.constraints.Constraints.blank;
import static griffon.plugins.validation.constraints.Constraints.list;
import static griffon.plugins.validation.constraints.Constraints.set;
import static griffon.plugins.validation.constraints.Constraints.size;

public class Credentials2 extends AbstractValidateable {
    private String username;
    private String password;

    private static PropertyConstraintDef username(@Nonnull List<ConstraintDef> constraints) {
        return new PropertyConstraintDef("username", constraints);
    }

    private static PropertyConstraintDef password(@Nonnull List<ConstraintDef> constraints) {
        return new PropertyConstraintDef("password", constraints);
    }

    public static final Collection<PropertyConstraintDef> CONSTRAINTS = set()
        .e(username(list(blank(false), size(3, 10))))
        .e(password(list(blank(false), size(8, 10))));

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

Here’s the Groovy version. Notice that constraints are defined using a block instead of a list

giffon.plugins.validation.GroovyCredentials.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
package griffon.plugins.validation

import org.codehaus.griffon.runtime.validation.AbstractValidateable

class GroovyCredentials extends AbstractValidateable {
    String username
    String password

    static final CONSTRAINTS = {
        username(blank: false, size: 3..10)
        password(blank: false, size: 8..10)
    }
}

1.2. Validating Basics

Call the validate method to validate a validateable class instance:

User user = application.injector.getInstance(User)
// TODO: update user properties
if (user.validate()) {
     // do something with user
} else {
    errorsTranslator(user.errors).each { println it }
}

The errors property on validateable classes is an instance of the Errors interface. The Errors interface provides methods to navigate the validation errors and also retrieve the original values.

1.2.1. Java DSL

POJOs must define their constraints as a Map of nested ConstraintDef instances. The Constraints class provides helper methods that can be statically imported; these methods make it very easy to create such nested structure.

The following conventions must be followed:

  • the class must have a public static final Map<String, List<ConstraintDef>> CONSTRAINTS field.

  • property names are used as keys.

  • a List of ConstraintDef is set as entry value.

As an alternative you may follow these conventions

  • the class must have a public static final Collection<PropertyConstraintDef> CONSTRAINTS field.

Constraints will be evaluated in the order they were defined, top to bottom, left to right.

1.2.2. Groovy DSL

POGOs must define their constraints as a block. Each method inside this block is treated as a constraint definition.

The following conventions must be followed:

  • the class must have a static final CONSTRAINTS closure field.

  • method names must match property names to be validated.

  • method arguments match constraint names by key.

Constraints will be evaluated in the order they were defined, top to bottom, left to right.

1.3. Validation and Internationalization

An important thing to note about errors is that error messages are not hard coded anywhere. Applications resolve messages from the i18n message bundles.

The codes themselves are dictated by a convention. For example consider these constraintss:

package com.acme

class User extends AbstractValidateable {
    static final CONSTRAINTS= {
         login size: 5..15, blank: false
         password size: 5..15, blank: false
         email email: true, blank: false
         age min: 18
    }

    String login
    String password
    String email
    int age
}

If a constraint is violated the plugin will, by convention, look for a message code of the form

[Class Name].[Property Name].[Constraint Code]

In the case of the blank constraint this would be `user.login.blank so you would need a message such as the following in your `griffon-app/i18n/messages.properties file:

griffon-app/i18n/messages.properties
user.login.blank=Your login name must be specified!

The class name is looked for both with and without a package, with the packaged version taking precedence. So for example, com.acme.myapp.User.login.blank will be used before user.login.blank.

You may inject an instance of ErrorMessagesResolver to resolve i18n messages for all errors.

1.4. The @Validatable AST Transformation

You can apply the @griffon.transform.Validateable AST transformation on any class. This injects the behavior of Validateable into said class. The previous GroovyCredentials shown earlier can be rewritten as follows

griffon.plugins.validation.GroovyCredentials.groovy
package griffon.plugins.validation

@griffon.transform.Validateable
class GroovyCredentials {
    String username
    String password

    static final CONSTRAINTS = {
        username(blank: false, size: 3..10)
        password(blank: false, size: 8..10)
    }
}

Griffon version: 2.12.0

2. Constraints

The following table summarizes all currently supported constraints

Constraint Definition Example

blank

Validates that a String value is not blank

login(blank: false)

creditCard

Validates that a String value is a valid credit card number

cardNumber(creditCard: true)

date

Validates that a String value follows a Date format

startDate(date: "yyyy-MM-dd")

email

Validates that a String value is a valid email address.

homeEmail(email: true)

inList

Validates that a value is within a range or collection of constrained values.

name(inList: ["Joe", "Fred", "Bob"])

matches

Validates that a String value matches a given regular expression.

login(matches: "[a-zA-Z]+")

max

Validates that a value does not exceed the given maximum value.

age(max: new Date()) price(max: 999F)

maxSize

Validates that a value’s size does not exceed the given maximum value.

children(maxSize: 25)

min

Validates that a value does not fall below the given minimum value.

age(min: new Date()) price(min: 0F)

minSize

Validates that a value’s size does not fall below the given minimum value.

children(minSize: 25)

notEqual

Validates that that a property is not equal to the specified value

login(notEqual: "Bob")

nullable

Allows a property to be set to null - defaults to false.

age(nullable: true)

range

Uses a Groovy range to ensure that a property’s value occurs within a specified range

age(range: 18..65)

scale

Set to the desired scale for floating point numbers (i.e., the number of digits to the right of the decimal point).

salary(scale: 2)

size

Uses a Groovy range to restrict the size of a collection or number or the length of a String.

children(size: 5..15)

url

Validates that a String value is a valid URL.

homePage(url: true)

validator

Adds custom validation to a field.

See documentation

2.1. Blank

Purpose

Validates that a String value is not blank.

Java
.e("login", list(blank(false)))
Groovy
login(blank: false)
Description

Set to false if a string value cannot be blank.

Messages
Error Codes

className.propertyName.blank, default.blank.message

Template

Property [{0}] of class [{1}] cannot be blank

2.2. CreditCard

Purpose

Validates that a String value is a valid credit card number.

Java
.e("cardNumber", list(creditCard(true)))
Groovy
cardNumber(creditCard: true)
Description

Set to true if a string must be a credit card number. Internally uses the org.apache.commons.validator.CreditCardValidator class.

Messages
Error Codes

className.propertyName.creditCard.invalid, default.invalid.creditCard.message

Template

Property [{0}] of class [{1}] with value [{2}] is not a valid credit card number

2.3. Date

Purpose

Validates that a String value can be transformed to a java.util.Date given a specific format.

Java
.e("startDate", list(date("yyyy-MM-dd")))
Groovy
startDate(date: 'yyyy-MM-dd')
Description

Set to a format that cna be parsed by java.text.SimpleDateFormat.

Messages
Error Codes

className.propertyName.not.date.invalid, default.not.date.message

Template

Property [{0}] of class [{1}] with value [{2}] is not a valid date given the format [{3}]

2.4. Email

Purpose

Validates that a String value is a valid email address.

Java
.e("homeEmail", list(email(true)))
Groovy
homeEmail(email: true)
Description

Set to true if a string must be an email address. Internally uses the org.apache.commons.validator.EmailValidator class.

Messages
Error Codes

className.propertyName.email.invalid, default.invalid.email.message

Template

Property [{0}] of class [{1}] with value [{2}] is not a valid e-mail address

2.5. InList

Purpose

Validates that a value is within a range or collection of constrained values.

Java
.e("name", list(inList(Arrays.asList("Joe", "Fred", "Bob"))))
Groovy
name inList: ["Joe", "Fred", "Bob"]
Description

Constrains a value so that it must be contained within the given list.

Messages
Error Codes

className.propertyName.not.inList, default.not.inlist.message

Template

Property [{0}] of class [{1}] with value [{2}] is not contained within the list [{3}]

2.6. Matches

Purpose

Validates that a String value matches a given regular expression.

Java
.e("login", list(matches("[a-zA-Z]+")))
Groovy
login matches: "[a-zA-Z]+"
Description

Applies a regular expression against a string value.

Messages
Error Codes

className.propertyName.matches.invalid, default.doesnt.match.message

Template

Property [{0}] of class [{1}] with value [{2}] does not match the required pattern [{3}]

2.7. Max

Purpose

Ensures a value does not exceed a maximum value.

Java
.e("age",   list(max(new Date())))
.e("price", list(max(999F)))
Groovy
age   max: new Date()
price max: 999F
Description

Sets the maximum value of a class that implements java.lang.Comparable. The type of the value must be the same as the property.

Note that constraints are only evaluated once which may be relevant for a constraint that relies on a value like an instance of java.util.Date.

Messages
Error Codes

className.propertyName.max.exceeded, default.invalid.max.message

Template

Property [{0}] of class [{1}] with value [{2}] exceeds maximum value [{3}]

2.8. MaxSize

Purpose

Ensures a value’s size does not exceed a maximum value.

Java
.e("children", list(maxSize(25)))
Groovy
children maxSize: 25
Description

Sets the maximum size of a collection or number property.

Messages
Error Codes

className.propertyName.maxSize.exceeded, default.invalid.max.size.message

Template

Property [{0}] of class [{1}] with value [{2}] exceeds maximum value [{3}]

2.9. Min

Purpose

Ensures a value does not fall below a minimum value.

Java
.e("age",   list(min(new Date())))
.e("price", list(min(0F)))
Groovy
age   min: new Date()
price min: 0F
Description

Sets the minimum value of a class that implements java.lang.Comparable. The type of the value must be the same as the property.

Note that constraints are only evaluated once which may be relevant for a constraint that relies on a value like an instance of java.util.Date.

Messages
Error Codes

className.propertyName.min.notmet, default.invalid.min.message

Template

Property [{0}] of class [{1}] with value [{2}] is less than minimum value [{3}]

2.10. MinSize

Purpose

Ensures a value’s size does not fall below a minimum value.

Java
.e("children", list(minSize(25)))
Groovy
children minSize: 25
Description

Sets the minimum size of a collection or number property.

Messages
Error Codes

className.propertyName.minSize.notmet, default.invalid.min.size.message

Template

Property [{0}] of class [{1}] with value [{2}] is less than the minimum size of [{3}]

2.11. NotEqual

Purpose

Ensures that a property is not equal a specified value

Java
.e("username", list(notEqual("Bob")))
Groovy
username notEqual: "Bob"
Messages
Error Codes

className.propertyName.notEqual, default.not.equal.message

Template

Property [{0}] of class [{1}] with value [{2}] cannot equal [{3}]

2.12. Nullable

Purpose

Allows a property to be set to null. By default null values for properties are not allowed.

Java
.e("age", list(nullable(false)))
Groovy
age(nullable: false)
Description

Set to true if the property allows null values.

Messages
Error Codes

className.propertyName.nullable, default.null.message

Template

Property [{0}] of class [{1}] must not be null

2.13. Range

Purpose

Uses a range to ensure that a property’s value is within a specified range.

Java
.e("age", list(range(18, 65)))
Groovy
age range: 18..65
Description

Set to a range which can contain numbers in the form of an IntRange, CharRange, LongRange, DoubleRange, FloatRange or EnumRange, depending on the argument types.

Messages
Error Codes

className.propertyName.range.toosmall, className.propertyName.range.toobig, default.invalid.range.message

Template

Property [{0}] of class [{1}] with value [{2}] does not fall within the valid range from [{3}] to [{4}]

2.14. Scale

Purpose

Set to the desired scale for floating point numbers (i.e., the number of digits to the right of the decimal point).

Java
.e("salary", list(scale(2)))
Groovy
salary scale: 2
Description

Set to the desired scale for floating point numbers (i.e., the number of digits to the right of the decimal point). This constraint is applicable for properties of the following types: java.lang.Float, java.lang.Double, and java.math.BigDecimal (and its subclasses). When validation is invoked, this constraint determines if the number includes more nonzero decimal places than the scale permits. If so, it rounds the number to the maximum number of decimal places allowed by the scale. This constraint does not generate validation error messages.

Messages
Error Codes

none

Template

none

2.15. Size

Purpose

Uses a range to restrict the size of a collection, a number, or the length of a String.

Java
.e("children", list(size(5,15)))
Groovy
children size: 5..15
Description

Sets the size of a collection, a number property, or String length.

Messages
Error Codes

className.propertyName.size.toosmall, className.propertyName.size.toobig, default.invalid.size.message

Template

Property [{0}] of class [{1}] with value [{2}] does not fall within the valid size range from [{3}] to [{4}]

2.16. Url

Purpose

To validate that a String value is a valid URL.

Java
.e("homePage", list(url(true)))
Groovy
homePage url: true
Description

Set to true if a string must be a URL. Internally uses the org.apache.commons.validator.UrlValidator class.

Messages
Error Codes

className.propertyName.url.invalid, default.invalid.url.message

Template

Property [{0}] of class [{1}] with value [{2}] is not a valid URL

2.17. Validator

Purpose

Adds custom validation to a field.

Java
.e("even", list(validator( (Number v) -> {val % 2 ? "" : "validator.invalid"} )))
.e("password2", list(validator( (Number v, Bean b) -> areEqual(b.password, val) ? "" : "passwords.do.not.match" )))
Groovy
even(validator: { val -> val % 2 ? '' : 'validator.invalid' })
password2(validator: { v, obj -> obj.password == val ? '' : 'passwords.do.not.match' })
Description

A custom validator is implemented by a Closure that takes up to two parameters. If the Closure accepts zero or one parameter, the parameter value will be the one being validated ("it" in the case of a zero-parameter Closure). If it accepts two parameters the first is the value and the second is the class instance being validated. This is useful when your validation needs access to other fields, for example when checking that two entered passwords are the same.

The closure can return:

  • null or empty String to indicate that the value is valid.

  • a String to indicate the error code to append to the "classname.propertyName." String used to resolve the error message. If a field-specific message cannot be resolved, the error code itself will be resolved allowing for global error messages.

Messages

For property validator

Error Codes

className.propertyName.validator.invalid, default.invalid.property.validator.message

Template

Property [{0}] of class [{1}] with value [{2}] is invalid

For Object validator

Error Codes

className.propertyName.validator.invalid, default.invalid.object.validator.message

Template

Property [{0}] of class [{1}] with value [{2}] is invalid

3. Build Configuration

3.1. Gradle

You have two options for configuring this plugin: automatic and manual.

3.1.1. Automatic

As long as the project has the org.codehaus.griffon.griffon plugin applied to it you may include the following snippet in build.gradle

dependencies {
    griffon 'org.codehaus.griffon.plugins:griffon-validation-plugin:2.0.0'
}

The griffon plugin will take care of the rest given its configuration.

3.1.2. Manual

You will need to configure any of the following blocks depending on your setup

dependencies {
    compile 'org.codehaus.griffon.plugins:griffon-validation-core:2.0.0'
}
dependencies {
    compile 'org.codehaus.griffon.plugins:griffon-validation-groovy:2.0.0'
}
Compile Only
dependencies {
    compileOnly 'org.codehaus.griffon.plugins:griffon-validation-groovy-compile:2.0.0'
}

3.2. Maven

First configure the griffon-validation-plugin BOM in your POM file, by placing the following snippet before the <build> element

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.codehaus.griffon.plugins</groupId>
            <artifactId>griffon-validation-plugin</artifactId>
            <version>2.0.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Next configure dependencies as required by your particular setup

<dependency>
    <groupId>org.codehaus.griffon.plugins</groupId>
    <artifactId>griffon-validation-core</artifactId>
</dependency>
<dependency>
    <groupId>org.codehaus.griffon.plugins</groupId>
    <artifactId>griffon-validation-groovy</artifactId>
</dependency>
Provided scope
<dependency>
    <groupId>org.codehaus.griffon.plugins</groupId>
    <artifactId>griffon-validation-groovy-compile</artifactId>
</dependency>

Don’t forget to configure all -compile dependencies with the maven-surefire-plugin, like so

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <classpathDependencyExcludes>
            <classpathDependencyExclude>
                org.codehaus.griffon:griffon-validation-groovy-compile
            </classpathDependencyExclude>
        </classpathDependencyExcludes>
    </configuration>
</plugin>

4. Modules

The following sections display all bindings per module. Use this information to successfully override a binding on your own modules or to troubleshoot a module binding if the wrong type has been applied by the Griffon runtime.

4.1. Validation

Module name: validation

bind(ConstraintsEvaluator.class)
    .to(DefaultConstraintsEvaluator.class)
    .asSingleton();

bind(ConstraintsValidator.class)
    .to(DefaultConstraintsValidator.class)
    .asSingleton();

bind(ConstrainedPropertyAssembler.class)
    .to(DefaultConstrainedPropertyAssembler.class);

bind(ErrorMessagesResolver.class)
    .to(DefaultErrorMessagesResolver.class)
    .asSingleton();

4.2. Groovy

Module name: validation-groovy

Depends on: validation

bind(ConstrainedPropertyAssembler.class)
    .to(GroovyAwareConstrainedPropertyAssembler.class);