No annotations present when reflecting a method using class.getDeclaredMethod - java

I'm writing some unit tests using reflection, and I'm having trouble retrieving annotations from method parameters.
I declared this interface:
private interface Provider {
void mock(#Email String email);
}
And I'm trying to reflect this method, as follows:
Class stringClass = String.class;
Method method = Provider.class.getDeclaredMethod("mock", String.class);
AnnotatedType annotatedType = method.getAnnotatedParameterTypes()[0];
Annotation annotation = annotatedType.getAnnotation(Annotation.class);
I'm expecting that annotation variable holds an instance of #Email annotation, but instead, its value is null.
Even this simple check returns false:
method.isAnnotationPresent(Email.class)
So, how can I retrieve the annotations for an specific param when reflecting a method?
Updated
It seems that in order to retrieve the parameters annotation I need to call method.getParameterAnnotations(). But the problem with this is that I don't know what annotations belong to what methods.

If you want annotation to be visible during program execution, you need to annotate it with #Retention(RetentionPolicy.RUNTIME):
private interface Provider {
void mock(#Email String email);
}
#Retention(RetentionPolicy.RUNTIME)
public #interface Email{}
#Test
public void test_annotation_existence() throws NoSuchMethodException {
Method method = Provider.class.getDeclaredMethod("mock", String.class);
Annotation[] firstParameterAnnotationsArray = method.getParameterAnnotations()[0];
boolean isAnnotationPresent = isAnnotationPresent(firstParameterAnnotationsArray, Email.class);
Assert.assertTrue("Annotation not present!", isAnnotationPresent);
}
private boolean isAnnotationPresent(Annotation[] annotationsArray, Class clazz) {
if (annotationsArray == null)
throw new IllegalArgumentException("Please pass a non-null array of Annotations.");
for(int i = 0; i < annotationsArray.length; i++ ) {
if (annotationsArray[i].annotationType().equals(clazz))
return true;
}
return false;
}

You have to make a distinction between the Java 8 type annotations and the (since Java 5) parameter annotations. The crucial thing about type annotations, is, that you have to declare the possibility of using your annotation as type annotation explicitly.
Consider the following example:
public class AnnoTest {
#Retention(RetentionPolicy.RUNTIME)
#interface Email {}
void example(#Email String arg) {}
public static void main(String[] args) throws ReflectiveOperationException {
Method method=AnnoTest.class.getDeclaredMethod("example", String.class);
System.out.println("parameter type annotations:");
AnnotatedType annotatedType = method.getAnnotatedParameterTypes()[0];
//Annotation annotation = annotatedType.getAnnotation(Annotation.class);
System.out.println(Arrays.toString(annotatedType.getAnnotations()));
System.out.println("parameter annotations:");
System.out.println(Arrays.toString(method.getParameterAnnotations()[0]));
}
}
it will print
parameter type annotations:
[]
parameter annotations:
[#AnnoTest$Email()]
In this case the annotation is a property of the parameter.
Now change it to (note the #Target)
public class AnnoTest {
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE_USE)
#interface Email {}
void example(#Email String arg) {}
public static void main(String[] args) throws ReflectiveOperationException {
Method method=AnnoTest.class.getDeclaredMethod("example", String.class);
System.out.println("parameter type annotations:");
AnnotatedType annotatedType = method.getAnnotatedParameterTypes()[0];
//Annotation annotation = annotatedType.getAnnotation(Annotation.class);
System.out.println(Arrays.toString(annotatedType.getAnnotations()));
System.out.println("parameter annotations:");
System.out.println(Arrays.toString(method.getParameterAnnotations()[0]));
}
}
which will print
parameter type annotations:
[#AnnoTest$Email()]
parameter annotations:
[]
instead. So now, the annotation is a feature of the parameter type, i.e. String. Conceptionally, the parameter type of the method is now #Email String (which seems to be the most logical choice, as it allows declaring types like List<#Email String>, but you have to understand how these new type annotations work and it doesn’t work together with pre-Java 8 libraries).
Care must be taken when enabling an annotation for both, parameters and type use, as this can create ambiguous annotations.
If that happens, the compiler will record the annotations for both, the parameter and the type, e.g.
when you change the target in the example to #Target({ElementType.TYPE_USE, ElementType.PARAMETER}), it will print
parameter type annotations:
[#AnnoTest$Email()]
parameter annotations:
[#AnnoTest$Email()]
similar issues may arise at method return types, resp. field types when enabling an annotation for “type use” and methods, resp. fields.

Related

Spring AOP - How to pass in a different number of parameters?

I have an Aspect:
#Aspect
#Component
public class BusinessAspect {
#Around("#annotation(Business)")
public Object getCorrespondingBusiness(ProceedingJoinPoint joinPoint, Business business) throws Throwable {
//BEFORE METHOD EXECUTION
Object data = joinPoint.getArgs()[0]; // gets first argument
int businessNumber = business.value(); // gets # in annotation
BusinessObj correspondingBusiness = getBusiness614(); // will make modular later
// This is where ACTUAL METHOD will get invoke
Object result = joinPoint.proceed( new Object[] { data, correspondingBusiness} );
// AFTER METHOD EXECUTION
System.out.println(result);
return result;
}
private BusinessObj getBusiness614() {
return valid business..
}
}
And here is the method that needs to access that correspondingBusiness object:
#Business(614)
public BusinessRule rangeFromGreaterThanRangeThrough(BusinessProfile businessProfile) {
return BusinessRule.businessRuleBuilder()
.withParameter("from", ...)
.withParameter("through", ...)
.withCrudOperationAction(...)
.withCrudOperationAction(...)
.setBusiness(correspondingBusiness) // not recognizing the parameter. compilation error?
).build();
}
Essentially, my issue is the correspondingBusiness object is not being recognized. I understand that you can manipulate and change parameters, but can you pass in extra parameters, or can you only change them? If I can only change them, how would I make it so I can call this method without having to pass in a second parameter? Creating an overload for each of these seems like a lot of unnecessary code.
Thank you!
Edit:
Here is the annotation interface for clarity:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Business {
int value();
}
The solution can be found in Spring documentation:
The parameter binding in advice invocations relies on matching names used in pointcut expressions to declared parameter names in (advice and pointcut) method signatures.
Define your Around advice as shown below and business parameter will be passed to the advice method.
#Around("execution(* *(..)) && #annotation(business)")

Make method accept only parameter with particular annotation

I have a method
public static void injectConfiguration(#Configurable Object bean) {}
And I have a class which holds field
public class LauncherComponentsHolder {
#Configurable
public RoomDao roomDao;
And I have main class, where I call that method and pass him that:
LauncherComponentsHolder root = new LauncherComponentsHolder();
root.roomDao = new RoomDaoImpl();
root.guestDao = new GuestDaoImpl();
root.maintenanceDao = new MaintenanceDaoImpl();
ConfigInjector.injectConfiguration(root.roomDao);
ConfigInjector.injectConfiguration(root.guestDao);
ConfigInjector.injectConfiguration(root.maintenanceDao);
Problem is that the method accepts all the 3 parameters, (no warnings, errors, nothing) however only roomDao is annotated. Annotation itself:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.PARAMETER, ElementType.FIELD})
public #interface Configurable {
}
How to make the restriction, so that injectConfiguration(#Configurable Object bean) would accept only field (or class instance) annotated with Configurable ?
You can accomplish this by using an annotation processor.
An example of such a tool is the Checker Framework.
It enables you to write type annotations in your program, then it type-checks the type annotations at compile time. It issues a warning if the type annotations in your program are not consistent with one another.
The easiest way for you to implement the checking would be to use the Subtyping Checker.
Here is an example from its manual:
import myPackage.qual.Encrypted;
...
public #Encrypted String encrypt(String text) {
// ...
}
// Only send encrypted data!
public void sendOverInternet(#Encrypted String msg) {
// ...
}
void sendText() {
// ...
#Encrypted String ciphertext = encrypt(plaintext);
sendOverInternet(ciphertext);
// ...
}
void sendPassword() {
String password = getUserPassword();
sendOverInternet(password);
}
When you invoke javac using a couple extra command-line arguments, javac issues an error for the second invocation of sendOverInternet but not the first one:
YourProgram.java:42: incompatible types.
found : #PossiblyUnencrypted java.lang.String
required: #Encrypted java.lang.String
sendOverInternet(password);
^

Allow custom annotation only one one method per class

I have a annotation #ToolExecution. At the moment I use annotation processing and throwing Error to ensure that only one method is annotated with this annotation.
Is there an native way to create a constraint and allow my custom annotation to be only applied to one annotation per class?
This should not be possible
#Tool(id = "scheduledtool")
public class ScheduledTool extends SimpleTool {
private String parameter;
#ToolExecution
public void configuration(#ToolParameters(fields = {"config"}, credentials = true) ToolParameter parameters) {
String parameter = parameters.getParameter("config");
this.parameter = parameter;
}
#ToolExecution
public void execute() {
toolLog(Level.INFO, "Configured Param: " + parameter);
toolLog(Level.INFO, "Finished scheduled tool");
}
}
You can't enforce a single method annotation per class at compile time, you can only do that type of validation at runtime.

Generic CDI producer method not working as expected

I have a CDI producer method which - depending on some conditions not relevant to this example - creates objects of different types:
public class TestProducer {
#Produces #TestQualifier
public Object create(InjectionPoint ip) {
if(something) {
return "a String";
} else {
return Integer.valueOf(42);
}
}
but when using this producer, I always get an error in the followin situation:
#Named("test")
public class TestComponent {
...
#Inject public void setA(#TestQualifier String stringValue) {
...
#Inject public void setB(#TestQualifier Integer integerValue) {
It only works when the create method of the producer has the expected type in the method signature:
public class TestProducer {
#Produces #SpringBean
public String create(InjectionPoint ip) {
Now the String get's injected correctly, but I have no way to also generate an integer from the producer method. But this is exactly what I want to avoid, since the producer itself should be completely generic.
Am I doing something wrong or is there no way to achieve the behaviour I want?
All CDI documentation makes it clear that CDI does typesafe dependency injection - and it is an exalted property of CDI. IMHO, what you are trying to do is just what CDI tries to avoid. You want the container to cast Object to each type and CDI does not work that way.
The injections points stringValue and integerValue can only receive a bean which has java.lang.String and java.lang.Integer in its list of bean types respectively. java.lang.Object does not satisfy this criterion.
I have two suggestions. First, since you have two or more injection points of different types, create two or more producer methods for that types:
public class TestProducer {
#Produces #TestQualifier
public String createString(InjectionPoint ip) {
if(something) {
return "a String";
} else {
// Some other value
}
}
#Produces #TestQualifier
public int createInt(InjectionPoint ip) {
if(something) {
return 42;
} else {
// Some other value
}
}
// ...
It works if the something condition is just to check the type of the injection point (what I am betting is the case).
However, if the something condition does decide the type using other criteria than the type of the injection point, I'd suggestion to do the "dirty job" yourself: inject the returned value in an Object-typed injection point and does the cast manually:
#Named("test")
public class TestComponent {
...
#Inject public void setA(#TestQualifier Object value) {
String stringValue = (String) value;
...
#Inject public void setB(#TestQualifier Object value) {
int intValue = (Integer) value;
The main point is that, unlike some other DI frameworks, CDI does not work against the Java type system - on the contrary, it heavily uses it. Do not try to fight against it but use this aspect of CDI in your favor :)
A producer for Object is strange anyway. I'm not sure if this is forbidden by the spec, or it's a bug, but I think you can make some clever workaround:
public class ValueHolder<T> {
private T value;
public T getValue() {
return value;
}
}
And then inject a ValueHolder<String> and ValueHolder<Integer>
Its possible create generic objects with CDI produces like that:
// the wrapper class
public class Wrapper<T> {
public final T bean;
public Wrapper(T bean){
this.bean = bean;
}
}
// the producer inside some class
#Produces
public <T> Wrapper<T> create(InjectionPoint p){
// with parameter 'p', it is possible retrieve the class type of <T>, at runtime
}
// the bean example 1
public class BeanA {
public void doFoo(){
// ...
}
}
// the bean example 2
public class BeanB {
public void doBar(){
// ...
}
}
// the class that uses the produced beans
public class SomeBean{
//// There on producer method, do you can retrieve the Class object of BeanA and BeanB, from type parameters of Wrapper.
#Inject
private Wrapper<BeanA> containerA;
#Inject
private Wrapper<BeanB> containerB;
public void doSomeThing(){
containerA.doFoo();
containerB.doBar();
}
}
Works on weld 2.2.0.
I think that works on some previous versions as well.
Your initializer methods will look for a managed bean with API types String and Integer, but your producer method bean only has API type (in case of producer method, return type) Object.
You can therefore only use Object in your initializer method injected fields and then discriminate between the types int the body of the receiver, or simply wrap them and the producer method in an actual type that can return Strings or Int (but I'd avoid the generics)

Annotation member which holds other annotations?

I want to create a custom annotation (using Java) which would accept other annotations as parameter, something like:
public #interface ExclusiveOr {
Annotation[] value();
}
But this causes compiler error "invalid type for annotation member".
Object[] also doesn't work.
Is there a way to do what I want?
The error is produced because you can't use interfaces as annotation values (change it to Comparable and you'll get the same error). From the JLS:
It is a compile-time error if the return type of a method declared in an annotation type is any type other than one of the following: one of the primitive types, String, Class and any invocation of Class, an enum type, an annotation type, or an array of one of the preceding types. It is also a compile-time error if any method declared in an annotation type has a signature that is override-equivalent to that of any public or protected method declared in class Object or in the interface annotation.Annotation.
I'm afraid I don't know of a good workaround, but now at least you know why you get the error.
Depending on the reason why you would want to specify other annotations there are multiple solutions:
An array of instances of a single annotation type
Probably not what you meant in your question, but if you want to specify multiple instances of a single annotation type it's certainly possible:
public #interface Test {
SomeAnnotation[] value();
}
An array of annotation types instead of instances
If you do not need to specify any parameters on the individual annotations you can just user their class objects instead of instances.
public #interface Test {
Class<? extends Annotation>[] value();
}
But an enum would of course also do the trick in most situations.
Use multiple arrays
If the set of possible annotation types you want to use is limited, you can create a separate parameter for each one.
public #interface Test {
SomeAnnotation[] somes() default { };
ThisAnnotation[] thiss() default { };
ThatAnnotation[] thats() default { };
}
Giving a default value to each member makes it possible to only specify arrays for the types you need.
You can do:
Class<? extends Annotation>[] value();
Not sure if that helps, but . . .
I myself hereby propose a workaround for the given problem:
Well, what I wanted to make possible was something like that:
#Contract({
#ExclusiveOr({
#IsType(IAtomicType.class),
#Or({
#IsType(IListType.class),
#IsType(ISetType.class)
})
})
})
Proposed workaround:
Define a class with parameter-less constructor (which will be called by your own annotation processor later) in following way:
final class MyContract extends Contract{
// parameter-less ctor will be handeled by annotation processor
public MyContract(){
super(
new ExclusiveOr(
new IsType(IAtomicType.class),
new Or(
new IsType(IListType.class),
new IsType(ISetType.class)
)
)
);
}
}
usage:
#Contract(MyContract.class)
class MyClass{
// ...
}
I just ran into this exact problem, but (inspired by #ivan_ivanovich_ivanoff) I have discovered a way to specify a bundle of any combination of Annotations as an annotation member: use a prototype / template class.
In this example I define a WhereOr (i.e. a "where clause" for my model annotation) which I need to contain arbitrary Spring meta-annotations (like #Qualifier meta-annotations).
The minor (?) defect in this is the forced dereferencing that separates the implementation of the where clause with the concrete type that it describes.
#Target({})
#Retention(RetentionPolicy.RUNTIME)
public #interface WhereOr {
Class<?>[] value() default {};
}
#Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface JsonModel {
Class<?> value();
WhereOr where() default #WhereOr;
}
public class Prototypes {
#Qualifier("myContext")
#PreAuthorize("hasRole('ROLE_ADMINISTRATOR')")
public static class ExampleAnd {
}
}
#JsonModel(
value = MusicLibrary.class,
where = #WhereOr(Prototypes.ExampleAnd.class)
)
public interface JsonMusicLibrary {
#JsonIgnore
int getMajorVersion();
// ...
}
I will programmatically extract the possible valid configurations from the "where clause" annotation. In this case I also use the prototypes class as a logical AND grouping and the array of classes as the logical OR.

Categories

Resources