Passing dynamic parameters to an annotation

javaxiss picture javaxiss · May 7, 2013 · Viewed 9.6k times · Source

I wonder if there is a possiblity to pass dynamically values to an annotation attribute.

I know that annotation are not designed to be modified but I'm using Hibernate Filters and condition to be put are not static in my case.

I think that the only solution is to use librairies whose aim is to read and modify byte code such as Javassist or ASM but it would be too much better if there is another solution.

ps: The difficulty in my case is that I should modify annotations (attribute's value) but librairies I mentioned above allow to create not to edit that's why I'm wondering for another solution

Thanks in advance

Answer

Will picture Will · May 19, 2013

I don't know if it integrates nicely with your frameworks, but i would like to suggest the following:

  • Create an annotation which receives a Class that implements the validation rule
  • Create an interface which the annotation can receive
  • Create an implementation for the interface which has the logic for your rule
  • Add the annotations to your model class
  • Create an annotation processor which applies the validation for each annotated field

I wrote the following example in Groovy, but using standard Java libs and idiomatic Java. Warn me if anything is unreadable:

import java.lang.annotation.*

// Our Rule interface
interface Rule<T> { boolean isValid(T t) }

// Here is the annotation which can receive a Rule class
@Retention(RetentionPolicy.RUNTIME)
@interface Validation { Class<? extends Rule> value() }

// An implementation of our Rule, in this case, for a Person's name
class NameRule implements Rule<Person> {
  PersonDAO dao = new PersonDAO()
  boolean isValid(Person person) {
    Integer mode = dao.getNameValidationMode()
    if (mode == 1) { // Don't hardcode numbers; use enums
      return person.name ==~ "[A-Z]{1}[a-z ]{2,25}" // regex matching
    } else if (mode == 2) {
      return person.name ==~ "[a-zA-Z]{1,25}"
    }
  }
}

After these declarations, the usage:

// Our model with an annotated field
class Person {
  @Validation(NameRule.class)
  String name
}

// Here we are mocking a database select to get the rule save in the database
// Don't use hardcoded numbers, stick to a enum or anything else
class PersonDAO { Integer getNameValidationMode() { return 1 } }

The processing of the annotations:

// Here we get each annotation and process it against the object
class AnnotationProcessor {
  String validate(Person person) {
    def annotatedFields = person.class.declaredFields.findAll { it.annotations.size() > 0 }
    for (field in annotatedFields) {
      for (annotation in field.annotations) {
        Rule rule = annotation.value().newInstance()
        if (! rule.isValid(person)) {
          return "Error: name is not valid"
        }
        else {
          return "Valid"
        }
      }
    }
  }
}

And tests:

// These two must pass
assert new AnnotationProcessor().validate( 
  new Person(name: "spongebob squarepants") ) == "Error: name is not valid"

assert new AnnotationProcessor().validate( 
  new Person(name: "John doe") ) == "Valid"

Also, take a look at GContracts, it provides some interesting validation-through-annotations model.