1
votes

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:

  1. I had to change the class and all methods to 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?
  2. Generally Kotlin generates setters and getters for properties with the given modifier (public/private/protected) backed by a private field. But when using 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)?
  3. What is annotated in the code above? The field or the generated methods? If it is the field: How can I annotate the setter only?
  4. Because all methods but private ones have to be 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 properties

EDIT: Added Ploblem 4 and changed the Implementation because of this problem to private with a setter.

2

2 Answers

1
votes
  1. yes
  2. I don't know background too, but you can workarond that by changing code to smth like

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

  1. You're annotating field there. To annotate getters/setter you can add @get: and @set: to annotations.
1
votes

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:

  1. If the dependency injection framework uses proxys open is necessary for the class and every method and property (but private ones).
  2. No. With 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.
  3. The field. As seen above you can write @Context set or @set:Context to annotate the setter.
  4. As stated in 1 all properties but private ones have to be declared as open.

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.