Jersey offers two classes to interact with annotations on resources:
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;
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With