shows how this can be done by using the constraint validator context passed to isValid()
to build a
custom constraint violation with a property node for the property passengers. Note that you also
could add several property nodes, pointing to a sub-entity of the validated bean.
ConstraintViolation
with custom property pathpackage org.hibernate.validator.referenceguide.chapter06.custompath;
public class ValidPassengerCountValidator
implements ConstraintValidator<ValidPassengerCount, Car> {
@Override
public void initialize(ValidPassengerCount constraintAnnotation) {
}
@Override
public boolean isValid(Car car, ConstraintValidatorContext constraintValidatorContext) {
if ( car == null ) {
return true;
}
boolean isValid = car.getPassengers().size() <= car.getSeatCount();
if ( !isValid ) {
constraintValidatorContext.disableDefaultConstraintViolation();
constraintValidatorContext
.buildConstraintViolationWithTemplate( "{my.custom.template}" )
.addPropertyNode( "passengers" ).addConstraintViolation();
}
return isValid;
}
}
6.3. Cross-parameter constraints
Bean Validation distinguishes between two different kinds of constraints.
Generic constraints (which have been discussed so far) apply to the annotated element, e.g. a type, field, container element, method parameter or return value etc. Cross-parameter constraints, in contrast, apply to the array of parameters of a method or constructor and can be used to express validation logic which depends on several parameter values.
In order to define a cross-parameter constraint, its validator class must be annotated with
@SupportedValidationTarget(ValidationTarget.PARAMETERS)
. The type parameter T
from the
ConstraintValidator
interface must resolve to either Object
or Object[]
in order to receive the
array of method/constructor arguments in the isValid()
method.
The following example shows the definition of a cross-parameter constraint which can be used to
check that two Date
parameters of a method are in the correct order:
package org.hibernate.validator.referenceguide.chapter06.crossparameter;
@Constraint(validatedBy = ConsistentDateParametersValidator.class)
@Target({ METHOD, CONSTRUCTOR, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Documented
public @interface ConsistentDateParameters {
String message() default "{org.hibernate.validator.referenceguide.chapter04." +
"crossparameter.ConsistentDateParameters.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
The definition of a cross-parameter constraint isn’t any different from defining a generic
constraint, i.e. it must specify the members message()
, groups()
and payload()
and be annotated with
@Constraint
. This meta annotation also specifies the corresponding validator, which is shown in
Example 6.15, “Generic and cross-parameter constraint”. Note that besides the element types METHOD
and CONSTRUCTOR
also ANNOTATION_TYPE
is specified as target of the annotation, in order to enable the creation of
composed constraints based on @ConsistentDateParameters
(see
Section 6.4, “Constraint composition”).
|
Cross-parameter constraints are specified directly on the declaration of a method or constructor,
which is also the case for return value constraints. In order to improve code readability, it is
therefore recommended to choose constraint names - such as |
package org.hibernate.validator.referenceguide.chapter06.crossparameter;
@SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class ConsistentDateParametersValidator implements
ConstraintValidator<ConsistentDateParameters, Object[]> {
@Override
public void initialize(ConsistentDateParameters constraintAnnotation) {
}
@Override
public boolean isValid(Object[] value, ConstraintValidatorContext context) {
if ( value.length != 2 ) {
throw new IllegalArgumentException( "Illegal method signature" );
}
//leave null-checking to @NotNull on individual parameters
if ( value[0] == null || value[1] == null ) {
return true;
}
if ( !( value[0] instanceof Date ) || !( value[1] instanceof Date ) ) {
throw new IllegalArgumentException(
"Illegal method signature, expected two " +
"parameters of type Date."
);
}
return ( (Date) value[0] ).before( (Date) value[1] );
}
}
As discussed above, the validation target PARAMETERS
must be configured for a cross-parameter
validator by using the @SupportedValidationTarget
annotation. Since a cross-parameter constraint
could be applied to any method or constructor, it is considered a best practice to check for the
expected number and types of parameters in the validator implementation.
As with generic constraints, null
parameters should be considered valid and @NotNull
on the
individual parameters should be used to make sure that parameters are not null
.
|
Similar to class-level constraints, you can create custom constraint violations on single parameters
instead of all parameters when validating a cross-parameter constraint. Just obtain a node builder
from the |
In rare situations a constraint is both, generic and cross-parameter. This is the case if a
constraint has a validator class which is annotated with
@SupportedValidationTarget({ValidationTarget.PARAMETERS, ValidationTarget.ANNOTATED_ELEMENT})
or if
it has a generic and a cross-parameter validator class.
When declaring such a constraint on a method which has parameters and also a return value, the
intended constraint target can’t be determined. Constraints which are generic and cross-parameter at
the same time must therefore define a member validationAppliesTo()
which allows the constraint user
to specify the constraint’s target as shown in Example 6.16, “Generic and cross-parameter constraint”.
package org.hibernate.validator.referenceguide.chapter06.crossparameter;
@Constraint(validatedBy = {
ScriptAssertObjectValidator.class,
ScriptAssertParametersValidator.class
})
@Target({ TYPE, FIELD, PARAMETER, METHOD, CONSTRUCTOR, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Documented
public @interface ScriptAssert {
String message() default "{org.hibernate.validator.referenceguide.chapter04." +
"crossparameter.ScriptAssert.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
String script();
ConstraintTarget validationAppliesTo() default ConstraintTarget.IMPLICIT;
}
The @ScriptAssert
constraint has two validators (not shown), a generic and a cross-parameter one and
thus defines the member validationAppliesTo()
. The default value IMPLICIT
allows to derive the
target automatically in situations where this is possible (e.g. if the constraint is declared on a
field or on a method which has parameters but no return value).
If the target can not be determined implicitly, it must be set by the user to either PARAMETERS
or
RETURN_VALUE
as shown in Example 6.17, “Specifying the target for a generic and cross-parameter constraint”.
@ScriptAssert(script = "arg1.size() <= arg0", validationAppliesTo = ConstraintTarget.PARAMETERS)
public Car buildCar(int seatCount, List<Passenger> passengers) {
//...
return null;
}
6.4. Constraint composition
Looking at the licensePlate
field of the Car
class in Example 6.10, “Applying the @CheckCase
constraint”, you see three
constraint annotations already. In more complex scenarios, where even more constraints could be applied
to one element, this might easily become a bit confusing. Furthermore, if there was a licensePlate
field in another class, you would have to copy all constraint declarations to the other class as
well, violating the DRY principle.
You can address this kind of problem by creating higher level constraints, composed from several
basic constraints. Example 6.18, “Creating a composing constraint @ValidLicensePlate
” shows a composed constraint annotation which
comprises the constraints @NotNull
, @Size
and @CheckCase
:
@ValidLicensePlate
package org.hibernate.validator.referenceguide.chapter06.constraintcomposition;
@NotNull
@Size(min = 2, max = 14)
@CheckCase(CaseMode.UPPER)
@Target({ METHOD, FIELD, ANNOTATION_TYPE, TYPE_USE })
@Retention(RUNTIME)
@Constraint(validatedBy = { })
@Documented
public @interface ValidLicensePlate {
String message() default "{org.hibernate.validator.referenceguide.chapter06." +
"constraintcomposition.ValidLicensePlate.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
To create a composed constraint, simply annotate the constraint declaration with its comprising
constraints. If the composed constraint itself requires a validator, this validator is to be
specified within the @Constraint
annotation. For composed constraints which don’t need an additional
validator such as @ValidLicensePlate
, just set validatedBy()
to an empty array.
Using the new composed constraint at the licensePlate
field is fully equivalent to the previous
version, where the three constraints were declared directly at the field itself:
ValidLicensePlate
package org.hibernate.validator.referenceguide.chapter06.constraintcomposition;
public class Car {
@ValidLicensePlate
private String licensePlate;
//...
}
The set of ConstraintViolation
s 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:
package org.hibernate.validator.referenceguide.chapter06.constraintcomposition.reportassingle;
//...
@ReportAsSingleViolation
public @interface ValidLicensePlate {
String message() default "{org.hibernate.validator.referenceguide.chapter06." +
"constraintcomposition.reportassingle.ValidLicensePlate.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
7. Value extraction
Value extraction is the process of extracting values from a container so that they can be validated.
It is used when dealing with container element constraints and cascaded validation inside containers.
7.1. Built-in value extractors
Hibernate Validator comes with built-in value extractors for the usual Java container
types so, except if you are using your own custom container types (or the ones
of external libraries such as Guava's Multimap
),
you should not have to add your own value extractors.
Built-in value extractors are present for all the following container types:
-
java.util.Iterable
; -
java.util.List
; -
java.util.Map
: for keys and values; -
java.util.Optional
,java.util.OptionalInt
,java.util.OptionalLong
andjava.util.OptionalDouble
; -
JavaFX's
ObservableValue
(see Section 7.4, “JavaFX value extractors” for more details).
The complete list of built-in value extractors with all the details on how they behave can be found in the Bean Validation specification.
7.2. Implementing a ValueExtractor
To extract values from a custom container, one needs to implement a ValueExtractor
.
|
Implementing a |
ValueExtractor
is a very simple API as the only purpose of a value extractor is to provide
the extracted values to a ValueReceiver
.
For instance, let’s consider the case of Guava’s Optional
. It is an easy example
as we can shape its value extractor after the java.util.Optional
one:
ValueExtractor
for Guava’s Optional
package org.hibernate.validator.referenceguide.chapter07.valueextractor;
public class OptionalValueExtractor
implements ValueExtractor<Optional<@ExtractedValue ?>> {
@Override
public void extractValues(Optional<?> originalValue, ValueReceiver receiver) {
receiver.value( null, originalValue.orNull() );
}
}
Some explanations are in order:
-
The
@ExtractedValue
annotation marks the type argument under consideration: it is going to be used to resolve the type of the validated value; -
We use the
value()
method of the receiver asOptional
is a pure wrapper type; -
We don’t want to add a node to the property path of the constraint violation as we want the violation to be reported as if it were directly on the property so we pass a
null
node name tovalue()
.
A more interesting example is the case of Guava’s Multimap
: we would like to be
able to validate both the keys and the values of this container type.
Let’s first consider the case of the values. A value extractor extracting them is required:
ValueExtractor
for Multimap
valuespackage org.hibernate.validator.referenceguide.chapter07.valueextractor;
public class MultimapValueValueExtractor
implements ValueExtractor<Multimap<?, @ExtractedValue ?>> {
@Override
public void extractValues(Multimap<?, ?> originalValue, ValueReceiver receiver) {
for ( Entry<?, ?> entry : originalValue.entries() ) {
receiver.keyedValue( "<multimap value>", entry.getKey(), entry.getValue() );
}
}
}
It allows to validate constraints for the values of the Multimap
:
Multimap
private Multimap<String, @NotBlank String> map1;
Another value extractor is required to be able to put constraints on the keys
of a Multimap
:
ValueExtractor
for Multimap
keyspackage org.hibernate.validator.referenceguide.chapter07.valueextractor;
public class MultimapKeyValueExtractor
implements ValueExtractor<Multimap<@ExtractedValue ?, ?>> {
@Override
public void extractValues(Multimap<?, ?> originalValue, ValueReceiver receiver) {
for ( Object key : originalValue.keySet() ) {
receiver.keyedValue( "<multimap key>", key, key );
}
}
}
Once these two value extractors are registered, you can declare constraints on the
keys and values of a Multimap
:
Multimap
private Multimap<@NotBlank String, @NotBlank String> map2;
The differences between the two value extractors may be a bit subtle at a first glance so let’s shed some light on them:
-
The
@ExtractedValue
annotation marks the targeted type argument (eitherK
orV
in this case). -
We use different node names (
<multimap key>
vs.<multimap value>
). -
In one case, we pass the values to the receiver (third argument of the
keyedValue()
call), in the other, we pass the keys.
Depending on your container type, you should choose the ValueReceiver
method fitting the best:
value()
-
for a simple wrapping container - it is used for
Optional
s iterableValue()
-
for an iterable container - it is used for
Set
s indexedValue()
-
for a container containing indexed values - it is used for
List
s keyedValue()
-
for a container containing keyed values - it is used for
Map
s. It is used for both the keys and the values. In the case of keys, the key is also passed as the validated value.
For all these methods, you need to pass a node name: it is the name included in
the node added to the property path of the constraint violation. As mentioned
earlier, if the node name is null
, no node is added to the property path:
it is be useful for pure wrapper types similar to Optional
.
The choice of the method used is important as it adds contextual information to the property path of the constraint violation e.g. the index or the key of the validated value.
7.3. Non generic containers
You might have noticed that, until now, we only implemented value extractors for generic containers.
Hibernate Validator also supports value extraction for non generic containers.
Let’s take the case of java.util.OptionalInt
which wraps a primitive int
into an Optional
-like container.
A first attempt at a value extractor for OptionalInt
would look like:
ValueExtractor
for OptionalInt
package org.hibernate.validator.referenceguide.chapter07.nongeneric;
public class OptionalIntValueExtractor
implements ValueExtractor<@ExtractedValue(type = Integer.class) OptionalInt> {
@Override
public void extractValues(OptionalInt originalValue, ValueReceiver receiver) {
receiver.value( null, originalValue.isPresent() ? originalValue.getAsInt() : null );
}
}
There is an obvious thing missing for a non generic container: we don’t have a type parameter. It has two consequences:
-
we cannot determine the type of the validated value using the type argument;
-
we cannot add constraints on the type argument (e.g.
Container<@NotNull String>
).
First things first, we need a way to tell Hibernate Validator that the value
extracted from an OptionalInt
is of type Integer
.
As you can see in the above example, the type
attribute of the @ExtractedValue
annotation allows to provide this information to the validation engine.
Then you have to tell the validation engine that the Min
constraint you want to
add to the OptionalInt
property relates to the wrapped value and not the wrapper.
Bean Validation provides the Unwrapping.Unwrap
payload for this situation:
Unwrapping.Unwrap
payload@Min(value = 5, payload = Unwrapping.Unwrap.class)
private OptionalInt optionalInt1;
If we take a step back, most - if not all - the constraints we would like to add to an
OptionalInt
property would be applied to the wrapped value so having a way to make it
the default would be nice.
This is exactly what the @UnwrapByDefault
annotation is for:
ValueExtractor
for OptionalInt
marked with @UnwrapByDefault
package org.hibernate.validator.referenceguide.chapter07.nongeneric;
@UnwrapByDefault
public class UnwrapByDefaultOptionalIntValueExtractor
implements ValueExtractor<@ExtractedValue(type = Integer.class) OptionalInt> {
@Override
public void extractValues(OptionalInt originalValue, ValueReceiver receiver) {
receiver.value( null, originalValue.isPresent() ? originalValue.getAsInt() : null );
}
}
When declaring this value extractor for OptionalInt
, constraint annotations will
by default be applied to the wrapped value:
@UnwrapByDefault
@Min(5)
private OptionalInt optionalInt2;
Note that you can still declare an annotation for the wrapper itself by using
the Unwrapping.Skip
payload:
Unwrapping.Skip
@NotNull(payload = Unwrapping.Skip.class)
@Min(5)
private OptionalInt optionalInt3;
|
The |
7.4. JavaFX value extractors
Bean properties in JavaFX are typically not of simple data types like String
or int
, but are wrapped in Property
types which allows to make them observable,
use them for data binding etc.
Thus, value extraction is required to be able to apply constraints on the wrapped values.
The JavaFX ObservableValue
value extractor is marked with @UnwrapByDefault
.
As such, the constraints hosted on the container target the wrapped value by
default.
Thus, you can constrain a StringProperty
as below:
StringProperty
@NotBlank
private StringProperty stringProperty;
Or a LongProperty
:
LongProperty
@Min(5)
private LongProperty longProperty;
The iterable property types, namely ReadOnlyListProperty
,
ListProperty
and their Set
and Map
counterparts are generic and, as such,
container element constraints can be used. Thus, they have specific value
extractors that are not marked with @UnwrapByDefault
.
A ReadOnlyListProperty
would naturally be constrained as a List
:
ReadOnlyListProperty
@Size(min = 1)
private ReadOnlyListProperty<@NotBlank String> listProperty;
7.5. Registering a ValueExtractor
Hibernate Validator does not detect automatically the value extractors in the classpath so they have to be registered.
There are several ways to register value extractors (in increasing order of priority):
- Provided by the validation engine itself
- Via the Java service loader mechanism
-
The file
META-INF/services/javax.validation.valueextraction.ValueExtractor
must be provided, with the fully-qualified names of one or more value extractor implementations as its contents, each on a separate line. - In the
META-INF/validation.xml
file -
See Section 8.1, “Configuring the validator factory in validation.xml” for more information about how to register value extractors in the XML configuration.
- By calling
Configuration#addValueExtractor(ValueExtractor<?>)
-
See Section 9.2.6, “Registering
ValueExtractor
s” for more information. - By invoking
ValidatorContext#addValueExtractor(ValueExtractor<?>)
-
It only declares the value extractor for this
Validator
instance.
A value extractor for a given type and type parameter specified at a higher priority overrides any other extractors for the same type and type parameter given at lower priorities.
7.6. Resolution algorithms
In most cases, you should not have to worry about this but, if you are overriding existing value extractors, you can find a detailed description of the value extractors resolution algorithms in the Bean Validation specification:
-
for cascaded validation,
-
and for implicit unwrapping.
One important thing to have in mind is that:
-
for container element constraints, the declared type is used to resolve the value extractors;
-
for cascaded validation, it is the runtime type.
8. Configuring via XML
So far we have used the default configuration source for Bean Validation, namely annotations. However, there also exist two kinds of XML descriptors allowing configuration via XML. The first descriptor describes general Bean Validation behaviour and is provided as META-INF/validation.xml. The second one describes constraint declarations and closely matches the constraint declaration approach via annotations. Let’s have a look at these two document types.
|
The XSD files are available via http://xmlns.jcp.org/xml/ns/validation/configuration and http://xmlns.jcp.org/xml/ns/validation/mapping. More information about the XML schemas can be found on the Bean Validation website. |
8.1. Configuring the validator factory in validation.xml
The key to enable XML configuration for Hibernate Validator is the file META-INF/validation.xml.
If this file exists on the classpath its configuration will be applied when the ValidatorFactory
gets created. Figure 1, “Validation configuration schema” shows a model view of the XML schema to which
validation.xml has to adhere.
Example 8.1, “validation.xml
”
shows the several configuration options of validation.xml. All settings are optional and the same
configuration options are also available programmatically through javax.validation.Configuration
. In
fact, the XML configuration will be overridden by values explicitly specified via the programmatic
API. It is even possible to ignore the XML configuration completely via
Configuration#ignoreXmlConfiguration()
. See also Section 9.2, “Configuring a ValidatorFactory
”.
validation.xml
<validation-config
xmlns="http://xmlns.jcp.org/xml/ns/validation/configuration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/validation/configuration
http://xmlns.jcp.org/xml/ns/validation/configuration/validation-configuration-2.0.xsd"
version="2.0">
<default-provider>com.acme.ValidationProvider</default-provider>
<message-interpolator>com.acme.MessageInterpolator</message-interpolator>
<traversable-resolver>com.acme.TraversableResolver</traversable-resolver>
<constraint-validator-factory>
com.acme.ConstraintValidatorFactory
</constraint-validator-factory>
<parameter-name-provider>com.acme.ParameterNameProvider</parameter-name-provider>
<clock-provider>com.acme.ClockProvider</clock-provider>
<value-extractor>com.acme.ContainerValueExtractor</value-extractor>
<executable-validation enabled="true">
<default-validated-executable-types>
<executable-type>CONSTRUCTORS</executable-type>
<executable-type>NON_GETTER_METHODS</executable-type>
<executable-type>GETTER_METHODS</executable-type>
</default-validated-executable-types>
</executable-validation>
<constraint-mapping>META-INF/validation/constraints-car.xml</constraint-mapping>
<property name="hibernate.validator.fail_fast">false</property>
</validation-config>
|
There must only be one file named META-INF/validation.xml on the classpath. If more than one is found an exception is thrown. |
The node default-provider
allows to choose the Bean Validation provider. This is useful if there is
more than one provider on the classpath. message-interpolator
, traversable-resolver
,
constraint-validator-factory
, parameter-name-provider
and clock-provider
allow to customize
the used implementations for the interfaces MessageInterpolator
, TraversableResolver
,
ConstraintValidatorFactory
, ParameterNameProvider
and ClockProvider
defined in the
javax.validation
package.
See the sub-sections of Section 9.2, “Configuring a ValidatorFactory
” for more information about these
interfaces.
value-extractor
allows to declare additional value extractors either to extract values from custom
container types or to override the built-in value extractors. See Chapter 7, Value extraction for
more information about how to implement javax.validation.valueextraction.ValueExtractor
.
executable-validation
and its subnodes define defaults for method validation. The Bean Validation
specification defines constructor and non getter methods as defaults. The enabled attribute acts as
global switch to turn method validation on and off (see also Chapter 3, Declaring and validating method constraints).
Via the constraint-mapping
element you can list an arbitrary number of additional XML files
containing the actual constraint configuration. Mapping file names must be specified using their
fully-qualified name on the classpath. Details on writing mapping files can be found in the next
section.
Last but not least, you can specify provider specific properties via the property
nodes. In the
example, we are using the Hibernate Validator specific hibernate.validator.fail_fast
property (see
Section 12.2, “Fail fast mode”).
8.2. Mapping constraints via constraint-mappings
Expressing constraints in XML is possible via files adhering to the schema seen in Figure 2, “Validation mapping schema”. Note that these mapping files are only processed if listed via constraint-mapping in validation.xml.
Example 8.2, “Bean constraints configured via XML” shows how the classes Car and RentalCar from Example 5.3, “Car” resp.
Example 5.9, “Class RentalCar
with redefined default group” could be mapped in XML.
<constraint-mappings
xmlns="http://xmlns.jcp.org/xml/ns/validation/mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/validation/mapping
http://xmlns.jcp.org/xml/ns/validation/mapping/validation-mapping-2.0.xsd"
version="2.0">
<default-package>org.hibernate.validator.referenceguide.chapter05</default-package>
<bean class="Car" ignore-annotations="true">
<field name="manufacturer">
<constraint annotation="javax.validation.constraints.NotNull"/>
</field>
<field name="licensePlate">
<constraint annotation="javax.validation.constraints.NotNull"/>
</field>
<field name="seatCount">
<constraint annotation="javax.validation.constraints.Min">
<element name="value">2</element>
</constraint>
</field>
<field name="driver">
<valid/>
</field>
<field name="partManufacturers">
<container-element-type type-argument-index="0">
<valid/>
</container-element-type>
<container-element-type type-argument-index="1">
<container-element-type>
<valid/>
<constraint annotation="javax.validation.constraints.NotNull"/>
</container-element-type>
</container-element-type>
</field>
<getter name="passedVehicleInspection" ignore-annotations="true">
<constraint annotation="javax.validation.constraints.AssertTrue">
<message>The car has to pass the vehicle inspection first</message>
<groups>
<value>CarChecks</value>
</groups>
<element name="max">10</element>
</constraint>
</getter>
</bean>
<bean class="RentalCar" ignore-annotations="true">
<class ignore-annotations="true">
<group-sequence>
<value>RentalCar</value>
<value>CarChecks</value>
</group-sequence>
</class>
</bean>
<constraint-definition annotation="org.mycompany.CheckCase">
<validated-by include-existing-validators="false">
<value>org.mycompany.CheckCaseValidator</value>
</validated-by>
</constraint-definition>
</constraint-mappings>
Example 8.3, “Method constraints configured via XML” shows how the constraints from Example 3.1, “Declaring method and constructor parameter constraints”, Example 3.4, “Declaring method and constructor return value constraints” and Example 3.3, “Specifying a constraint’s target” can be expressed in XML.
<constraint-mappings
xmlns="http://xmlns.jcp.org/xml/ns/validation/mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/validation/mapping
http://xmlns.jcp.org/xml/ns/validation/mapping/validation-mapping-2.0.xsd"
version="2.0">
<default-package>org.hibernate.validator.referenceguide.chapter08</default-package>
<bean class="RentalStation" ignore-annotations="true">
<constructor>
<return-value>
<constraint annotation="ValidRentalStation"/>
</return-value>
</constructor>
<constructor>
<parameter type="java.lang.String">
<constraint annotation="javax.validation.constraints.NotNull"/>
</parameter>
</constructor>
<method name="getCustomers">
<return-value>
<constraint annotation="javax.validation.constraints.NotNull"/>
<constraint annotation="javax.validation.constraints.Size">
<element name="min">1</element>
</constraint>
</return-value>
</method>
<method name="rentCar">
<parameter type="Customer">
<constraint annotation="javax.validation.constraints.NotNull"/>
</parameter>
<parameter type="java.util.Date">
<constraint annotation="javax.validation.constraints.NotNull"/>
<constraint annotation="javax.validation.constraints.Future"/>
</parameter>
<parameter type="int">
<constraint annotation="javax.validation.constraints.Min">
<element name="value">1</element>
</constraint>
</parameter>
</method>
<method name="addCars">
<parameter type="java.util.List">
<container-element-type>
<valid/>
<constraint annotation="javax.validation.constraints.NotNull"/>
</container-element-type>
</parameter>
</method>
</bean>
<bean class="Garage" ignore-annotations="true">
<method name="buildCar">
<parameter type="java.util.List"/>
<cross-parameter>
<constraint annotation="ELAssert">
<element name="expression">...</element>
<element name="validationAppliesTo">PARAMETERS</element>
</constraint>
</cross-parameter>
</method>
<method name="paintCar">
<parameter type="int"/>
<return-value>
<constraint annotation="ELAssert">
<element name="expression">...</element>
<element name="validationAppliesTo">RETURN_VALUE</element>
</constraint>
</return-value>
</method>
</bean>
</constraint-mappings>
The XML configuration is closely mirroring the programmatic API. For this reason it should suffice
to just add some comments. default-package
is used for all fields where a class name is expected. If
the specified class is not fully qualified the configured default package will be used. Every
mapping file can then have several bean nodes, each describing the constraints on the entity with
the specified class name.
|
A given class can only be configured once across all configuration files. The same applies for
constraint definitions for a given constraint annotation. It can only occur in one mapping file. If
these rules are violated a |
Setting ignore-annotations
to true
means that constraint annotations placed on the configured bean
are ignored. The default for this value is true. ignore-annotations
is also available for the nodes
class
, fields
, getter
, constructor
, method
, parameter
, cross-parameter
and return-value
.
If not explicitly specified on these levels the configured bean value applies.
The nodes class
, field
, getter
, container-element-type
, constructor
and method
(and its sub node parameter) determine on which level the constraint gets placed.
The valid
node is used to enable cascaded validation and the constraint
node to add a constraint
on the corresponding level.
Each constraint definition must define the class via the annotation
attribute.
The constraint attributes required by the Bean Validation specification (message
, groups
and
payload
) have dedicated nodes. All other constraint specific attributes are configured using the
element
node.
|
|
The class
node also allows to reconfigure the default group sequence (see
Section 5.4, “Redefining the default group sequence”) via the group-sequence
node. Not shown in the example is the use
of convert-group
to
specify group conversions (see Section 5.5, “Group conversion”). This node is available on field
,
getter
, container-element-type
, parameter
and return-value
and specifies a from
and a to
attributes to specify the groups.
Last but not least, the list of ConstraintValidator
instances associated to a given constraint
can be altered via the constraint-definition
node. The annotation attribute represents the constraint
annotation being altered. The validated-by
element represent the (ordered) list of ConstraintValidator
implementations associated to the constraint. If include-existing-validator
is set to false
,
validators defined on the constraint annotation are ignored. If set to true
, the list of constraint
validators described in XML is concatenated to the list of validators specified on the annotation.
|
One use case for constraint-definition is to change the default constraint definition for Using XML to register a regular expression based constraint definition for
@URL
|
9. Bootstrapping
In Section 2.2.1, “Obtaining a Validator
instance”, you already saw one way of creating a Validator
instance - via
Validation#buildDefaultValidatorFactory()
. In this chapter, you will learn how to use the other
methods in javax.validation.Validation
in order to bootstrap specifically configured validators.
9.1. Retrieving ValidatorFactory
and Validator
You obtain a Validator
by retrieving a ValidatorFactory
via one of the static methods on
javax.validation.Validation
and calling getValidator()
on the factory instance.
Example 9.1, “Bootstrapping default ValidatorFactory
and Validator
” shows how to obtain a validator from the default
validator factory:
ValidatorFactory
and Validator
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();
|
The generated |
Bean Validation supports working with several providers such as Hibernate Validator within one
application. If more than one provider is present on the classpath, it is not guaranteed which one
is chosen when creating a factory via buildDefaultValidatorFactory()
.
In this case, you can explicitly specify the provider to use via Validation#byProvider()
, passing the
provider’s ValidationProvider
class as shown in Example 9.2, “Bootstrapping ValidatorFactory
and Validator
using a specific provider”.
ValidatorFactory
and Validator
using a specific providerValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure()
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
Note that the configuration object returned by configure()
allows to specifically customize the
factory before calling buildValidatorFactory()
. The available options are discussed later in this
chapter.
Similarly you can retrieve the default validator factory for configuration which is demonstrated in
Example 9.3, “Retrieving the default ValidatorFactory
for configuration”.
ValidatorFactory
for configurationValidatorFactory validatorFactory = Validation.byDefaultProvider()
.configure()
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
|
If a |
9.1.1. ValidationProviderResolver
By default, available Bean Validation providers are discovered using the Java Service Provider mechanism.
For that purpose, each provider includes the file META-
INF/services/javax.validation.spi.ValidationProvider, containing the fully qualified classname of
its ValidationProvider
implementation. In the case of Hibernate Validator, this is
org.hibernate.validator.HibernateValidator
.
Depending on your environment and its classloading specifics, provider discovery via the Java’s
service loader mechanism might not work. In this case, you can plug in a custom
ValidationProviderResolver
implementation which performs the provider retrieval. An example is OSGi,
where you could implement a provider resolver which uses OSGi services for provider discovery.
To use a custom provider resolver, pass it via providerResolver()
as shown in
Example 9.4, “Using a custom ValidationProviderResolver
”.
ValidationProviderResolver
package org.hibernate.validator.referenceguide.chapter09;
public class OsgiServiceDiscoverer implements ValidationProviderResolver {
@Override
public List<ValidationProvider<?>> getValidationProviders() {
//...
return null;
}
}
ValidatorFactory validatorFactory = Validation.byDefaultProvider()
.providerResolver( new OsgiServiceDiscoverer() )
.configure()
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
9.2. Configuring a ValidatorFactory
By default, validator factories retrieved from Validation
and any validators they create are
configured as per the XML descriptor META-INF/validation.xml (see Chapter 8, Configuring via XML),
if present.
If you want to disable the XML based configuration, you can do so by invoking
Configuration#ignoreXmlConfiguration()
.
The different values of the XML configuration can be accessed via
Configuration#getBootstrapConfiguration()
. This can for instance be helpful if you want to integrate
Bean Validation into a managed environment and want to create managed instances of the objects
configured via XML.
Using the fluent configuration API, you can override one or more of the settings when bootstrapping
the factory. The following sections show how to make use of the different options. Note that the
Configuration
class exposes the default implementations of the different extension points which can
be useful if you want to use these as delegates for your custom implementations.
9.2.1. MessageInterpolator
Message interpolators are used by the validation engine to create user readable error messages from constraint message descriptors.
In case the default message interpolation algorithm described in Chapter 4, Interpolating constraint error messages
is not sufficient for your needs, you can pass in your own implementation of the MessageInterpolator
interface via Configuration#messageInterpolator()
as shown in
Example 9.5, “Using a custom MessageInterpolator
”.
MessageInterpolator
package org.hibernate.validator.referenceguide.chapter09;
public class MyMessageInterpolator implements MessageInterpolator {
@Override
public String interpolate(String messageTemplate, Context context) {
//...
return null;
}
@Override
public String interpolate(String messageTemplate, Context context, Locale locale) {
//...
return null;
}
}
ValidatorFactory validatorFactory = Validation.byDefaultProvider()
.configure()
.messageInterpolator( new MyMessageInterpolator() )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
9.2.2. TraversableResolver
In some cases the validation engine should not access the state of a bean property. The most obvious example for that is a lazily loaded property or association of a JPA entity. Validating this lazy property or association would mean that its state would have to be accessed, triggering a load from the database.
Which properties can be accessed and which ones not is controlled by querying the
TraversableResolver
interface. Example 9.6, “Using a custom TraversableResolver
” shows how to use a
custom traversable resolver implementation.
TraversableResolver
package org.hibernate.validator.referenceguide.chapter09;
public class MyTraversableResolver implements TraversableResolver {
@Override
public boolean isReachable(
Object traversableObject,
Node traversableProperty,
Class<?> rootBeanType,
Path pathToTraversableObject,
ElementType elementType) {
//...
return false;
}
@Override
public boolean isCascadable(
Object traversableObject,
Node traversableProperty,
Class<?> rootBeanType,
Path pathToTraversableObject,
ElementType elementType) {
//...
return false;
}
}
ValidatorFactory validatorFactory = Validation.byDefaultProvider()
.configure()
.traversableResolver( new MyTraversableResolver() )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
If no specific traversable resolver has been configured, the default behavior is to consider all properties as reachable and cascadable. When using Hibernate Validator together with a JPA 2 provider such as Hibernate ORM, only those properties will be considered reachable which already have been loaded by the persistence provider and all properties will be considered cascadable.
By default, the traversable resolver calls are cached per validation call.
This is especially important in a JPA environment where calling isReachable()
has a significant cost.
This caching adds some overhead. In the case your custom traversable resolver is very fast, it might be better to consider turning off the cache.
You can disable the cache either via the XML configuration:
TraversableResolver
result cache via the XML configuration<?xml version="1.0" encoding="UTF-8"?>
<validation-config
xmlns="http://xmlns.jcp.org/xml/ns/validation/configuration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/validation/configuration validation-configuration-2.0.xsd"
version="2.0">
<default-provider>org.hibernate.validator.HibernateValidator</default-provider>
<property name="hibernate.validator.enable_traversable_resolver_result_cache">false</property>
</validation-config>
or via the programmatic API:
TraversableResolver
result cache via the programmatic APIValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure()
.traversableResolver( new MyFastTraversableResolver() )
.enableTraversableResolverResultCache( false )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
9.2.3. ConstraintValidatorFactory
ConstraintValidatorFactory
is the extension point for customizing how constraint validators are
instantiated and released.
The default ConstraintValidatorFactory
provided by Hibernate Validator requires a public no-arg
constructor to instantiate ConstraintValidator
instances (see Section 6.1.2, “The constraint validator”).
Using a custom ConstraintValidatorFactory
offers for example the possibility to use dependency
injection in constraint validator implementations.
To configure a custom constraint validator factory call Configuration#constraintValidatorFactory()
(see Example 9.9, “Using a custom ConstraintValidatorFactory
”.
ConstraintValidatorFactory
package org.hibernate.validator.referenceguide.chapter09;
public class MyConstraintValidatorFactory implements ConstraintValidatorFactory {
@Override
public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
//...
return null;
}
@Override
public void releaseInstance(ConstraintValidator<?, ?> instance) {
//...
}
}
ValidatorFactory validatorFactory = Validation.byDefaultProvider()
.configure()
.constraintValidatorFactory( new MyConstraintValidatorFactory() )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
|
Any constraint implementations relying on |
|
|
9.2.4. ParameterNameProvider
In case a method or constructor parameter constraint is violated, the ParameterNameProvider
interface is used to retrieve the parameter name and make it available to the user via the
property path of the constraint violation.
The default implementation returns parameter names as obtained through the Java reflection API.
If you compile your sources using the -parameters
compiler flag, the actual parameter names as
in the source code will be returned. Otherwise synthetic names in the form of arg0
, arg1
etc.
will be used.
To use a custom parameter name provider either pass an instance of
the provider during bootstrapping as shown in Example 9.10, “Using a custom ParameterNameProvider
”,
or specify the fully qualified class name of the provider as value for
the <parameter-name-provider>
element in the META-INF/validation.xml file
(see Section 8.1, “Configuring the validator factory in validation.xml”). This is demonstrated in
Example 9.10, “Using a custom ParameterNameProvider
”.
ParameterNameProvider
package org.hibernate.validator.referenceguide.chapter09;
public class MyParameterNameProvider implements ParameterNameProvider {
@Override
public List<String> getParameterNames(Constructor<?> constructor) {
//...
return null;
}
@Override
public List<String> getParameterNames(Method method) {
//...
return null;
}
}
ValidatorFactory validatorFactory = Validation.byDefaultProvider()
.configure()
.parameterNameProvider( new MyParameterNameProvider() )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
|
Hibernate Validator comes with a custom |
9.2.5. ClockProvider
and temporal validation tolerance
For time related validation (@Past
and @Future
constraints for instance), it might be useful to define what is
considered now
.
This is especially important when you want to test your constraints in a reliable manner.
The reference time is defined by the ClockProvider
contract. The responsibility of the ClockProvider
is to
provide a java.time.Clock
defining now
for time related validators.
ClockProvider
package org.hibernate.validator.referenceguide.chapter09;
import java.time.Clock;
import java.time.ZonedDateTime;
import javax.validation.ClockProvider;
public class FixedClockProvider implements ClockProvider {
private Clock clock;
public FixedClockProvider(ZonedDateTime dateTime) {
clock = Clock.fixed( dateTime.toInstant(), dateTime.getZone() );
}
@Override
public Clock getClock() {
return clock;
}
}
ValidatorFactory validatorFactory = Validation.byDefaultProvider()
.configure()
.clockProvider( new FixedClockProvider( ZonedDateTime.of( 2016, 6, 15, 0, 0, 0, 0, ZoneId.of( "Europe/Paris" ) ) ) )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
Alternatively, you can specify the fully-qualified classname of a ClockProvider
implementation using the
<clock-provider>
element when configuring the default validator factory via META-INF/validation.xml
(see Chapter 8, Configuring via XML).
|
When validating You can obtain the For instance, this might be useful if you want to replace the default message of the |
When dealing with distributed architectures, you might need some tolerance when applying temporal constraints
such as @Past
or @Future
.
You can set a temporal validation tolerance by bootstrapping your ValidatorFactory
as below:
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure()
.temporalValidationTolerance( Duration.ofMillis( 10 ) )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
Alternatively, you can define it in the XML configuration by setting the hibernate.validator.temporal_validation_tolerance
property
in your META-INF/validation.xml.
The value of this property must be a long
defining the tolerance in milliseconds.
|
When implementing your own temporal constraints, you might need to have access to the temporal validation tolerance. It can be obtained by calling the Note that to get access to this context at initialization, your constraint validator has to implement
the |
9.2.6. Registering ValueExtractor
s
As mentioned in Chapter 7, Value extraction, additional value extractors can be registered during bootstrapping
(see Section 7.5, “Registering a ValueExtractor
” for the other ways to register a value extractor).
Example 9.13, “Registering additional value extractors” shows how we would register the value extractors we previously created
to extract the keys and the values of Guava’s Multimap
.
ValidatorFactory validatorFactory = Validation.byDefaultProvider()
.configure()
.addValueExtractor( new MultimapKeyValueExtractor() )
.addValueExtractor( new MultimapValueValueExtractor() )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
9.2.7. Adding mapping streams
As discussed earlier, you can configure the constraints applied to your Java beans using XML based constraint mappings.
Besides the mapping files specified in META-INF/validation.xml, you can add further mappings via
Configuration#addMapping()
(see Example 9.14, “Adding constraint mapping streams”). Note that the passed input
stream(s) must adhere to the XML schema for constraint mappings presented in
Section 8.2, “Mapping constraints via constraint-mappings
”.
InputStream constraintMapping1 = null;
InputStream constraintMapping2 = null;
ValidatorFactory validatorFactory = Validation.byDefaultProvider()
.configure()
.addMapping( constraintMapping1 )
.addMapping( constraintMapping2 )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
You should close any passed input stream after the validator factory has been created.
9.2.8. Provider-specific settings
Via the configuration object returned by Validation#byProvider()
, provider specific options can be
configured.
In the case of Hibernate Validator, this e.g. allows you to enable the fail fast mode and pass one or more programmatic constraint mappings as demonstrated in Example 9.15, “Setting Hibernate Validator specific options”.
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure()
.failFast( true )
.addMapping( (ConstraintMapping) null )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
Alternatively, provider-specific options can be passed via Configuration#addProperty()
. Hibernate
Validator supports enabling the fail fast mode that way, too:
addProperty()
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure()
.addProperty( "hibernate.validator.fail_fast", "true" )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
Refer to Section 12.2, “Fail fast mode” and Section 12.4, “Programmatic constraint definition and declaration” to learn more about the fail fast mode and the constraint declaration API.
9.2.9. Configuring the ScriptEvaluatorFactory
For constraints like @ScriptAssert
and @ParameterScriptAssert
, it might be useful to configure
how the script engines are initialized and how the script evaluators are built.
This can be done by setting a custom implementation of ScriptEvaluatorFactory
.
In particular, this is important for modular environments (e.g. OSGi), where user might face issues with modular class loading and JSR 223. It also allows to use any custom script engine, not necessarily based on the JSR 223 (e.g. Spring Expression Language).
<IP_ADDRESS>. XML configuration
To specify the ScriptEvaluatorFactory
via XML, you need to define the hibernate.validator.script_evaluator_factory
property.
ScriptEvaluatorFactory
via XML<validation-config
xmlns="http://xmlns.jcp.org/xml/ns/validation/configuration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/validation/configuration
http://xmlns.jcp.org/xml/ns/validation/configuration/validation-configuration-2.0.xsd"
version="2.0">
<property name="hibernate.validator.script_evaluator_factory">
org.hibernate.validator.referenceguide.chapter09.CustomScriptEvaluatorFactory
</property>
</validation-config>
In this case, the specified ScriptEvaluatorFactory
must have a no-arg constructor.
<IP_ADDRESS>. Programmatic configuration
To configure it programmatically, you need to pass an instance of ScriptEvaluatorFactory
to the ValidatorFactory
.
This gives more flexibility in the configuration of the ScriptEvaluatorFactory
. Example 9.18, “Defining the ScriptEvaluatorFactory
programmatically”
shows how this can be done.
ScriptEvaluatorFactory
programmaticallyValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure()
.scriptEvaluatorFactory( new CustomScriptEvaluatorFactory() )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
<IP_ADDRESS>. Custom ScriptEvaluatorFactory
implementation examples
This section shows a couple of custom ScriptEvaluatorFactory
implementations that can be used
in modular environments as well as one using the Spring Expression Language
for writing constraint scripts.
Problems with modular environments and JSR 223 come from the class loading. The class loader where the script engine is available might be different from the one of Hibernate Validator. Thus the script engine wouldn’t be found using the default strategy.
To solve this issue, the MultiClassLoaderScriptEvaluatorFactory
class below can be introduced:
/*
* Hibernate Validator, declare and validate application constraints
*
* License: Apache License, Version 2.0
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
*/
package org.hibernate.validator.osgi.scripting;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import org.hibernate.validator.spi.scripting.AbstractCachingScriptEvaluatorFactory;
import org.hibernate.validator.spi.scripting.ScriptEngineScriptEvaluator;
import org.hibernate.validator.spi.scripting.ScriptEvaluationException;
import org.hibernate.validator.spi.scripting.ScriptEvaluator;
import org.hibernate.validator.spi.scripting.ScriptEvaluatorFactory;
/**
* {@link ScriptEvaluatorFactory} that allows you to pass multiple {@link ClassLoader}s that will be used
* to search for {@link ScriptEngine}s. Useful in environments similar to OSGi, where script engines can be
* found only in {@link ClassLoader}s different from default one.
*
* @author Marko Bekhta
*/
public class MultiClassLoaderScriptEvaluatorFactory extends AbstractCachingScriptEvaluatorFactory {
private final ClassLoader[] classLoaders;
public MultiClassLoaderScriptEvaluatorFactory(ClassLoader... classLoaders) {
if ( classLoaders.length == 0 ) {
throw new IllegalArgumentException( "No class loaders were passed" );
}
this.classLoaders = classLoaders;
}
@Override
protected ScriptEvaluator createNewScriptEvaluator(String languageName) {
for ( ClassLoader classLoader : classLoaders ) {
ScriptEngine engine = new ScriptEngineManager( classLoader ).getEngineByName( languageName );
if ( engine != null ) {
return new ScriptEngineScriptEvaluator( engine );
}
}
throw new ScriptEvaluationException( "No JSR 223 script engine found for language " + languageName );
}
}
and then declared with:
Validator validator = Validation.byProvider( HibernateValidator.class )
.configure()
.scriptEvaluatorFactory(
new MultiClassLoaderScriptEvaluatorFactory( GroovyScriptEngineFactory.class.getClassLoader() )
)
.buildValidatorFactory()
.getValidator();
This way, it is possible to pass multiple ClassLoader
instances: typically the class loaders of the wanted ScriptEngine
s.
An alternative approach for OSGi environments can be to use the OsgiScriptEvaluatorFactory
defined below:
/*
* Hibernate Validator, declare and validate application constraints
*
* License: Apache License, Version 2.0
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
*/
package org.hibernate.validator.osgi.scripting;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import javax.validation.ValidationException;
import org.hibernate.validator.spi.scripting.AbstractCachingScriptEvaluatorFactory;
import org.hibernate.validator.spi.scripting.ScriptEngineScriptEvaluator;
import org.hibernate.validator.spi.scripting.ScriptEvaluator;
import org.hibernate.validator.spi.scripting.ScriptEvaluatorFactory;
import org.hibernate.validator.spi.scripting.ScriptEvaluatorNotFoundException;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
/**
* {@link ScriptEvaluatorFactory} suitable for OSGi environments. It is created
* based on the {@code BundleContext} which is used to iterate through {@code Bundle}s and find all {@link ScriptEngineFactory}
* candidates.
*
* @author Marko Bekhta
*/
public class OsgiScriptEvaluatorFactory extends AbstractCachingScriptEvaluatorFactory {
private final List<ScriptEngineManager> scriptEngineManagers;
public OsgiScriptEvaluatorFactory(BundleContext context) {
this.scriptEngineManagers = Collections.unmodifiableList( findManagers( context ) );
}
@Override
protected ScriptEvaluator createNewScriptEvaluator(String languageName) throws ScriptEvaluatorNotFoundException {
return scriptEngineManagers.stream()
.map( manager -> manager.getEngineByName( languageName ) )
.filter( Objects::nonNull )
.map( engine -> new ScriptEngineScriptEvaluator( engine ) )
.findFirst()
.orElseThrow( () -> new ValidationException( String.format( "Unable to find script evaluator for '%s'.", languageName ) ) );
}
private List<ScriptEngineManager> findManagers(BundleContext context) {
return findFactoryCandidates( context ).stream()
.map( className -> {
try {
return new ScriptEngineManager( Class.forName( className ).getClassLoader() );
}
catch (ClassNotFoundException e) {
throw new ValidationException( "Unable to instantiate '" + className + "' based engine factory manager.", e );
}
} ).collect( Collectors.toList() );
}
/**
* Iterates through all bundles to get the available {@link ScriptEngineFactory} classes
*
* @return the names of the available ScriptEngineFactory classes
*
* @throws IOException
*/
private List<String> findFactoryCandidates(BundleContext context) {
return Arrays.stream( context.getBundles() )
.filter( Objects::nonNull )
.filter( bundle -> !"system.bundle".equals( bundle.getSymbolicName() ) )
.flatMap( this::toStreamOfResourcesURL )
.filter( Objects::nonNull )
.flatMap( url -> toListOfFactoryCandidates( url ).stream() )
.collect( Collectors.toList() );
}
private Stream<URL> toStreamOfResourcesURL(Bundle bundle) {
Enumeration<URL> entries = bundle.findEntries(
"META-INF/services",
"javax.script.ScriptEngineFactory",
false
);
return entries != null ? Collections.list( entries ).stream() : Stream.empty();
}
private List<String> toListOfFactoryCandidates(URL url) {
try ( BufferedReader reader = new BufferedReader( new InputStreamReader( url.openStream(), "UTF-8" ) ) ) {
return reader.lines()
.map( String::trim )
.filter( line -> !line.isEmpty() )
.filter( line -> !line.startsWith( "#" ) )
.collect( Collectors.toList() );
}
catch (IOException e) {
throw new ValidationException( "Unable to read the ScriptEngineFactory resource file", e );
}
}
}
and then declared with:
Validator validator = Validation.byProvider( HibernateValidator.class )
.configure()
.scriptEvaluatorFactory(
new OsgiScriptEvaluatorFactory( FrameworkUtil.getBundle( this.getClass() ).getBundleContext() )
)
.buildValidatorFactory()
.getValidator();
It is designed specifically for OSGi environments and allows you to pass the BundleContext
which will be used to search
for ScriptEngineFactory
as a parameter.
As already mentioned, you can also use script engines that are not based on JSR 223.
For instance, to use the Spring Expression Language,
you can define a SpringELScriptEvaluatorFactory
as:
package org.hibernate.validator.referenceguide.chapter09;
public class SpringELScriptEvaluatorFactory extends AbstractCachingScriptEvaluatorFactory {
@Override
public ScriptEvaluator createNewScriptEvaluator(String languageName) {
if ( !"spring".equalsIgnoreCase( languageName ) ) {
throw new IllegalStateException( "Only Spring EL is supported" );
}
return new SpringELScriptEvaluator();
}
private static class SpringELScriptEvaluator implements ScriptEvaluator {
private final ExpressionParser expressionParser = new SpelExpressionParser();
@Override
public Object evaluate(String script, Map<String, Object> bindings) throws ScriptEvaluationException {
try {
Expression expression = expressionParser.parseExpression( script );
EvaluationContext context = new StandardEvaluationContext( bindings.values().iterator().next() );
for ( Entry<String, Object> binding : bindings.entrySet() ) {
context.setVariable( binding.getKey(), binding.getValue() );
}
return expression.getValue( context );
}
catch (ParseException | EvaluationException e) {
throw new ScriptEvaluationException( "Unable to evaluate SpEL script", e );
}
}
}
}
This factory allows to use Spring Expression Language in ScriptAssert
and ParameterScriptAssert
constraints:
@ScriptAssert(script = "value > 0", lang = "spring")
public class Foo {
private final int value;
private Foo(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
9.3. Configuring a Validator
When working with a configured validator factory it can occasionally be required to apply a
different configuration to a single Validator
instance. Example 9.25, “Configuring a Validator
instance via usingContext()
” shows how this can
be achieved by calling ValidatorFactory#usingContext()
.
Validator
instance via usingContext()
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.usingContext()
.messageInterpolator( new MyMessageInterpolator() )
.traversableResolver( new MyTraversableResolver() )
.getValidator();
10. Using constraint metadata
The Bean Validation specification provides not only a validation engine, but also an API for
retrieving constraint metadata in a uniform way, no matter whether the constraints are declared
using annotations or via XML mappings. Read this chapter to learn more about this API and its
possibilities. You can find all the metadata API types in the package javax.validation.metadata
.
The examples presented in this chapter are based on the classes and constraint declarations shown in Example 10.1, “Example classes”.
package org.hibernate.validator.referenceguide.chapter10;
public class Person {
public interface Basic {
}
@NotNull
private String name;
//getters and setters ...
}
package org.hibernate.validator.referenceguide.chapter10;
public interface Vehicle {
public interface Basic {
}
@NotNull(groups = Vehicle.Basic.class)
String getManufacturer();
}
package org.hibernate.validator.referenceguide.chapter10;
@ValidCar
public class Car implements Vehicle {
public interface SeverityInfo extends Payload {
}
private String manufacturer;
@NotNull
@Size(min = 2, max = 14)
private String licensePlate;
private Person driver;
private String modelName;
public Car() {
}
public Car(
@NotNull String manufacturer,
String licencePlate,
Person driver,
String modelName) {
this.manufacturer = manufacturer;
this.licensePlate = licencePlate;
this.driver = driver;
this.modelName = modelName;
}
public void driveAway(@Max(75) int speed) {
//...
}
@LuggageCountMatchesPassengerCount(
piecesOfLuggagePerPassenger = 2,
validationAppliesTo = ConstraintTarget.PARAMETERS,
payload = SeverityInfo.class,
message = "There must not be more than {piecesOfLuggagePerPassenger} pieces " +
"of luggage per passenger."
)
public void load(List<Person> passengers, List<PieceOfLuggage> luggage) {
//...
}
@Override
@Size(min = 3)
public String getManufacturer() {
return manufacturer;
}
public void setManufacturer(String manufacturer) {
this.manufacturer = manufacturer;
}
@Valid
@ConvertGroup(from = Default.class, to = Person.Basic.class)
public Person getDriver() {
return driver;
}
//further getters and setters...
}
package org.hibernate.validator.referenceguide.chapter10;
public class Library {
@NotNull
private String name;
private List<@NotNull @Valid Book> books;
//getters and setters ...
}
package org.hibernate.validator.referenceguide.chapter10;
public class Book {
@NotEmpty
private String title;
@NotEmpty
private String author;
//getters and setters ...
}
10.1. BeanDescriptor
The entry point into the metadata API is the method Validator#getConstraintsForClass()
, which
returns an instance of the BeanDescriptor
interface. Using this
descriptor, you can obtain metadata for constraints declared directly on the bean itself (class- or
property-level), but also retrieve metadata descriptors representing single properties, methods and
constructors.
Example 10.2, “Using BeanDescriptor
” demonstrates how to retrieve a BeanDescriptor
for the
Car
class and how to use this descriptor in form of assertions.
|
If a constraint declaration hosted by the requested class is invalid, a |
BeanDescriptor
BeanDescriptor carDescriptor = validator.getConstraintsForClass( Car.class );
assertTrue( carDescriptor.isBeanConstrained() );
//one class-level constraint
assertEquals( 1, carDescriptor.getConstraintDescriptors().size() );
//manufacturer, licensePlate, driver
assertEquals( 3, carDescriptor.getConstrainedProperties().size() );
//property has constraint
assertNotNull( carDescriptor.getConstraintsForProperty( "licensePlate" ) );
//property is marked with @Valid
assertNotNull( carDescriptor.getConstraintsForProperty( "driver" ) );
//constraints from getter method in interface and implementation class are returned
assertEquals(
2,
carDescriptor.getConstraintsForProperty( "manufacturer" )
.getConstraintDescriptors()
.size()
);
//property is not constrained
assertNull( carDescriptor.getConstraintsForProperty( "modelName" ) );
//driveAway(int), load(List<Person>, List<PieceOfLuggage>)
assertEquals( 2, carDescriptor.getConstrainedMethods( MethodType.NON_GETTER ).size() );
//driveAway(int), getManufacturer(), getDriver(), load(List<Person>, List<PieceOfLuggage>)
assertEquals(
4,
carDescriptor.getConstrainedMethods( MethodType.NON_GETTER, MethodType.GETTER )
.size()
);
//driveAway(int)
assertNotNull( carDescriptor.getConstraintsForMethod( "driveAway", int.class ) );
//getManufacturer()
assertNotNull( carDescriptor.getConstraintsForMethod( "getManufacturer" ) );
//setManufacturer() is not constrained
assertNull( carDescriptor.getConstraintsForMethod( "setManufacturer", String.class ) );
//Car(String, String, Person, String)
assertEquals( 1, carDescriptor.getConstrainedConstructors().size() );
//Car(String, String, Person, String)
assertNotNull(
carDescriptor.getConstraintsForConstructor(
String.class,
String.class,
Person.class,
String.class
)
);
You can determine whether the specified class hosts any class- or property-level constraints via
isBeanConstrained()
. Method or constructor constraints are not considered by isBeanConstrained()
.
The method getConstraintDescriptors()
is common to all descriptors derived from ElementDescriptor
(see Section 10.4, “ElementDescriptor
”) and returns a set of descriptors representing the
constraints directly declared on the given element. In case of BeanDescriptor
, the bean’s class-
level constraints are returned. More details on ConstraintDescriptor
can be found in
Section 10.7, “ConstraintDescriptor
”.
Via getConstraintsForProperty()
, getConstraintsForMethod()
and getConstraintsForConstructor()
you
can obtain a descriptor representing one given property or executable element, identified by its
name and, in case of methods and constructors, parameter types. The different descriptor types
returned by these methods are described in the following sections.
Note that these methods consider constraints declared at super-types according to the rules for
constraint inheritance as described in Section 2.1.5, “Constraint inheritance”. An example is the
descriptor for the manufacturer
property, which provides access to all constraints defined on
Vehicle#getManufacturer()
and the implementing method Car#getManufacturer()
. null
is returned in
case the specified element does not exist or is not constrained.
The methods getConstrainedProperties()
, getConstrainedMethods()
and getConstrainedConstructors()
return (potentially empty) sets with all constrained properties, methods and constructors,
respectively. An element is considered constrained if it has at least one constraint or is marked
for cascaded validation. When invoking getConstrainedMethods()
, you can specify the type of the
methods to be returned (getters, non-getters or both).
10.2. PropertyDescriptor
The interface
PropertyDescriptor
represents one given property of a
class. It is transparent whether constraints are declared on a field or a property getter, provided
the JavaBeans naming conventions are respected. Example 10.3, “Using PropertyDescriptor
” shows
how to use the PropertyDescriptor
interface.
PropertyDescriptor
PropertyDescriptor licensePlateDescriptor = carDescriptor.getConstraintsForProperty(
"licensePlate"
);
//"licensePlate" has two constraints, is not marked with @Valid and defines no group conversions
assertEquals( "licensePlate", licensePlateDescriptor.getPropertyName() );
assertEquals( 2, licensePlateDescriptor.getConstraintDescriptors().size() );
assertTrue( licensePlateDescriptor.hasConstraints() );
assertFalse( licensePlateDescriptor.isCascaded() );
assertTrue( licensePlateDescriptor.getGroupConversions().isEmpty() );
PropertyDescriptor driverDescriptor = carDescriptor.getConstraintsForProperty( "driver" );
//"driver" has no constraints, is marked with @Valid and defines one group conversion
assertEquals( "driver", driverDescriptor.getPropertyName() );
assertTrue( driverDescriptor.getConstraintDescriptors().isEmpty() );
assertFalse( driverDescriptor.hasConstraints() );
assertTrue( driverDescriptor.isCascaded() );
assertEquals( 1, driverDescriptor.getGroupConversions().size() );
Using getConstraintDescriptors()
, you can retrieve a set of ConstraintDescriptors
providing more
information on the individual constraints of a given property. The method isCascaded()
returns
true
if the property is marked for cascaded validation (either using the @Valid
annotation or via
XML), false
otherwise. Any configured group conversions are returned by getGroupConversions()
. See
Section 10.6, “GroupConversionDescriptor
” for more details on GroupConversionDescriptor
.
10.3. MethodDescriptor
and ConstructorDescriptor
Constrained methods and constructors are represented by the interfaces
MethodDescriptor
ConstructorDescriptor
, respectively.
Example 10.4, “Using MethodDescriptor
and ConstructorDescriptor
” demonstrates how to work with these
descriptors.
MethodDescriptor
and ConstructorDescriptor
//driveAway(int) has a constrained parameter and an unconstrained return value
MethodDescriptor driveAwayDescriptor = carDescriptor.getConstraintsForMethod(
"driveAway",
int.class
);
assertEquals( "driveAway", driveAwayDescriptor.getName() );
assertTrue( driveAwayDescriptor.hasConstrainedParameters() );
assertFalse( driveAwayDescriptor.hasConstrainedReturnValue() );
//always returns an empty set; constraints are retrievable by navigating to
//one of the sub-descriptors, e.g. for the return value
assertTrue( driveAwayDescriptor.getConstraintDescriptors().isEmpty() );
ParameterDescriptor speedDescriptor = driveAwayDescriptor.getParameterDescriptors()
.get( 0 );
//The "speed" parameter is located at index 0, has one constraint and is not cascaded
//nor does it define group conversions
assertEquals( "speed", speedDescriptor.getName() );
assertEquals( 0, speedDescriptor.getIndex() );
assertEquals( 1, speedDescriptor.getConstraintDescriptors().size() );
assertFalse( speedDescriptor.isCascaded() );
assert speedDescriptor.getGroupConversions().isEmpty();
//getDriver() has no constrained parameters but its return value is marked for cascaded
//validation and declares one group conversion
MethodDescriptor getDriverDescriptor = carDescriptor.getConstraintsForMethod(
"getDriver"
);
assertFalse( getDriverDescriptor.hasConstrainedParameters() );
assertTrue( getDriverDescriptor.hasConstrainedReturnValue() );
ReturnValueDescriptor returnValueDescriptor = getDriverDescriptor.getReturnValueDescriptor();
assertTrue( returnValueDescriptor.getConstraintDescriptors().isEmpty() );
assertTrue( returnValueDescriptor.isCascaded() );
assertEquals( 1, returnValueDescriptor.getGroupConversions().size() );
//load(List<Person>, List<PieceOfLuggage>) has one cross-parameter constraint
MethodDescriptor loadDescriptor = carDescriptor.getConstraintsForMethod(
"load",
List.class,
List.class
);
assertTrue( loadDescriptor.hasConstrainedParameters() );
assertFalse( loadDescriptor.hasConstrainedReturnValue() );
assertEquals(
1,
loadDescriptor.getCrossParameterDescriptor().getConstraintDescriptors().size()
);
//Car(String, String, Person, String) has one constrained parameter
ConstructorDescriptor constructorDescriptor = carDescriptor.getConstraintsForConstructor(
String.class,
String.class,
Person.class,
String.class
);
assertEquals( "Car", constructorDescriptor.getName() );
assertFalse( constructorDescriptor.hasConstrainedReturnValue() );
assertTrue( constructorDescriptor.hasConstrainedParameters() );
assertEquals(
1,
constructorDescriptor.getParameterDescriptors()
.get( 0 )
.getConstraintDescriptors()
.size()
);
getName()
returns the name of the given method or constructor. The methods
hasConstrainedParameters()
and hasConstrainedReturnValue()
can be used to perform a quick check
whether an executable element has any parameter constraints (either constraints on single parameters
or cross-parameter constraints) or return value constraints.
Note that constraints are not directly exposed on MethodDescriptor
and ConstructorDescriptor
,
but rather on dedicated descriptors representing an executable’s parameters, its return value and
its cross-parameter constraints. To get hold of one of these descriptors, invoke
getParameterDescriptors()
, getReturnValueDescriptor()
or getCrossParameterDescriptor()
,
respectively.
These descriptors provide access to the element’s constraints (getConstraintDescriptors()
) and, in
the case of parameters and return value, to its configuration for cascaded validation (isValid()
and
getGroupConversions()
). For parameters, you also can retrieve the index and the name, as returned by
the currently used parameter name provider (see Section 9.2.4, “ParameterNameProvider
”) via getName()
and getIndex()
.
|
Getter methods following the JavaBeans naming conventions are considered as bean properties but also as constrained methods. That means you can retrieve the related metadata either by obtaining a |
10.4. ElementDescriptor
The ElementDescriptor
interface is the common base class for the
individual descriptor types such as BeanDescriptor
, PropertyDescriptor
etc. Besides
getConstraintDescriptors()
it provides some more methods common to all descriptors.
hasConstraints()
allows for a quick check whether an element has any direct constraints (e.g. class-
level constraints in case of BeanDescriptor
).
getElementClass()
returns the Java type of the element
represented by a given descriptor. More specifically, the method returns
-
the object type when invoked on
BeanDescriptor
, -
the type of a property or parameter when invoked on
PropertyDescriptor
orParameterDescriptor
respectively, -
Object[].class
when invoked onCrossParameterDescriptor
, -
the return type when invoked on
ConstructorDescriptor
,MethodDescriptor
orReturnValueDescriptor
.void.class
will be returned for methods which don’t have a return value.
Example 10.5, “Using ElementDescriptor methods
” shows how these methods are used.
ElementDescriptor methods
PropertyDescriptor manufacturerDescriptor = carDescriptor.getConstraintsForProperty(
"manufacturer"
);
assertTrue( manufacturerDescriptor.hasConstraints() );
assertEquals( String.class, manufacturerDescriptor.getElementClass() );
CrossParameterDescriptor loadCrossParameterDescriptor = carDescriptor.getConstraintsForMethod(
"load",
List.class,
List.class
).getCrossParameterDescriptor();
assertTrue( loadCrossParameterDescriptor.hasConstraints() );
assertEquals( Object[].class, loadCrossParameterDescriptor.getElementClass() );
Finally, ElementDescriptor
offers access to the ConstraintFinder
API which allows you to query for
constraint metadata in a fine grained way. Example 10.6, “Usage of ConstraintFinder
” shows how to retrieve a
ConstraintFinder
instance via findConstraints()
and use the API to query for constraint metadata.
ConstraintFinder
PropertyDescriptor manufacturerDescriptor = carDescriptor.getConstraintsForProperty(
"manufacturer"
);
//"manufacturer" constraints are declared on the getter, not the field
assertTrue(
manufacturerDescriptor.findConstraints()
.declaredOn( ElementType.FIELD )
.getConstraintDescriptors()
.isEmpty()
);
//@NotNull on Vehicle#getManufacturer() is part of another group
assertEquals(
1,
manufacturerDescriptor.findConstraints()
.unorderedAndMatchingGroups( Default.class )
.getConstraintDescriptors()
.size()
);
//@Size on Car#getManufacturer()
assertEquals(
1,
manufacturerDescriptor.findConstraints()
.lookingAt( Scope.LOCAL_ELEMENT )
.getConstraintDescriptors()
.size()
);
//@Size on Car#getManufacturer() and @NotNull on Vehicle#getManufacturer()
assertEquals(
2,
manufacturerDescriptor.findConstraints()
.lookingAt( Scope.HIERARCHY )
.getConstraintDescriptors()
.size()
);
//Combining several filter options
assertEquals(
1,
manufacturerDescriptor.findConstraints()
.declaredOn( ElementType.METHOD )
.lookingAt( Scope.HIERARCHY )
.unorderedAndMatchingGroups( Vehicle.Basic.class )
.getConstraintDescriptors()
.size()
);
Via declaredOn()
you can search for ConstraintDescriptors
declared on certain element types. This is
useful to find property constraints declared on either fields or getter methods.
unorderedAndMatchingGroups()
restricts the resulting constraints to those matching the given
validation group(s).
lookingAt()
allows to distinguish between constraints directly specified on the element
(Scope.LOCAL_ELEMENT
) or constraints belonging to the element but hosted anywhere in the class
hierarchy (Scope.HIERARCHY
).
You can also combine the different options as shown in the last example.
|
Order is not respected by |
10.5. ContainerDescriptor
and ContainerElementTypeDescriptor
The ContainerDescriptor
interface is the common interface for all the elements that support container element constraints and cascading
validation (PropertyDescriptor
, ParameterDescriptor
, ReturnValueDescriptor
).
It has a single method getConstrainedContainerElementTypes()
that returns a set of ContainerElementTypeDescriptor
.
ContainerElementTypeDescriptor
extends ContainerDescriptor
to support nested container element constraints.
ContainerElementTypeDescriptor
contains the information about the container, the constraints and the cascading
validation.
Example 10.7, “Using ContainerElementTypeDescriptor
” shows how to use getConstrainedContainerElementTypes()
to retrieve the set of ContainerElementTypeDescriptor
.
ContainerElementTypeDescriptor
PropertyDescriptor booksDescriptor = libraryDescriptor.getConstraintsForProperty(
"books"
);
Set<ContainerElementTypeDescriptor> booksContainerElementTypeDescriptors =
booksDescriptor.getConstrainedContainerElementTypes();
ContainerElementTypeDescriptor booksContainerElementTypeDescriptor =
booksContainerElementTypeDescriptors.iterator().next();
assertTrue( booksContainerElementTypeDescriptor.hasConstraints() );
assertTrue( booksContainerElementTypeDescriptor.isCascaded() );
assertEquals(
0,
booksContainerElementTypeDescriptor.getTypeArgumentIndex().intValue()
);
assertEquals(
List.class,
booksContainerElementTypeDescriptor.getContainerClass()
);
Set<ConstraintDescriptor<?>> constraintDescriptors =
booksContainerElementTypeDescriptor.getConstraintDescriptors();
ConstraintDescriptor<?> constraintDescriptor =
constraintDescriptors.iterator().next();
assertEquals(
NotNull.class,
constraintDescriptor.getAnnotation().annotationType()
);
10.6. GroupConversionDescriptor
All those descriptor types that represent elements which can be subject of cascaded validation
(i.e., PropertyDescriptor
, ParameterDescriptor
and ReturnValueDescriptor
) provide access to the
element’s group conversions via getGroupConversions()
. The returned set contains a
GroupConversionDescriptor
for each configured conversion, allowing to retrieve
source and target groups of the conversion. Example 10.8, “Using GroupConversionDescriptor
”
shows an example.
GroupConversionDescriptor
PropertyDescriptor driverDescriptor = carDescriptor.getConstraintsForProperty( "driver" );
Set<GroupConversionDescriptor> groupConversions = driverDescriptor.getGroupConversions();
assertEquals( 1, groupConversions.size() );
GroupConversionDescriptor groupConversionDescriptor = groupConversions.iterator()
.next();
assertEquals( Default.class, groupConversionDescriptor.getFrom() );
assertEquals( Person.Basic.class, groupConversionDescriptor.getTo() );
10.7. ConstraintDescriptor
Last but not least, the
ConstraintDescriptor
interface describes a
single constraint together with its composing constraints. Via an instance of this interface you get
access to the constraint annotation and its parameters.
Example 10.9, “Using ConstraintDescriptor
”
shows how to retrieve default constraint attributes (such as message template, groups etc.) as well
as custom constraint attributes (piecesOfLuggagePerPassenger
) and other metadata such as the
constraint’s annotation type and its validators from a ConstraintDescriptor
.
ConstraintDescriptor
//descriptor for the @LuggageCountMatchesPassengerCount constraint on the
//load(List<Person>, List<PieceOfLuggage>) method
ConstraintDescriptor<?> constraintDescriptor = carDescriptor.getConstraintsForMethod(
"load",
List.class,
List.class
).getCrossParameterDescriptor().getConstraintDescriptors().iterator().next();
//constraint type
assertEquals(
LuggageCountMatchesPassengerCount.class,
constraintDescriptor.getAnnotation().annotationType()
);
//standard constraint attributes
assertEquals( SeverityInfo.class, constraintDescriptor.getPayload().iterator().next() );
assertEquals(
ConstraintTarget.PARAMETERS,
constraintDescriptor.getValidationAppliesTo()
);
assertEquals( Default.class, constraintDescriptor.getGroups().iterator().next() );
assertEquals(
"There must not be more than {piecesOfLuggagePerPassenger} pieces of luggage per " +
"passenger.",
constraintDescriptor.getMessageTemplate()
);
//custom constraint attribute
assertEquals(
2,
constraintDescriptor.getAttributes().get( "piecesOfLuggagePerPassenger" )
);
//no composing constraints
assertTrue( constraintDescriptor.getComposingConstraints().isEmpty() );
//validator class
assertEquals(
Arrays.<Class<?>>asList( LuggageCountMatchesPassengerCount.Validator.class ),
constraintDescriptor.getConstraintValidatorClasses()
);
11. Integrating with other frameworks
Hibernate Validator is intended to be used to implement multi-layered data validation, where constraints are expressed in a single place (the annotated domain model) and checked in various different layers of the application. For this reason there are multiple integration points with other technologies.
11.1. ORM integration
Hibernate Validator integrates with both Hibernate ORM and all pure Java Persistence providers.
|
When lazy loaded associations are supposed to be validated it is recommended to place the constraint on the getter of the association. Hibernate ORM replaces lazy loaded associations with proxy instances which get initialized/loaded when requested via the getter. If, in such a case, the constraint is placed on field level, the actual proxy instance is used which will lead to validation errors. |
11.1.1. Database schema-level validation
Out of the box, Hibernate ORM will translate the constraints you have defined for
your entities into mapping metadata. For example, if a property of your entity is annotated
@NotNull
, its columns will be declared as not null
in the DDL schema generated by Hibernate ORM.
If, for some reason, the feature needs to be disabled, set hibernate.validator.apply_to_ddl
to
false
. See also Section 2.3.1, “Bean Validation constraints” and Section 2.3.2, “Additional constraints”.
You can also limit the DDL constraint generation to a subset of the defined constraints by setting
the property org.hibernate.validator.group.ddl
. The property specifies the comma-separated, fully
specified class names of the groups a constraint has to be part of in order to be considered for DDL
schema generation.
11.1.2. Hibernate ORM event-based validation
Hibernate Validator has a built-in Hibernate event listener -
org.hibernate.cfg.beanvalidation.BeanValidationEventListener
-
which is part of Hibernate ORM. Whenever a PreInsertEvent
,
PreUpdateEvent
or PreDeleteEvent
occurs, the listener will verify all constraints of the entity
instance and throw an exception if any constraint is violated. Per default, objects will be checked
before any inserts or updates are made by Hibernate ORM. Pre deletion events will per default not
trigger a validation. You can configure the groups to be validated per event type using the
properties javax.persistence.validation.group.pre-persist
,
javax.persistence.validation.group.pre-update
and javax.persistence.validation.group.pre-remove
.
The values of these properties are the comma-separated fully specified class names of the groups
to validate. Example 11.1, “Manual configuration of BeanValidationEvenListener
” shows the default values for these
properties. In this case they could also be omitted.
On constraint violation, the event will raise a runtime ConstraintViolationException
which contains
a set of ConstraintViolation
instances describing each failure.
If Hibernate Validator is present in the classpath, Hibernate ORM will use it transparently.
To avoid validation even though Hibernate Validator is in the classpath, set
javax.persistence.validation.mode
to none.
|
If the beans are not annotated with validation annotations, there is no runtime performance cost. |
In case you need to manually set the event listeners for Hibernate ORM, use the following configuration in hibernate.cfg.xml:
BeanValidationEvenListener
<hibernate-configuration>
<session-factory>
...
<property name="javax.persistence.validation.group.pre-persist">
javax.validation.groups.Default
</property>
<property name="javax.persistence.validation.group.pre-update">
javax.validation.groups.Default
</property>
<property name="javax.persistence.validation.group.pre-remove"></property>
...
<event type="pre-update">
<listener class="org.hibernate.cfg.beanvalidation.BeanValidationEventListener"/>
</event>
<event type="pre-insert">
<listener class="org.hibernate.cfg.beanvalidation.BeanValidationEventListener"/>
</event>
<event type="pre-delete">
<listener class="org.hibernate.cfg.beanvalidation.BeanValidationEventListener"/>
</event>
</session-factory>
</hibernate-configuration>
11.1.3. JPA
If you are using JPA 2 and Hibernate Validator is in the classpath, the JPA2 specification requires
that Bean Validation gets enabled. The properties javax.persistence.validation.group.pre-persist
,
javax.persistence.validation.group.pre-update
and javax.persistence.validation.group.pre-remove
as
described in Section 11.1.2, “Hibernate ORM event-based validation” can in this case be configured in
persistence.xml. persistence.xml also defines a node validation-mode which can be set to AUTO
,
CALLBACK
or NONE
. The default is AUTO
.
11.2. JSF & Seam
When working with JSF2 or JBoss Seam and Hibernate Validator (Bean Validation) is present in the
runtime environment, validation is triggered for every field in the application. Example 11.2, “Usage of Bean Validation within JSF2”
shows an example of the f:validateBean
tag in a JSF page. The validationGroups
attribute is optional
and can be used to specify a comma separated list of validation groups. The default is
javax.validation.groups.Default
. For more information refer to the Seam documentation or the JSF 2
specification.
<h:form>
<f:validateBean validationGroups="javax.validation.groups.Default">
<h:inputText value=#{model.property}/>
<h:selectOneRadio value=#{model.radioProperty}> ... </h:selectOneRadio>
<!-- other input components here -->
</f:validateBean>
</h:form>
|
The integration between JSF 2 and Bean Validation is described in the "Bean Validation Integration"
chapter of JSR-314. It is interesting to know that JSF
2 implements a custom javax.faces.validator.BeanValidator.MESSAGE={1}: {0} The default is: javax.faces.validator.BeanValidator.MESSAGE={0} |
11.3. CDI
As of version 1.1, Bean Validation is integrated with CDI (Contexts and Dependency Injection for JavaTM EE).
This integration provides CDI managed beans for Validator
and ValidatorFactory
and enables
dependency injection in constraint validators as well as custom message interpolators, traversable
resolvers, constraint validator factories, parameter name providers, clock providers and value
extractors.
Furthermore, parameter and return value constraints on the methods and constructors of CDI managed beans will automatically be validated upon invocation.
When your application runs on a Java EE container, this integration is enabled by default. When working with CDI in a Servlet container or in a pure Java SE environment, you can use the CDI portable extension provided by Hibernate Validator. To do so, add the portable extension to your class path as described in Section 1.1.2, “CDI”.
11.3.1. Dependency injection
CDI’s dependency injection mechanism makes it very easy to retrieve ValidatorFactory
and Validator
instances and use them in your managed beans. Just annotate instance fields of your bean with
@javax.inject.Inject
as shown in Example 11.3, “Retrieving validator factory and validator via @Inject
”.
@Inject
package org.hibernate.validator.referenceguide.chapter11.cdi.validator;
@ApplicationScoped
public class RentalStation {
@Inject
private ValidatorFactory validatorFactory;
@Inject
private Validator validator;
//...
}
The injected beans are the default validator factory and validator instances. In order to configure them - e.g. to use a custom message interpolator - you can use the Bean Validation XML descriptors as discussed in Chapter 8, Configuring via XML.
If you are working with several Bean Validation providers, you can make sure that factory and
validator from Hibernate Validator are injected by annotating the injection points with the
@HibernateValidator
qualifier which is demonstrated in Example 11.4, “Using the @HibernateValidator
qualifier annotation”.
@HibernateValidator
qualifier annotationpackage org.hibernate.validator.referenceguide.chapter11.cdi.validator.qualifier;
@ApplicationScoped
public class RentalStation {
@Inject
@HibernateValidator
private ValidatorFactory validatorFactory;
@Inject
@HibernateValidator
private Validator validator;
//...
}
|
The fully-qualified name of the qualifier annotation is
|
Via @Inject
you also can inject dependencies into constraint validators and other Bean Validation
objects such as MessageInterpolator
implementations etc.
Example 11.5, “Constraint validator with injected bean”
demonstrates how an injected CDI bean is used in a ConstraintValidator
implementation to determine
whether the given constraint is valid or not. As the example shows, you also can work with the
@PostConstruct
and @PreDestroy
callbacks to implement any required construction and destruction
logic.
package org.hibernate.validator.referenceguide.chapter11.cdi.injection;
public class ValidLicensePlateValidator
implements ConstraintValidator<ValidLicensePlate, String> {
@Inject
private VehicleRegistry vehicleRegistry;
@PostConstruct
public void postConstruct() {
//do initialization logic...
}
@PreDestroy
public void preDestroy() {
//do destruction logic...
}
@Override
public void initialize(ValidLicensePlate constraintAnnotation) {
}
@Override
public boolean isValid(String licensePlate, ConstraintValidatorContext constraintContext) {
return vehicleRegistry.isValidLicensePlate( licensePlate );
}
}
11.3.2. Method validation
The method interception facilities of CDI allow for a very tight integration with Bean Validation’s method validation functionality. Just put constraint annotations to the parameters and return values of the executables of your CDI beans and they will be validated automatically before (parameter constraints) and after (return value constraints) a method or constructor is invoked.
Note that no explicit interceptor binding is required, instead the required method validation interceptor will automatically be registered for all managed beans with constrained methods and constructors.
|
The interceptor |
You can see an example in Example 11.6, “CDI managed beans with method-level constraints”.
package org.hibernate.validator.referenceguide.chapter11.cdi.methodvalidation;
@ApplicationScoped
public class RentalStation {
@Valid
public RentalStation() {
//...
}
@NotNull
@Valid
public Car rentCar(
@NotNull Customer customer,
@NotNull @Future Date startDate,
@Min(1) int durationInDays) {
//...
return null;
}
@NotNull
List<Car> getAvailableCars() {
//...
return null;
}
}
package org.hibernate.validator.referenceguide.chapter11.cdi.methodvalidation;
@RequestScoped
public class RentCarRequest {
@Inject
private RentalStation rentalStation;
public void rentCar(String customerId, Date startDate, int duration) {
//causes ConstraintViolationException
rentalStation.rentCar( null, null, -1 );
}
}
Here the RentalStation
bean hosts several method constraints. When invoking one of the RentalStation
methods from another bean such as RentCarRequest
, the constraints of the invoked method are
automatically validated. If any illegal parameter values are passed as in the example, a
ConstraintViolationException
will be thrown by the method interceptor, providing detailed
information on the violated constraints. The same is the case if the method’s return value violates
any return value constraints.
Similarly, constructor constraints are validated automatically upon invocation. In the example the
RentalStation
object returned by the constructor will be validated since the constructor return
value is marked with @Valid
.
<IP_ADDRESS>. Validated executable types
Bean Validation allows for a fine-grained control of the executable types which are automatically
validated. By default, constraints on constructors and non-getter methods are validated. Therefore
the @NotNull
constraint on the method RentalStation#getAvailableCars()
in
Example 11.6, “CDI managed beans with method-level constraints” does not get validated when the method is invoked.
You have the following options to configure which types of executables are validated upon invocation:
-
Configure the executable types globally via the XML descriptor META-INF/validation.xml; see Section 8.1, “Configuring the validator factory in validation.xml” for an example
-
Use the
@ValidateOnExecution
annotation on the executable or type level
If several sources of configuration are specified for a given executable, @ValidateOnExecution
on
the executable level takes precedence over @ValidateOnExecution
on the type level and
@ValidateOnExecution
generally takes precedence over the globally configured types in META-
INF/validation.xml.
Example 11.7, “Using @ValidateOnExecution
” shows how to use the @ValidateOnExecution
annotation:
@ValidateOnExecution
package org.hibernate.validator.referenceguide.chapter11.cdi.methodvalidation.configuration;
@ApplicationScoped
@ValidateOnExecution(type = ExecutableType.ALL)
public class RentalStation {
@Valid
public RentalStation() {
//...
}
@NotNull
@Valid
@ValidateOnExecution(type = ExecutableType.NONE)
public Car rentCar(
@NotNull Customer customer,
@NotNull @Future Date startDate,
@Min(1) int durationInDays) {
//...
return null;
}
@NotNull
public List<Car> getAvailableCars() {
//...
return null;
}
}
Here the method rentCar()
won’t be validated upon invocation because it is annotated with
@ValidateOnExecution(type = ExecutableType.NONE)
. In contrast, the constructor and the
method getAvailableCars()
will be validated due to @ValidateOnExecution(type =
ExecutableType.ALL)
being given on the type level. ExecutableType.ALL
is a more compact form for
explicitly specifying all the types CONSTRUCTORS
, GETTER_METHODS
and NON_GETTER_METHODS
.
|
Executable validation can be turned off globally by specifying
|
Note that when a method overrides or implements a super-type method, the configuration will be taken
from that overridden or implemented method (as given via @ValidateOnExecution
on the method itself
or on the super-type). This protects a client of the super-type method from an unexpected alteration
of the configuration, e.g. disabling validation of an overridden executable in a sub-type.
In case a CDI managed bean overrides or implements a super-type method and this super-type method
hosts any constraints, it can happen that the validation interceptor is not properly registered with
the bean, resulting in the bean’s methods not being validated upon invocation. In this case you can
specify the executable type IMPLICIT
on the sub-class as shown in
Example 11.8, “Using ExecutableType.IMPLICIT
”, which makes sure that all required metadata is discovered
and the validation interceptor kicks in when the methods on ExpressRentalStation
are invoked.
ExecutableType.IMPLICIT
package org.hibernate.validator.referenceguide.chapter11.cdi.methodvalidation.implicit;
@ValidateOnExecution(type = ExecutableType.ALL)
public interface RentalStation {
@NotNull
@Valid
Car rentCar(
@NotNull Customer customer,
@NotNull @Future Date startDate,
@Min(1) int durationInDays);
@NotNull
List<Car> getAvailableCars();
}
package org.hibernate.validator.referenceguide.chapter11.cdi.methodvalidation.implicit;
@ApplicationScoped
@ValidateOnExecution(type = ExecutableType.IMPLICIT)
public class ExpressRentalStation implements RentalStation {
@Override
public Car rentCar(Customer customer, Date startDate, @Min(1) int durationInDays) {
//...
return null;
}
@Override
public List<Car> getAvailableCars() {
//...
return null;
}
}
11.4. Java EE
When your application runs on a Java EE application server such as WildFly,
you also can obtain Validator
and ValidatorFactory
instances via @Resource
injection in
managed objects such as EJBs etc., as shown in Example 11.9, “Retrieving Validator
and ValidatorFactory
via @Resource
injection”.
Validator
and ValidatorFactory
via @Resource
injectionpackage org.hibernate.validator.referenceguide.chapter11.javaee;
public class RentalStationBean {
@Resource
private ValidatorFactory validatorFactory;
@Resource
private Validator validator;
//...
}
Alternatively you can obtain a validator and a validator factory from JNDI under the names "java:comp/Validator" and "java:comp/ValidatorFactory", respectively.
Similar to CDI-based injection via @Inject
, these objects represent default validator and validator
factory and thus can be configured using the XML descriptor META-INF/validation.xml (see
Chapter 8, Configuring via XML).
When your application is CDI-enabled, the injected objects are CDI-aware as well and e.g. support dependency injection in constraint validators.
11.5. JavaFX
Hibernate Validator also provides support for the unwrapping of JavaFX properties. If JavaFX is present
on the classpath, ValueExtractor
s for JavaFX properties are automatically registered.
See Section 7.4, “JavaFX value extractors” for examples and further discussion.
12. Hibernate Validator Specifics
In this chapter you will learn how to make use of several features provided by Hibernate Validator in addition to the functionality defined by the Bean Validation specification. This includes the fail fast mode, the API for programmatic constraint configuration and the boolean composition of constraints.
New APIs or SPIs are tagged with the org.hibernate.validator.Incubating
annotation as long as they
are under development. This means that such elements (e.g. packages, types, methods, constants etc.)
may be incompatibly altered - or removed - in subsequent releases. Usage of incubating API/SPI members
is encouraged (so the development team can get feedback on these new features) but you should be
prepared for updating code which is using them as needed when upgrading to a new version of Hibernate
Validator.
|
Using the features described in the following sections may result in application code which is not portable between Bean Validation providers. |
12.1. Public API
Let’s start, however, with a look at the public API of Hibernate Validator. Below you can find a list of all packages belonging to this API and their purpose. Note that when a package is part of the public API this is not necessarily true for its sub-packages.
org.hibernate.validator
-
Classes used by the Bean Validation bootstrap mechanism (eg. validation provider, configuration class); for more details see Chapter 9, Bootstrapping.
org.hibernate.validator.cfg
,org.hibernate.validator.cfg.context
,org.hibernate.validator.cfg.defs
,org.hibernate.validator.spi.cfg
-
Hibernate Validator’s fluent API for constraint declaration; in
org.hibernate.validator.cfg
you will find theConstraintMapping
interface, inorg.hibernate.validator.cfg.defs
all constraint definitions and inorg.hibernate.validator.spi.cfg
a callback for using the API for configuring the default validator factory. Refer to Section 12.4, “Programmatic constraint definition and declaration” for the details. org.hibernate.validator.constraints
,org.hibernate.validator.constraints.br
,org.hibernate.validator.constraints.pl
-
Some useful custom constraints provided by Hibernate Validator in addition to the built-in constraints defined by the Bean Validation specification; the constraints are described in detail in Section 2.3.2, “Additional constraints”.
org.hibernate.validator.constraintvalidation
-
Extended constraint validator context which allows to set custom attributes for message interpolation. Section 12.11.1, “
HibernateConstraintValidatorContext
” describes how to make use of that feature. org.hibernate.validator.group
,org.hibernate.validator.spi.group
-
The group sequence provider feature which allows you to define dynamic default group sequences in function of the validated object state; the specifics can be found in Section 5.4, “Redefining the default group sequence”.
org.hibernate.validator.messageinterpolation
,org.hibernate.validator.resourceloading
,org.hibernate.validator.spi.resourceloading
-
Classes related to constraint message interpolation; the first package contains Hibernate Validator’s default message interpolator,
ResourceBundleMessageInterpolator
. The latter two packages provide theResourceBundleLocator
SPI for the loading of resource bundles (see Section 4.2.1, “ResourceBundleLocator
”) and its default implementation. org.hibernate.validator.parameternameprovider
-
A
ParameterNameProvider
based on the Paranamer library, see Section 12.12, “Paranamer basedParameterNameProvider
”. org.hibernate.validator.propertypath
-
Extensions to the
javax.validation.Path
API, see Section 12.7, “Extensions of the Path API”. org.hibernate.validator.spi.constraintdefinition
-
An SPI for registering additional constraint validators programmatically, see Section 12.13, “Providing constraint definitions”.
|
The public packages of Hibernate Validator fall into two categories: while the actual API parts are
intended to be invoked or used by clients (e.g. the API for programmatic constraint declaration
or the custom constraints), the SPI (service provider interface) packages contain interfaces which
are intended to be implemented by clients (e.g. |
Any packages not listed in that table are internal packages of Hibernate Validator and are not intended to be accessed by clients. The contents of these internal packages can change from release to release without notice, thus possibly breaking any client code relying on it.
12.2. Fail fast mode
Using the fail fast mode, Hibernate Validator allows to return from the current validation as soon as the first constraint violation occurs. This can be useful for the validation of large object graphs where you are only interested in a quick check whether there is any constraint violation at all.
Example 12.1, “Using the fail fast validation mode” shows how to bootstrap and use a fail fast enabled validator.
package org.hibernate.validator.referenceguide.chapter12.failfast;
public class Car {
@NotNull
private String manufacturer;
@AssertTrue
private boolean isRegistered;
public Car(String manufacturer, boolean isRegistered) {
this.manufacturer = manufacturer;
this.isRegistered = isRegistered;
}
//getters and setters...
}
Validator validator = Validation.byProvider( HibernateValidator.class )
.configure()
.failFast( true )
.buildValidatorFactory()
.getValidator();
Car car = new Car( null, false );
Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car );
assertEquals( 1, constraintViolations.size() );
Here the validated object actually fails to satisfy both the constraints declared on the Car
class,
yet the validation call yields only one ConstraintViolation
since the fail fast mode is enabled.
|
There is no guarantee in which order the constraints are evaluated, i.e. it is not deterministic
whether the returned violation originates from the |
Refer to Section 9.2.8, “Provider-specific settings” to learn about the different ways of enabling the fail fast mode when bootstrapping a validator.
12.3. Relaxation of requirements for method validation in class hierarchies
The Bean Validation specification defines a set of preconditions which apply when defining constraints on methods within class hierarchies. These preconditions are defined in section 5.6.5 of the Bean Validation 2.0 specification. See also Section 3.1.4, “Method constraints in inheritance hierarchies” in this guide.
As per specification, a Bean Validation provider is allowed to relax these preconditions. With Hibernate Validator you can do this in one of two ways.
First you can use the configuration properties hibernate.validator.allow_parameter_constraint_override, hibernate.validator.allow_multiple_cascaded_validation_on_result and hibernate.validator.allow_parallel_method_parameter_constraint in validation.xml. See example Example 12.2, “Configuring method validation behaviour in class hierarchies via properties”.
<?xml version="1.0" encoding="UTF-8"?>
<validation-config
xmlns="http://xmlns.jcp.org/xml/ns/validation/configuration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/validation/configuration validation-configuration-2.0.xsd"
version="2.0">
<default-provider>org.hibernate.validator.HibernateValidator</default-provider>
<property name="hibernate.validator.allow_parameter_constraint_override">true</property>
<property name="hibernate.validator.allow_multiple_cascaded_validation_on_result">true</property>
<property name="hibernate.validator.allow_parallel_method_parameter_constraint">true</property>
</validation-config>
Alternatively these settings can be applied during programmatic bootstrapping.
HibernateValidatorConfiguration configuration = Validation.byProvider( HibernateValidator.class ).configure();
configuration.allowMultipleCascadedValidationOnReturnValues( true )
.allowOverridingMethodAlterParameterConstraint( true )
.allowParallelMethodsDefineParameterConstraints( true );
By default, all of these properties are false, implementing the default behavior as defined in the Bean Validation specification.
|
Changing the default behaviour for method validation will result in non specification-conforming and non portable application. Make sure to understand what you are doing and that your use case really requires changes to the default behaviour. |
12.4. Programmatic constraint definition and declaration
As per the Bean Validation specification, you can define and declare constraints using Java annotations and XML based constraint mappings.
In addition, Hibernate Validator provides a fluent API which allows for the programmatic configuration of constraints. Use cases include the dynamic addition of constraints at runtime depending on some application state or tests where you need entities with different constraints in different scenarios but don’t want to implement actual Java classes for each test case.
By default, constraints added via the fluent API are additive to constraints configured via the standard configuration capabilities. But it is also possible to ignore annotation and XML configured constraints where required.
The API is centered around the ConstraintMapping
interface. You obtain a new mapping via
HibernateValidatorConfiguration#createConstraintMapping()
which you then can configure in a fluent
manner as shown in Example 12.4, “Programmatic constraint declaration”.
HibernateValidatorConfiguration configuration = Validation
.byProvider( HibernateValidator.class )
.configure();
ConstraintMapping constraintMapping = configuration.createConstraintMapping();
constraintMapping
.type( Car.class )
.property( "manufacturer", FIELD )
.constraint( new NotNullDef() )
.property( "licensePlate", FIELD )
.ignoreAnnotations( true )
.constraint( new NotNullDef() )
.constraint( new SizeDef().min( 2 ).max( 14 ) )
.type( RentalCar.class )
.property( "rentalStation", METHOD )
.constraint( new NotNullDef() );
Validator validator = configuration.addMapping( constraintMapping )
.buildValidatorFactory()
.getValidator();
Constraints can be configured on multiple classes and properties using method chaining. The
constraint definition classes NotNullDef
and SizeDef are helper classes which allow to configure
constraint parameters in a type-safe fashion. Definition classes exist for all built-in constraints
in the org.hibernate.validator.cfg.defs
package. By calling ignoreAnnotations()
any constraints
configured via annotations or XML are ignored for the given element.
|
Each element (type, property, method etc.) may only be configured once within all the constraint
mappings used to set up one validator factory. Otherwise a |
|
It is not supported to add constraints to non-overridden supertype properties and methods by configuring a subtype. Instead you need to configure the supertype in this case. |
Having configured the mapping, you must add it back to the configuration object from which you then can obtain a validator factory.
For custom constraints, you can either create your own definition classes extending ConstraintDef
or
you can use GenericConstraintDef
as seen in Example 12.5, “Programmatic declaration of a custom constraint”.
ConstraintMapping constraintMapping = configuration.createConstraintMapping();
constraintMapping
.type( Car.class )
.property( "licensePlate", FIELD )
.constraint( new GenericConstraintDef<>( CheckCase.class )
.param( "value", CaseMode.UPPER )
);
Container element constraints are supported by the programmatic API, using containerElementType()
.
Example 12.6, “Programmatic declaration of a nested container element constraint” show an example where constraints are declared on nested container elements.
ConstraintMapping constraintMapping = configuration.createConstraintMapping();
constraintMapping
.type( Car.class )
.property( "manufacturer", FIELD )
.constraint( new NotNullDef() )
.property( "licensePlate", FIELD )
.ignoreAnnotations( true )
.constraint( new NotNullDef() )
.constraint( new SizeDef().min( 2 ).max( 14 ) )
.property( "partManufacturers", FIELD )
.containerElementType( 0 )
.constraint( new NotNullDef() )
.containerElementType( 1, 0 )
.constraint( new NotNullDef() )
.type( RentalCar.class )
.property( "rentalStation", METHOD )
.constraint( new NotNullDef() );
As demonstrated, the parameters passed to containerElementType()
are the path of type argument
indexes used to obtain the desired nested container element type.
By invoking valid()
you can mark a member for cascaded validation which is equivalent to annotating
it with @Valid
. Configure any group conversions to be applied during cascaded validation using the
convertGroup()
method (equivalent to @ConvertGroup
). An example can be seen in
Example 12.7, “Marking a property for cascaded validation”.
ConstraintMapping constraintMapping = configuration.createConstraintMapping();
constraintMapping
.type( Car.class )
.property( "driver", FIELD )
.constraint( new NotNullDef() )
.valid()
.convertGroup( Default.class ).to( PersonDefault.class )
.property( "partManufacturers", FIELD )
.containerElementType( 0 )
.valid()
.containerElementType( 1, 0 )
.valid()
.type( Person.class )
.property( "name", FIELD )
.constraint( new NotNullDef().groups( PersonDefault.class ) );
You can not only configure bean constraints using the fluent API but also method and constructor constraints. As shown in Example 12.8, “Programmatic declaration of method and constructor constraints” constructors are identified by their parameter types and methods by their name and parameter types. Having selected a method or constructor, you can mark its parameters and/or return value for cascaded validation and add constraints as well as cross-parameter constraints.
As shown in the example, valid()
can be also invoked on a container element type.
ConstraintMapping constraintMapping = configuration.createConstraintMapping();
constraintMapping
.type( Car.class )
.constructor( String.class )
.parameter( 0 )
.constraint( new SizeDef().min( 3 ).max( 50 ) )
.returnValue()
.valid()
.method( "drive", int.class )
.parameter( 0 )
.constraint( new MaxDef().value( 75 ) )
.method( "load", List.class, List.class )
.crossParameter()
.constraint( new GenericConstraintDef<>(
LuggageCountMatchesPassengerCount.class ).param(
"piecesOfLuggagePerPassenger", 2
)
)
.method( "getDriver" )
.returnValue()
.constraint( new NotNullDef() )
.valid();
Last but not least you can configure the default group sequence or the default group sequence provider of a type as shown in the following example.
ConstraintMapping constraintMapping = configuration.createConstraintMapping();
constraintMapping
.type( Car.class )
.defaultGroupSequence( Car.class, CarChecks.class )
.type( RentalCar.class )
.defaultGroupSequenceProviderClass( RentalCarGroupSequenceProvider.class );
12.5. Applying programmatic constraint declarations to the default validator factory
If you are not bootstrapping a validator factory manually
but work with the default factory as configured via META-INF/validation.xml
(see Chapter 8, Configuring via XML),
you can add one or more constraint mappings by creating one or several constraint mapping contributors.
To do so, implement the ConstraintMappingContributor
contract:
ConstraintMappingContributor
implementationpackage org.hibernate.validator.referenceguide.chapter12.constraintapi;
public class MyConstraintMappingContributor implements ConstraintMappingContributor {
@Override
public void createConstraintMappings(ConstraintMappingBuilder builder) {
builder.addConstraintMapping()
.type( Marathon.class )
.property( "name", METHOD )
.constraint( new NotNullDef() )
.property( "numberOfHelpers", FIELD )
.constraint( new MinDef().value( 1 ) );
builder.addConstraintMapping()
.type( Runner.class )
.property( "paidEntryFee", FIELD )
.constraint( new AssertTrueDef() );
}
}
You then need to specify the fully-qualified class name of the contributor implementation in META-INF/validation.xml,
using the property key hibernate.validator.constraint_mapping_contributors
. You can specify several
contributors by separating them with a comma.
12.6. Advanced constraint composition features
12.6.1. Validation target specification for purely composed constraints
In case you specify a purely composed constraint - i.e. a constraint which has no validator itself but is solely made up from other, composing constraints - on a method declaration, the validation engine cannot determine whether that constraint is to be applied as a return value constraint or as a cross-parameter constraint.
Hibernate Validator allows to resolve such ambiguities by specifying the @SupportedValidationTarget
annotation on the
declaration of the composed constraint type as shown in Example 12.11, “Specifying the validation target of a purely composed constraint”.
The @ValidInvoiceAmount
does not declare any validator, but it is solely composed by the @Min
and @NotNull
constraints. The @SupportedValidationTarget
ensures that the constraint is applied to the method return value when
given on a method declaration.
package org.hibernate.validator.referenceguide.chapter12.purelycomposed;
@Min(value = 0)
@NotNull
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {})
@SupportedValidationTarget(ValidationTarget.ANNOTATED_ELEMENT)
@ReportAsSingleViolation
public @interface ValidInvoiceAmount {
String message() default "{org.hibernate.validator.referenceguide.chapter11.purelycomposed."
+ "ValidInvoiceAmount.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@OverridesAttribute(constraint = Min.class, name = "value")
long value();
}
12.6.2. Boolean composition of constraints
Bean Validation specifies that the constraints of a composed constraint (see Section 6.4, “Constraint composition”) are all combined via a logical AND. This means all of the composing constraints need to return true to obtain an overall successful validation.
Hibernate Validator offers an extension to this and allows you to compose constraints via a logical OR or NOT. To do so, you have to use the ConstraintComposition annotation and the enum CompositionType with its values AND, OR and ALL_FALSE.
Example 12.12, “OR composition of constraints” shows how to build a composed constraint @PatternOrSize
where only one of the composing constraints needs to be valid in order to pass the validation.
Either the validated string is all lower-cased or it is between two and three characters long.
package org.hibernate.validator.referenceguide.chapter12.booleancomposition;
@ConstraintComposition(OR)
@Pattern(regexp = "[a-z]")
@Size(min = 2, max = 3)
@ReportAsSingleViolation
@Target({ METHOD, FIELD })
@Retention(RUNTIME)
@Constraint(validatedBy = { })
public @interface PatternOrSize {
String message() default "{org.hibernate.validator.referenceguide.chapter11." +
"booleancomposition.PatternOrSize.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
|
Using ALL_FALSE as composition type implicitly enforces that only a single violation will get reported in case validation of the constraint composition fails. |
12.7. Extensions of the Path API
Hibernate Validator provides an extension to the javax.validation.Path
API.
For nodes of ElementKind.PROPERTY
and ElementKind.CONTAINER_ELEMENT
it allows to obtain the value of the
represented property.
To do so, narrow down a given node to the type org.hibernate.validator.path.PropertyNode
or
org.hibernate.validator.path.ContainerElementNode
respectively using Node#as()
, as
shown in the following example:
Building building = new Building();
// Assume the name of the person violates a @Size constraint
Person bob = new Person( "Bob" );
Apartment bobsApartment = new Apartment( bob );
building.getApartments().add( bobsApartment );
Set<ConstraintViolation<Building>> constraintViolations = validator.validate( building );
Path path = constraintViolations.iterator().next().getPropertyPath();
Iterator<Path.Node> nodeIterator = path.iterator();
Path.Node node = nodeIterator.next();
assertEquals( node.getName(), "apartments" );
assertSame( node.as( PropertyNode.class ).getValue(), bobsApartment );
node = nodeIterator.next();
assertEquals( node.getName(), "resident" );
assertSame( node.as( PropertyNode.class ).getValue(), bob );
node = nodeIterator.next();
assertEquals( node.getName(), "name" );
assertEquals( node.as( PropertyNode.class ).getValue(), "Bob" );
This is also very useful to obtain the element of Set
properties on the property path (e.g. apartments
in the example) which otherwise could not be identified (unlike for Map
and List
, there is no key nor index
in this case).
12.8. Dynamic payload as part of ConstraintViolation
In some cases automatic processing of violations can be aided, if the constraint violation provides additional data - a so called dynamic payload. This dynamic payload could for example contain hints to the user on how to resolve the violation.
Dynamic payloads can be set in custom constraints using HibernateConstraintValidatorContext
.
This is shown in example Example 12.14, “ConstraintValidator
implementation setting a dynamic payload” where the
javax.validation.ConstraintValidatorContext
is unwrapped to HibernateConstraintValidatorContext
in order to call
withDynamicPayload
.
ConstraintValidator
implementation setting a dynamic payloadpackage org.hibernate.validator.referenceguide.chapter12.dynamicpayload;
import static org.hibernate.validator.internal.util.CollectionHelper.newHashMap;
public class ValidPassengerCountValidator implements ConstraintValidator<ValidPassengerCount, Car> {
private static final Map<Integer, String> suggestedCars = newHashMap();
static {
suggestedCars.put( 2, "Chevrolet Corvette" );
suggestedCars.put( 3, "Toyota Volta" );
suggestedCars.put( 4, "Maserati GranCabrio" );
suggestedCars.put( 5, " Mercedes-Benz E-Class" );
}
@Override
public void initialize(ValidPassengerCount constraintAnnotation) {
}
@Override
public boolean isValid(Car car, ConstraintValidatorContext context) {
if ( car == null ) {
return true;
}
int passengerCount = car.getPassengers().size();
if ( car.getSeatCount() >= passengerCount ) {
return true;
}
else {
if ( suggestedCars.containsKey( passengerCount ) ) {
HibernateConstraintValidatorContext hibernateContext = context.unwrap(
HibernateConstraintValidatorContext.class
);
hibernateContext.withDynamicPayload( suggestedCars.get( passengerCount ) );
}
return false;
}
}
}
On the constraint violation processing side, a javax.validation.ConstraintViolation
can then in turn be
unwrapped to HibernateConstraintViolation
in order to retrieve the dynamic payload for further processing.
ConstraintViolation
's dynamic payloadCar car = new Car( 2 );
car.addPassenger( new Person() );
car.addPassenger( new Person() );
car.addPassenger( new Person() );
Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car );
assertEquals( 1, constraintViolations.size() );
ConstraintViolation<Car> constraintViolation = constraintViolations.iterator().next();
@SuppressWarnings("unchecked")
HibernateConstraintViolation<Car> hibernateConstraintViolation = constraintViolation.unwrap(
HibernateConstraintViolation.class
);
String suggestedCar = hibernateConstraintViolation.getDynamicPayload( String.class );
assertEquals( "Toyota Volta", suggestedCar );
12.9. ParameterMessageInterpolator
Hibernate Validator requires per default an implementation of the Unified EL (see Section 1.1.1, “Unified EL”) to be available. This is needed to allow the interpolation of constraint error messages using EL expressions as defined by the Bean Validation specification.
For environments where you cannot or do not want to provide an EL implementation, Hibernate Validator
offers a non EL based message interpolator - org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator
.
Refer to Section 4.2, “Custom message interpolation” to see how to plug in custom message interpolator implementations.
|
Constraint messages containing EL expressions will be returned un-interpolated by
|
12.10. ResourceBundleLocator
With ResourceBundleLocator
, Hibernate Validator provides an additional SPI which allows to retrieve
error messages from other resource bundles than ValidationMessages while still using the actual
interpolation algorithm as defined by the specification. Refer to
Section 4.2.1, “ResourceBundleLocator
” to learn how to make use of that SPI.
12.11. Custom contexts
The Bean Validation specification offers at several points in its API the possibility to unwrap a
given interface to an implementor specific subtype. In the case of constraint violation creation in
ConstraintValidator
implementations as well as message interpolation in MessageInterpolator
instances, there exist unwrap()
methods for the provided context instances -
ConstraintValidatorContext
respectively MessageInterpolatorContext
. Hibernate Validator provides
custom extensions for both of these interfaces.
12.11.1. HibernateConstraintValidatorContext
HibernateConstraintValidatorContext
is a subtype of ConstraintValidatorContext
which allows you to:
-
set arbitrary parameters for interpolation via the Expression Language message interpolation facility using
HibernateConstraintValidatorContext#addExpressionVariable(String, Object)
orHibernateConstraintValidatorContext#addMessageParameter(String, Object)
.Example 150. Custom@Future
validator injecting an expression variablepackage org.hibernate.validator.referenceguide.chapter12.context; import java.time.Instant; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import javax.validation.constraints.Future; import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext; public class MyFutureValidator implements ConstraintValidator<Future, Instant> { @Override public void initialize(Future constraintAnnotation) { } @Override public boolean isValid(Instant value, ConstraintValidatorContext context) { if ( value == null ) { return true; } HibernateConstraintValidatorContext hibernateContext = context.unwrap( HibernateConstraintValidatorContext.class ); Instant now = Instant.now( context.getClockProvider().getClock() ); if ( !value.isAfter( now ) ) { hibernateContext.disableDefaultConstraintViolation(); hibernateContext .addExpressionVariable( "now", now ) .buildConstraintViolationWithTemplate( "Must be after ${now}" ) .addConstraintViolation(); return false; } return true; } }
Example 151. Custom@Future
validator injecting a message parameterpackage org.hibernate.validator.referenceguide.chapter12.context; import java.time.Instant; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import javax.validation.constraints.Future; import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext; public class MyFutureValidatorMessageParameter implements ConstraintValidator<Future, Instant> { @Override public void initialize(Future constraintAnnotation) { } @Override public boolean isValid(Instant value, ConstraintValidatorContext context) { if ( value == null ) { return true; } HibernateConstraintValidatorContext hibernateContext = context.unwrap( HibernateConstraintValidatorContext.class ); Instant now = Instant.now( context.getClockProvider().getClock() ); if ( !value.isAfter( now ) ) { hibernateContext.disableDefaultConstraintViolation(); hibernateContext .addMessageParameter( "now", now ) .buildConstraintViolationWithTemplate( "Must be after {now}" ) .addConstraintViolation(); return false; } return true; } }
Apart from the syntax, the main difference between message parameters and expression variables is that message parameters are simply interpolated whereas expression variables are interpreted using the expression language engine. In practice, it should not change anything.
Note that the parameters specified via
addExpressionVariable(String, Object)
andaddMessageParameter(String, Object)
are global and apply to all constraint violations created by thisisValid()
invocation. This includes the default constraint violation, but also all violations created by theConstraintViolationBuilder
. You can, however, update the parameters between invocations ofConstraintViolationBuilder#addConstraintViolation()
. -
set an arbitrary dynamic payload - see Section 12.8, “Dynamic payload as part of
ConstraintViolation
”
12.11.2. HibernateMessageInterpolatorContext
Hibernate Validator also offers a custom extension of MessageInterpolatorContext
, namely
HibernateMessageInterpolatorContext
(see Example 12.16, “HibernateMessageInterpolatorContext
”). This
subtype was introduced to allow a better integration of Hibernate Validator into Glassfish. The
root bean type was in this case needed to determine the right class loader for the message resource
bundle. If you have any other use cases, let us know.
HibernateMessageInterpolatorContext
* @author Gunnar Morling
* @author Guillaume Smet
* @since 5.0
*/
public interface HibernateMessageInterpolatorContext extends MessageInterpolator.Context {
/**