Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I get resource annotations in a Jersey ContainerResponseFilter

Jersey offers two classes to interact with annotations on resources:

  • ResourceFilterFactory, one class can inherit it to be triggered one time when the application starts
  • ContainerRequestFilter, ContainerResponseFilter, several classes can inherit them to be triggered on every request/response

ResourceFilterFactory defines a create method (to implement) that take an AbstractMethod which gives access to methods and classes annotations.

ContainerRequestFilter and ContainerResponseFilter defines a filter method (to implement) that take request/response but those give solely access to the called method annotation, not the class one.

I'm trying to implement a CacheControl annotation that defines HTTP cache headers the following way.

@Path("/path")
@CacheControl(maxAge = 172800)
public class Resource
{   
    @GET
    @Path("/{id}")
    @CacheControl(mustRevalidate = true)
    public Response get(@PathParam("id") Long id)
    {
        ...
    }
}

My problem is that I don't want to create a new CacheControlFilter for every REST method defined in my application.

public class FilterFactory implements ResourceFilterFactory
{    
    @Override
    public List<ResourceFilter> create(AbstractMethod method)
    {
        List<ResourceFilter> filters = newArrayList();
        if (isAnnotationPresent(method, CacheControl.class))
            filters.add(new CacheControlFilter(method));
        return filters;
    }

    private boolean isAnnotationPresent(AbstractMethod method, Class<? extends Annotation> clazz)
    {
        return method.isAnnotationPresent(clazz) || method.getResource().isAnnotationPresent(clazz);
    }
}

Is there a way to access the AbstractMethod without instancing a CacheContronlFilter for every REST method?

public class CacheControlFilter implements ResourceFilter, ContainerResponseFilter
{
    private AbstractMethod method;

    public CacheControlFilter(AbstractMethod method)
    {
        this.method = method;
    }

    @Override
    public ContainerResponse filter(ContainerRequest request, ContainerResponse response)
    {
        putCacheControlIfExists(response, method.getAnnotations());
        putCacheControlIfExists(response, method.getResource().getAnnotations());
        return response;
    }

    private void putCacheControlIfExists(ContainerResponse response, Annotation[] annotations)
    {
        CacheControl annotation = findCacheControl(annotations);
        if (annotation != null)
            response.getHttpHeaders().put(CACHE_CONTROL, createCacheControlHeader(annotation));
    }

    private CacheControl findCacheControl(Annotation[] annotations)
    {
        for (Annotation annotation : annotations)
            if (annotation instanceof CacheControl)
                return (CacheControl) annotation;
        return null;
    }

    private List<Object> createCacheControlHeader(CacheControl annotation)
    {
        javax.ws.rs.core.CacheControl header = new javax.ws.rs.core.CacheControl();
        header.setMaxAge(annotation.maxAge());
        header.setMustRevalidate(annotation.mustRevalidate());
        header.setNoCache(annotation.noCache());
        header.setNoStore(annotation.noStore());
        header.setNoTransform(annotation.noTransform());
        header.setProxyRevalidate(annotation.proxyRevalidate());
        return Lists.<Object> newArrayList(Splitter.on(',').split(header.toString()));
    }

    @Override
    public ContainerRequestFilter getRequestFilter()
    {
        return null;
    }

    @Override
    public ContainerResponseFilter getResponseFilter()
    {
        return this;
    }
}
like image 290
yves amsellem Avatar asked Nov 29 '11 17:11

yves amsellem


1 Answers

Why is it important to not have a separate instance of the filter for every applicable method? There may be a lot of concurrent access, so if you don't want these to be separate instances, they would have to be mutable and you would have to get into the threadlocals mess (to store the abstract method currently applicable for the given thread). Not sure if that's what you really want. Having a separate object for each is not that expensive.

UPDATE: Also note, you don't want to create a new instance for every method. You just want to do it for methods with any @CacheControl annotation attached to them or to their resources, right? Also you can share filter instances for common @CacheControl values - i.e. if a method uses the same cache control setting as some other method, reuse the same filter for that, if not, create a separate instance of the filter for that method. In other words - you can have one filter per one distinct cache-control setting as opposed to one filter per method - as you don't really care about the method - you care about the annotations attached to it.

like image 111
Martin Matula Avatar answered Nov 20 '22 03:11

Martin Matula