
Question:
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?
Solution:1
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.
Solution:2
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;
Solution:3
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).
Solution:4
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.
Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
EmoticonEmoticon