-
-
Notifications
You must be signed in to change notification settings - Fork 198
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
hibernate-validator: better support for custom ConstraintValidatorFactory #3596
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,17 +7,18 @@ | |
|
||
import static jakarta.validation.Validation.byProvider; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.function.Consumer; | ||
|
||
import org.hibernate.validator.HibernateValidator; | ||
import org.hibernate.validator.HibernateValidatorConfiguration; | ||
|
||
import edu.umd.cs.findbugs.annotations.NonNull; | ||
import io.jooby.Context; | ||
import io.jooby.Extension; | ||
import io.jooby.Jooby; | ||
import io.jooby.StatusCode; | ||
import io.jooby.*; | ||
import io.jooby.internal.hibernate.validator.CompositeConstraintValidatorFactory; | ||
import io.jooby.validation.BeanValidator; | ||
import jakarta.validation.ConstraintValidatorFactory; | ||
import jakarta.validation.ConstraintViolationException; | ||
import jakarta.validation.Validator; | ||
|
||
|
@@ -53,18 +54,32 @@ | |
*/ | ||
public class HibernateValidatorModule implements Extension { | ||
private static final String CONFIG_ROOT_PATH = "hibernate.validator"; | ||
// TODO: remove it on next major | ||
private Consumer<HibernateValidatorConfiguration> configurer; | ||
private StatusCode statusCode = StatusCode.UNPROCESSABLE_ENTITY; | ||
private String title = "Validation failed"; | ||
private boolean disableDefaultViolationHandler = false; | ||
private boolean logException = false; | ||
private List<ConstraintValidatorFactory> factories; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just curious, do you have real use cases in mind where you need multiple factories? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't need it if we rely on DI. So this is for people who doesn't like DI (reflective or not), so they can just do:
|
||
private final HibernateValidatorConfiguration configuration; | ||
|
||
public HibernateValidatorModule(@NonNull HibernateValidatorConfiguration configuration) { | ||
this.configuration = configuration; | ||
} | ||
|
||
public HibernateValidatorModule() { | ||
this(byProvider(HibernateValidator.class).configure()); | ||
} | ||
|
||
/** | ||
* Setups a configurer callback. | ||
* | ||
* @param configurer Configurer callback. | ||
* @return This module. | ||
* @deprecated Use {@link | ||
* HibernateValidatorModule#HibernateValidatorModule(HibernateValidatorConfiguration)} | ||
*/ | ||
@Deprecated | ||
public HibernateValidatorModule doWith( | ||
@NonNull final Consumer<HibernateValidatorConfiguration> configurer) { | ||
this.configurer = configurer; | ||
|
@@ -118,28 +133,53 @@ public HibernateValidatorModule disableViolationHandler() { | |
return this; | ||
} | ||
|
||
/** | ||
* Add a custom {@link ConstraintValidatorFactory}. This factory is allowed to returns <code>null | ||
* </code> allowing next factory to create an instance (default or one provided by DI). | ||
* | ||
* @param factory Factory. | ||
* @return This module. | ||
*/ | ||
public HibernateValidatorModule with(ConstraintValidatorFactory factory) { | ||
if (factories == null) { | ||
factories = new ArrayList<>(); | ||
} | ||
this.factories.add(factory); | ||
return this; | ||
} | ||
|
||
@Override | ||
public void install(@NonNull Jooby app) throws Exception { | ||
var config = app.getConfig(); | ||
var hbvConfig = byProvider(HibernateValidator.class).configure(); | ||
|
||
if (config.hasPath(CONFIG_ROOT_PATH)) { | ||
config | ||
.getConfig(CONFIG_ROOT_PATH) | ||
.root() | ||
.forEach( | ||
(k, v) -> | ||
hbvConfig.addProperty(CONFIG_ROOT_PATH + "." + k, v.unwrapped().toString())); | ||
configuration.addProperty(CONFIG_ROOT_PATH + "." + k, v.unwrapped().toString())); | ||
} | ||
|
||
// Set default constraint validator factory. | ||
var delegateFactory = | ||
new CompositeConstraintValidatorFactory( | ||
app, configuration.getDefaultConstraintValidatorFactory()); | ||
if (this.factories != null) { | ||
this.factories.forEach(delegateFactory::add); | ||
this.factories.clear(); | ||
} | ||
configuration.constraintValidatorFactory(delegateFactory); | ||
if (configurer != null) { | ||
configurer.accept(hbvConfig); | ||
configurer.accept(configuration); | ||
} | ||
|
||
try (var factory = hbvConfig.buildValidatorFactory()) { | ||
Validator validator = factory.getValidator(); | ||
app.getServices().put(Validator.class, validator); | ||
app.getServices().put(BeanValidator.class, new BeanValidatorImpl(validator)); | ||
var services = app.getServices(); | ||
try (var factory = configuration.buildValidatorFactory()) { | ||
var validator = factory.getValidator(); | ||
services.put(Validator.class, validator); | ||
services.put(BeanValidator.class, new BeanValidatorImpl(validator)); | ||
// Allow to access validator factory so hibernate can access later | ||
var constraintValidatorFactory = factory.getConstraintValidatorFactory(); | ||
services.put(ConstraintValidatorFactory.class, constraintValidatorFactory); | ||
|
||
if (!disableDefaultViolationHandler) { | ||
app.error( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
/* | ||
* Jooby https://jooby.io | ||
* Apache License Version 2.0 https://jooby.io/LICENSE.txt | ||
* Copyright 2014 Edgar Espina | ||
*/ | ||
package io.jooby.internal.hibernate.validator; | ||
|
||
import java.util.Deque; | ||
import java.util.LinkedList; | ||
|
||
import org.slf4j.Logger; | ||
|
||
import io.jooby.Jooby; | ||
import jakarta.validation.ConstraintValidator; | ||
import jakarta.validation.ConstraintValidatorFactory; | ||
|
||
public class CompositeConstraintValidatorFactory implements ConstraintValidatorFactory { | ||
private final Logger log; | ||
private final ConstraintValidatorFactory defaultFactory; | ||
private final Deque<ConstraintValidatorFactory> factories = new LinkedList<>(); | ||
|
||
public CompositeConstraintValidatorFactory( | ||
Jooby registry, ConstraintValidatorFactory defaultFactory) { | ||
this.log = registry.getLog(); | ||
this.defaultFactory = defaultFactory; | ||
this.factories.addLast(new RegistryConstraintValidatorFactory(registry)); | ||
} | ||
|
||
public ConstraintValidatorFactory add(ConstraintValidatorFactory factory) { | ||
this.factories.addFirst(factory); | ||
return this; | ||
} | ||
|
||
@Override | ||
public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) { | ||
if (isBuiltIn(key)) { | ||
// use default factory for built-in constraint validators | ||
return defaultFactory.getInstance(key); | ||
} else { | ||
for (var factory : factories) { | ||
var instance = factory.getInstance(key); | ||
if (instance != null) { | ||
return instance; | ||
} | ||
} | ||
// fallback or fail | ||
return defaultFactory.getInstance(key); | ||
} | ||
} | ||
|
||
@Override | ||
public void releaseInstance(ConstraintValidator<?, ?> instance) { | ||
if (isBuiltIn(instance.getClass())) { | ||
defaultFactory.releaseInstance(instance); | ||
} else { | ||
if (instance instanceof AutoCloseable closeable) { | ||
try { | ||
closeable.close(); | ||
} catch (Exception e) { | ||
log.debug("Failed to release constraint", e); | ||
} | ||
} | ||
} | ||
} | ||
|
||
private boolean isBuiltIn(Class<?> key) { | ||
var name = key.getName(); | ||
return name.startsWith("org.hibernate.validator") || name.startsWith("jakarta.validation"); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* | ||
* Jooby https://jooby.io | ||
* Apache License Version 2.0 https://jooby.io/LICENSE.txt | ||
* Copyright 2014 Edgar Espina | ||
*/ | ||
package io.jooby.internal.hibernate.validator; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import io.jooby.Registry; | ||
import io.jooby.exception.RegistryException; | ||
import jakarta.validation.ConstraintValidator; | ||
import jakarta.validation.ConstraintValidatorFactory; | ||
|
||
public class RegistryConstraintValidatorFactory implements ConstraintValidatorFactory { | ||
private final Logger log = LoggerFactory.getLogger(getClass()); | ||
private final Registry registry; | ||
|
||
public RegistryConstraintValidatorFactory(Registry registry) { | ||
this.registry = registry; | ||
} | ||
|
||
@Override | ||
public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) { | ||
try { | ||
return registry.require(key); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should work for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure if possible to implement a registry for Dagger, will add a note. |
||
} catch (RegistryException notfound) { | ||
return null; | ||
} | ||
} | ||
|
||
@Override | ||
public void releaseInstance(ConstraintValidator<?, ?> instance) { | ||
if (instance instanceof AutoCloseable closeable) { | ||
try { | ||
closeable.close(); | ||
} catch (Exception e) { | ||
log.debug("Failed to release constraint", e); | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for
avaje
it's important to add@Singleton
annotation, otherwise, the instantiation will fail, and the error message is not clear - it hides the real reason related to DI.