3

I have a code generator, that generates interfaces for JAX-RS endpoints and my backend app implements these interfaces, to provide the business logic.

The problem now is, that I cannot use name-bound container filters to enhance the business logic or to add security: Any @NameBinding marker annotation on the implementing class or its methods is ignored and the corresponding filter is not called.

Here is a minimal example: (code is in Kotlin, but the issue is the same when implemented in pure Java)

// generated
data class FooDto(val filtered: Boolean)

// generated
@Path("/")
interface OpenApiGeneratedInterface {
    @GET
    @Path("/foo/bar")
    @Produces("application/json")
    fun foo(): FooDto
}

// my implementation
class ImplementingApiController : OpenApiGeneratedInterface {
    @TestMarker
    override fun foo() = FooDto(filtered = false)
}

// may come from external dependency
@NameBinding
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
annotation class TestMarker

// may come from external dependency
@Provider
@TestMarker
class TestFilter: ContainerResponseFilter {
    override fun filter(
        requestContext: ContainerRequestContext,
        responseContext: ContainerResponseContext,
    ) {
        responseContext.entity = FooDto(filtered = true)
    }
}

When a request to /foo/bar is made, I get {"filtered":false}, so the filter is not running. When I move the @TestMarker annotation from ImplementingApiController::foo to OpenApiGeneratedInterface::foo, I instead get {"filtered":true}, so the filter did run this time. Note, that modifying the interface is not possible in reality, since the real interfaces are generated. I only did this in the example to show that the filter is working in general.

The problem seems to be, that the system only looks for marker annotations on the interfaces and never on the implementing classes.

Here is the complete picture; I am in control of:

  • The ImplementingApiController class
  • The system that the app is running on (so I can change the configuration or add more filters/interceptors)

I have no or nearly no control over:

  • The OpenApiGeneratedInterface interface (is generated from an OpenAPI spec)
  • The DTO classes like FooDto (also generated)
  • The code generator that creates these interfaces (it is a distant project)
  • The @TestMarker annotation and its corresponding filter (come from yet another project)

This leaves me with little wiggle room to get this working.

Is this even possible in this constellation, and if yes, how would this work?

What I have tried so far:

  • Add a @Path or @Provider annotation to ImplementingApiController to force the system to use this class for annotation discovery (did not work)
  • Add a javax.ws.rs.container.DynamicFeature and wire up the filters by searching the interface implementations via reflection (could work, but it will get really ugly, when interface and implementation are not managed by the same class loader)
  • Add my own ContainerResponseFilter that is always active and call the actual filters dynamically (also requires the same reflection madness like with a DynamicFeature)

Further ideas:

  • Change the code generator to omit the JAX-RS annotations in the interface and annotate everything myself (works, but almost completely defeats the point)
  • Change the code generator to include all kinds of marker annotations that I need (then I run into cyclic dependency problems when building the generated code)
LostMekkaSoft
  • 143
  • 1
  • 7
  • If I remember correctly I had a similar problem. In order to convince JAX-RS to read annotations from my implementation class, I had to place `@Path` on the implementation and *omit* it from the interface. If done like that, JAX-RS would combine all the other annotations from my class+the interface and give me what I want. *If* this is correct, you could go for the option of doing minimal changes to the generator. – Nikos Paraskevopoulos Nov 18 '20 at 09:21
  • Thanks for the response, Nikos. Unfortunately this does not work :( – LostMekkaSoft Nov 18 '20 at 13:16
  • 1
    actually, it changes the situation in a subtile way, which I can detect by using a `DynamicFeature`: - when `@Path("/")` is only on the interface, only the interface gets discovered and the complete impleentation class gets ignored - when `@Path("/")` is only on the implementation class, the implementation class is discovered, but only the annotations on the interface are used, still. I am currently experimenting with a `DynamicFeature`, that would correct this by looking for marker annotations manually... **this could work!** – LostMekkaSoft Nov 18 '20 at 14:06
  • 1
    Looking back at JAX-RS v2.1 spec, chapter 3.6, it describes exactly this behavior. It says, among other interesting things: "*annotations are inherited [...] provided that the [subclass] method and its parameters do not have any JAX-RS annotations of their own*", "*If a subclass or implementation method has any JAX-RS annotations then all of the annotations on the superclass or interface method are ignored*" and "*it is recommended to always repeat annotations instead of relying on annotation inheritance*". – Nikos Paraskevopoulos Nov 18 '20 at 14:23
  • I **almost** got it working. I was able to write a `DynamicFeature`, that correctly detects all inheritance cases where additional marker interfaces would be ignored and tries to register the relevant filters. [code gist](https://gist.github.com/LostMekka/658bbe636b4b8ff95b464d9a32c3f836) But the system does not let me register a filter that is already auto-registered with `@Provider`. I need to somehow **enable** the filter instead of registering it... – LostMekkaSoft Nov 18 '20 at 17:31

0 Answers0