I'm exploring annotations and came to a point where some annotations seems to have a hierarchy among them.
I'm using annotations to generate code in the background for Cards. There are different Card types (thus different code and annotations) but there are certain elements that are common among them like a name.
#Target(value = {ElementType.TYPE})
public #interface Move extends Page{
String method1();
String method2();
}
And this would be the common Annotation:
#Target(value = {ElementType.TYPE})
public #interface Page{
String method3();
}
In the example above I would expect Move to inherit method3 but I get a warning saying that extends is not valid with annotations. I was trying to have an Annotation extends a common base one but that doesn't work. Is that even possible or is just a design issue?
You can annotate your annotation with a base annotation instead of inheritance. This is used in Spring framework.
To give an example
#Target(value = {ElementType.ANNOTATION_TYPE})
public #interface Vehicle {
}
#Target(value = {ElementType.TYPE})
#Vehicle
public #interface Car {
}
#Car
class Foo {
}
You can then check if a class is annotated with Vehicle using Spring's AnnotationUtils:
Vehicle vehicleAnnotation = AnnotationUtils.findAnnotation (Foo.class, Vehicle.class);
boolean isAnnotated = vehicleAnnotation != null;
This method is implemented as:
public static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType) {
return findAnnotation(clazz, annotationType, new HashSet<Annotation>());
}
#SuppressWarnings("unchecked")
private static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType, Set<Annotation> visited) {
try {
Annotation[] anns = clazz.getDeclaredAnnotations();
for (Annotation ann : anns) {
if (ann.annotationType() == annotationType) {
return (A) ann;
}
}
for (Annotation ann : anns) {
if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {
A annotation = findAnnotation(ann.annotationType(), annotationType, visited);
if (annotation != null) {
return annotation;
}
}
}
}
catch (Exception ex) {
handleIntrospectionFailure(clazz, ex);
return null;
}
for (Class<?> ifc : clazz.getInterfaces()) {
A annotation = findAnnotation(ifc, annotationType, visited);
if (annotation != null) {
return annotation;
}
}
Class<?> superclass = clazz.getSuperclass();
if (superclass == null || Object.class == superclass) {
return null;
}
return findAnnotation(superclass, annotationType, visited);
}
AnnotationUtils also contains additional methods for searching for annotations on methods and other annotated elements. The Spring class is also powerful enough to search through bridged methods, proxies, and other corner-cases, particularly those encountered in Spring.
Unfortunately, no. Apparently it has something to do with programs that read the annotations on a class without loading them all the way. See Why is it not possible to extend annotations in Java?
However, types do inherit the annotations of their superclass if those annotations are #Inherited.
Also, unless you need those methods to interact, you could just stack the annotations on your class:
#Move
#Page
public class myAwesomeClass {}
Is there some reason that wouldn't work for you?
In addition to Grygoriys answer of annotating annotations.
You can check e.g. methods for containing a #Qualifier annotation (or an annotation annotated with #Qualifier) by this loop:
for (Annotation a : method.getAnnotations()) {
if (a.annotationType().isAnnotationPresent(Qualifier.class)) {
System.out.println("found #Qualifier annotation");//found annotation having Qualifier annotation itself
}
}
What you're basically doing, is to get all annotations present on the method and of those annotations you get their types and check those types if they're annotated with #Qualifier. Your annotation needs to be Target.Annotation_type enabled as well to get this working.
Check out https://github.com/blindpirate/annotation-magic , which is a library I developed when I had the same question.
#interface Animal {
boolean fluffy() default false;
String name() default "";
}
#Extends(Animal.class)
#Animal(fluffy = true)
#interface Pet {
String name();
}
#Extends(Pet.class)
#interface Cat {
#AliasFor("name")
String value();
}
#Extends(Pet.class)
#interface Dog {
String name();
}
#interface Rat {
#AliasFor(target = Animal.class, value = "name")
String value();
}
#Cat("Tom")
class MyClass {
#Dog(name = "Spike")
#Rat("Jerry")
public void foo() {
}
}
Pet petAnnotation = AnnotationMagic.getOneAnnotationOnClassOrNull(MyClass.class, Pet.class);
assertEquals("Tom", petAnnotation.name());
assertTrue(AnnotationMagic.instanceOf(petAnnotation, Animal.class));
Animal animalAnnotation = AnnotationMagic.getOneAnnotationOnClassOrNull(MyClass.class, Animal.class);
assertTrue(animalAnnotation.fluffy());
Method fooMethod = MyClass.class.getMethod("foo");
List<Animal> animalAnnotations = AnnotationMagic.getAnnotationsOnMethod(fooMethod, Animal.class);
assertEquals(Arrays.asList("Spike", "Jerry"), animalAnnotations.stream().map(Animal::name).collect(toList()));
Related
I have an enum :
public enum Vehicle {
CAR,
BUS,
BIKE,
}
I intend to use these enum values as annotations : #Vehicle.CAR, #Vehicle.BUS, #Vehicle.BIKE. Does java allow me to define them as annotations ?
No You can not do this. But if you want to use enum in annotation you can do like this
class Person {
#Presentable({
#Restriction(type = RestrictionType.LENGTH, value = 5),
#Restriction(type = RestrictionType.FRACTION_DIGIT, value = 2)
})
public String name;
}
enum RestrictionType {
NONE, LENGTH, FRACTION_DIGIT;
}
#Retention(RetentionPolicy.RUNTIME)
#interface Restriction {
//The below fixes the compile error by changing type from String to RestrictionType
RestrictionType type() default RestrictionType.NONE;
int value() default 0;
}
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
#interface Presentable {
Restriction[] value();
}
You can't use enum as annotations. But you can add the enum as an element of the annotation.
The enum
public enum Priority {
LOW,
MEDIUM,
HIGH
}
The annotation
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD})
public #interface TestAnnotation {
Priority priority() default Priority.MEDIUM;
}
The annotation usage
#TestAnnotation(priority = Priority.HIGH)
public void method() {
//Do something
}
I know hibernate validator supports TYPE_USE annotations: though it does not define its own, it lets you define and use custom ones.
I could define and validate correctly such an annotation (code soon), but then I want to map the error into a path that is used to display the error to the user.
Given then following sample
public class SampleTest {
private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
public static class LimitedSizeStringValidator implements ConstraintValidator<LimitedSize, String> {
private LimitedSize constraint;
#Override
public void initialize(LimitedSize constraintAnnotation) {
this.constraint = constraintAnnotation;
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
String s = Ensure.notNull(value);
return s.length() >= constraint.min() &&
s.length() <= constraint.max();
}
}
#Retention(RUNTIME)
#Documented
#Target({TYPE_USE})
#Constraint(validatedBy = {LimitedSizeStringValidator.class})
public #interface LimitedSize {
String message() default "{javax.validation.constraints.Size.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int min() default 0;
int max() default Integer.MAX_VALUE;
}
private static class TestBean {
#Valid
private Collection<#LimitedSize(max = 3) String> strings = new ArrayList<>();
#Valid
private Collection<InnerBean> beans = new ArrayList<>();
}
private static class InnerBean {
#Min(3)
private final int value;
private InnerBean(int value) {
this.value = value;
}
}
#Test
public void testBeanInvalid() {
TestBean testBean = new TestBean();
assertThat(validator.validate(testBean)).isEmpty();
testBean.strings.add("ok");
testBean.strings.add("ok2");
testBean.beans.add(new InnerBean(4));
assertThat(validator.validate(testBean)).isEmpty();
testBean.strings.add("not_ok");
testBean.beans.add(new InnerBean(2));
Set<ConstraintViolation<TestBean>> violations = validator.validate(testBean);
assertThat(violations).hasSize(2);
StreamSupport.stream(violations.spliterator(), false)
.forEach(v -> {
System.out.println(v.getPropertyPath());
System.out.println(v.getMessage());
v.getPropertyPath().forEach(p -> System.out.print("'" + p.getName() + (p.getIndex() != null ? "[" + p.getIndex() + "]" : "") + "' -> "));
System.out.println();
});
}
}
I would like map the errors in an object like
errors: [
["beans", "1", "value"],
["strings", "2"]
]
As in my sample, my approach at the moment is by navigating the violation path (http://docs.oracle.com/javaee/7/api/javax/validation/ConstraintViolation.html#getPropertyPath--) which works perfectly for the first case, but fails for the second (I cannot find a way to retrieve the index of the failing object). I think the reason is in the implementation of javax.validation.Path.PropertyNode in hibernate-validator (I am currently on version 5.2.4.Final, and the code looks the same as in the linked 5.2.1.Final. For reference:
#Override
public final Integer getIndex() {
if ( parent == null ) {
return null;
}
else {
return parent.index;
}
}
With TYPE_USE this approach cannot work in my opinion, because the failing object is a leaf, thus no child node can retrieve the index from it.
Nice enough, hibernate implementation of javax.validation.Path overrides the toString method is way such that violation.getPropertyPath().toString() is beans[1].value and strings[2] (in the sample code above).
So, to the question(s): is my navigation approach wrong and there is another way to extract such a mapping from the ConstraintViolation? Or is this a feature request for hibernate developers (I can see that before TYPE_USE annotations the getIndex approach they implemented was totally fine?
It just feels strange I am the first one with this problem (I tried to google and could not find anything related, the closest being: https://github.com/hibernate/hibernate-validator/pull/441) so I am wondering whether the mistake is mine rather than a hibernate limitation
I agree that the index should be set for that value and think you uncovered an issue in Hibernate Validator. Could you open an issue in our JIRA tracker?
Btw. the notion of TYPE_USE level constraints will be standardized as of Bean Validation 2.0. So there may be some more changes coming up in this area, specifically I'm wondering what Kind that node should have (currently it's PROPERTY which seems questionable).
I need to do some additional business logic for specified filed (with a custom annotation) after hibernate load this entity. So, I created a hibernate interceptor like this. But what confused me is that I can't get the annotation information. The encryptAnnotation is always null in the following codes.
public class HibernateInterceptor extends EmptyInterceptor {
public boolean onLoad(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
for (int i = 0; i < types.length; i++) {
Type type = types[i];
if (type instanceof StringType) {
StringType stringType = (StringType) types[i];
Encrypt encryptAnnotation = stringType.getJavaTypeDescriptor().getJavaTypeClass().getAnnotation(Encrypt.class);
if (encryptAnnotation != null) {
//todo: decrypt field
return true;
}
}
}
return false;
}
}
Here is the Entity and annotation definition:
#Entity
#Table(name = "table_name")
public class Trade implements Serializable {
#Encrypt
private String shiptoAddr;
}
#Target({ElementType.FIELD, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Encrypt {
}
You are trying to obtain the annotation from the mapping information and basically in the end you are trying to find the annotation on the String class, that is obviously not going to work.
Instead you need to detected all fields on the passed in entity object and check if the annotation is present on a field.
suppose i have the following scenario:
public class EntityA {
private List<EntityB> listOfBs;
}
im trying to cascade validation to the list of Bs only if running under a certain validation group. so ideally, this:
public class EntityA {
#Valid(groups = {SomeSpecificGroup.class})
private List<EntityB> listOfBs;
}
unfortunately, #Valid does not have a groups() property. so i figured i'd try something like:
#Constraint(validatedBy = { CascadedValidator.class })
#Target({ ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface CascadedValidation {
Class<?>[] groups() default { };
}
and write a validator (CascadedValidator) that upon activation will do the cascade (==will validate all elements of the collection its placed on).
my issue is how do i perform the cascaded validation?
so far i have this:
public class CascadedValidator implements ConstraintValidator<CascadedValidation, Object>{
private Class<?>[] groups;
#Override public void initialize(CascadedValidation constraintAnnotation) {
groups = constraintAnnotation.groups();
}
#Override public boolean isValid(Object value, ConstraintValidatorContext context) {
if (value == null || !(value instanceof Iterable)) {
return true;
}
for (Object item : (Iterable)value) {
//validate item using the groups?!
}
}
}
i know i could implement the actual validation by creating another Validator "inline":
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<Object>> violations;
if (decideIfCascade(groups)) {
for (Object item : (Iterable)value) {
if (groups!=null && groups.length>0) {
violations = validator.validate(item, groups);
} else {
violations = validator.validate(item);
}
if (!violations.isEmpty()) {
return false;
}
}
}
return true;
but this just smells bad to me.
surely there's a sane/normal/easy way of doing this?
EDIT - the actual use case
my API accept both EntityA (which has a list of Bs) and EntityB as top-level entities (so you can send a single B directly). both A and B have an id property, but i only require a non-null id on the top level object submitted. so if the service gets an A with an idea and several "blank" Bs its ok, but if i get a B as a top level parameter it must have an id.
You should not invoke the validation engine from within a ConstraintValidator implementation.
If you are on Bean Validation 1.1, have a look at group conversions which give you control over the validation groups propagated upon cascaded validation. E.g. you could do the following:
#Valid
#ConvertGroup(from = Default.class, to = SomeSpecificGroup.class)
private List<EntityB> listOfBs;
I want to add annotations on my classes properties, and then iterate all my properties with the ability to lookup the annotations also.
So for example, I have a class like:
public class User {
#Annotation1
private int id;
#Annotation2
private String name;
private int age;
// getters and setters
}
Now I want to be able to loop through my properties, and be able to know what annotation (if any) is on the property.
I want to know how to do this using just java, but also curious if using either spring, guava or google guice would make this any easier (if they have any helpers to do this easier).
Here is an example that utilizes the (barely maintained) bean instrospection framework. It's an all Java solution that you can extend to fit your needs.
public class BeanProcessor {
public static void main(String[] args) {
try {
final Class<?> beanClazz = BBean.class;
BeanInfo info = Introspector.getBeanInfo(beanClazz);
PropertyDescriptor[] propertyInfo = info.getPropertyDescriptors();
for (final PropertyDescriptor descriptor : propertyInfo) {
try {
final Field field = beanClazz.getDeclaredField(descriptor
.getName());
System.out.println(field);
for (final Annotation annotation : field
.getDeclaredAnnotations()) {
System.out.println("Annotation: " + annotation);
}
} catch (final NoSuchFieldException nsfe) {
// ignore these
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Below is the way to create your own annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface Annotation1 {
public String name();
public String value();
}
After defining your annotation, use the annotation as you mentioned in your question and you can use the below reflection method to get the annotated class details
Class aClass = User.class;
Annotation[] annotations = aClass.getAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof Annotation1){
Annotation1 myAnnotation = (Annotation1) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}
I created the method below which creates a stream of all fields in a class and it's superclasses which have a specific annotation.
There are other ways to do it. But I think this solution is very easy to reuse and practical because when you need to know those fields, it is usually to do an action on each field. And a Stream is exactly what you need to do that.
public static Stream<Field> getAnnotatedFieldStream(Class<?> theClass, Class<? extends Annotation> annotationType) {
Class<?> classOrSuperClass = theClass;
Stream<Field> stream = Stream.empty();
while(classOrSuperClass != Object.class) {
stream = Stream.concat(stream, Stream.of(classOrSuperClass.getDeclaredFields()));
classOrSuperClass = classOrSuperClass.getSuperclass();
}
return stream.filter(f -> f.isAnnotationPresent(annotationType));
}
you would use reflection to get the fields of the class and then call something like getAnnotations() on each field.