Custom annotation not called - java

I have made custom annotation for validation of my bean field. I use #Age(value = 10) annotation for validation age. I have write code as below.
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Documented
#Constraint(validatedBy = AgeConstraintValidator.class)
#Target( { ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface Age {
String message() default "{Age is not valid }";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int value();
}
This is code for age constrain validator
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class AgeConstraintValidator implements ConstraintValidator< Age, Long> {
private Age age;
#Override
public void initialize(Age age) {
this.age = age;
}
#Override
public boolean isValid(Long dob, ConstraintValidatorContext context) {
System.out.print(" age in annotion");
if(dob != age.value()){
return true;
}
return false;
}
}
Now when i use #Age( value = 10) in my bean so it is not called Age annotation. Can anyone tell me any fault in my code. When i create my bean object and assign age differ for test but i can not get any validation on bean 's field .

Spring will not take this custom annotation automatically. You have to let Spring know about by defining a BeanPostProcessor. create a class which implements it
For e-g
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.BeansException;
public class InitHelloWorld implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeforeInitialization : " + beanName);
return bean; // you can return any other object as well
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("AfterInitialization : " + beanName);
return bean; // you can return any other object as well
}
}
and mention about this bean in your spring config xml as below
<bean class="<your pack>.InitHelloWorld" />

Related

Annotation for DataSize validation

Is there any standard validator annotation I can use for a DataSize field?
Something like javax #Min and #Max, or Hibernate's #DurationMin and #DurationMax.
For class DataSize, there's no standard validator, so you should implement your own. But it's quite easy :)
Annotation interface:
import org.springframework.util.unit.DataUnit;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
#Documented
#Constraint(validatedBy = DataSizeMaxValidator.class)
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface DataSizeMax {
String message() default "must be less than or equal to {value} {unit}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
long value();
DataUnit unit();
}
Validator:
import org.springframework.stereotype.Component;
import org.springframework.util.unit.DataSize;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
#Component
public class DataSizeMaxValidator implements ConstraintValidator<DataSizeMax, DataSize> {
private DataSize dataSizeMax;
#Override
public void initialize(DataSizeMax dataSizeMax) {
this.dataSizeMax = DataSize.of(dataSizeMax.value(), dataSizeMax.unit());
}
#Override
public boolean isValid(DataSize value, ConstraintValidatorContext context) {
return dataSizeMax.compareTo(value) >= 0;
}
}
That's it. Then use this annotation as here:
import pizza.nikiforov.validators.DataSizeMax;
import org.springframework.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;
public class Data {
#DataSizeMax(value = 15L, unit = DataUnit.MEGABYTES)
private DataSize dataSize;
// other fields
// constructors, getters and setters
}

Extend class level RequestMapping with custom annotation

I would like to create a custom annotation in my Spring Boot application which always adds a prefix to my class level RequestMapping path.
My Controller:
import com.sagemcom.smartvillage.smartvision.common.MyApi;
import org.springframework.web.bind.annotation.GetMapping;
#MyApi("/users")
public class UserController {
#GetMapping("/stackoverflow")
public String get() {
return "Best users";
}
}
My custom annotation
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#RestController
#RequestMapping(path = "/api")
public #interface MyApi {
#AliasFor(annotation = RequestMapping.class)
String value();
}
GOAL: a mapping like this in the end: /api/users/stackoverflow
Notes:
server.servlet.context-path is not an option because I want to create
several of these
I'm using Spring Boot version 2.0.4
I was not able to find an elegant solution for the issue. However, this worked:
Slightly modified annotation, because altering behavior of value turned out to be more difficult.
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#RestController
#RequestMapping
public #interface MyApi {
#AliasFor(annotation = RequestMapping.class, attribute = "path")
String apiPath();
}
Bean Annotation Processor
import com.sagemcom.smartvillage.smartvision.common.MyApi;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
#Component
public class MyApiProcessor implements BeanPostProcessor {
private static final String ANNOTATIONS = "annotations";
private static final String ANNOTATION_DATA = "annotationData";
public Object postProcessBeforeInitialization(#NonNull final Object bean, String beanName) throws BeansException {
MyApi myApi = bean.getClass().getAnnotation(MyApi.class);
if (myApi != null) {
MyApi alteredMyApi = new MyApi() {
#Override
public Class<? extends Annotation> annotationType() {
return MyApi.class;
}
#Override
public String apiPath() {
return "/api" + myApi.apiPath();
}
};
alterAnnotationOn(bean.getClass(), MyApi.class, alteredMyApi);
}
return bean;
}
#Override
public Object postProcessAfterInitialization(#NonNull Object bean, String beanName) throws BeansException {
return bean;
}
#SuppressWarnings("unchecked")
private static void alterAnnotationOn(Class clazzToLookFor, Class<? extends Annotation> annotationToAlter, Annotation annotationValue) {
try {
// In JDK8 Class has a private method called annotationData().
// We first need to invoke it to obtain a reference to AnnotationData class which is a private class
Method method = Class.class.getDeclaredMethod(ANNOTATION_DATA, null);
method.setAccessible(true);
// Since AnnotationData is a private class we cannot create a direct reference to it. We will have to manage with just Object
Object annotationData = method.invoke(clazzToLookFor);
// We now look for the map called "annotations" within AnnotationData object.
Field annotations = annotationData.getClass().getDeclaredField(ANNOTATIONS);
annotations.setAccessible(true);
Map<Class<? extends Annotation>, Annotation> map = (Map<Class<? extends Annotation>, Annotation>) annotations.get(annotationData);
map.put(annotationToAlter, annotationValue);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Controller:
import com.sagemcom.smartvillage.smartvision.common.MyApi;
import org.springframework.web.bind.annotation.GetMapping;
#MyApi(apiPath = "/users")
public class UserController {
#GetMapping("/stackoverflow")
public String get() {
return "Best users";
}
}

Spring MVC Custom validation showing error code HV000074.

I am trying to implement a custom password match validation in spring boot. But I am getting an error as follows:-
PasswordMatch contains Constraint annotation, but does not contain a message parameter
I am following this link https://www.baeldung.com/registration-with-spring-mvc-and-spring-security for custom validation. The problem is I am getting error as this.
javax.validation.ConstraintDefinitionException: HV000074: com.bikram.booking.validation.PasswordMatch contains Constraint annotation, but does not contain a message parameter.
at org.hibernate.validator.internal.metadata.core.ConstraintHelper.assertMessageParameterExists(ConstraintHelper.java:915)
I have searched solutions on web but couldn't find the decent soultion.
My Modal is
package com.bikram.booking.dto;
import com.bikram.booking.validation.PasswordMatch;
import com.bikram.booking.validation.ValidEmail;
import javax.validation.constraints.*;
#PasswordMatch
public class UserDto {
#NotNull
#Size(min = 6, message = "Password should be more than 6 characters")
#NotEmpty(message = "Please provide a password")
private String password;
#NotNull
#Size(min = 6, message = "Password should be more than 6 characters")
private String confirmPassword;
}
My Interface is
package com.bikram.booking.validation;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#Target({ TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = PasswordMatchValidator.class)
#Documented
public #interface PasswordMatch {
String messages() default "Sorry, passwords does not match";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And Implementation is
package com.bikram.booking.validation;
import com.bikram.booking.dto.UserDto;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class PasswordMatchValidator implements ConstraintValidator<PasswordMatch, Object> {
#Override
public void initialize(PasswordMatch constraintAnnotation) {
}
#Override
public boolean isValid(Object obj, ConstraintValidatorContext constraintValidatorContext) {
UserDto userDto = (UserDto) obj;
return true;
}
}
Any hints will be higly appreciable.
Rename messages() to message() in PasswordMatch :
public #interface PasswordMatch {
String message() default "Sorry, passwords does not match";
....
}

Validating member List variables for null values

The structure that I have is something like below:
Class A{
String str;
int i;
List<B> bs;
C c;
#NotNull
List<D> ds;
}
Class B{
#NotNull
List<E> es;
}
Class C{
List<String> s;
}
Class E{
#NotNull
List<String> s;
}
For the list variables that are annotated with #NotNull I need to throw validation error if any of them variables has one or more null objects. While for the other list variables I just need to remove the nulls;
What would be the best way to achieve this?
If you are using validation 2.0+ you can put annotation inside: List<#NotNull String> s;
You should define custom annotation for validating.
so define custom annotation like bellow.
#Target({ElementType.FIELD, ElementType.PARAMETER,ElementType.ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = ValidateListValidator.class)
#Documented
public #interface ValidateList {
}
and implement ValidateListValidator like this:
public class ValidateListValidator implements ConstraintValidator<ValidateList, List<Object>> {
private ValidateList validateList;
#Override
public void initialize(ValidateList validateList) {
this.validateList = validateList;
}
#Override
public boolean isValid( List<Object> list, ConstraintValidatorContext constraintValidatorContext) {
return list.stream().noneMatch(Objects::isNull);
}
}
and for test it
#Test
public void test() {
boolean valid = validator.isValid(Arrays.asList("test","this",null),context);
assertThat(valid, is(false));
}
This is the final code that I wrote, just a few tweaks to the code that Hadi posted. I hope it helps:
Annotation:
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
#Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = ListValidator.class)
#Documented
public #interface ValidList {
String message() default "Null values are not allowed in array fields.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Validator Class:
import java.util.List;
import java.util.Objects;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class ListValidator implements ConstraintValidator<ValidList, List<? extends Object>> {
#Override
public boolean isValid(List<? extends Object> list, ConstraintValidatorContext context) {
return list.stream().noneMatch(Objects::isNull);
}
#Override
public void initialize(ValidList constraintAnnotation) {}
}

How to validate 2 field with OR condition?

I want to validate two fields of a Request Class in manner that Either one field is valid OR another field is valid.
Eg:
Request Bean
public class CarRequest {
#NotEmpty
private String customerName;
#NotEmpty
private String customerId;
Controller Method
public #ResponseBody CarResponse addCar(
#ModelAttribute #Valid CarRequest request, BindingResult results)
throws RuntimeException, ValidationException {
if (results.hasErrors()) {
LOG.error("error occured while adding the car");
throw new ValidationException(
"Error Occoured while validiating car request");
}
}
Here I want to check that either customerName should be NotEmpty OR customerId should be NotEmpty. then my validation should pass. How can I implement it . Please suggest!!
You need to create custom validator to validate multiple fields.
create a custom annotation:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Documented
#Constraint(validatedBy = CarRequestValidator.class)
#Target({ ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface RequestAnnotation {
String message() default "{RequestAnnotation}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
create a custom validator:
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class CarRequestValidator implements
ConstraintValidator<RequestAnnotation, CarRequest> {
#Override
public void initialize(RequestAnnotation constraintAnnotation) {
}
#Override
public boolean isValid(CarRequest value, ConstraintValidatorContext context) {
// validation logic goes here
return false;
}
}
Now, annotate your model with custom annotation:
#RequestAnnotation
public class CarRequest {
private String customerName;
private String customerId;
}

Categories