Kotlin annotation not in fieldDecleration or compiled java - java

For my data objects in Kotlin I have added a custom annotation for GSON to have an exclusion rule.
In the past this has worked perfectly, now it does not show up in my class reflection (this.javaClass.declaredFields[3].annotations is null) nor does it show up in the compiled java output.
I have tried different things, like upgrading my kotlin version, adding kotlin-kapt, using different #Retention types, restarting my computer (you never know) and have looked at other annotations. Those other annotations (for instance for Hibernate a #OneToOne) shows up with no issue.
Annotation definition:
#Retention(AnnotationRetention.RUNTIME)
#Repeatable
#Target(
AnnotationTarget.FIELD,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER,
AnnotationTarget.PROPERTY,
AnnotationTarget.VALUE_PARAMETER
)
annotation class ExcludeFromJSON
Usage in data class:
#Entity
#Table(name = "user")
class User (
var username: String = "",
var email: String = "",
#ExcludeFromJSON
private var password: String
) {}
I expect the annotation to show up in the javaClass reflection and in the compiled java code. It does neither.
Compiled password var (no annotation...):
private final var password: kotlin.String /* compiled code */`

You should qualify the annotation with the appropriate use-site target:
#field:ExcludeFromJSON
private var password: String
This will cause the annotation to be present on the Java field generated by this property.
From the Kotlin Reference regarding Annotation Use-site Targets:
When you're annotating a property or a primary constructor parameter, there are multiple Java elements which are generated from the corresponding Kotlin element, and therefore multiple possible locations for the annotation in the generated Java bytecode. [...]
[...]
The full list of supported use-site targets is:
file;
property (annotations with this target are not visible to Java);
field;
get (property getter);
set (property setter);
receiver (receiver parameter of an extension function or property);
param (constructor parameter);
setparam (property setter parameter);
delegate (the field storing the delegate instance for a delegated property).
[...]
If you don't specify a use-site target, the target is chosen according to the #Target annotation of the annotation being used. If there are multiple applicable targets, the first applicable target from the following list is used:
param;
property;
field.
The three important things to take away from that are:
Annotations in Kotlin have a certain amount of ambiguity1 regarding where they're ultimately applied. For instance, placing your annotation where you did means said annotation could be applied to one of at least five different locations: property, field, getter, setter, setter parameter.
An annotation applied to a Kotlin property is not visible on the Java side.
An annotation on a Kotlin property, without a use-site target, will only be applied to the backing field if it's not also applicable to being applied to the Kotlin property.
Your annotation has both AnnotationTarget.FIELD and AnnotationTarget.PROPERTY in its #Target annotation. Since you don't specify a use-site target, the property takes precedence—meaning the annotation is not visible to Java.
For more information regarding properties and fields, see the Properties and Fields page of the Kotlin Reference.
1. It's not technically ambiguous, as everything is well defined.

Related

Class parameter name for default type in spring-kafka listener

I'm attempting to change the default type value for kafka listener with property "spring.json.value.default.type=" using my own annotation in spring-kafka. Currently, it's possible to overwrite it with following values:
properties="spring.json.value.default.type=com.package.class" which is canonical name of class.
I've made an annotation that sets the following value:
#MyAnnotation(topic = Topics.BUILD_CONFIG_CREATED, defaultType = ConstantsClass.TYPE_HEADER + "prz.student.finger.kafkaBSC.MyObjectDTO")
Is there any way to avoid hard typing the class name?
I would like to implement the option to use the following code(just giving the class that was imported):
#MyAnnotation(topic = Topics.BUILD_CONFIG_CREATED, defaultType = MyObjectDTO.class)
The closest to I've got is adding in my annotation:
#AliasFor(annotation = KafkaListener.class, attribute = "properties")
String defaultType() default headerType()+dtoType().getCanonicalName().toString();
String headerType() default "spring.json.value.default.type=";
Unfortunately, the constraints regarding the compilation time values for class in annotation blocks me from implementing it. Is there any way to inject the cannonical name without hard typing it, or any other way to implement this?
The properties property can contain SpEL (see its Javadocs).
Something like #{#someBean.type.name}; where someBean is a bean with a method public Class<?> getType().

Java annotation cannot be found per reflection on a Kotlin data class

Given this Java annotation
#Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#JacksonAnnotation
public #interface JsonProperty
and this Kotlin data class
#JsonIgnoreProperties(ignoreUnknown = true)
data class LossDocument(#JsonProperty("id") val id: String)
I would expect to find the annotation either here
LossDocument::class.java.declaredFields[0].annotations
or here
LossDocument::class.java.declaredMethods.first { it.name == "getId" }
but both have zero annotations. Is this a bug? Per 53843771, my impression is this should work. I'm using Kotlin 1.4.0.
When I declare the annotation explicitly as #field:JsonProperty("id") I can find it without problem using LossDocument::class.java.declaredFields[1].annotations.
When you're annotating a property or a primary constructor parameter, there are multiple Java elements which are generated from the corresponding Kotlin element, and therefore multiple possible locations for the annotation in the generated Java bytecode.
If you don't specify a use-site target, the target is chosen according to the #Target annotation of the annotation being used. If there are multiple applicable targets, the first applicable target from the following list is used:
param, property, field. -- Annotation Use-site Targets
In your case the annotation is placed on the constructor parameter.

Retrieve annotations from KType

I have a simple TYPE_USE annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
public #interface Cool {
}
And the following sample Kotlin class:
class Item(
id: Long? = null,
var names: List<#Cool String> = emptyList())
Is there any way to extract the annotation using Java reflection?
Item.class.getMethod("getName").getAnnotatedReturnType() loses the annotations, same with getting the field.
Can I even get the annotation from Kotlin?
Item::class.memberProperties.elementAt(0).returnType return a KType which has the annotation, but I don't see a way to extract it. Nor to obtain an AnnotatedType from KType, even though I have JDK8 extensions.
All I see is KType#javaType but this returns Type, not AnnotatedType... so it looses the annotations again.
Edit: this is a bug and has been reported. There is not yet a target version, but its priority has been set to Major. This was fixed in Kotlin 1.3.
TL; DR: no...?
The item annotated with #Cool is the first type argument, so you need to retrieve it:
val type = Item::class.memberProperties.elementAt(0).returnType
val arg = type.arguments[0]
println(arg) // KTypeProjection(variance=INVARIANT, type=#Cool kotlin.String)
Unfortunately there doesn't seem to be a way to retrieve the annotations on a KType (as you have mentioned).
Strangely, this is a very internal process. Looking into the source for KTypeImpl shows that toString is implemented via ReflectionObjectRenderer.renderType(type), (where type is a KotlinType) which is delegated to DescriptorRenderer.FQ_NAMES_IN_TYPES, which we can see is a DescriptorRenderer with modifiers ALL.
The renderer checks if the type is a subclass of kotlin.reflect.jvm.internal.impl.descriptors.annotations.Annotated, and then accesses its annotations property.
I tried this:
val retType = Item::class.memberProperties.elementAt(0).returnType
val arg = retType.arguments[0]
println(arg) // KTypeProjection(variance=INVARIANT, type=#Cool kotlin.String)
val type = arg.type!!
println(type)
val field = type::class.memberProperties.first { it.name == "type" }
val kotlinType = field.call(type) as Annotated
println(kotlinType)
println(kotlinType.annotations)
Unfortunately, I get a ClassNotFoundException for org.jetbrains.kotlin.types.KotlinType, so that option is gone.
Equally strangely, KType is not a subtype of KAnnotatedElement (which is why it has no annotations property).
I suppose this may have been an oversight, since KTypeImpl wraps a KotlinType, which does contain annotations.

Java annotations: pass value of annotation attribute to another annotation

I have interface Resource and several classes implementing it, for example Audio, Video... Further, I have created custom annotation MyAnnotation with Class type param:
#MyAnnotation(type = Audio.class)
class Audio {
...
}
#MyAnnotation(type = Video.class)
class Video{
...
}
In some other place in code I have to use Interface Resource as a returned type:
public class Operations<T extends Resource> {
....
#OtherAnnotation(type = Audio.class (if audio), type = Video.class (if video) )
T getResource();
....
}
The question is how to appropriatelly annotate annotation #OtherAnnotation depending of what kind of Resource type will be returned ?
What you are asking is for dynamic values for annotation attributes.
However annotations can only be set at compile time which is the reason why their values can only be compile time constants. You may only read them at runtime.
There was a similar question in which someone tried to generate the annotation value , it's answer explains why there is no way to dynamically generate a value used in annotation in a bit more detail. In that question there was an attempt to use a final class variable generated with a static method.
There are annotation processors which offer a bit more flexibility by handling placeholders. However i don't think this fits your case, as you want the dynamic values at runtime.
This answer refers to spring's use of the expression language for the Value annotation in which the placeholder (#Value("#{systemProperties.dbName})") gets overrided with the data from one of the property sources defined ( example in spring boot )
In any case, you will have to rethink your architecture a bit.

how to pass request parameters to a java annotation

i am using play framework , i need to check the user's permissions with #secure annotation , but i get a problem here :
#secure(UID=???)
public static void removeFavorite(Long storyId,Long userId){
}
can any one tell me how to pass the "userId" parameter to "UID" in the annotation ?
PS : the "userId" parameter is in request scope.
many thanks!
AFAIK you can't change annotations at runtime (at least not without dynamic code generation). Additionally, annotations are static, i.e. they apply to classes or class members (fields, methods etc.) and can't be changed per instance. Thus you can't pass the userId to that annotation.
I dont know what#securedoes, but generally, you'd read the annotation at runtime and optionally check its static parameters and if those checks succeed you'd read theuserId` parameter and do whatever is appropriate when that annotation is present.

Categories