Java Repeatable annotation not recognized when it's only one parameter - java

I want to create an option class that holds a key-value structure.
This Map is filled with the content of a configuration file at runtime.
To validate that Config I want do define required keys per annotation of the Option class like:
// Map must contain a entry with key 'foo' and key 'bar'
#requiredKey("foo")
#requiredKey("bar")
class Options {
Map<String, String> optionsMap;
}
Therefore I created a repeatable annotation:
#Retention(RetentionPolicy.RUNTIME)
public #interface requiredKeys {
requireKey[] value();
}
#Repeatable(requiredKeys)
public #interface requredKey {
String value();
}
At runtime I call requiredKey[] anno = options.getAnnotationsByType(requiredKey.class))
This works fine if the number of specified annotations is > 1. But if the number of annotations is exactly one, I cant obtain it (getAnnotationsByType returns an empty array)
working:
#requiredKey("foo")
#requiredKey("bar")
class Options {
Map<String, String> optionsMap;
}
// anno holds 'foo' and 'bar'
requiredKey[] anno = options.getAnnotationsByType(requiredKey.class))
not working:
#requiredKey("foo")
class Options {
Map<String, String> optionsMap;
}
// anno is empty
requiredKey[] anno = options.getAnnotationsByType(requiredKey.class))
I don't understand this behaviour :(
So my Questions are:
what's the explanation for such an behaviour?
how can I get this working?
Thanks

You need to add to your #requiredKey the retention policy:
#Retention(RetentionPolicy.RUNTIME)
#Repeatable(requiredKeys)
public #interface requredKey {
String value();
}
If you don't, then when you create a class with one annotation, Java does not create a requiredKeys annotation because you have only one Annotation. Therefor, the retention policy of the #requiredKey is applied. In your case, you had none, which means that your annotation won't be visible by the JVM.
Other comments: Please use Capital letter for your class/annotations.
#Retention(RetentionPolicy.RUNTIME)
public #interface RequiredKeys {
requireKey[] value();
}
#Repeatable(RequiredKeys)
public #interface RequiredKey {
String value();
}

Related

Add an annotation to check if any of the class object fields is null java

I want to add an annotation which essentially verifies if any of the fields in the java class object is null. It should return true if any of the object values is null, otherwise false.. i.e
#NameBinding
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.CLASS)
public #interface IsNull {}
I should be able to use this annotation on any of the classes
Eg:
#IsNull
public class EmployeeRequest {
String name;
}
Usage:
EmployeeRequest empRequest = new EmployeeRequest();
if(empRequest.isNull())
fail request
Is this possible?
I want to add this as an annotation because , i want to use this isNull on different classes.

Using one annotation as a member of another annotation in Java

I am new to Java annotation. I have used the following annotation in my Spring boot application as follows:
Original annotation definition:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface MyAnnotation {
EntityType entityType();
ActionType actionType();
Resource resourceType();
}
Now I would like to move actionType() and resourceType() to a different annotation say MySubAnnotation and use it in the original above annotation MyAnnotation as follows:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface MyAnnotation {
EntityType entityType();
MySubAnnotation mySubAnnotation();
}
But I am facing an issue with using this as follows:
#MyAnnotation(entityType = EntityType.MY_ENTITY,
mySubAnnotation = <???>) <---HERE I CANNOT UNDERSTAND WHAT TO SPECIFY
#MySubAnnotation(actionType=ActionType.UPDATE,
resourceType=Resource.MY_RESOURCE)
public void myMethod() {
...
}
As mentioned above, I cannot understand what to specify for sub annotation. Could anyone please help here? Thanks.
You didn’t include the declaration of your MySubAnnotation. Besides that, the syntax for the actual annotation values is not different for nested annotations. You just have to place it after the =:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface MyAnnotation {
EntityType entityType();
MySubAnnotation mySubAnnotation();
}
#Retention(RetentionPolicy.RUNTIME)
#Target({})
public #interface MySubAnnotation {
ActionType actionType();
Resource resourceType();
}
#MyAnnotation(
entityType = EntityType.MY_ENTITY,
mySubAnnotation = #MySubAnnotation(
actionType = ActionType.UPDATE,
resourceType = Resource.MY_RESOURCE
)
)
public void myMethod() {
}
Note that in this example, MySubAnnotation has an empty list of targets, i.e. #Target({}) which permits it only as a value within other annotations. Of course, you could add other permitted targets. That would not affect its use as “sub annotation”, as that’s always allowed.
But there’s not much advantage in designing annotation parts as sub annotation here. All you’ve achieved, is requiring more typing. One imaginable possibility, is to provided a default here, e.g.
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface MyAnnotation {
EntityType entityType();
MySubAnnotation mySubAnnotation() default
#MySubAnnotation(actionType=ActionType.UPDATE, resourceType=Resource.MY_RESOURCE);
}
The difference to just specifying defaults for actionType and resourceType is that now, the developer may use the default for MySubAnnotation, i.e. both values, or has to specify explicit values for both, they can not override only one of them.

Java Annotation how to get current ElemenType of specific annotation

My annotation:
#Target({ElementType.TYPE, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface ObjectName {
String name() default "";
String field() default "";
}
Some class with my annotation
#ObjectName("a_")
public class A {
#ObjectName("field_")
String filed;
}
Problem - when i get all my "ObjectName" annotations from class above, how can i get annotation's ElementType value (field, class or method type)?
So i want something like this
public void process(Class<?> clazz) {
Annotation[] annotations = clazz.getAnnotations();
for (Annotation anno : annotations) {
if (anno instanceof ObjectName) {
ObjectName annObjName = (ObjectName) anno;
Target target = anno.getAnnotation(Target.class);
if (target.getType().equals(ElementType.TYPE)
doThat(annObjName.name());
else if (target.getType().equals(ElementType.FIELD)
doThis(annObjName.field());
}
}
}
Can i even do this?
How can i do this or how can i find out if this annotation declared on filed or class?
You can't.
All you can do is look at where you call getAnnotations(), because you seem to incorrectly believe that clazz.getAnnotations() will return all annotations on everything in the class. That is false. When you call clazz.getAnnotations(), you will only get the annotations directly on the class. To get annotations on fields, you must call clazz.getFields(), and then call getAnnotations() on the Field elements. So there's no risk of getting them mixed up as long as you keep those straight.

Spring Boot annotation attribute overriding doesn't work on beans

I'm trying to create a custom annotation for enabling functionality based on feature flags. I'm basing my approach on the ConditionalOnProperty annotation, but to enable code reuse I want to define a common prefix that can be used wherever I need this functionality.
I have defined my annotation as below.
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE, ElementType.METHOD })
#Documented
#ConditionalOnProperty(prefix = "foo.bar")
public #interface ConditionalOnFeature {
#AliasFor(annotation = ConditionalOnProperty.class, attribute = "value")
String[] value();
}
I'm using the AliasFor annotation to set the value attribute in the ConditionalOnProperty annotation, and hardcoding my prefix as foo.bar (all my features will sit under this property). As far as I understand Spring annotation overriding, this should work.
However when I use this on a class (for example a controller), the code fails to compile, stating that The name or value attribute of #ConditionalOnProperty must be specified.
#RestController
#ConditionalOnFeature("enable-fancy-controller")
public class FancyController {
#RequestMapping(value = "/fancy-method", method = RequestMethod.GET)
public String fancyMethod() {
return "Fancy";
}
}
However it works perfectly fine when I instead use my annotation on a method. Compiles successfully and behaves as intended.
#RestController
public class FancierController {
#ConditionalOnFeature("enable-fancier-method")
#RequestMapping(value = "/fancier-method", method = RequestMethod.GET)
public String fancierMethod() {
return "Fancier";
}
}
Have I configured my annotation wrong? Or is this some bug with Spring?

Define custom annotation within another custom annotation with default value

I am trying to declare custom annotation in following way:
Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE, ElementType.METHOD})
public #interface InnerAnnotation {
}
Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE, ElementType.METHOD})
public #interface OuterAnnotation {
public String default "";
public InnerAnnotation innerAnnotation(); //here I wanted to do "public InnerAnnotation innerAnnotation() default {some default value}"
}
I wanted to use it in a way:
class first{
#OuterAnnotation(value = "new") //wanted to declare something like this without need to define innerAnnotation
public void func(){
}
}
I wanted to assign some default value to inner annotation usage(so that I don't have to provide any mandatory value while using it), but some how I am not able to do that as compiler asks for compile time constant for this.Can any please suggest how to use inner annotation with any default value ?
The syntax for what you what is as follows:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE, ElementType.METHOD})
public #interface OuterAnnotation {
public String default "";
public InnerAnnotation innerAnnotation() default #InnerAnnotation(); //this does the trick;
}

Categories