Reputation: 1257
I have converted a REST resource in a JEE application formally written in Java to Kotlin. The application runs in a Wildfly Application Server with Weld as Dependency Injection framework.
This is the final implementation I came up with:
@Path("/myResource")
open class MyResource {
@Context
private lateinit var context: SecurityContext
open protected setSecurityContext(securityContext: SecurityContext) {
this.context = securityContext
}
@POST
@Path("/change")
@Transactional
@Consumes(MediaType.APPLICATION_JSON)
open internal fun change(data: MyChangeData, @Context uriInfo: UriInfo): Response {
// ...
}
}
The setter is for testing purposes. With Mockito or other mocking frameworks that can set private fields this is not neccessary.
I had some issues with this implementation:
open
to allow the CDI Container to create a proxy for this bean. As I understand this topic there is no other way to allow Weld to do its work without allowing subclassing?lateinit
the field is generated with the same visibility as the getters and setters (Kotlin in Action, p. 146). I don't understand the background for this special behaviour. Using public
property causes an error in Weld reporting that public fields are not allowed. How can I declare that the field should be private but the getter and setter is protected (to initialize the Resource in tests)?open
all properties but private ones are rejected by the Weld container: Kotlin creates Getters and Setters with the same visibility and the container tries to proxy the bean proxying all methods for the bean. This does not work because the generated getters and setters are not open
. For private properties there simply is no problem because the container does not proxy private methods. As I see there is no possibility to declare a getter/setter as open
therefor it is not possible to use protected propertiesEDIT: Added Ploblem 4 and changed the Implementation because of this problem to private with a setter.
Upvotes: 1
Views: 1325
Reputation: 1257
The best solution I could find is to declare the property as open protected
:
@Context
open protected lateinit var context: SecurityContext
This way the container can override it and tests from java or groovy will see the setter as package protected.
If you want to annotate the setter only (which I would prefer but is more verbose) you can use:
open protected lateinit var context: SecurityContext @Context set
or better and shorter:
@set:Context
open protected lateinit var context: SecurityContext
Unfortunately this will not work with Kotlin tests because protected
vars can only be seen by subclasses in Kotlin. Here you have to write a seperate accessor:
@Context
open protected lateinit var context: SecurityContext
open internal fun setTheSecurityContext(context: SecurityContext) ...
Or you can use a secondary constructor:
open class MyResource() {
constructor(context: SecurityContext): this() {
this.context = context
}
Note that the primary empty constructor has to be still present.
And here are the separate answers for my questions:
open
is necessary for the class and every method and property (but private
ones).lateinit
the property has the same visibility as the getters and setters. If you want to do this you have to declare the property as nullable. But then you have to use !!
on every access which is awkward.@Context set
or @set:Context
to annotate the setter.EDIT: Note that private properties also cause problems (tested with Wildfly). My recommendation is not to use private at all but protected.
EDIT 2: Note that these workarounds are only necessary on an EJB (like a REST-Resource). With simple CDI Beans this is no proplem until they are proxied by an aspect or such. Then this post would also apply.
Upvotes: 0
Reputation: 2931
open class A {
@Context
private lateinit var _backing: SecurityContext
open protected var field: SecurityContext
get() = _backing
set(value) { _backing = value }
}
Also, you can use constructor injection
@get:
and @set:
to annotations.Upvotes: 1