Skip to content
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

Add another example for a constraint validator with an injected bean #46130

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
38 changes: 38 additions & 0 deletions docs/src/main/asciidoc/validation.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,44 @@ The scope you choose for your `ConstraintValidator` bean is important:
- If the `ConstraintValidator` bean implements the `initialize(A constraintAnnotation)` method and depends on the state of the constraint annotation, use the `@Dependent` scope to make sure each annotation context has a separate and properly configured instance of the `ConstraintValidator` bean.
====

When injecting beans relying on the runtime configuration, use `@Inject Instance<..>`. Since constraints are initialised
Copy link
Member

Choose a reason for hiding this comment

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

Let's use US English given the method is called initialize():

Suggested change
When injecting beans relying on the runtime configuration, use `@Inject Instance<..>`. Since constraints are initialised
When injecting beans relying on the runtime configuration, use `@Inject Instance<..>`. Since constraints are initialized

at build time, it is not possible to completely preconfigure the constraint in the `initialize(..)` method,
as the runtime information will be missing at that point. The `initialize(..)` method in such scenarios can still be used
to read the constraint annotation parameters, and perform any work not relying on the runtime configuration.
Hence, it is recommended that any heavy configuration work happens as part of the injected bean initialisation
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Hence, it is recommended that any heavy configuration work happens as part of the injected bean initialisation
Hence, it is recommended that any heavy configuration work happens as part of the injected bean initialization

and methods used in the `ConstraintValidator#isValid(..)` implementation are as fast as possible:

[source,java]
----
@ApplicationScoped
public class MyConstraintValidator implements ConstraintValidator<MyConstraint, String> {

@Inject
Instance<MyService> service;

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

return service.get().validate(value);
}
}

@ApplicationScoped
public class MyService {

public MyService(..) {
// perform all possible "initialisation" work
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// perform all possible "initialisation" work
// perform all possible "initialization" work

Copy link
Member

Choose a reason for hiding this comment

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

You're talking about runtime configuration but then we don't see it in the example?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hey 😃 👋🏻
thanks for taking a look and for the suggestions!
Yeah ... I didn't want to make this example long and with some code that is, most likely, irrelevant to the user, so I thought I could get away with just a comment 🙈 😃.

would the following work here:

@ApplicationScoped
public class MyService {

	private final Predicate<String> validationFunction;

	@Inject
	public MyService(MyRuntimeConfigProperties config) {
		// perform all possible "initialization" work, e.g.:
		if (config.complexValidationEnabled()) {
			validationFunction = s -> ...
		} else {
			validationFunction = String::isBlank;
		}
	}

	public boolean validate(String value) {
		// perform the validation
		return validationFunction.test(value);
	}
}

}

public boolean validate(String value) {
// perform the validation
}
}
----

=== Validation and localization

By default, constraint violation messages will be returned in the build system locale.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import io.quarkus.it.hibernate.validator.groups.MyBeanWithGroups;
import io.quarkus.it.hibernate.validator.groups.ValidationGroups;
import io.quarkus.it.hibernate.validator.injection.InjectedConstraintValidatorConstraint;
import io.quarkus.it.hibernate.validator.injection.InjectedRuntimeConstraintValidatorConstraint;
import io.quarkus.it.hibernate.validator.injection.MyService;
import io.quarkus.it.hibernate.validator.orm.TestEntity;
import io.quarkus.runtime.StartupEvent;
Expand Down Expand Up @@ -199,6 +200,12 @@ public String testInjection() {

result.append(formatViolations(validator.validate(new BeanWithInjectedConstraintValidatorConstraint("Invalid value"))));

result.append(formatViolations(
validator.validate(new BeanWithInjectedRuntimeConstraintValidatorConstraint("any text is valid"))));

result.append(formatViolations(
validator.validate(new BeanWithInjectedRuntimeConstraintValidatorConstraint("numbers 12345 don't work"))));

return result.build();
}

Expand Down Expand Up @@ -432,6 +439,20 @@ public String getValue() {
}
}

public static class BeanWithInjectedRuntimeConstraintValidatorConstraint {

@InjectedRuntimeConstraintValidatorConstraint
private String value;

public BeanWithInjectedRuntimeConstraintValidatorConstraint(String value) {
this.value = value;
}

public String getValue() {
return value;
}
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@InterceptorBinding
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.quarkus.it.hibernate.validator.injection;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Instance;
import jakarta.inject.Inject;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

@ApplicationScoped
public class InjectedRuntimeConstraintValidator
implements ConstraintValidator<InjectedRuntimeConstraintValidatorConstraint, String> {

@Inject
Instance<MyRuntimeService> service;

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

return service.get().validate(value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.quarkus.it.hibernate.validator.injection;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

@Target({ ElementType.TYPE_USE, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = { InjectedRuntimeConstraintValidator.class })
public @interface InjectedRuntimeConstraintValidatorConstraint {

String message() default "{InjectedRuntimeConstraintValidatorConstraint.message}";

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

Class<? extends Payload>[] payload() default {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.quarkus.it.hibernate.validator.injection;

import io.smallrye.config.ConfigMapping;

@ConfigMapping(prefix = "io.quarkus.it.hibernate.validator.injection")
public interface MyRuntimeConfiguration {

String pattern();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.quarkus.it.hibernate.validator.injection;

import java.util.regex.Pattern;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

@ApplicationScoped
public class MyRuntimeService {

private final Pattern pattern;

@Inject
public MyRuntimeService(MyRuntimeConfiguration configuration) {
this.pattern = Pattern.compile(configuration.pattern());
}

public boolean validate(String value) {
return pattern.matcher(value).matches();
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
MyCustomConstraint.message = invalid MyOtherBean
InjectedConstraintValidatorConstraint.message = InjectedConstraintValidatorConstraint violation
InjectedRuntimeConstraintValidatorConstraint.message = InjectedRuntimeConstraintValidatorConstraint violation
pattern.message=Value is not in line with the pattern
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ quarkus.datasource.db-kind=h2
quarkus.datasource.jdbc.url=jdbc:h2:tcp://localhost/mem:test
quarkus.hibernate-orm.database.generation = drop-and-create

io.quarkus.it.hibernate.validator.injection.pattern=[^0-9]++
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,9 @@ public void testNoProduces() {
public void testInjection() throws Exception {
StringBuilder expected = new StringBuilder();
expected.append("passed").append("\n");
expected.append("failed: value (InjectedConstraintValidatorConstraint violation)");
expected.append("failed: value (InjectedConstraintValidatorConstraint violation)\n");
expected.append("passed").append("\n");
expected.append("failed: value (InjectedRuntimeConstraintValidatorConstraint violation)");

RestAssured.when()
.get("/hibernate-validator/test/injection")
Expand Down
Loading