4

I have a RESTful server implementation as well as a library for clients to make the calls, all using JAX-RS. The server components are divided up into interface FooResource and implementation FooResourceService.

In order for the client and server libraries to share RESTful path and other definitions, I wanted to split out the FooResource interface into its own project:

@Path(value = "foo")
public interface FooResource {

  @GET
  public Bar getBar(@PathParam(value = "{id}") int id) {

I want to set some headers in the response. One easy way to do this is to use @Context HttpServletResponse in the method signature:

  public Bar getBar(@PathParam(value = "{id}") int id, @Context HttpServletResponse servletResponse) {

But the problem is that this exposes implementation details in the interface. More specifically, it suddenly requires my REST definition project (which is shared between the client and server library) to pull in the javax.servlet-api dependency---something the client has no need up (or desire for).

How can my RESTful resource service implementation set HTTP response headers without pulling in that dependency in the resource interface?

I saw one post recommending I inject the HttpServletResponse as a class member. But how would this work if my resource service implementation is a singleton? Does it use some sort of proxy with thread locals or something that figures out the correct servlet response even though the singleton class is used simultaneously by multiple threads? Are there any other solutions?

Community
  • 1
  • 1
Garret Wilson
  • 18,219
  • 30
  • 144
  • 272

3 Answers3

5

The correct answer seems to be to inject an HttpServletResponse in the member variable of the implementation, as I noted that another post had indicated.

@Context  //injected response proxy supporting multiple threads
private HttpServletResponse servletResponse;

Even though peeskillet indicated that the semi-official list for Jersey doesn't list HttpServletResponse as one of the proxy-able types, when I traced through the code at least RESTEasy seems to be creating a proxy (org.jboss.resteasy.core.ContextParameterInjector$GenericDelegatingProxy@xxxxxxxx). So as far as I can tell, thread-safe injection of a singleton member variable seems to be occurring.

See also https://stackoverflow.com/a/10076327/421049 .

Garret Wilson
  • 18,219
  • 30
  • 144
  • 272
2

So injecting HttpServletResponse seems like a no go. Only certain proxy-able types are inject-able into singletons. I believe the complete list is as follows:

HttpHeaders, Request, UriInfo, SecurityContext

This is somewhat pointed out in the JAX-RS spec, but is explained more clearly in the Jersey reference guide

The exception exists for specific request objects which can injected even into constructor or class fields. For these objects the runtime will inject proxies which are able to simultaneously server more request. These request objects are HttpHeaders, Request, UriInfo, SecurityContext. These proxies can be injected using the @Context annotation.

SecurityContext may be Jersey specific, as it's not stated in the spec, but I'm not sure.

Now those types mentioned above don't really do much for you because they are all request contexts and nothing to set the response.

One Idea though is to use a javax.ws.rs.container.ContainerResponseFilter, along with the HttpHeaders to set a temporary request header. You can access that header through the ContainerRequestContext passed to the filter method. Then just set the response header through the ContainerResponseContext, also passed to the filter method. If the the header is not specific to the context of that resource method, then it's even easier. Just set the header in the filter.

But let's say the header is dependent on the execution of the resource method. Then you could do something like

@Singleton
@Path("/singleton")
public class SingletonResource {

    @Context
    javax.ws.rs.core.HttpHeaders headers;

    @GET
    public String getHello() {

        String result = resultFromSomeCondition(new Object());
        headers.getRequestHeaders().putSingle("X-HELLO", result);
        return "Hello World";
    }

    private String resultFromSomeCondition(Object condition) {
        return "World";
    }
}

Then the ContainerResponseFilter might look something like this

@Provider
public class SingletonContainerResponseFilter 
                            implements ContainerResponseFilter {

    @Override
    public void filter(ContainerRequestContext crc, 
            ContainerResponseContext crc1) throws IOException {
        String header = crc.getHeaderString("X-HELLO");
        crc1.getHeaders().putSingle("X-HELLO", "World");
    } 
}

And just so only the singleton classes run through this filter, we can simply use a @NameBinding annotation

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.ws.rs.NameBinding;

@NameBinding
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SingletonHeader {}

...

@SingletonHeader
public class SingletonResource {

...

@SingletonHeader
public class SingletonContainerResponseFilter 
                        implements ContainerResponseFilter {

This is the only way I can think to handle this situation.


Resources:

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • See my other [answer](http://stackoverflow.com/a/27648777/421049) here. I tried it and it seems to work with RESTEasy. – Garret Wilson Dec 25 '14 at 16:45
  • 1
    So it works when you do package scanning with `@Singleton` annotation, but when you register with `getSingletons()` in the `Application` class, that's when it fails (the NPE I was getting). In the latter case, with Jersey, I would just get an exception saying something like "Not within a Request scope". `@Singleton` still fails with Jersey also. Just some more FYIs for any others who come across this. – Paul Samsotha Dec 25 '14 at 17:28
  • Makes sense though, if the container is to handle the injection, why instantiating it in `getSingletons()` doesn't work. – Paul Samsotha Dec 25 '14 at 17:46
  • Strange... I'm using it in `getSingletons()` with no problem. In fact I'm even delegating to a `PicoContainer` to do the actual instantiation, so whatever wrapping must be happening after the instances are returned. – Garret Wilson Dec 25 '14 at 22:31
  • Yes, in RESTEasy at least what gets installed in the container is an actual `FooResourceService`, but its private member has been injected with a proxy. – Garret Wilson Dec 25 '14 at 22:37
-2
@Path("/foo")
public interface FooResource {

    @GET
    @Path("{id}")
    public Response getBar(@PathParam("id") int id) {
        Bar bar = new Bar();
        //Do some logic on bar
        return Response.ok().entity(bar).header("header-name", "header-value").build()
    }
}

Returns a JSON representation of the instance of bar with a status code 200 and header header-name with value header-value. It should look something along the lines of:

{
    "bar-field": "bar-field-value",
    "bar-field-2": "bar-field-2"
}
Garret Wilson
  • 18,219
  • 30
  • 144
  • 272
Eduardo
  • 6,900
  • 17
  • 77
  • 121
  • 2
    I don't want a `Response` return in the signature. I want a `Bar` return in the signature. – Garret Wilson Dec 25 '14 at 14:12
  • 2
    My browser is not doing software development. My developers are. I want to create a clean API that indicates semantically what is being returned. I want to relegate JAX-RS specific items to annotations and implementation code. – Garret Wilson Dec 25 '14 at 14:14