I am trying to write a Validator that should validate the value of a property in application.properties
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = BaseUrlStartsWithHttpsValidator.class)
public #interface CheckBaseUrlStartsWithHttps {
String message() default "Base url does not start with https:// check your configuration, "
+ "Found: ${validatedValue}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String value() default "";
}
It's a simple validation I am just checking if the String starts with https://.
and the way I am trying to use it by annotating the field with it so:
#CheckBaseUrlStartsWithHttps
#Value("${my.base.url}")
private String baseUrl;
But it seems not to do the trick I have tried changing the #Target type is it even possible to validate properties this way, I am using Spring Framework.
So figured it out my self upon reading, reading, reading and trying different things. Turns out that in the class where I am reading in the property I had to annotate the class itself with #Validated and #ConfigurationProperties(prefix = "my") then my check was working as supposed to. So the end product is:
public class BaseUrlValidator implements ConstraintValidator<CheckBaseUrl, String> {
#Override
public boolean isValid(#Nullable String value, #Nullable ConstraintValidatorContext context) {
if (value == null) {
return false;
}
return value.startsWith("https://");
}
}
and
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = BaseUrlValidator.class)
public #interface CheckBaseUrl {
String message() default "Base URL should start with https://. Found: ${validatedValue}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String value() default "";
}
and
#Service
#Validated
#ConfigurationProperties(prefix = "my")
public class MyService {
#CheckBaseUrl
#Value("${my.base.url}")
private String baseUrl;
...
}
Only thing that might be a bit annoying though is that this will make the application fail on startup if the urls is not configured correctly, which is in its own probably a good thing such that it can be fixed right away, but I would rather want it to fail on runtime when it is accessed and throw a RumetimeException instead. Anyway this seems to do the trick.
Related
I have a custom annotation called Matches which has a default value for message. This is essentially a class-level custom constraint validator but that's not part of the problem.
#Constraint(validatedBy = MatchesValidator.class)
#Documented
#Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Repeatable(Matches.List.class)
public #interface Matches {
String field();
String otherField();
String message() default "{com.example.api.validation.constraints.Matches.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
#Documented
#Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#interface List {
Matches[] value();
}
}
This is a simple constraint that will be applied on a class to validate if two fields have the same value:
#Matches(field = "password", otherField = "confirmPassword")
class UserRegistration {
private String password;
private String confirmPassword;
// Getters
}
I'm writing a test using JUnit and Mockito and and I'm trying to mock the Matches annotation:
#Test
void isValid_whenFieldsDoNotMatch_thenReturnsFalse() {
Matches invalidPropertyMatches = Mockito.mock(Matches.class);
when(invalidPropertyMatches.field()).thenReturn("password");
when(invalidPropertyMatches.field()).thenReturn("confirmPassword");
when(invalidPropertyMatches.message()).thenCallRealMethod(); // This throws
}
I want that when the Matches.message() value is called, I get back the default defined value "{com.example.api.validation.constraints.Matches.message}", so I added:
when(invalidPropertyMatches.message()).thenCallRealMethod();
However, this throws the following exception:
org.mockito.exceptions.base.MockitoException:
Cannot call abstract real method on java object!
Calling real methods is only possible when mocking non abstract method.
//correct example:
when(mockOfConcreteClass.nonAbstractMethod()).thenCallRealMethod();
It essentially says that Matches.message() is abstract and cannot be called. Are annotation properties in Java abstract? Is there a way to fix this?
I'm trying to implement a custom annotation to validate my fields. The idea is that the validation fails whenever the annotated field is null. Something like this.
#RequiredProperty
public abstract Person getPerson();
Now if this returns a null Person, I'd like the validation to fail (ideally with a custom message "Person field is null").
I tried to do it like this.
#Documented
#Constraint(validatedBy = RequiredPropertyValidator.class)
#Retention(RetentionPolicy.RUNTIME)
#Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE})
#ReportAsSingleViolation
public #interface RequiredProperty {
String message() default "{javax.validation.constraints.RequiredProperty.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And validator.
public class RequiredPropertyValidator implements ConstraintValidator<RequiredProperty, Object> {
#Override
public void initialize(RequiredProperty constraintAnnotation) {
}
#Override
public boolean isValid(Object property, ConstraintValidatorContext context) {
return property != null;
}
}
However this won't work. It doesn't validate at all. Object property is never even passed to the isValid method. Any advice on how to get it working?
UPDATE
Removing the empty initialize method got it working. However, I'm not sure how to create a custom error message that the "Person" field is null. Is that possible?
I created a custom message in .properties file, but this is just a static message, and I'd like to capture the actual field from runtime.
i have a java project and use a custom hibernate validator in that. According to Hibernate Docs , custom error messages should be defined as key-value in ValidateMessages.properties and this file must be created in "classpath" directory.
my problem is that classpath is under "target" directory and it will be deleted after clean-build the project so the created .properties file will be gone. how it can be solved?
`#Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = NCValidator.class)
#Documented
public #interface NC {
String message() default "{msg}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};}
///////////////////////////////
` public class NCValidator implements
ConstraintValidator<NC, String> {
#Override
public void initialize(NC constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
#Override
public boolean isValid(String string, ConstraintValidatorContext
context) {
...
...
}
}`
and use this custom validator in a class like this:
`#ValidateNC
default public String getNC() {
return (String) get("nC");
}
`
Put your .properties file under your resources folder or anywhere in your system and point to it.
Like that
#NotNull(code=10000)
#Size(min=5, max=10, code=10001)
Java bean validation has 3 properties: message, payload and groups. I wanna add a new one: code.
I've checked some docs, like https://docs.jboss.org/hibernate/validator/5.0/reference/en-US/html/validator-customconstraints.html and https://docs.oracle.com/javaee/6/tutorial/doc/gkfgx.html. It seems not possible?
Short answer to your question: No, you can't just add an extra field, nor can you use inheritance too add the extra field. See this question which explains why Java doesn't allow inheritance with annotations.
What you'll have to do is create your own particular version of the #Size annotation. But you should be able to apply the #Size annotation to your custom annotation so that #Size is checked automatically when you run your validations.
Your annotation would probably look something like this:
#Constraint(validatedBy = { })
#Target({ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#ReportAsSingleViolation
//The only downside of this approach
//is that you have to hardcode your min and max values
#Size(min=5, max=10)
public #interface CodedSize {
String message() default "{Default Validation Message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int code() default 0;
}
If you wanted to specify the size as well in the annotation, you could do that, and write a custom validator that validates your annotation.
#Constraint(validatedBy = { CodedSizeValidator.class })
#Target({ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
public #interface CodedSize {
String message() default "{Default Validation Message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int minSize() default 5;
int maxSize() default 10;
int code() default 0;
}
Then your custom validator looks something like this:
public class CodedSizeValidator implements ConstraintValidator<CodedSize, String> {
private int minSize;
private int maxSize;
private int code;
#Override
public void initialize(CodedSize constraintAnnotation){
this.minSize = constraintAnnotation.minSize();
this.maxSize = constraintAnnotation.maxSize();
this.code = constraintAnnotation.code();
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
boolean isValid = false;
if(value == null || value.isEmpty()) {
//if a null or empty value is valid, then set it here.
isValid = true;
} else {
//Logic here to determine if your value is valid by size constraints.
}
return isValid;
}
}
I used String because that seemed the most applicable, but you could easily use Number, or even a generic type to allow you to use this annotation on more than one field. The advantage of doing it this way is that if you want to add a null check in with this validation, you can do so.
The ConstraintValidatorContext can be used to build out your error message if multiple validations fail. You can make this as detailed as you want, but be aware that this code can spaghetti-fy very quickly.
I want to validate a field 'foo' against either of two constraints, i.e. something like this
#ConstraintA OR #ConstraintB
private String foo;
Is this possible?
This is possible with Hibernate Validator, but only using a Hibernate Validator specific extension. Using it is not standard conform to Bean Validation.
You will have to use boolean composition of constraints as described here - http://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#section-boolean-constraint-composition
You will need a "wrapper" constraint. Something like this:
#ConstraintComposition(OR)
#PConstraintA
#ConstraintB
#ReportAsSingleViolation
#Target({ METHOD, FIELD })
#Retention(RUNTIME)
#Constraint(validatedBy = { })
public #interface ConstraintAOrB {
String message() default "{com.acme.ConstraintAOrB.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}