Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions documentation/src/main/asciidoc/ch06.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,63 @@ It is not included in the constraint violations, unless a specific `ConstraintVa
payload to emitted constraint violations by using the <<section-dynamic-payload,constraint violation dynamic payload mechanism>>.
====

[[constraint-validator-shared-data]]
===== Constraint validator initialization shared data

While the <<constraint-validator-payload,constraint payload>> is intended to be used within the `isValid(..)` method,
initialization shared data opens up a way for constraint validators to access a shared instance within the `initialize(..)`
call. This can be used to cache and reuse elements required to construct a constraint validator. For example,
internally, this mechanism is used by the pattern constraint validator to reuse the `java.util.regex.Pattern` instances
when multiple constraints rely on the same pattern string and flags.

This shared data is accessible through the `HibernateConstraintValidator` extension,
more specifically through the `HibernateConstraintValidatorInitializationContext#getSharedData(..)` methods.
The first overloaded variant is `getSharedData(Class<C>)`, which is intended to be used when the shared data is
provided during the Hibernate Validator factory configuration.

[[example-constraint-validator-shared-data-definition-validatorfactory]]
.Defining a constraint validator initialization shared data during the `ValidatorFactory` initialization
====
[source, JAVA, indent=0]
----
include::{sourcedir}/org/hibernate/validator/referenceguide/chapter06/constraintvalidatorshareddata/ConstraintValidatorPayloadTest.java[tags=setSharedData]
----
====

Once you have set the constraint validator initialization shared data, it can be accessed in your constraint validators as shown in the example below:

[[example-constraint-validator-shared-data-usage]]
.Accessing the constraint validator initialization shared data in a constraint validator
====
[source, JAVA, indent=0]
----
include::{sourcedir}/org/hibernate/validator/referenceguide/chapter06/constraintvalidatorshareddata/ZipCodeValidator.java[tags=include]
----
<1> Implement the Hibernate Validator `HibernateConstraintValidator` extension to have access to the initialization context.
<2> Retrieve the shared data from the initialization context.
<3> Perform some actions with the shared data instance.
====

The other overloaded method to access the shared data `getSharedData(Class<C>, Supplier<V>)` is intended
for cases where the shared data is created lazily
and is not defined at the time of configuring the validator factory.

[[example-constraint-validator-shared-data-usage-alternative]]
.Accessing the constraint validator lazy initialization shared data in a constraint validator
====
[source, JAVA, indent=0]
----
include::{sourcedir}/org/hibernate/validator/referenceguide/chapter06/constraintvalidatorshareddata/ParsableDateTimeFormatValidator.java[tags=include]
----
<1> Implement the Hibernate Validator `HibernateConstraintValidator` extension to have access to the initialization context.
<2> Retrieve the shared data from the initialization context, providing the supplier that will be executed
if the `DateTimeFormatterCache` is not yet available in the current initialization context.
<3> Perform some actions with the shared data instance.
<4> A simple wrapper around the map to cache the formatters.
Compared to the use of a static field cache, using the shared data has the benefit that it is tied to the initialization context
and will be garbage collected along with it.
====

[[validator-customconstraints-errormessage]]
==== The error message

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.validator.referenceguide.chapter06.constraintvalidatorshareddata;

import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;

import org.hibernate.validator.HibernateValidator;

import org.junit.Test;

@SuppressWarnings("unused")
public class ConstraintValidatorPayloadTest {

@Test
public void setSharedData() {
//tag::setSharedData[]
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure()
.addConstraintValidatorInitializationSharedData( ZipCodeCatalog.class, readZipCodeCatalog() )
.buildValidatorFactory();

Validator validator = validatorFactory.getValidator();
//end::setSharedData[]
}

private ZipCodeCatalog readZipCodeCatalog() {
return new ZipCodeCatalog();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.validator.referenceguide.chapter06.constraintvalidatorshareddata;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;

@Target({ METHOD, FIELD, ANNOTATION_TYPE, TYPE_USE })
@Retention(RUNTIME)
@Constraint(validatedBy = ParsableDateTimeFormatValidator.class)
@Documented
public @interface ParsableDateTimeFormat {

String dateFormat() default "dd/MM/yyyy";

String message() default "{org.hibernate.validator.referenceguide.chapter06.constraintvalidatorshareddata.FutureString.message}";

Class<?>[] groups() default { };

Class<? extends Payload>[] payload() default { };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
//spotless:off
//tag::include[]
package org.hibernate.validator.referenceguide.chapter06.constraintvalidatorshareddata;
//end::include[]

import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.hibernate.validator.constraintvalidation.HibernateConstraintValidator;
import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorInitializationContext;

import jakarta.validation.ConstraintValidatorContext;
import jakarta.validation.metadata.ConstraintDescriptor;

//spotless:on
//tag::include[]
public class ParsableDateTimeFormatValidator implements HibernateConstraintValidator<ParsableDateTimeFormat, String> { // <1>

private DateTimeFormatter formatter;

@Override
public void initialize(ConstraintDescriptor<ParsableDateTimeFormat> constraintDescriptor,
HibernateConstraintValidatorInitializationContext initializationContext) {
formatter = initializationContext.getSharedData( DateTimeFormatterCache.class, DateTimeFormatterCache::new ) // <2>
.get( constraintDescriptor.getAnnotation().dateFormat() ); // <3>
}

@Override
public boolean isValid(String dateTime, ConstraintValidatorContext constraintContext) {
if ( dateTime == null ) {
return true;
}

try {
formatter.parse( dateTime );
}
catch (DateTimeParseException e) {
return false;
}
return true;
}

private static class DateTimeFormatterCache { // <4>
private final Map<String, DateTimeFormatter> cache = new ConcurrentHashMap<>();

DateTimeFormatter get(String format) {
return cache.computeIfAbsent( format, DateTimeFormatter::ofPattern );
}
}
}
//end::include[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.validator.referenceguide.chapter06.constraintvalidatorshareddata;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;

@Target({ METHOD, FIELD, ANNOTATION_TYPE, TYPE_USE })
@Retention(RUNTIME)
@Constraint(validatedBy = ZipCodeValidator.class)
@Documented
public @interface ZipCode {

String countryCode();

String message() default "{org.hibernate.validator.referenceguide.chapter06.constraintvalidatorshareddata.ZipCode.message}";

Class<?>[] groups() default { };

Class<? extends Payload>[] payload() default { };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.validator.referenceguide.chapter06.constraintvalidatorshareddata;

public class ZipCodeCatalog {
public ZipCodeCountryCatalog country(String s) {
return new ZipCodeCountryCatalog();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.validator.referenceguide.chapter06.constraintvalidatorshareddata;

public class ZipCodeCountryCatalog {
public boolean contains(String zip) {
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
//spotless:off
//tag::include[]
package org.hibernate.validator.referenceguide.chapter06.constraintvalidatorshareddata;

import org.hibernate.validator.constraintvalidation.HibernateConstraintValidator;
import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorInitializationContext;
//end::include[]

import jakarta.validation.ConstraintValidatorContext;
import jakarta.validation.metadata.ConstraintDescriptor;

//spotless:on
//tag::include[]
public class ZipCodeValidator implements HibernateConstraintValidator<ZipCode, String> { // <1>

private ZipCodeCountryCatalog countryCatalog;

@Override
public void initialize(ConstraintDescriptor<ZipCode> constraintDescriptor,
HibernateConstraintValidatorInitializationContext initializationContext) {
countryCatalog = initializationContext.getSharedData( ZipCodeCatalog.class ) // <2>
.country( constraintDescriptor.getAnnotation().countryCode() ); // <3>
}

@Override
public boolean isValid(String zip, ConstraintValidatorContext constraintContext) {
if ( zip == null ) {
return true;
}

return countryCatalog.contains( zip );
}
}
//end::include[]
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,30 @@ public interface BaseHibernateValidatorConfiguration<S extends BaseHibernateVali
@Incubating
S constraintValidatorPayload(Object constraintValidatorPayload);

/**
* Allows adding a shared data object which will be available during the constraint validators initialization to all constraints.
* If the method is called multiple times passing different instances of the same class,
* only the instance passed last will be available for that type.
*
* @param constraintValidatorInitializationSharedService the data to retrieve from the constraint validator initializers
* @return {@code this} following the chaining method pattern
* @since 9.1.0
*/
@Incubating
S addConstraintValidatorInitializationSharedData(Object constraintValidatorInitializationSharedService);

/**
* Allows adding a shared data object which will be available during the constraint validators initialization to all constraints.
* If the method is called multiple times passing the same {@code dataClass},
* only the instance passed last will be available for that type.
*
* @param constraintValidatorInitializationSharedData the data to retrieve from the constraint validator initializers
* @return {@code this} following the chaining method pattern
* @since 9.1.0
*/
@Incubating
<T, V extends T> S addConstraintValidatorInitializationSharedData(Class<T> dataClass, V constraintValidatorInitializationSharedData);

/**
* Allows to set a getter property selection strategy defining the rules determining if a method is a getter
* or not.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import java.time.Clock;
import java.time.Duration;
import java.util.function.Supplier;

import jakarta.validation.ClockProvider;

Expand Down Expand Up @@ -56,4 +57,35 @@ public interface HibernateConstraintValidatorInitializationContext {
*/
@Incubating
Duration getTemporalValidationTolerance();

/**
* Returns an instance of the specified data type or {@code null} if the current context does not
* contain such data.
* The requested data type must match the one with which it was originally added with
* {@link org.hibernate.validator.HibernateValidatorConfiguration#addConstraintValidatorInitializationSharedData(Object)}.
*
* @param type the type of data to retrieve
* @return an instance of the specified type or {@code null} if the current constraint initialization context does not
* contain an instance of such type
*
* @since 9.1.0
* @see org.hibernate.validator.HibernateValidatorConfiguration#addConstraintValidatorInitializationSharedData(Object)
*/
@Incubating
<C> C getSharedData(Class<C> type);

/**
* Returns an instance of the specified data type or attempts to create it with a supplier, if the current context does not
* contain such data.
*
* @param type the type of data to retrieve
* @param createIfNotPresent the supplier to create an instance of shared data, if it is not already present in this context.
* @return an instance of the specified type or {@code null} if the current constraint initialization context does not
* contain an instance of such type
*
* @since 9.1.0
* @see org.hibernate.validator.HibernateValidatorConfiguration#addConstraintValidatorInitializationSharedData(Object)
*/
@Incubating
<C, V extends C> C getSharedData(Class<C> type, Supplier<V> createIfNotPresent);
}
Loading
Loading