31

I'm just getting acquainted with implementing REST web services in Java using JAX-RS and I ran into the following problem. One of my resource classes requires access to a storage backend, which is abstracted away behind a StorageEngine interface. I would like to inject the current StorageEngine instance into the resource class serving the REST requests and I thought a nice way of doing this would be by using the @Context annotation and an appropriate ContextResolver class. This is what I have so far:

In MyResource.java:

class MyResource {
    @Context StorageEngine storage;
    [...]
}

In StorageEngineProvider.java:

@Provider
class StorageEngineProvider implements ContextResolver<StorageEngine> {
    private StorageEngine storage = new InMemoryStorageEngine();

    public StorageEngine getContext(Class<?> type) {
        if (type.equals(StorageEngine.class))
            return storage;
        return null;
    }
}

I'm using com.sun.jersey.api.core.PackagesResourceConfig to discover the providers and the resource classes automatically, and according to the logs, it picks up the StorageEngineProvider class nicely (timestamps and unnecessary stuff left out intentionally):

INFO: Root resource classes found:
    class MyResource
INFO: Provider classes found:
    class StorageEngineProvider

However, the value of storage in my resource class is always null - neither the constructor of StorageEngineProvider nor its getContext method is called by Jersey, ever. What am I doing wrong here?

Tamás
  • 47,239
  • 12
  • 105
  • 124

5 Answers5

20

I don't think there's a JAX-RS specific way to do what you want. The closest would be to do:

@Path("/something/")
class MyResource {
    @Context
    javax.ws.rs.ext.Providers providers;

    @GET
    public Response get() {
        ContextResolver<StorageEngine> resolver = providers.getContextResolver(StorageEngine.class, MediaType.WILDCARD_TYPE);
        StorageEngine engine = resolver.get(StorageEngine.class);
        ...
    }
}

However, I think the @javax.ws.rs.core.Context annotation and javax.ws.rs.ext.ContextResolver is really for types related to JAX-RS and supporting JAX-RS providers.

You may want to look for Java Context and Dependency Injection (JSR-299) implementations (which should be available in Java EE 6) or other dependency injection frameworks such as Google Guice to help you here.

Bryant Luk
  • 2,448
  • 21
  • 22
  • 2
    FWIW you can also do the slightly shorter: @Context ContextResolver storageEngineResolver; – Doug Moscrop Aug 11 '11 at 03:17
  • How ridiculous that it's so difficult to do something as sane as provide alternate backing implementations in test and production contexts! At least this works without having to drag in some other framework. – Ian Will Mar 07 '14 at 04:20
  • 1
    Doug's solution (FROM COMMENTS) did not work for me (with RestEasy). – V G Mar 12 '14 at 16:48
15

Implement a InjectableProvider. Most likely by extending PerRequestTypeInjectableProvider or SingletonTypeInjectableProvider.

@Provider
public class StorageEngineResolver extends SingletonTypeInjectableProvider<Context, StorageEngine>{
    public MyContextResolver() {
        super(StorageEngine.class, new InMemoryStorageEngine());
    }
}

Would let you have:

@Context StorageEngine storage;
Chase
  • 3,123
  • 1
  • 30
  • 35
  • 2
    Jersey-specific, but a good suggestion. CXF does something similar with a [`ContextProvider`](http://sberyozkin.blogspot.co.uk/2012/03/custom-jax-rs-contexts-in-cxf-260.html). – Donal Fellows Dec 28 '13 at 21:41
2

I found another way. In my case i want to provide the user currently logged in as a User entity from my persitence layer. This is the class:

@RequestScoped
@Provider
public class CurrentUserProducer implements Serializable, ContextResolver<User> {

    /**
     * Default
     */
    private static final long serialVersionUID = 1L;


    @Context
    private SecurityContext secContext;

    @Inject
    private UserUtil userUtil;

    /**
     * Tries to find logged in user in user db (by name) and returns it. If not
     * found a new user with role {@link UserRole#USER} is created.
     * 
     * @return found user or a new user with role user
     */
    @Produces
    @CurrentUser
    public User getCurrentUser() {
        if (secContext == null) {
            throw new IllegalStateException("Can't inject security context - security context is null.");
        }
        return userUtil.getCreateUser(secContext.getUserPrincipal().getName(),
                                      secContext.isUserInRole(UserRole.ADMIN.name()));
    }

    @Override
    public User getContext(Class<?> type) {
        if (type.equals(User.class)) {
            return getCurrentUser();
        }
        return null;
    }

}

I only used implements ContextResolver<User> and @Provider to get this class discovered by Jax-Rs and get SecurityContext injected. To get the current user i use CDI with my Qualifier @CurrentUser. So on every place where i need the current user i type:

@Inject
@CurrentUser
private User user;

And indeed

@Context
private User user;

does not work (user is null).

dermoritz
  • 12,519
  • 25
  • 97
  • 185
1

If anyone is using Resteasy this is what worked for me.

If you add something like this:

ResteasyContext.pushContext(StorageEngine.class, new StorageEngine());

into something like a jaxrs filter, it allows you to do something like this:

@GET
@Path("/some/path")
public Response someMethod(@Context StorageEngine myStorageEngine) {
 ...
}

This is specific to Resteasy, which doesn't have something like SingletonTypeInjectableProvider.

jsolum
  • 196
  • 2
  • 15
0

A pattern that works for me: Add some fields on your Application subclass that provide the objects you need to inject. Then use an abstract base class to do the "injection":

public abstract class ServiceBase {

    protected Database database;

    @Context
    public void setApplication(Application app) {
        YourApplication application = (YourApplication) app;
        database = application.getDatabase();
    }
}

All your services that need to access the database may now extend ServiceBase and have the database available automatically via the protected field (or a getter, if you prefer that).

This works for me with Undertow and Resteasy. In theory this should work across all JAX-RS implementations since injection of the Application is supported by the standard AFAICS, but I haven't tested it in other settings.

For me, the advantage over Bryant's solution was that I don't have to write some resolver class just so I can get at my application-scoped singletons like the database.

Fabian Streitel
  • 2,702
  • 26
  • 36