JavaBlog.fr / Java.lu DEVELOPMENT,Java Java : Annotation : Example of validation with annotation (mandatory, target, maxlength…)

Java : Annotation : Example of validation with annotation (mandatory, target, maxlength…)

Hello,

I would present you an example concerning the annotation in JAVA: validation POJO with annotation.

Brief presentation
Introduced with Java SE 5, the annotations are more increasingly used in the development of applications. Annotations provide informations about a class and they have no direct effect on the annotated code. Annotations can be preserved at runtime (RetentionPolicy.RUNTIME) or are only available at development time, during the compile (RetentionPolicy.SOURCE).

There are standard annotations and Java allows the definition of custom annotations.

For example, the following standard annotations are often used in Java:
@Override:
This annotation added to a method, notifies the Java compiler to check if the annotated method really overrides a method of an interface or the extended class.

01@Override
02public String toString(){
03    return this.getClass().getName()
04            + "[id="+id
05            + ", name="+name
06            + ", price="+price
07            + ", numberOfRead="+numberOfRead
08            + ", lastReadDateTime="+lastReadDateTime
09            + ", authors="+authors
10            + "]";
11}

@Deprecated
This annotation added to a field, method or constructor, indicates that the annotated element should not be used anymore. It is possible to add this annotation to a class, however, this doesn’t deprecate automatically all its fields and methods.

@Target
This annotation specifies where it is legal to use an annotation type. The possible values are constants from the
java.lang.annotation.ElementType enumeration which provides a simple classification of the declared elements in a Java program.
There are the following constants:

  • ANNOTATION_TYPE – Annotation type declaration – used for other annotations.
    1@MyAnnotation
    2public @interface AnotherAnnotation {..}
  • CONSTRUCTOR – Constructor declaration
    1public class MyAnnotatedClass {
    2    @MyAnnotation
    3    public MyAnnotatedClass() {..}
    4}
  • FIELD – Field declaration (includes enum constants)
    1@MyAnnotation
    2private String myAnnotatedField;
  • LOCAL_VARIABLE – Local variable declaration – It can’t be read at runtime, so it is used only for compile-time things, like the @SuppressWarnings annotation.
    1public void someMethod() {
    2    @MyAnnotation int myLocalVariable = 0;
    3}
  • METHOD – Method declaration
    1@MyAnnotation
    2public void myAnnotatedMethod() {..}
  • PACKAGE – Package declaration
    1@MyAnnotation
    2package com.ho.myannotatedpackage;
  • PARAMETER – Parameter declaration
    1public void myMethod(@MyAnnotation param) {..}
  • TYPE – Class, interface (including annotation type), or enum declaration
    1@MyAnnotation
    2public class MyAnnotatedClass {..}

It is possible to specify multiple ElementTypes for a given annotation:

1@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})

Example : validation with annotations

When an annotation is implemented, it is necessary to tell two things to the compiler:

  • where it can be applied (that’s the @Target annotation);
  • what its retention policy is (aptly named @RetentionPolicy; some annotations are kept only at the source level, others at runtime).

Below, the official description of annotations retention policy:

1+ RetentionPolicy.SOURCE: Discard during the compile. These annotations don't make any sense after the compile has completed, so they aren't written to the bytecode.
2 Example: @Override, @SuppressWarnings
3 
4+ RetentionPolicy.CLASS: Discard during class load. Useful when doing bytecode-level post-processing. Somewhat surprisingly, this is the default.
5 
6+RetentionPolicy.RUNTIME: Do not discard. The annotation should be available for reflection at runtime. Example: @Deprecated

In our example, we will use the annotations with the JDK 1.6 for validate the POJO values on two criterias ans set its name:

  • an annotation Mandatory to specify if a field is mandatory or not,
  • an annotation MaxLength to specify the max length of a String field,
  • an annotation FiledName to set the field’s name,

STEP 1
Create a new project named test_annot_valid with the following librairies in test_annot_valid\lib folder:

  • commons-beanutils-1.9.0.jar,
  • commons-lang-2.6.jar,
  • commons-logging-1.1.3.jar,

For information, our project structure will be:
test_annot_valid-scr1

STEP 2
We will create the definition of new annotations by creating 3 new classes in the package “com.ho.annotations.common.annotations”:

FieldName:

01package com.ho.annotations.common.annotations;
02 
03import java.lang.annotation.ElementType;
04import java.lang.annotation.Retention;
05import java.lang.annotation.RetentionPolicy;
06import java.lang.annotation.Target;
07 
08@Retention(RetentionPolicy.RUNTIME)
09@Target(ElementType.FIELD)
10public @interface FieldName {
11     
12    EnumFieldName name();
13     
14    public enum EnumFieldName{
15        ID,
16        NAME,
17        PRICE,
18        LAST_READ_DATETIME,
19        NB_OF_READ,
20        AUTHORS
21        ;
22    }
23}

Mandatory:

01package com.ho.annotations.common.annotations;
02 
03import java.lang.annotation.ElementType;
04import java.lang.annotation.Retention;
05import java.lang.annotation.RetentionPolicy;
06import java.lang.annotation.Target;
07 
08 
09/**
10 * Marks the field as Mandatory : a null value is illegal.
11 * @author huseyin
12 *
13 */
14@Retention(RetentionPolicy.RUNTIME)
15@Target(ElementType.FIELD)
16public @interface Mandatory {
17 
18}

Maxlength:

01package com.ho.annotations.common.annotations;
02 
03import java.lang.annotation.ElementType;
04import java.lang.annotation.Retention;
05import java.lang.annotation.RetentionPolicy;
06import java.lang.annotation.Target;
07 
08/**
09 * Specify the maximum length of the annotated field.
10 * @author huseyin
11 *
12 */
13@Retention(RetentionPolicy.RUNTIME)
14@Target(ElementType.FIELD)
15public @interface MaxLength {
16    int maxLength();
17}

STEP 3
Then, it is necessary to create POJO on whom the previous annotations must be applied. The class AbstractEntity will contain a list of errors corresponding to validation errors:

01package com.ho.annotations.data.entity;
02 
03import java.util.ArrayList;
04import java.util.List;
05 
06public abstract class AbstractEntity {
07 
08    // ------------------------- PROTECTED ATTRIBUTES
09    protected List<ErrorEntity> errors = new ArrayList<ErrorEntity>();
10     
11    // ------------------------------- PUBLIC METHODS
12    public void addError(ErrorEntity error){
13        this.errors.add(error);
14    }
15     
16    public void addErrors(List<ErrorEntity> errors){
17        this.errors.addAll(errors);
18    }
19     
20    public boolean hasError(){
21        return errors!=null && errors.size()>0;
22    }
23     
24    public List<ErrorEntity> getErrors(){
25        List<ErrorEntity> result = new ArrayList<ErrorEntity>();
26        for(ErrorEntity error : errors){
27            result.add(error);
28        }//end-for
29        return result;
30    }
31}

And an error entity ErrorEntity class representing one error that occured when validating the object:

01package com.ho.annotations.data.entity;
02 
03import com.ho.annotations.common.annotations.FieldName;
04 
05/**
06 * Entity class representing one error that occured when validating the object
07 * @author huseyin
08 *
09 */
10public class ErrorEntity extends AbstractEntity{
11     
12    public static enum ErrorLevel{
13        ERROR,
14        WARNING,
15        INFO,
16        ;
17    }
18     
19    // --------------------------- PRIVATE ATTRIBUTES
20    private Integer id;
21    private FieldName.EnumFieldName enumFieldName;
22    private String error;
23    private Integer entityId;
24    private ErrorLevel errorLevel;
25     
26    // ---------------------------------- CONSTRUCTOR
27    public ErrorEntity(){}
28     
29    public ErrorEntity(FieldName.EnumFieldName enumFieldName, String error){
30        this.enumFieldName = enumFieldName;
31        this.error = error;
32        this.errorLevel = ErrorLevel.ERROR;
33    }
34     
35    public ErrorEntity(FieldName.EnumFieldName enumFieldName, String error, ErrorLevel errorLevel){
36        this.enumFieldName = enumFieldName;
37        this.error = error;
38        this.errorLevel = errorLevel;
39    }
40     
41    // ---------------------------- PUBLIC ATTRIBUTES
42    @Override
43    public String toString(){
44        StringBuilder errorBuilder = new StringBuilder();
45        errorBuilder.append(getEnumFieldName());
46        errorBuilder.append(" ");
47        errorBuilder.append(getError());
48        return errorBuilder.toString();
49    }
50     
51     
52    // --------------------------------- GET/SET TERS
53    public Integer getId() {
54        return id;
55    }
56 
57    public void setId(Integer id) {
58        this.id = id;
59    }
60 
61    public FieldName.EnumFieldName getEnumFieldName() {
62        return enumFieldName;
63    }
64 
65    public void setEnumFieldName(FieldName.EnumFieldName enumFieldName) {
66        this.enumFieldName = enumFieldName;
67    }
68 
69    //...etc.
70     
71    // ------------------------- PROTECTED ATTRIBUTES
72}

Finally, the entity class BookEntity will be validated:

01package com.ho.annotations.data.entity;
02 
03import java.util.Calendar;
04 
05import com.ho.annotations.common.annotations.FieldName;
06import com.ho.annotations.common.annotations.Mandatory;
07import com.ho.annotations.common.annotations.MaxLength;
08 
09/**
10 * Entity mapping the table BOOK
11 * @author huseyin
12 *
13 */
14public class BookEntity extends AbstractEntity{
15 
16    // --------------------------- PRIVATE ATTRIBUTES
17    // ID
18    @Mandatory
19    @FieldName(name=FieldName.EnumFieldName.ID)
20    private Integer id;
21 
22    // NAME
23    @Mandatory
24    @MaxLength(maxLength = 10)
25    @FieldName(name=FieldName.EnumFieldName.NAME)
26    private String name;
27 
28    // PRICE
29    @Mandatory
30    @FieldName(name=FieldName.EnumFieldName.PRICE)
31    private Double price;
32     
33    // NB_OF_READ
34    @FieldName(name=FieldName.EnumFieldName.NB_OF_READ)
35    private Integer numberOfRead;
36 
37    // LAST_READ_DATETIME
38    @FieldName(name=FieldName.EnumFieldName.LAST_READ_DATETIME)
39    private Calendar lastReadDateTime;
40 
41    // AUTHORS
42    @FieldName(name=FieldName.EnumFieldName.AUTHORS)
43    private String[] authors;
44     
45    // ---------------------------------- CONSTRUCTOR
46    public BookEntity(){}
47     
48    // ------------------------------- PUBLIC METHODS
49    @Override
50    public String toString(){
51        return this.getClass().getName()
52                + "[id="+id
53                + ", name="+name
54                + ", price="+price
55                + ", numberOfRead="+numberOfRead
56                + ", lastReadDateTime="+lastReadDateTime
57                + ", authors="+authors
58                + "]";
59    }
60 
61    // --------------------------------- GET/SET TERS
62    public Integer getId() {
63        return id;
64    }
65 
66    public void setId(Integer id) {
67        this.id = id;
68    }
69    //...etc.
70}

So, the book entity has the following fields:

  • id containing the technical ID : mandatory field named ID,
  • name corresponding to the book’s name : mandatory field named NAME with a max length of 10 characters,
  • price corresponding to the book’s price : mandatory field named PRICE,
  • numberOfRead corresponding to the number of read of this book : no mandatory field named NB_OF_READ,
  • lastReadDateTime corresponding to the last read datetime of this book : no mandatory field named LAST_READ_DATETIME,
  • authors containing the book’s authors : no mandatory field named AUTHORS,

STEP 4
In this step, we create an utility class ValidatorUtil to handle the validation of entities:

01package com.ho.annotations.validator;
02 
03import java.lang.reflect.Field;
04import java.util.ArrayList;
05import java.util.List;
06 
07import org.apache.commons.beanutils.PropertyUtils;
08 
09import com.ho.annotations.common.annotations.FieldName;
10import com.ho.annotations.common.annotations.Mandatory;
11import com.ho.annotations.common.annotations.MaxLength;
12import com.ho.annotations.data.entity.AbstractEntity;
13import com.ho.annotations.data.entity.ErrorEntity;
14 
15/**
16 * This class handles the validation of the entity.
17 * @author huseyin
18 */
19public final class ValidatorUtil {
20 
21    // ---------------------------------- CONSTRUCTOR
22    private ValidatorUtil(){}
23 
24    // ------------------------------- PUBLIC METHODS
25    /**
26     * Validation of the given entity annotations
27     * @param myEntity
28     * @throws Exception
29     */
30    public static void validateEntity(AbstractEntity myEntity) throws Exception{
31        myEntity.addErrors(validateAnnotatedFields(myEntity));
32    }
33 
34     
35    // ------------------------------ PRIVATE METHODS
36    /**
37     * Validates all the annotated field of the given entities.
38     * Supported annotation are {@see Mandatory} {@see MaxLength}
39     * @param myEntity
40     * @return
41     * @throws Exception
42     */
43    private static List<ErrorEntity> validateAnnotatedFields(AbstractEntity myEntity) throws Exception{
44        List<ErrorEntity> errors = new ArrayList<ErrorEntity>();
45         
46        for(Field field : myEntity.getClass().getDeclaredFields()){
47            // Mandatory field
48            final Mandatory mandatory = field.getAnnotation(Mandatory.class);
49            if(mandatory != null){
50                final Object fieldValue = PropertyUtils.getProperty(myEntity, field.getName());
51                if(fieldValue == null){
52                    final FieldName fieldName = field.getAnnotation(FieldName.class);
53                    errors.add(new ErrorEntity(fieldName.name(), "Mandatory."));
54                }
55            }// Mandatory
56             
57            // MaxLength
58            final MaxLength maxLength = field.getAnnotation(MaxLength.class);
59            if(maxLength != null){
60                final Object fieldValue = PropertyUtils.getProperty(myEntity, field.getName());
61                if(fieldValue instanceof String ){
62                    if(maxLength.maxLength() < ((String)fieldValue).length()){
63                        final FieldName fieldName = field.getAnnotation(FieldName.class);
64                        errors.add(new ErrorEntity(fieldName.name(), "Value is too large. Max length is: "+maxLength.maxLength()));
65                    }
66                }
67            }// MaxLength
68                     
69        }//end-for
70         
71        return errors;
72    }
73}

The important point of this class is the part concerning the checking of annotations on entity object:

1// Mandatory field
2final Mandatory mandatory = field.getAnnotation(Mandatory.class);
3if(mandatory != null){
4    final Object fieldValue = PropertyUtils.getProperty(myEntity, field.getName());
5    if(fieldValue == null){
6        final FieldName fieldName = field.getAnnotation(FieldName.class);
7        errors.add(new ErrorEntity(fieldName.name(), "Mandatory."));
8    }
9}// Mandatory
01// MaxLength
02final MaxLength maxLength = field.getAnnotation(MaxLength.class);
03if(maxLength != null){
04    final Object fieldValue = PropertyUtils.getProperty(myEntity, field.getName());
05    if(fieldValue instanceof String ){
06        if(maxLength.maxLength() < ((String)fieldValue).length()){
07            final FieldName fieldName = field.getAnnotation(FieldName.class);
08            errors.add(new ErrorEntity(fieldName.name(), "Value is too large. Max length is: "+maxLength.maxLength()));
09        }
10    }
11}// MaxLength

STEP 5
Finally, we are creating a main method TestValidator to check our valiation based on annotations:

01package com.ho.annotations.validator.test;
02 
03import java.util.GregorianCalendar;
04import java.util.Iterator;
05 
06import com.ho.annotations.data.entity.AbstractEntity;
07import com.ho.annotations.data.entity.BookEntity;
08import com.ho.annotations.data.entity.ErrorEntity;
09import com.ho.annotations.validator.ValidatorUtil;
10 
11public class TestValidator {
12 
13    public static void main(String[] args) {
14        try{
15            // Test n°1 : All fields are filled => OK
16            {
17                System.out.println("----- Test n°1 : All fields are filled ----");
18                final BookEntity book = new BookEntity();
19                book.setId(456789); // Mandatory
20                book.setName("Germinal"); // Mandatory
21                book.setPrice(123.23); // Mandatory
22                book.setNumberOfRead(12);
23                book.setLastReadDateTime(GregorianCalendar.getInstance());
24                book.setAuthors(new String[]{"Emile Zola"});
25                ValidatorUtil.validateEntity(book);
26                printErrors(book);
27            }
28             
29            // Test n°2 : Some optional fields are missing => OK
30            {
31                System.out.println("----- Test n°2 : Some optional fields are missing ----");
32                final BookEntity book = new BookEntity();
33                book.setId(456789); // Mandatory
34                book.setName("Germinal"); // Mandatory
35                book.setPrice(123.23); // Mandatory
36                book.setNumberOfRead(12);
37                book.setLastReadDateTime(GregorianCalendar.getInstance());
38                ValidatorUtil.validateEntity(book);
39                printErrors(book);
40            }
41 
42            // Test n°3 : Some mandatory fields are missing => NOK Errors messages
43            {
44                System.out.println("----- Test n°3 : Some mandatory fields are missing ----");
45                final BookEntity book = new BookEntity();
46                book.setId(456789); // Mandatory
47                book.setName(null); // Mandatory
48                book.setPrice(123.23); // Mandatory
49                book.setNumberOfRead(12);
50                book.setLastReadDateTime(GregorianCalendar.getInstance());
51                book.setAuthors(new String[]{"Emile Zola"});
52                ValidatorUtil.validateEntity(book);
53                printErrors(book);
54            }
55             
56            // Test n°4 : All fields are filled but a field's value is too large (maxlength is reached) => NOK Errors messages
57            {
58                System.out.println("----- Test n°4 : All fields are filled but a field's value is too large (maxlength is reached) ----");
59                final BookEntity book = new BookEntity();
60                book.setId(456789); // Mandatory
61                book.setName("dsf sdf ezrezk jlezjr ezrezlrjezlrjezlrjez lrjezlkjr ezrezjrezrezrez"); // Mandatory
62                book.setPrice(123.23); // Mandatory
63                book.setNumberOfRead(12);
64                book.setLastReadDateTime(GregorianCalendar.getInstance());
65                book.setAuthors(new String[]{"Emile Zola"});
66                ValidatorUtil.validateEntity(book);
67                printErrors(book);
68            }          
69        }catch(Throwable th){
70            th.printStackTrace();
71        }
72    }
73     
74 
75    private static void printErrors(AbstractEntity myEntity){
76        if(myEntity!=null && myEntity.hasError()){
77            for (Iterator<ErrorEntity> iterator = myEntity.getErrors().iterator(); iterator.hasNext();) {
78                ErrorEntity currErrorEntity = iterator.next();
79                System.out.println(currErrorEntity);
80            }//end-for
81        }else{
82            System.out.println("No error found in the validation of this entity!");
83        }//end-if
84    }  
85}

The outputs are:

1----- Test n°1 : All fields are filled ----
2No error found in the validation of this entity!
3----- Test n°2 : Some optional fields are missing ----
4No error found in the validation of this entity!
5----- Test n°3 : Some mandatory fields are missing ----
6NAME Mandatory.
7----- Test n°4 : All fields are filled but a field's value is too large (maxlength is reached) ----
8NAME Value is too large. Max length is: 10

That’s all!!!!

Huseyin OZVEREN

Leave a Reply

Your email address will not be published.

Time limit is exhausted. Please reload CAPTCHA.

Related Post