If you want to check the input with Java
, it is Bean Validation
. It's easy to use and convenient because it only adds annotations: smile :.
Now, let's create an annotation that checks if the property value is included in the array of strings passed as an argument, as shown below.
AcceptedStringValues.java
@Documented
@Constraint(validatedBy = {AcceptedStringValuesValidator.class})
@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface AcceptedStringValues {
String message() default "{com.neriudon.example.validator.AcceptedStringValues.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String[] value();
@Target({METHOD, FIELD})
@Retention(RUNTIME)
@Documented
public @interface List {
AcceptedStringValues[] value();
}
}
Validator
checks if the string passed tovalue ()
of the ʻAcceptedStringValues` annotation contains the return value of the annotated field or method.
AcceptedStringValuesValidator.java
public class AcceptedStringValuesValidator
implements ConstraintValidator<AcceptedStringValues, String> {
// accepted values array
private String[] validValues;
@Override
public void initialize(AcceptedStringValues constraintAnnotation) {
validValues = constraintAnnotation.value();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
// check to exist value or not in accepted values array
return Arrays.stream(validValues).anyMatch(s -> Objects.equals(value, s));
}
}
The test code looks like ↓.
ValidationSampleApplicationTests.java
public class ValidationSampleApplicationTests {
private ValidatorFactory validatorFactory;
private Validator validator;
@Before
public void setup() {
validatorFactory = Validation.buildDefaultValidatorFactory();
validator = validatorFactory.getValidator();
}
@Test
public void acceptedStringValuesNormal() throws UnsupportedEncodingException {
AcceptedStringValuesSample sample = new AcceptedStringValuesSample("1");
Set<ConstraintViolation<AcceptedStringValuesSample>> result = validator.validate(sample);
// no error
assertThat(result.isEmpty(), is(true));
}
@Test
public void acceptedStringValuesNg() throws Exception {
AcceptedStringValuesSample sample = new AcceptedStringValuesSample("0");
Set<ConstraintViolation<AcceptedStringValuesSample>> result = validator.validate(sample);
// error
assertThat(result.size(), is(1));
// assert error value and message
result.stream().forEach(r -> {
assertThat(r.getInvalidValue(), is("0"));
assertThat(r.getMessage(), is("not accepted value."));
});
}
private static class AcceptedStringValuesSample {
@AcceptedStringValues({"1", "2", "3", "4", "5"})
private String code;
public AcceptedStringValuesSample(String code) {
this.code = code;
}
}
}
You know that error messages are automatically called if you create ValidationMessages.properties
under the classpath and set the message with the value specified in message
of the annotation class as the key.
In this example, it looks like ↓.
ValidationMessages.properties
com.neriudon.example.validator.AcceptedStringValues.message = not accepted values.
It may look like ↑, but if the message is fixed, it may be difficult for the user to understand.
In Bean Validation
, the property value of the annotation class can be embedded in the error message, so try to output the value set in the value of the annotation class in{value}
as a character string.
ValidationMessages.properties
com.neriudon.example.validator.AcceptedStringValues.message = not contained accepted values: {value}.
Will result in the message not contained accepted values: [1, 2, 3, 4, 5] .
.
Bean Validation
has supported EL expressions since 1.1!
By specifying $ {validatedValue}
, you can embed the object that caused the error ...
ValidationMessages.properties
com.neriudon.example.validator.AcceptedStringValues.message = ${validatedValue} is not contained accepted values.
This will result in the message xxx is not contained accepted values.
. (XXX is the object that caused the error)
** However, $ {validatedValue}
outputs the object in error as it is, so do not use it when handling confidential information such as passwords. ** **
Well, the main subject is from here.
So far we have dealt with String
, but with ʻInteger` we create an annotation with the same functionality.
AcceptedIntegerValues.java
@Documented
@Constraint(validatedBy = { AcceptedIntegerValuesValidator.class })
@Target({ METHOD, FIELD })
@Retention(RUNTIME)
public @interface AcceptedIntegerValues {
String message() default "{com.neriudon.example.validator.AcceptedIntegerValues.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int[] value();
@Target({ METHOD, FIELD })
@Retention(RUNTIME)
@Documented
@interface List {
AcceptedIntegerValues[] value();
}
}
AcceptedIntegerValuesValidator.java
public class AcceptedIntegerValuesValidator implements ConstraintValidator<AcceptedIntegerValues, Integer> {
// accepted values array
private Integer[] validValues;
@Override
public void initialize(AcceptedIntegerValues constraintAnnotation) {
validValues = ArrayUtils.toObject(constraintAnnotation.value());
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
// check to exist value or not in accepted values array
return Arrays.stream(validValues).anyMatch(s -> Objects.equals(value, s));
}
}
Set an error message ...
ValidationMessages.properties
com.neriudon.example.validator.AcceptedIntegerValues.message = not contained accepted values: {value}.
Now, test
TestCode.java
@Test
public void acceptedIntegerValuesNormal() {
AcceptedIntegerValuesSample sample = new AcceptedIntegerValuesSample(1);
Set<ConstraintViolation<AcceptedIntegerValuesSample>> result = validator.validate(sample);
assertThat(result.isEmpty(), is(true));
}
@Test
public void acceptedIntegerValuesNg() {
AcceptedIntegerValuesSample sample = new AcceptedIntegerValuesSample(0);
Set<ConstraintViolation<AcceptedIntegerValuesSample>> result = validator.validate(sample);
assertThat(result.size(), is(1));
result.stream().forEach(r -> {
assertThat(r.getInvalidValue(), is(0));
});
}
private static class AcceptedIntegerValuesSample {
@AcceptedIntegerValues({ 1, 2, 3, 4, 5 })
private int code;
public AcceptedIntegerValuesSample(int code) {
this.code = code;
}
}
Then ...
javax.validation.ValidationException: HV000149: An exception occurred during message interpolation
at org.hibernate.validator.internal.engine.ValidationContext.interpolate(ValidationContext.java:477)
at org.hibernate.validator.internal.engine.ValidationContext.createConstraintViolation(ValidationContext.java:322)
at org.hibernate.validator.internal.engine.ValidationContext.lambda$createConstraintViolations$0(ValidationContext.java:279)
at java.util.stream.ReferencePipeline$3$1.accept(Unknown Source)
at java.util.Collections$2.tryAdvance(Unknown Source)
at java.util.Collections$2.forEachRemaining(Unknown Source)
at java.util.stream.AbstractPipeline.copyInto(Unknown Source)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(Unknown Source)
at java.util.stream.AbstractPipeline.evaluate(Unknown Source)
at java.util.stream.ReferencePipeline.collect(Unknown Source)
at org.hibernate.validator.internal.engine.ValidationContext.createConstraintViolations(ValidationContext.java:280)
at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateSingleConstraint(ConstraintTree.java:182)
at org.hibernate.validator.internal.engine.constraintvalidation.SimpleConstraintTree.validateConstraints(SimpleConstraintTree.java:68)
at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateConstraints(ConstraintTree.java:73)
at org.hibernate.validator.internal.metadata.core.MetaConstraint.doValidateConstraint(MetaConstraint.java:127)
at org.hibernate.validator.internal.metadata.core.MetaConstraint.validateConstraint(MetaConstraint.java:120)
at org.hibernate.validator.internal.engine.ValidatorImpl.validateMetaConstraint(ValidatorImpl.java:533)
at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForSingleDefaultGroupElement(ValidatorImpl.java:496)
at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForDefaultGroup(ValidatorImpl.java:465)
at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForCurrentGroup(ValidatorImpl.java:430)
at org.hibernate.validator.internal.engine.ValidatorImpl.validateInContext(ValidatorImpl.java:380)
at org.hibernate.validator.internal.engine.ValidatorImpl.validate(ValidatorImpl.java:169)
at com.neriudon.example.ValidationSampleApplicationTests.acceptedIntegerValuesNg(ValidationSampleApplicationTests.java:98)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:73)
at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:83)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)
Caused by: java.lang.ClassCastException: [I cannot be cast to [Ljava.lang.Object;
at org.hibernate.validator.internal.engine.messageinterpolation.ParameterTermResolver.interpolate(ParameterTermResolver.java:30)
at org.hibernate.validator.internal.engine.messageinterpolation.InterpolationTerm.interpolate(InterpolationTerm.java:64)
at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.interpolate(ResourceBundleMessageInterpolator.java:76)
at org.hibernate.validator.messageinterpolation.AbstractMessageInterpolator.interpolateExpression(AbstractMessageInterpolator.java:385)
at org.hibernate.validator.messageinterpolation.AbstractMessageInterpolator.interpolateMessage(AbstractMessageInterpolator.java:274)
at org.hibernate.validator.messageinterpolation.AbstractMessageInterpolator.interpolate(AbstractMessageInterpolator.java:220)
at org.hibernate.validator.internal.engine.ValidationContext.interpolate(ValidationContext.java:468)
... 55 more
Something went wrong: weary :.
It seems that casting from the array of ʻInteger from "
[I cannot be cast to [Ljava.lang.Object; `" has failed.
Let's try to make it String
with an EL expression.
ValidationMessages.properties
com.neriudon.example.validator.AcceptedIntegerValues.message = not contained accepted values: ${value.toString()}.
When I tested it with this, I didn't get an error, but it looks like this:
not contained accepted values: [I@6e9319f. //Integer[]Edition
not contained accepted values: [1, 2, 3, 4, 5]. // String[]Edition
So, let's convert from ʻInteger [] to
String with ʻArrays.toString
.
ValidationMessages.properties
com.neriudon.example.validator.AcceptedIntegerValues.message = not contained accepted values: ${java.util.Arrays.toString(value)}.
So, when you run it ...
javax.el.PropertyNotFoundException: ELResolver cannot handle a null base Object with identifier [java]
There is no object like java! I got the error: disappointed_relieved :.
Going back to the beginning, [EL expression of Hibernate Validator 5 error message](http://docs.jboss.org/hibernate/validator/5.2/reference/en-US/html/ch04.html#section-interpolation- Read about with-message-expressions). Then ...
The validation engine makes the following objects available in the EL context:
the attribute values of the constraint mapped to the attribute names the currently validated value (property, bean, method parameter etc.) under the name validatedValue a bean mapped to the name formatter exposing the var-arg method format(String format, Object… args) which behaves like java.util.Formatter.format(String format, Object… args).
If you translate it freely ...
The validation engine can use the following objects in EL expressions:
java.util.Formatter.format (String format, Object… args)
based formattingIn other words, nothing else can be used ...? Even if you refer to the sample below the link, you didn't process the message by calling the class provided by Java. / (^ O ^) \ Nantekotai.
But it's not without its way. In the tips written below the sample, it is written as follows.
Only actual constraint attributes can be interpolated using message parameters in the form {attributeName}. When referring to the validated value or custom expression variables added to the interpolation context (see Section 11.9.1, “HibernateConstraintValidatorContext”), an EL expression in the form ${attributeName} must be used.
If you translate it freely ...
If you set the value you want to use for the context, you can refer to it with $ {}
!
And that. Set based on the sample written in 11.9.1. HibernateConstraintValidatorContext Let's try.
Add the process of converting ʻInterger []to
String and storing it in the context as ʻacceptedValuesToString
to the ʻisValid` method.
AcceptedIntegerValuesValidator.java
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
// add acceptedValuesToString variable converted accepted integer values to string
context.unwrap(HibernateConstraintValidatorContext.class).addExpressionVariable("acceptedValuesToString", Arrays.toString(validValues));
// check to exist value or not in accepted values array
return Arrays.stream(validValues).anyMatch(s -> Objects.equals(value, s));
}
So, if you call $ {acceptedValuesToString}
in ValidationMessages.properties
...
ValidationMessages.properties
com.neriudon.example.validator.AcceptedIntegerValues.message = not contained accepted values: ${acceptedValuesToString}.
I got the error message not contained accepted values: [1, 2, 3, 4, 5] .
! I did it: stuck_out_tongue_closed_eyes :!
In this article, I showed you how to make arbitrary values callable in Bean Validation error messages. However, there are two problems with this method.
--It is necessary to explain to the user the values that can be used in the error message.
Since the default Validation function is extended, there is no problem if you develop it individually, but if you publish it as a library, for example, you need to describe what values can be used in JavaDoc etc. ..
--This feature itself may change in the future
~~ Written in WARN under the HibernateConstraintValidatorContext section, this feature itself is subject to change in the future. ~~
~~ As the person who wrote the article says, it's a feature that shouldn't be used too often: rolling_eyes :. ~~
In Hibernate Validator 6.0.13.Final, the above warning was removed. Was it fixed?
So if your project decides to use Hibernate Validator
, this feature is useful (which one).
Recommended Posts