Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

HV-2004 Add constraint initialization payload #1587

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
marko-bekhta wants to merge 3 commits into hibernate:main
base: main
Choose a base branch
Loading
from marko-bekhta:feat/HV-2004-add-constraint-initialization-payload

Conversation

Copy link
Member

@marko-bekhta marko-bekhta commented Mar 13, 2025

https://hibernate.atlassian.net/browse/HV-2004

I didn't want to have some hardcoded cache for patterns within the pattern constraint validator impl itself. Hence I was thinking that leveraging something similar to what we have for validation with payload could work.
initialization payload would be available to any constraints. As for patterns and their caching, limiting it to the predefined scope makes sense as we have all constraints initialized and can clear out the cache.


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on licensing, please check here.


@marko-bekhta marko-bekhta force-pushed the feat/HV-2004-add-constraint-initialization-payload branch from b5a14d2 to c5b8c43 Compare August 25, 2025 16:46
* @see org.hibernate.validator.HibernateValidatorConfiguration#addConstraintValidatorInitializationPayload(Object)
*/
@Incubating
<C> C getConstraintValidatorInitializationPayload(Class<C> type);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a payload thout, is it? More of a shared/reusable/cached context?

Also I wouldn't include ConstraintValidator in the name considering we're in HibernateConstraintValidatorInitializationContext already.

Suggested change
<C> C getConstraintValidatorInitializationPayload(Class<C> type);
<C> C getCached(Class<C> type);

or

Suggested change
<C> C getConstraintValidatorInitializationPayload(Class<C> type);
<C> C getCache(Class<C> type);

Alternatively if you really intend a cache here, you could make this closer to what @Sanne added to Hibernate ORM recently: https://github.com/hibernate/hibernate-orm/blob/bfa7102e228887a7f9e45211a7b051e5b1791215/hibernate-core/src/main/java/org/hibernate/internal/util/cache/InternalCacheFactory.java#L14 . Which would allow integrators to plug a custom cache provider, like Caffeine, which is known to have very good performance.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That being said if the cache is only there for bootstrap, and gets cleared/deleted once bootstrap is finished, using Caffeine is probably overkill...

Copy link
Member Author

@marko-bekhta marko-bekhta Aug 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah ... naming is hard 😔 🙂
Yes, it's closer to shared context. The other thought that I had about this: if we were always running the validator in the CDI context and with the predefined scope (i.e. all metadata is built at boot and not dynamically at runtime), I'd think of PatternConstraintInitializer as a bean that we inject into the PatternValidator, and the scope of such beans would be the init phase of the validator factory... but we aren't always running in CDI 🙈

@marko-bekhta marko-bekhta force-pushed the feat/HV-2004-add-constraint-initialization-payload branch from c5b8c43 to 9648588 Compare September 3, 2025 08:45
@marko-bekhta marko-bekhta marked this pull request as draft September 3, 2025 08:45
 and use it to cache patterns in the predefined factory
Signed-off-by: marko-bekhta <marko.prykladna@gmail.com>
Signed-off-by: marko-bekhta <marko.prykladna@gmail.com>
Signed-off-by: marko-bekhta <marko.prykladna@gmail.com>
@marko-bekhta marko-bekhta force-pushed the feat/HV-2004-add-constraint-initialization-payload branch from 9648588 to 3267cb0 Compare September 3, 2025 12:22
@marko-bekhta marko-bekhta marked this pull request as ready for review September 3, 2025 12:39
Pattern.Flag[] flags = parameters.flags();
int intFlag = 0;
for ( Pattern.Flag flag : flags ) {
intFlag = intFlag | flag.getValue();
}

try {
pattern = java.util.regex.Pattern.compile( parameters.regexp(), intFlag );
pattern = initializationContext.getConstraintValidatorInitializationSharedService( PatternConstraintInitializer.class )
Copy link
Member Author

@marko-bekhta marko-bekhta Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought ... that shared service might be a good fit for the name 🙂 🫣

Copy link
Member

@yrodiere yrodiere Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say that a service is generally stateless, but YMMV :)

If cache really rubs you the wrong way, maybe sharedData?

By the way, why getConstraintValidatorInitialization*? This is a method in initializationContext, so get* would be enough, no?

Copy link
Member Author

@marko-bekhta marko-bekhta Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If cache really rubs you the wrong way, maybe sharedData?

ha 🙂 yeah ... I don't know, cache somehow doesn't feel right 😔... since you could pass around anything, not just a map of patterns ... so maybe if it was initContext.getCache().get(smth) then cache seems like a nice fit. But when it's initContext.getSomethingFromTheInitCache(smth) ... yeah I don't know 😔

Comment on lines +19 to +42
public interface HibernateValidatorFactoryObserver {
/**
* A callback invoked upon the successful creation of a factory.
*
* @param factory The fully initialized factory.
*/
default void factoryCreated(HibernateValidatorFactory factory) {
}

/**
* A callback invoked just before the factory is shut down.
*
* @param factory The factory that is about to be closed.
*/
default void factoryClosing(HibernateValidatorFactory factory) {
}

/**
* A callback invoked after the factory has been successfully shut down.
*
* @param factory The closed factory.
*/
default void factoryClosed(HibernateValidatorFactory factory) {
}
Copy link
Member Author

@marko-bekhta marko-bekhta Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and since clearing the cache for the pattern "shared service" explicitly felt more like a "hack" I've added this observer to react to HV factory lifecycle events and used it to clear the cache instead...

Copy link
Member

@yrodiere yrodiere Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or... hear me out...

Map<Class<?>, Object> sharedData = new HashMap<>();
<T> T getSharedData(Class<T> clazz) {
 return sharedData.computeIfAbsent(clazz, ignored -> clazz.getConstructor().newInstance());
}

... and then everything gets garbage-collected when the context does.

Or do you really need to pass something that gets created before all this, or to close some files after initialization?

This seems very complicated to me, I must be missing a requirement.

Copy link
Member Author

@marko-bekhta marko-bekhta Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I'll have to see how it is done for the predefined scope... I don't remember if the init context there is "short lived" or if it stays available for the whole lifetime of the factory... it should stay available for the regular case, as for it, the metadata is built lazily on the first validation attempt...

PatternConstraintInitializer patternConstraintInitializer, List<HibernateValidatorFactoryObserver> sharedServicesObservers) {
HibernateConstraintValidatorInitializationSharedServiceManager configured = null;
if ( configurationState instanceof AbstractConfigurationImpl<?> hibernateSpecificConfig ) {
if ( hibernateSpecificConfig.getConstraintValidatorPayload() != null ) {
Copy link
Member

@yrodiere yrodiere Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if ( hibernateSpecificConfig.getConstraintValidatorPayload() != null ) {
if ( hibernateSpecificConfig.getConstraintValidatorInitializationSharedServiceManager() != null ) {

... I think?

Comment on lines +19 to +42
public interface HibernateValidatorFactoryObserver {
/**
* A callback invoked upon the successful creation of a factory.
*
* @param factory The fully initialized factory.
*/
default void factoryCreated(HibernateValidatorFactory factory) {
}

/**
* A callback invoked just before the factory is shut down.
*
* @param factory The factory that is about to be closed.
*/
default void factoryClosing(HibernateValidatorFactory factory) {
}

/**
* A callback invoked after the factory has been successfully shut down.
*
* @param factory The closed factory.
*/
default void factoryClosed(HibernateValidatorFactory factory) {
}
Copy link
Member

@yrodiere yrodiere Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or... hear me out...

Map<Class<?>, Object> sharedData = new HashMap<>();
<T> T getSharedData(Class<T> clazz) {
 return sharedData.computeIfAbsent(clazz, ignored -> clazz.getConstructor().newInstance());
}

... and then everything gets garbage-collected when the context does.

Or do you really need to pass something that gets created before all this, or to close some files after initialization?

This seems very complicated to me, I must be missing a requirement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Reviewers

@yrodiere yrodiere yrodiere left review comments

Assignees
No one assigned
Labels
None yet
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

AltStyle によって変換されたページ (->オリジナル) /