3

I have a class like this:

   public class MyResource(){

       public MyResource(@Context ServletContext context){
            context.setAttribute("someAttribute","someVal");
       }

       @PUT
       public void someMethod(){
         ...
       }
   }

and I would like to this using annotations (i.e. JAX-RS/Jersey reads the value of the annotation and writes it into ServletContext so that I can access this value somewhere else where I inject the ServletContext in the request scope.)

@MyCustomAnnotation(name="someVal")
public class MyResource(){


}
LAC
  • 852
  • 4
  • 11
  • 31

1 Answers1

4

Annotation needs to be treated by some code.

You need to create a filter, that processes your custom annotation, before your method is called.

see : https://jersey.java.net/documentation/latest/filters-and-interceptors.html

Creating a filter, should be fairly easy, but it is not enough. It will get called, but won't know in what context it will be called. By context, I mean which class / method will be called right after the filter is executed. In this example I assumed your annotation (called MyCustomAnnotation) can be applied to class / method.

For this, you need to create a "Dynamic Feature" that will bind a different instance of the filter, for each possible context.

In details :

For a given JAX-RS class :

@MyCustomAnnotation(name="someVal")
class MyClass{

    @GET
    @MyCustomAnnotation(name="someConfig")
    public Object myMethod(){
    ...
    }

    @GET
    @MyCustomAnnotation(name="otherConfig")
    public Object myOtherMethod(){
    ...
    }

}

First, create your annotation (I guess you know, but just to be clear) :

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCustomAnnotation {

    String name() default "";

}

Then, create a filter. Notice the special constructor. A different instance of the filter will be created for each possible context. The right instance of the filter will be used in a specific context. This way it will know in what context (Class / Method) is is called. This way, using intro-spectation, your filter can behave however you like, based of the annotation you used on your target class and/or method :

@Priority(Priorities.AUTHORIZATION - 1)
public class MyFilter implements ContainerRequestFilter {

    private final Class<?> _class;
    private final Method method;
    private MyCustomAnnotation classAnnotation;
    private MyCustomAnnotation methodAnnotation;

    public MyFilter(Class<?> _class, Method method) {

        this._class = _class;
        this.method = method;

        this.classAnnotation = _class.getAnnotation(MyCustomAnnotation.class);
        this.methodAnnotation = method.getAnnotation(MyCustomAnnotation.class);

    }

    @Override
    public void filter(ContainerRequestContext requestContext) {

        // your code goes here!

        // based on classAnnotation and/or methodAnnotation, 
        // add whatever you want to the requestContext

    }
}

Ok, so now we have an annotation, a filter that process this annotation, now we need to bind dynamically to class / methods that are annotated

public class MyFilterDynamicFeature implements DynamicFeature {

    @Override
    public void configure(final ResourceInfo resourceInfo, final FeatureContext configuration) {

        //if the class or the method is annotated, bind a new instance of our filter to this method
        if(resourceInfo.getResourceClass().getAnnotation(MyCustomAnnotation.class)!=null || resourceInfo.getResourceMethod().getAnnotation(MyCustomAnnotation.class)!=null){            
            configuration.register(new MyFilter(resourceInfo.getResourceClass(), resourceInfo.getResourceMethod()));
        }

    }
}

In your JAX-RS configuration... register your new DynamicFeature

public class MyRestConfig extends ResourceConfig {

    public RestConfig() {

        // your configs...

        packages("com.yourpackage.rest");

        // ...

        // handle @MyCustomAnnotation annotations
        register(MyFilterDynamicFeature.class);

        // ...    
    }

}

I hope this is clear. Recap of what you need to do

  1. create your annotation
  2. annotate your JAX-RS class / method with your annotation
  3. create a filter that will process your annotation
  4. create a dynamic feature, that will bind a different instance of the filter for each different context (method / class combination, where at least one or the other is annotated with your annotation)
  5. register the dynamic feature in your rest config

----Update--------

Rather than using the Dynamic Feature, you should be able to inject the ressource info at runtime

@Context
private ResourceInfo resourceInfo;
Filip
  • 906
  • 3
  • 11
  • 33
  • Thanks for the solution. I have still two questions. I was able to write into ContainerRequest by getting it from ContainerRequestContext and using the setProperty method, however I cannot read that data from javax.ws.rs.core.Request in my ExceptionMapper because it is wrapped in the com.sun.Proxy and cannot be casted to ContainerRequest. I was unable to find a workaround and couldn't get the ContainerRequest injected in the ExceptionMapper. – LAC Nov 12 '15 at 19:13
  • And I assume that by using the ResourceInfo you mean just throwing away both the filter and the DynamicFeature and using it in my ExceptionMapper. If this is correct.. I don't know why the ResourceInfo is always null in my ExceptionMapper. The only thing that I can get injected is the javax.ws.rs.core.Request. (I am using JerseyTest) – LAC Nov 12 '15 at 19:15
  • No, just the dynamicFeature... (inject resourceInfo as a class member) in the filter itself – Filip Nov 12 '15 at 22:59
  • I don't really see how the exception mapper relates to processing your annotation, maybe consider opening a new question. – Filip Nov 12 '15 at 23:03
  • Why would you want to write to the request anyways in an exceptionMapper? The request processing flow was interrupted by a un-catched runtimeException... For all I know ExceptionMapper are used to generate proper error responses, if the response as not been commited yet. – Filip Nov 12 '15 at 23:11
  • The whole point of the annotation for me is to read the data of the annotated class/method later in the ExceptionMapper. I think you misread my last comment. I'm writing into request in the filter and reading from request in the ExceptionMapper. I still should find the proper way for reading this data from the Request in the ExceptionMapper to write it back to the response...Thanks anyway for the response. – LAC Nov 12 '15 at 23:47
  • then.. yes... maybe you don't need the filter after all if you can inject resourceInfo in your ExceptionMapper. Sorry i didn't understand what your were doing with the annotation. – Filip Nov 13 '15 at 02:07