Is it possible to add custom property to java bean validation annotation? - java

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.

Related

How to use application.properties values in javax.validation annotations

I have a variable called notification.max-time-to-live in application.yaml file and want to use this as the value of javax.validation.constraints.#Max() annotation.
I've tried in many ways (using env.getProperty(), #Value, etc) and it says it must be a constant value, is there any way to do this?
I know this does not directly answer my question and as M. Deinum already said the answer is no. Nonetheless it's a simple workaround.
It's true that #Max and other javax annotations do not let us use dynamic values, however, we can create a custom annotation (as M. Deinum suggested) that uses values from application.yaml with spring #Value.
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Constraint(validatedBy = ValidTimeToLiveValidator.class)
public #interface ValidTimeToLive {
String message() default "must be less than or equal to %s";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
And the respective validator.
public class ValidTimeToLiveValidator implements ConstraintValidator<ValidTimeToLive, Integer> {
#Value("${notification.max-time-to-live}")
private int maxTimeToLive;
#Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
// leave null-checking to #NotNull
if (value == null) {
return true;
}
formatMessage(context);
return value <= maxTimeToLive;
}
private void formatMessage(ConstraintValidatorContext context) {
String msg = context.getDefaultConstraintMessageTemplate();
String formattedMsg = String.format(msg, this.maxTimeToLive);
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(formattedMsg)
.addConstraintViolation();
}
}
Now we just need to add this custom annotation in the respective class.
public class Notification {
private String id;
#ValidTimeToLive
private Integer timeToLive;
// ...
}

Java Bean Validation for long type

I can't find a way to validate when a long variable comes with null value. I have to validate BigDecimal and long variables, for BigDecimal my custom annotation works fine, but for long type doesn't work. I'm using the Number class to wrap the incomming type and validate the value.
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = NotNullNumberValidator.class)
#Documented
public #interface NotNullNumber {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
My NotNullNumberValidator class
class NotNullNumberValidator implements ConstraintValidator<NotNullNumber, Number> {
#Override
public boolean isValid(Number value, ConstraintValidatorContext context) {
return value != null;
}
}
Use of the Anootation
#NotNullNumber(message = "message for BigDecimal validation")
private BigDecimal subtotal; //works fine
#NotNullNumber(message = "message for long validation")
private long fechaPago;// not working}
Am I in the rigth way or there is another way to do this? #NotNull annotation doesn't make the job.
EDIT: I am using this validation with a #RequestBody, I want to validate if the JSON field (long) fechaPago is present in the request body.
I know that with the wrapper class Long works, but I can't change the variable type (the rules are the rules here).
I see you're using primitive long which has no idea of nulls, the validator should work fine if you convert it to the wrapper

How to reject not valid value from List in Spring validator?

I receive list of domain object ids deserialized from JSON client request body:
#JsonProperty("workgroups")
private List<WorkgroupId> workgroupIds = new ArrayList<>();
I need to validate these ids in org.springframework.validation.Validator.
for (WorkgroupId workgroupId : project.getWorkgroupIds()) {
if (!domainObjectTools.doesWorkgroupExist(workgroupId)) {
// reject this invalid value here...
}
}
Question
How to reject invalid value in org.springframework.validation.Errors?
Validator interface:
#Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = { CustomValidatorValidator.class })
#Documented
public #interface CustomValidator{
String message() default "Put here your default message";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
ValidatorImplementation:
public final class CustomValidatorValidator implements ConstraintValidator<CustomValidator, List<WorkgroupId>> {
#Override
public void initialize(CustomValidator constraintAnnotation) {
}
#Override
public boolean isValid(List<WorkgroupId> yourlist, ConstraintValidatorContext context) {
if (yourlist== null)
return true;
else
return yourlist.stream().anyMatch(s -> /* filter here as you want */);
}
}
Notice we return true if the field is null, I do it like this because I just put #NotNull constraint if I need not to be null so I have more control over the constraint.
Finally:
#JsonProperty("workgroups")
#CustomValidator
private List<WorkgroupId> workgroupIds = new ArrayList<>();
P.S: I don't understand why you initialize the list in this last code. If that's a field you're supposed to receive through the request then you don't need to initialize it, the json deserializer will initialize it with the incoming field in the json.
You can use reject() and/or rejectValue() along with the field/error code/defaultMessage or along with a custom validator to reject the values.

Validate #RequestParam to be in a list of enum

I want to limit the user of Spring MVC to access only certain values of the enum, I need to throw constraint exception when the requested param contains the restricted value.
Enum Example:
public enum EnumActionValues {
WAIT,
OFFLINE,
LOGGED_IN,
LOGGED_OUT,
OTHERS,
//
;
public List<EnumActionValues> getManuallyAllowedActions() {
return Arrays.asList(
WAIT,
OFFLINE,
OTHERS
);
}
}
In the above enum I want to webrequest to the Controller should contain only getManuallyAllowedActions, the LOGGED_IN and LOGGED_OUT shouldn't be allowed by user, which will be used internally.
Is there any direct annotations to be used with #Valid/#Validated.
You can have a custom annotation and a validator that goes with it.
Your annotation could look like this:
#Documented
#Constraint(validatedBy = YourConstraintValidator.class)
#Target( { ElementType.FIELD } )
#Retention(RetentionPolicy.RUNTIME)
public #interface YourConstraint
{
String message() default "Invalid enum";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And your validator would be:
public class YourConstraintValidator implements ConstraintValidator<YourConstraint, EnumActionValues> {
#Override
public void initialize(YourConstraint constraint) {
}
#Override
public boolean isValid(EnumActionValues obj, ConstraintValidatorContext context) {
return obj == null || obj.getManuallyAllowedActions().contains(obj);
}
}
This validator allows for the enum to be null so it will still work in case the enum is null in the request.
Note that you will have to use #ModelAttribute annotation instead of #RequestParam for this to work.
I think your requirement here is very specific and you probably have to write the check yourself. Something like this should do the trick:
public ResponseEntity someEndpoint(final EnumActionValues aAction) {
if ((aAction != null) && !EnumActionValues.getManuallyAllowedActions().contains(aAction)) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}
...
}

How to set default group in bean validation context

I'm working with bean validations and I'm searching for a possibility to set a default group of my own bean validation annotation.
I have something (working) like this:
Application.class (calling validate on MyBean)
public class Application {
public static void main(String[] args) {
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<MyBean>> violations =
validator.validate(new MyBean(), SecondStep.class);
}
}
MyBean.class (the bean itself; here is what I want to prevent)
public class MyBean {
// I don't want to write this "groups" attribute every time, because it's very clear,
// that this should only be validated for the second step, isn't it?
#RequiredBySecondStep(groups=SecondStep.class)
private Object myField;
}
RequiredBySecondStep.class (the bean validation annotation)
#Documented
#Target(FIELD)
#Retention(RUNTIME)
#Constraint(validatedBy = RequiredBySecondStepValidator.class)
public #interface RequiredBySecondStep {
String message() default "may not be null on the second step";
Class<?>[] groups() default {}; // <-- here I want to set SecondStep.class
Class<? extends Payload>[] payload() default {};
}
RequiredBySecondStepValidator.class (an implemented constraint validator)
public class RequiredBySecondStepValidator implements ConstraintValidator<RequiredBySecondStep, Object> {
public void initialize(RequiredBySecondStep constraintAnnotation) {
}
public boolean isValid(Object object, ConstraintValidatorContext constraintContext) {
return object != null;
}
}
SecondStep.class (the bean validation group)
public interface SecondStep {
}
Unfortunately, it's not possible by specification, to set the default group in the RequiredBySecondStep annotation like this:
Class<?>[] groups() default SecondStep.class;
// and using just the following in the bean:
#RequiredBySecondStep
private Object myField;
This will result in a RuntimeException:
javax.validation.ConstraintDefinitionException: Default value for
groups() must be an empty array
Furthermore, there is not only a SecondStep. There are probably 5 different groups which I want to annotate directly with a #RequiredByFirstStep or #RequiredByFifthStep.
Is there a good way to implement this?
I think you got it all a bit wrong. There is indeed to way to do what you want and that's because the aspect of constraints and their validation via a ConstraintValidator is orthogonal to groups and groups sequences. Per design a constraint (annotation and its validator) should be independent of the group getting validated. Even if you would get this to work, it would not be portable constraints. Personally, I would re-think what you want to achieve. #RequiredByFirstStep does not tell you what the requirement is. You should develop constraints which imply what they are valiating (a string length, not null, etc), when or better in which condition they are executed is a then controlled by group interfaces.

Categories