Custom Context Providers for CXF with the Context Annotation

Apache CXF is an open source web services framework for Java. We use it to generate the RESTful API that serves the data for our Companion mobile event apps.

More specifically we use the JAX-RS part of CXF. A typical very simple web service definition might look like this:

@GET
@Path("/exhibitors/{exhibitorId}")
public Response getExhibitor(@PathParam("exhibitorId") final Long exhibitorId) {
    ExhibitorEntity exhibitor = exhibitorDao.getById(exhibitorId);
    return Response.ok(exhibitor).build();
}

This service takes a bit of the incoming URL such as /exhibitors/23 and puts 23 into the exhibitorId parameter that is passed to the getExhibitor method. This method can now use that exhibitorId and get a real object and return it. Pretty simple.

Now lets get a bit more complicated and add some checking to make sure we return a nice 404 if no such exhibitor exists.

@GET
@Path("/{exhibitorId}")
public Response getExhibitor(@PathParam("exhibitorId") final Long exhibitorId) {
    final ExhibitorEntity exhibitor = exhibitorDao.getById(exhibitorId);
    if (exhibitor == null) {
        return Response.status(Status.NOT_FOUND).build();
    }
    return Response.ok(exhibitor).build();
}

Now some security checking...

@GET
@Path("/{exhibitorId}")
@PreAuthorize("@securityService.canAdmin(authentication, #exhibitorId)")
public Response getExhibitor(@PathParam("exhibitorId") final Long exhibitorId) {
    final ExhibitorEntity exhibitor = exhibitorDao.getById(exhibitorId);
    if (exhibitor == null) {
        return Response.status(Status.NOT_FOUND).build();
    }
    return Response.ok(exhibitor).build();
}

...oh and some conditional caching...

@GET
@Path("/{exhibitorId}")
@PreAuthorize("@securityService.canAdmin(authentication, #exhibitorId)")
public Response getExhibitor(@PathParam("exhibitorId") final Long exhibitorId) {
    final ExhibitorEntity exhibitor = exhibitorDao.getById(exhibitorId);
    if (exhibitor == null) {
        return Response.status(Status.NOT_FOUND).build();
    }
    ResponseBuilder response = Response.ok(exhibitor);
    if (exhibitor.isPublic()) {
        CacheControl cacheControl = new CacheControl();
        cacheControl.setNoCache(true);
        response.cacheControl(cacheControl);
    }
    return response.build();
}

Our code is all of a sudden completely full of cross cutting concerns around security and caching and not found checking. In our particular case, the security code that gets passed the "Long exhibitorId" has to itself then go and lookup the exhibitor out of the database, making essentially a duplicate database call...wasteful.

Imagine we had more of these services such as:

  • /exhibitors/23/categories
  • /exhibitors/23/products
  • /exhibitors/23/offers

You can bet that we'd have to put the same caching and 404 checking code in all of those too. What a pain.

Solution - @Context

CXF provides the ability to inject a set of standard objects called contexts into your web service methods and classes using the @Context annotation.

For instance, if you want access to the HTTP headers for a request, you can do this:

@GET
@Path("/{exhibitorId}")
public Response getExhibitor(@Context HttpHeaders headers, @PathParam("exhibitorId") final Long exhibitorId) {
    Map<String, Cookie> cookies = headers.getCookies();
    return Response.ok().build();
}

You can inject all sorts of useful classes such as:

  • UriInfo
  • SecurityContext
  • HttpHeaders
  • ContextResolver
  • HttpServletRequest
  • HttpServletResponse
  • ServletContext
  • ServletConfig

The best bit is that you can provide your own too. This is what we'll do to stop us having to continually look up our ExhibitorEntity.

We need a few parts to make this work:

@Service(value = "exhibitorContextProvider")
public final class ExhibitorContextProvider implements ContextProvider<ExhibitorEntity> {
    
    @Autowired
    private ExhibitorContext context;
    
    @SuppressWarnings("unused")
    private ExhibitorContextProvider() {
        super();
    }
    
    public ExhibitorContextProvider(final ExhibitorContext context) {
        super();
        this.context = context;
    }
    
    @Override
    public ExhibitorEntity createContext(final Message message) {
        return context.getExhibitor();
    }
}

The standard CXF context providers usually rely on something in the Message to get the actual context object, such as some piece of the request, such as the headers as we saw before. However, in our case, the Message does not contain what we need, so we @Autowire in a Spring managed object which we can get our ExhibitorEntity from.

public final class ExhibitorContextImpl implements ExhibitorContext {

    private static final ThreadLocal<ExhibitorEntity> CONTEXT = new ThreadLocal<ExhibitorEntity>();

    @Override
    public ExhibitorEntity getExhibitor() {
        return CONTEXT.get();
    }

    @Override
    public void setExhibitor(final ExhibitorEntity exhibitor) {
        CONTEXT.set(exhibitor);
    }
 
    @Override
    public void clear() {
        CONTEXT.set(null);
    }
}

This is where we really store our ExhibitorEntity. As we want to get the ExhibitorEntity just once per request, we will store it in a ThreadLocal.

Finally, we need to somehow put our ExhibitorEntity into this ExhibitorContextImpl class so that our custom provider ExhibitorContextProvider can get it our and put it into the CXF workflow with the @Context annotation.

For this we use a custom class that implements org.apache.cxf.jaxrs.ext.RequestHandler.

@Service(value = "exhibitorCxfRequestHandler")
public final class ExhibitorCxfRequestHandler implements RequestHandler {

    public static final String TEMPLATE_KEY = "jaxrs.template.parameters";
    public static final String PATH_KEY = "exhibitorId";
    
    @Autowired
    private ExhibitorDao exhibitorDao;

    @Autowired
    private ExhibitorContext exhibitorContext;

    public ExhibitorCxfRequestHandler(final ExhibitorDao exhibitorDao, final ExhibitorContext exhibitorContext) {
        super();
        this.exhibitorDao = exhibitorDao;
        this.exhibitorContext = exhibitorContext;
    }

    @SuppressWarnings("unused")
    private ExhibitorCxfRequestHandler() {
        super();
    }

    @SuppressWarnings("unchecked")
    @Override
    public Response handleRequest(final Message m, final ClassResourceInfo resourceClass) {
        final MetadataMap<String, String> metadataMap = (MetadataMap<String, String>) m.get(TEMPLATE_KEY);
        if (metadataMap == null) {
            return Response.status(Status.NOT_FOUND).build();
        }
        if (metadataMap.containsKey(PATH_KEY)) {
            final long exhibitorId = Long.parseLong(metadataMap.getFirst(PATH_KEY));
            final ExhibitorEntity exhibitorEntity = exhibitorDao.getById(exhibitorId);
            if (exhibitorEntity == null) {
                final ErrorsWrapper errorsWrapper = new ErrorsWrapper();
                errorsWrapper.getErrors().put("exhibitor", "Exhibitor not found");
                return Response.status(Status.NOT_FOUND).entity(errorsWrapper).build();
            }
            exhibitorContext.setExhibitor(exhibitorEntity);
        }
        return null;
    }
}

So what is happening here is that we are finding the value for the bit of the web service path defined by {exhibitorId} and using that to lookup our exhibitor. We then stash that away in our thread local context class.

<ref bean="exhibitorCxfRequestHandler" />
<ref bean="exhibitorContextProvider" />

We also have to wire our new custom classes into CXF. As shown above, we just need to put them into our JAXRS section in our Spring config.

Finally we can now get the ExhibitorEntity class in our web service with the @Context annotation.

@GET
@Path("/{exhibitorId}")
@PreAuthorize("@securityService.canAdmin(authentication, #exhibitor)")
public Response getExhibitor(@Context final ExhibitorEntity exhibitor) {
    return Response.ok(exhibitor).build();
}

Because we now fetch the ExhibitorEntity up front, it makes this class much simpler. We can also now pass the full exhibitor into our @PreAuthorize annotation so we don't have to look it up there as well.

I've not shown it, but we can now easily add an implementation of org.apache.cxf.jaxrs.ext.ResponseHandler to handle the caching by accessing the same @Context ExhibitorEntity.

Conclusion

This refactor has reduced the amount of code in our web services, and reduced the amount of duplicate code where we'd have to look up and check our ExhibitorEntity whenever we wanted access to it. It has also made our web service much easier to test as we need one less mock object to go and fetch the exhibitor. On top of this, our code is of course much faster now as we will be doing just one database lookup of ExhibitorEntity rather than one for each of the 404 check, security and caching.

This site uses cookies. Continue to use the site as normal if you are happy with this, or read more about cookies and how to manage them.

X