I'm new in JAVA. I created a new custom annotation for custom validation in SPRING, but when I run the app I see this error:
The annotation #CourseCode must define the attribute groups
The annotation #CourseCode must define the attribute message
The annotation #CourseCode must define the attribute payload
The annotation #CourseCode must define the attribute value
Here is my implementation:
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.validation.Constraint;
import javax.validation.Payload;
#Constraint(validatedBy = CourseCodeConstraintValidator.class)
#Target({ ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface CourseCode {
public String value() default "course-";
public String message() default "must start with course-";
public Class<?>[] groups() default {};
public Class<? extends Payload>[] payload() default {};
}
Here is my usage
#CourseCode
private String courseCode;
Here is my CourseCodeConstraintValidator.
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class CourseCodeConstraintValidator implements ConstraintValidator<CourseCode, String>{
private String coursePrefix;
#Override
public void initialize(CourseCode courseCode) {
coursePrefix = courseCode.value();
}
#Override
public boolean isValid(String code, ConstraintValidatorContext constraintValidatorContext) {
boolean result = code.startsWith(coursePrefix);
return result;
}
}
I expected the default values I defined in the class would be used.
After lots of hours of research, I gave up for a couple of days. Now I came back to it and suddenly discovered that switching perspective in eclipse after restarting the PC has done the trick and now it works as I expected.
Thanks to everybody who tried to help.
Related
I have created a custom validator to validate Strings. It works on a single String but not on a List of Strings. This is what I have tried so far:
#Get("/test1")
public String test1(
#QueryValue(value = "ids") List<#DurationPattern String> ids) { //NOT WORKING
return "not working";
}
#Get("/test2")
public String test2(
#QueryValue(value = "id") #DurationPattern String id){ //WORKS
//it does not get here which is what I want.
return "done";
}
My #DurationPattern code:
package my.package;
import javax.validation.Constraint;
import java.lang.annotation.Documented;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import my.package.DurationPattern.List;
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE, TYPE, LOCAL_VARIABLE, PACKAGE, TYPE_PARAMETER, MODULE })
#Repeatable(List.class)
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = { })
public #interface DurationPattern {
String message() default "invalid duration ({validatedValue})";
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE, TYPE, LOCAL_VARIABLE, PACKAGE, TYPE_PARAMETER, MODULE})
#Retention(RUNTIME)
#Documented
public #interface List {
DurationPattern[] value();
}
}
Actual Validator:
package my.package;
import io.micronaut.context.annotation.Factory;
import io.micronaut.validation.validator.constraints.ConstraintValidator;
import javax.inject.Singleton;
#Factory
public class MyValidatorFactory {
#Singleton
ConstraintValidator<DurationPattern, CharSequence> durationPatternValidator() {
return (value, annotationMetadata, context) -> {
System.out.println("Please Print!!! It doesn't for Strings within List");
return value == null || value.toString().matches("^PT?[\\d]+[SMHD]{1}$");
};
}
}
Basically, create an implementation of io.micronaut.validation.validator.constraints.ConstraintValidator and provide them inside #Constraint(validatedBy = { })
#Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
#Repeatable(Regexp.List.class)
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = {DurationPatternValidator.class, DurationPatternValidatorList.class}) //to validate a single string or a list of strings
public #interface DurationPattern {
...
}
#Singleton
public class RegexValidatorList implements ConstraintValidator<DurationPattern, Collection<CharSequence>> {
#Override
public boolean isValid(...) {
....
}
And then in the controller's handler methods you would use
#Get("/test1")
public String test1(
#QueryValue(value = "ids") #DurationPattern List<String> ids) { //#DurationPattern would appear outside of diamond brackets for it to work.
return "working now";
}
I would like to create an annotation that takes a min value = 0, for attemptCount property in my Class. I created a:
package com.ctp.interactive.bl.core.validation.validator;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.constraints.Min;
#Min(value = ValidAttemptCount.MIN_VALUE)
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target(java.lang.annotation.ElementType.FIELD)
#Constraint(validatedBy = {})
public #interface ValidAttemptCount {
int MIN_VALUE = 0;
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
But I think that it doesn't work only by put the annotation above the property eg:
#ValidAttemptCount
#Column(name = "attempt_count")
private Integer attemptCount;
What is the missing step here?
In my API project, I have a custom ConstraintValidator to check whether e-mail addresses aren't already associated with another account.
Interface:
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Constraint(validatedBy = EmailUniqueConstraintValidator.class)
#Target({ElementType.FIELD, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface EmailUnique {
String message() default "cannot already be associated with another account";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
The validator:
import com.demo.demoapp.repo.UserRepository;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class EmailUniqueConstraintValidator implements ConstraintValidator<EmailUnique, String> {
private UserRepository userRepository;
public EmailUniqueConstraintValidator(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return value == null || !userRepository.existsByEmail(value);
}
}
This validator is added to the user creation request object and will correctly check the existance of the e-mail address and give back an error to BindingResult if it's the case.
But when editing the user, things become more complicated. The user object has to be passed to the API entirely except for the object so this constraint validator would always be triggered. So I would like to remove the #EmailUnique annotation from the class and manually trigger it in the Service. Is that possible?
I wrote a custom validation to validate an id (Which is a path Param) as UUID in my #GET method of #RestController but this validation doesn't seem to be working. Even during the debug the control doesn't go to custom validation.
#RestController
#RequestMapping("/rateplan")
#Validated
public class RatePlanServiceController {
#RequestMapping(value = "/{ratePlanId}", method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
public void get(#UUID #PathVariable("ratePlanId") String ratePlanId) {
loggerFactory.warn("Get with Rate plan id {}", ratePlanId);
loggerFactory.info("Get with Rate plan id {}", ratePlanId);
loggerFactory.error("Get with Rate plan id {}", ratePlanId);
loggerFactory.debug("Get with Rate plan id {}", ratePlanId);
// return iRatePlanService.getRatePlan(ratePlanId);
}
}
I wrote the custom annotation for validation of UUID as follow.
import org.springframework.stereotype.Component;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.regex.Pattern;
#Target(ElementType.PARAMETER)
#Constraint(validatedBy = {UUID.IdValidator.class})
#Retention(RetentionPolicy.RUNTIME)
public #interface UUID {
String message() default "{invalid.uuid}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
#Component
public class IdValidator implements ConstraintValidator<UUID, String> {
private static final Pattern id_PATTERN =
Pattern.compile("^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$");
public boolean isValid(String value, ConstraintValidatorContext context) {
if (!(id_PATTERN.matcher(value).matches())) {
return false;
}
return true;
}
public void initialize(UUID parameters) {
}
}
}
Can anyone let me know why is it not working. Even if I provide a garbage ratePlanId like '345%#7^34' it able to go inside GET method.
Solved this by adding a bean in Application configuration file. To validate a path Param in Spring you need to add this bean in your configuration class.
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
Given:
public #interface MyAnnotation(){
public SomeType[] value();
}
in Java 7 is it possible to do something like:
#MyAnnotation({
value1,
#MyAnnotation({subValue1, subvalue2, ...}) value2,
...
valueN
})
public Object someProperty;
?
You can. This is an example from Jackson library (leaving out the comments):
package com.fasterxml.jackson.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.FIELD,
ElementType.METHOD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#JacksonAnnotation
public #interface JsonSubTypes {
public Type[] value();
public #interface Type {
/**
* Class of the subtype
*/
public Class<?> value();
/**
* Logical type name used as the type identifier for the class
*/
public String name() default "";
}
}
And here is an example usage:
#JsonSubTypes({
#JsonSubTypes.Type(value = TestSystemEvent.class, name = "TestEvent"),
})
public interface SystemEvent {
}
How to use annotation in an annotation?
Maybe like this
public #interface MyAnnotation(){
public SomeType[] value();
public MyAnnotation[] refine();
}
#MyAnnotation(
{value1, value2},
refine={ #MyAnnotation({valueX, valueY}), #MyAnnotation(valueM) }
)
public Object someProperty;
Also, in Java 8, you can have Repeatable annotations - so you may refine or add to your 'primary' (e.g. the first) other refinements brought in by subsequent repetitions of the same annotation.