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
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
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
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:
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
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 |
---|---|---|
Validates that a String value is not blank |
|
|
Validates that a String value is a valid credit card number |
|
|
Validates that a String value follows a Date format |
|
|
Validates that a String value is a valid email address. |
|
|
Validates that a value is within a range or collection of constrained values. |
|
|
Validates that a String value matches a given regular expression. |
|
|
Validates that a value does not exceed the given maximum value. |
|
|
Validates that a value’s size does not exceed the given maximum value. |
|
|
Validates that a value does not fall below the given minimum value. |
|
|
Validates that a value’s size does not fall below the given minimum value. |
|
|
Validates that that a property is not equal to the specified value |
|
|
Allows a property to be set to null - defaults to false. |
|
|
Uses a Groovy range to ensure that a property’s value occurs within a specified range |
|
|
Set to the desired scale for floating point numbers (i.e., the number of digits to the right of the decimal point). |
|
|
Uses a Groovy range to restrict the size of a collection or number or the length of a String. |
|
|
Validates that a String value is a valid URL. |
|
|
Adds custom validation to a field. |
See documentation |
2.1. Blank
Validates that a String value is not blank.
.e("login", list(blank(false)))
login(blank: false)
Set to false
if a string value cannot be blank.
Error Codes |
|
Template |
Property [{0}] of class [{1}] cannot be blank |
2.2. CreditCard
Validates that a String value is a valid credit card number.
.e("cardNumber", list(creditCard(true)))
cardNumber(creditCard: true)
Set to true
if a string must be a credit card number. Internally uses the org.apache.commons.validator.CreditCardValidator
class.
Error Codes |
|
Template |
Property [{0}] of class [{1}] with value [{2}] is not a valid credit card number |
2.3. Date
Validates that a String value can be transformed to a java.util.Date
given a specific format.
.e("startDate", list(date("yyyy-MM-dd")))
startDate(date: 'yyyy-MM-dd')
Set to a format that cna be parsed by java.text.SimpleDateFormat
.
Error Codes |
|
Template |
Property [{0}] of class [{1}] with value [{2}] is not a valid date given the format [{3}] |
2.4. Email
Validates that a String value is a valid email address.
.e("homeEmail", list(email(true)))
homeEmail(email: true)
Set to true
if a string must be an email address. Internally uses the org.apache.commons.validator.EmailValidator
class.
Error Codes |
|
Template |
Property [{0}] of class [{1}] with value [{2}] is not a valid e-mail address |
2.5. InList
Validates that a value is within a range or collection of constrained values.
.e("name", list(inList(Arrays.asList("Joe", "Fred", "Bob"))))
name inList: ["Joe", "Fred", "Bob"]
Constrains a value so that it must be contained within the given list.
Error Codes |
|
Template |
Property [{0}] of class [{1}] with value [{2}] is not contained within the list [{3}] |
2.6. Matches
Validates that a String value matches a given regular expression.
.e("login", list(matches("[a-zA-Z]+")))
login matches: "[a-zA-Z]+"
Applies a regular expression against a string value.
Error Codes |
|
Template |
Property [{0}] of class [{1}] with value [{2}] does not match the required pattern [{3}] |
2.7. Max
Ensures a value does not exceed a maximum value.
.e("age", list(max(new Date())))
.e("price", list(max(999F)))
age max: new Date()
price max: 999F
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
.
Error Codes |
|
Template |
Property [{0}] of class [{1}] with value [{2}] exceeds maximum value [{3}] |
2.8. MaxSize
Ensures a value’s size does not exceed a maximum value.
.e("children", list(maxSize(25)))
children maxSize: 25
Sets the maximum size of a collection or number property.
Error Codes |
|
Template |
Property [{0}] of class [{1}] with value [{2}] exceeds maximum value [{3}] |
2.9. Min
Ensures a value does not fall below a minimum value.
.e("age", list(min(new Date())))
.e("price", list(min(0F)))
age min: new Date()
price min: 0F
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
.
Error Codes |
|
Template |
Property [{0}] of class [{1}] with value [{2}] is less than minimum value [{3}] |
2.10. MinSize
Ensures a value’s size does not fall below a minimum value.
.e("children", list(minSize(25)))
children minSize: 25
Sets the minimum size of a collection or number property.
Error Codes |
|
Template |
Property [{0}] of class [{1}] with value [{2}] is less than the minimum size of [{3}] |
2.11. NotEqual
Ensures that a property is not equal a specified value
.e("username", list(notEqual("Bob")))
username notEqual: "Bob"
Error Codes |
|
Template |
Property [{0}] of class [{1}] with value [{2}] cannot equal [{3}] |
2.12. Nullable
Allows a property to be set to null
. By default null
values for properties are not allowed.
.e("age", list(nullable(false)))
age(nullable: false)
Set to true
if the property allows null values.
Error Codes |
|
Template |
Property [{0}] of class [{1}] must not be null |
2.13. Range
Uses a range to ensure that a property’s value is within a specified range.
.e("age", list(range(18, 65)))
age range: 18..65
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.
Error Codes |
|
Template |
Property [{0}] of class [{1}] with value [{2}] does not fall within the valid range from [{3}] to [{4}] |
2.14. Scale
Set to the desired scale for floating point numbers (i.e., the number of digits to the right of the decimal point).
.e("salary", list(scale(2)))
salary scale: 2
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.
Error Codes |
none |
Template |
none |
2.15. Size
Uses a range to restrict the size of a collection, a number, or the length of a String.
.e("children", list(size(5,15)))
children size: 5..15
Sets the size of a collection, a number property, or String length.
Error Codes |
|
Template |
Property [{0}] of class [{1}] with value [{2}] does not fall within the valid size range from [{3}] to [{4}] |
2.16. Url
To validate that a String value is a valid URL.
.e("homePage", list(url(true)))
homePage url: true
Set to true
if a string must be a URL. Internally uses the org.apache.commons.validator.UrlValidator
class.
Error Codes |
|
Template |
Property [{0}] of class [{1}] with value [{2}] is not a valid URL |
2.17. Validator
Adds custom validation to a field.
.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" )))
even(validator: { val -> val % 2 ? '' : 'validator.invalid' })
password2(validator: { v, obj -> obj.password == val ? '' : 'passwords.do.not.match' })
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.
For property validator
Error Codes |
|
Template |
Property [{0}] of class [{1}] with value [{2}] is invalid |
For Object validator
Error Codes |
|
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'
}
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>
<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();