I'm trying to apply multiple common annotations at once with a custom validation annotation like this:
#Retention(RetentionPolicy.RUNTIME)
#Length(max=25, min=1, message="invalid length")
#NotNull
#Pattern(regexp = "[a-zA-Z0-9]{1, 25})")
public #interface MyAnnotation {
}
And using it in my model classes like this:
#MyAnnotation
public String firstName;
None of these validation are working, but they work as expected when used in the model class itself. I also tried registering MyAnnotation in the applications run method, and that didn't work either.
environment.jersey().register(MyAnnotation.class);
What else do I need to do in order to use custom validations?
Either annotate the String directly:
#Pattern(regexp = "[a-zA-Z0-9]{1, 25})")
#NotNull
#Length(max=25, min=1, message="invalid length")
public String firstName;
Or create a validator, something like:
class MyAnnotatationValidator implements ConstraintValidator<MyAnnotation, String>{
#Override
public void initialize(MyAnnotation a){}
#Override
public boolean isValid(String s, ConstraintValidationContext c) {
return s != null && (s.length() > 0 && s.length() < 26) && s.matches("[a-zA-Z0-9]{1, 25})";
}
}
And have
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy=MyAnnotatationValidator.class)
public #interface MyAnnotation {
String message() default = "{MyAnnotation}";
Class<?>[] groups() default {};
Class<? extends Payload> payload() deafult {};
}
According to the JSR-303
A constraint definition may have attributes that are specified at the time the constraint is applied to a JavaBean. The properties are mapped as annotation elements. The annotation element names message, groups and payload are considered reserved names; annotation elements starting with valid are not allowed; a constraint may use any other element name for its attributes.
So you must add message, groups and payload attributes to your #MyAnnotation.
A composition is done by annotating the composed constraint, example:
#Length(max=25, min=1, message="invalid length")
#NotNull
#Pattern(regexp = "[a-zA-Z0-9]{1, 25})")
#Documented
#Target({ANNOTATION_TYPE, METHOD, FIELD, CONSTRUCTOR, PARAMETER})
#Retention(RUNTIME)
public #interface MyAnnotation {
String message() default "My annotation message";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Related
I created a custom annotation, for a custom data type. Currently my entity looks like this:
#Entity
public class contact {
#Valid
#Randomizer(EmailAddressValidator.class)
#EmailAddressField
#Convert(converter = EmailAddressConverter.class)
#JsonSerialize(using = EmailAddressSerializer.class)
#Column(name = "email")
private EmailAddress email;
}
But I would like to have the following
#Entity
public class contact {
#EmailAddressField
#Column(name = "email")
private EmailAddress email;
}
What do I have to do in my own annotation, so that I don't have to write the other annotations for each property?
I would like to have everything together and only have to write one annotation to the property, for clarity.
My annotation currently looks like this:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.TYPE_USE})
#Constraint(
validatedBy = {EmailAddressValidator.class}
)
#Documented
public #interface EmailAddressField {
Class<?>[] groups() default {};
String message() default "No Email Address";
Class<? extends Payload>[] payload() default {};
}
But if I change that to this it doesn't work:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.TYPE_USE})
#Constraint(
validatedBy = {EmailAddressValidator.class}
)
#Documented
// -- Annotations from property
#Valid
#Randomizer(EmailAddressValidator.class)
#Convert(converter = EmailAddressConverter.class)
#JsonSerialize(using = EmailAddressSerializer.class)
#Inherited
// --
public #interface EmailAddressField {
Class<?>[] groups() default {};
String message() default "No Email Address";
Class<? extends Payload>[] payload() default {};
}
Java annotations are interfaces that can be applied to methods, classes, and fields and they are not capable of having other annotations as values because they are not objects, they are a form of metadata that don't have any methods or states and their main purpose is to provide metadata that can be read and processed by other tools such as the Java compiler or JVM.
A possible solution to avoid writing the following boilerplate codes would be creating a custom annotation processor that uses the AbstractProcessor class from the javax.annotation.processing package. This would allow you to define custom annotations and specify the actions that should be taken when they are encountered in your code during the compile time.
Here is the documentation; https://docs.oracle.com/en/java/javase/11/docs/api/java.compiler/javax/annotation/processing/Processor.html
https://docs.oracle.com/en/java/javase/11/docs/api/java.compiler/javax/annotation/processing/AbstractProcessor.html
Add following dependencies in the gradle , for configuruing #AutoService
dependencies {
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc5'
compileOnly 'com.google.auto.service:auto-service:1.0-rc5'
}
And here is the sample code;
#AutoService(Processor.class)
#SupportedAnnotationTypes("com.example.EmailAddressField")
#SupportedSourceVersion(SourceVersion.RELEASE_8)
public class EmailAddressFieldProcessor extends AbstractProcessor {
private Elements elementUtils;
#Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.elementUtils = processingEnv.getElementUtils();
}
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
if (element.getKind() == ElementKind.FIELD) {
VariableElement field = (VariableElement) element;
AnnotationMirror validAnnotation = elementUtils.getAnnotationMirror(Valid.class);
field.getAnnotationMirrors().add(validAnnotation);
AnnotationMirror convertAnnotation = elementUtils.getAnnotationMirror(Convert.class);
field.getAnnotationMirrors().add(convertAnnotation);
// BlahBlah.class annotation..... etc...
}
}
}
return true;
}
}
I have a custom interface:
#Target({ TYPE, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = { MyCustomValidator.class })
#Documented
public #interface ValidData {
String message() default EMPTY;
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
and I have a class that - until now - had this validator attached:
#ValidData(groups = AfterDefaultGroup.class)
public class RecoverData {
private String data;
It works, but I need to move the validator from class level to field level. I tried this:
public class RecoverData {
#ValidData(groups = AfterDefaultGroup.class)
private String data;
but I'm getting compilation error here:
ValidData not applicable to field
How can I fix it?
The #Target annotation defines where this annotation can be applied.
You are restricting it to TYPE and ANNOTATION_TYPEright now which doesn't allow to use it on fields.
According to the documentation you have to use ElementType.FIELD
Is there are any java annotation(s) that can validate like the example below?
String test;
test = null; //valid
test = ""; //invalid
test = " "; //invalid
test = "Some values"; //valid
You need to create a custom annotation: #NullOrNotBlank
First create the custom annotation: NullOrNotBlank.java
#Target( {ElementType.FIELD})
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = NullOrNotBlankValidator.class)
public #interface NullOrNotBlank {
String message() default "{javax.validation.constraints.NullOrNotBlank.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default {};
}
Then the actual validator: NullOrNotBlankValidator.java
public class NullOrNotBlankValidator implements ConstraintValidator<NullOrNotBlank, String> {
public void initialize(NullOrNotBlank parameters) {
// Nothing to do here
}
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
return value == null || value.trim().length() > 0;
}
}
There isn't such an annotation in either javax.validation or Hibernate Validator. There was a request to add one to Hibernate Validator but it was closed as "won't fix" due to the possibility of writing your own relatively easily. The suggest solution was to either use your own annotation type defined like this:
#ConstraintComposition(OR)
#Null
#NotBlank
#ReportAsSingleViolation
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
#Constraint(validatedBy = { })
public #interface NullOrNotBlank {
String message() default "{org.hibernate.validator.constraints.NullOrNotBlank.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
or to use the #Pattern annotation with a regular expression that requires a non-whitespace character to be present (as the Pattern annotation accepts nulls and does not match them against the pattern).
Where is a nice javax.validation.constraints.Pattern annotation.
You can annotate the field with:
#Pattern(regexp = "^(?!\\s*$).+", message = "must not be blank")
This checks if field matches regex. The regex itself is something but not blank (see details here). It uses negative lookahead.
This is possible without creating a custom annotation, by using javax.validation.constraints.Size
// Null values are considered valid
#Size(min=1) String test;
The best way is to create your own constraint validator,
//custom annotation
#Documented
#Constraint(validatedBy = CustomCheck.class)
#Target( { ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface CustomConstarint {
String message() default "Invalid data";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
//validation logic goes here
public class CustomCheck implements
ConstraintValidator<CustomConstarint, String> {
#Override
public void initialize(CustomConstarint customConstarint) {
}
#Override
public boolean isValid(String field,
ConstraintValidatorContext cxt) {
//write your logic to validate the field
}
}
Did you try Hibernate-Validator? I think that's what you are looking for.
import javax.validation.constraints.NotNull;
import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.NotEmpty;
public class MyModel {
#NotNull
private String str1;
#NotEmpty
private String str2;
#NotBlank
private String str3;
}
I am confused about the case that have multiple constraint annotations on a field, below:
public class Student
{
#NotNull
#Size(min = 2, max = 14, message = "The name '${validatedValue}' must be between {min} and {max} characters long")
private String name;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}
Test case:
public class StudentTest
{
private static Validator validator;
#BeforeClass
public static void setUp()
{
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
System.out.println(Locale.getDefault());
}
#Test
public void nameTest()
{
Student student = new Student();
student.setName(null);
Set<ConstraintViolation<Student>> constraintViolations = validator.validateProperty(student, "name");
System.out.println(constraintViolations.size());
System.out.println(constraintViolations.iterator().next().getMessage());
}
}
The result is:
1
Can't be null
That is, when the #NotNull constraint is violated, it will not continue. Yes, this is the right situation. When one check is failed, we don't want it check the next constraint. But the situation is different when I used custom constraint.
I defined two custom constraints ACheck and BCheck.
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = { ACheckValidator.class })
public #interface ACheck
{
String message() default "A check error";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = { BCheckValidator.class })
public #interface BCheck
{
String message() default "B check error";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
public class ACheckValidator implements ConstraintValidator<ACheck, String>
{
public void initialize(ACheck constraintAnnotation)
{
}
public boolean isValid(String value, ConstraintValidatorContext context)
{
return false;
}
}
public class BCheckValidator implements ConstraintValidator<BCheck, String>
{
public void initialize(BCheck constraintAnnotation)
{
}
public boolean isValid(String value, ConstraintValidatorContext context)
{
return false;
}
}
There is not specific info about custom constraint, and I change the Student.java and use custom constraint like that:
#ACheck
#BCheck
private String name;
Test again, and the result is:
2
B check error
That is, when the #ACheck constraint is violatedm, it also wil check #BCheck, Why this happens, anything else I had ignored?
when the #NotNull constraint is violated, it will not continue
That is incorrect. It will continue checking all the other constraints. It's just that the Size validator considers a null value as an acceptable value. The reason is that typically, you want
a non-null, minimum size value: then you apply both constraints
or a nullable value, which must have a minimum size if the value is present: then you only apply Size.
You're misunderstanding those validators - they have no guarantee of order in which they are evaluated.
By default, constraints are evaluated in no particular order, regardless of which groups they belong to.
So that means that either your ACheck or your BCheck could have failed, or both; it's not determined which failure will occur first.
If you want to be able to define an ordering with two distinct annotations, then you would have to use a #GroupSequence to specify that.
Alternatively, if you want to fail fast, then configure the validator to do so.
Validator validator = Validation.byProvider( HibernateValidator.class )
.configure()
.failFast( true )
.buildValidatorFactory()
.getValidator();
I would personally discourage that approach as it implies that a user that fails validations must make repeated requests to the resource every time one thing is wrong, as opposed to getting everything that is wrong up front.
#Pattern(regexp = "^[M|F]{1}$", message ="Must be M or F")
private Character gender;
Result:
javax.validation.UnexpectedTypeException: HV000030: No validator could be found for type: java.lang.Character.
How can achieve the following:
apply hibernate validation to the character using a regex pattern
restricting the return type to be a single letter (that's why I chose char)
to everything of this in a single method?
I had similiar problem and i didnt find any default hibernate validator annotation for that. But there is easy way to create custom annotation. (look here https://docs.jboss.org/hibernate/validator/5.1/reference/en-US/html/validator-customconstraints.html) Below example with sex:
#Target({ METHOD, FIELD, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = SexValidator.class)
#Documented
public #interface Sex
{
String message() default "{customValidator.sex";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
#Target({ FIELD, METHOD, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Documented
#interface List
{
Sex[] value();
}
}
public class SexValidator implements ConstraintValidator<Sex, Character> {
public void initialize(Sex sex)
{
// used only if your annotation has attributes
}
public boolean isValid(Character sex, ConstraintValidatorContext constraintContext)
{
// Bean Validation specification recommends to consider null values as
// being valid. If null is not a valid value for an element, it should
// be annotated with #NotNull explicitly.
if (sex == null)
{
return true;
}
if (sex.equals('F') || sex.equals('M'))
return true;
else
{
return false;
}
}
}
#Column(name = "sex", columnDefinition = "char(1)")
#NotNull
#Sex
private Character sex;