How to Inject an Object into Jersey Request Context

How to inject an object into jersey request context?

You could just use ContainterRequestContext.setProperty(String, Object). Then just inject the ContainerRequestContext

@Override
public void filter(ContainerRequestContext crc) throws IOException {
MyObject obj = new MyObject();
crc.setProperty("myObject", myObject);
}

@POST
public Response getResponse(@Context ContainerRequestContext crc) {
return Response.ok(crc.getProperty("myObject")).build();
}

Another option to inject the MyObject directly is to use the HK2 functionality Jersey 2 offers.

Create a factory the inject the ContainerRequestContext and return the MyObject. For example

import javax.inject.Inject;
import javax.ws.rs.container.ContainerRequestContext;
import jetty.plugin.test.domain.MyObject;
import org.glassfish.hk2.api.Factory;

public class MyObjectFactory implements Factory<MyObject> {

private final ContainerRequestContext context;

@Inject
public MyObjectFactory(ContainerRequestContext context) {
this.context = context;
}

@Override
public MyObject provide() {
return (MyObject)context.getProperty("myObject");
}

@Override
public void dispose(MyObject t) {}
}

You then need to bind the factory:

public class InjectApplication extends ResourceConfig {

public InjectApplication() {
...
register(new AbstractBinder(){
@Override
protected void configure() {
bindFactory(MyObjectFactory.class)
.to(MyObject.class)
.in(RequestScoped.class);
}
});
}
}

With the same setting of the property as in the filter example above, you can then just inject the MyObject with the @Context

@GET
public Response getTest(@Context MyObject myObject) {
return Response.ok(myObject.getMessage()).build();
}


  • See more at Custom Injection


UPDATE

Please see this question for a problem with this implementation.

See Also:

  • If you're using web.xml instead of a ResourceConfig

Spring Jersey to inject ContainerRequestContext on request scoped class

I found a workaround. I could inject the Spring HttpServletRequest into my AuthorizationFilter and set the SessionContext on this one.

@Autowired
private HttpServletRequest request;

....

request.setAttribute("mySessionContext", sessionContext);

And then because HttpServletRequest is known to spring and besically represents the same thing in my SessionContextProvider I do the same:

@Autowired
private HttpServletRequest request;

@Override
public SessionContext getSessionContext() {
return (SessionContext) request.getAttribute("mySessionContext");
}

I dont think this is the best solution possible. But it works. Ill wait for peeskillet for any other input if there is a better solution.

Regards
Jonas

jersey 2 context injection based upon HttpRequest without singleton

So when you instantiate the resource to make it a singleton, Jersey tries to do all the injection on startup. This means that attempt to access any object that is inherently request scoped, will fail... UNLESS... the object is proxiable.

Some objects are made proxiable by Jersey, and this is by design, and by specification. For example HttpHeaders, UriInfo, SecurityContext, and a few others listed here. Though HttpServletRequest is not listed, it is also one of the objects that are proxiable.

What it means to be proxiable is that instead of injecting the actual object (which doesn't exist until there is request), a proxy is injected. When calls are made on the proxy, they get forwarded to the actual object that is available in the current request. You can try to print/log the class of the HttpServletRequest and you will see that the class is actually com.sun.proxy.ProxyX instead of HttpServletRequestSomeImpl. This is the magic of Java's dynamic proxies at work.

The current problem you are facing is the injection of Datastore. It is inherently request scoped because it's creation in dependent on request context information, i.e. the headers. So during injection, it fails on this call to obtain the ContainerRequest inside your factory

ContainerRequest request = getContainerRequest();

The error message being "Not inside a request scope", which makes perfect sense as there is no request when we try to obtain it.

So how can we fix this? Well we need to make the Datastore proxiable.
Generally, the way you can do this is by configuring it during binding declaration, for example

bindFactory(...).proxy(true).proxyForSameScope(false).to(...);

The proxy(true) method makes it proxiable, and the proxyForSameScope(false) says that if we are trying to inject into the same scope, it should not be a proxy, but the actual instance.

One problem with your current configuration is that you are binding the factory to the factory

bind(TenantDatastoreFactory.class)
.to(TenantDatastoreFactory.class)
.in(Singleton.class);

This makes sense for your current implementation, as you are trying to inject the factory into the TenantDatastoreFactoryProvider. But what we actually need to make the proxying work, is for the factory to be binded to the actual Datastore:

bindFactory(TenantDatastoreFactory.class)
.proxy(true)
.proxyForSameScope(false)
.to(Datastore.class)
.in(RequestScoped.class);

So now we have taken out the binding of the factory, we can't inject it. So we just have to problem of returning a Factory from the createValueFactory method. We don't want to just return TenantDatastoreFactory instance because we'll still face the same problem where the provide method is called to get the Datastore. To get around this, we can do the following

@Override
protected Factory<?> createValueFactory(Parameter parameter) {
Class<?> paramType = parameter.getRawType();
TenantDatastore annotation = parameter.getAnnotation(TenantDatastore.class);
if (annotation != null && paramType.isAssignableFrom(Datastore.class)) {
return getFactory();
}
return null;
}

private Factory<Object> getFactory() {
return new Factory<Object>() {

@Context
Datastore datastore;

@Override
public Object provide() {
return datastore;
}

@Override
public void dispose(Object t) {}
};
}

So we are creating a Factory dynamically, where we inject the proxied Datastore. Now when Jersey tries to inject the resource class, it will inject the proxy, and the provide method is never called on start up. It is only called when we try o actually use the Datastore during the request.

It may seem redundant, that we have both the TenantDatastoreFactory and the anonymous Factory created as runtime. But this is necessary to make the Datastore proxiable and make sure the provide() method is never called on startup.

Another note, is that if you don't require parameter injection, you could've simplified the implementation by taking out the TenantDatastoreFactoryProvider. This is only required for parameter injection. All we need is the InjectionResolver to handle the custom annotation, and the factory to create the Datastore. The InjectionResolver implementation would need to be changed as follows

public class TenantDatastoreInjectionResolver 
implements InjectionResolver<TenantDatastore> {

@Inject
@Named(InjectionResolver.SYSTEM_RESOLVER_NAME)
InjectionResolver<Inject> systemInjectionResolver;

@Override
public Object resolve(Injectee injectee, ServiceHandle<?> handle) {
if (Datastore.class == injectee.getRequiredType()) {
return systemInjectionResolver.resolve(injectee, handle);
}
return null;
}

@Override
public boolean isConstructorParameterIndicator() { return false; }
@Override
public boolean isMethodParameterIndicator() { return false; }
}

Then in the binder, just take out the TenantDatastoreFactoryProvider

@Override
public void configure() {
bindFactory(TenantDatastoreFactory.class)
.proxy(true)
.proxyForSameScope(false)
.to(Datastore.class)
.in(RequestScoped.class);
bind(TenantDatastoreInjectionResolver.class)
.to(new TypeLiteral<InjectionResolver<TenantDatastore>>() {
})
.in(Singleton.class);
}

Again this is only if you don't require parameter injection.

See Also

  • Injecting Request Scoped Objects into Singleton Scoped Object with HK2 and Jersey

How to bind custom context to Jersey request

UPDATE now that Jersey 2.7 is out, the solution is simpler.

It sounds like you want a RequestScoped binding. Here is how you might set things up with Jersey 2.7:

You will need a request filter, and because RestContext will be RequestScoped, you can inject a provider into your filter, set some properties on it, and know they will be available for the remainder of the request.

@Priority(Priorities.AUTHORIZATION) // filter deals with roles, comes after AUTHENTICATION
public class RestContextFilter implements ContainerRequestFilter
{
// you need to inject a provider, rather than the class directly
// because this filter is instantiated before the request scope is ready
private Provider<RestContext> rcProvider;

@Inject
public RestContextFilter(Provider<RestContext> rcProvider)
{
this.rcProvider = rcProvider;
}

@Override
public void filter(ContainerRequestContext requestContext) throws IOException
{
// now you're in a request scope and can get your context
RestContext rc = rcProvider.get();

// set some properties on rc here (current user or roles or whatever)
}
}

You need to register the filter and bind RestContext in your ResourceConfig using an HK2 binding:

public class MyResourceConfig extends ResourceConfig
{
public MyResourceConfig()
{
register(RestContextFilter.class);

register(new AbstractBinder()
{
@Override
protected void configure()
{
bindFactory(new Factory<RestContext>()
{
@Override
public RestContext provide()
{
return new RestContext();
}

// this will get called at the end of the request
// allowing you to close your request scoped object
@Override
public void dispose(RestContext instance)
{
instance.close();
}
}, RequestScoped.class).to(RestContext.class).in(RequestScoped.class);
}
});
}
}

Then you can inject RestContext in your resources, and it will have all of the info set up by your filter. For example:

@Path("/my/path")
public class MyResource
{
private RestContext rc;

@Inject
public MyResource(RestContext rc)
{
this.rc = rc;
}

@POST
@Consumes(MediaType.APPLICATION_JSON)
public void upload(MyPostObj data)
{
// do stuff here, and rc is all set up
}
}

I tend to prefer constructor injection over field injection because I think it makes your dependencies more obvious for other developers reading your code. Also sorry if my style of braces annoys you, I come from the wonderful world of .NET :).

Jersey 2: filters and @Context injections

Yes it's safe. To understand the problem, you should understand how scopes work. In any framework that deals with scopes (and injection), the feature is implemented similarly. If an object is in a singleton scope and another object in a lesser scope needs to be injected, usually a proxy of the object will be injected instead. When a call is made on the object, it's actually a call on the proxy.

Though the spec may not mention the HttpServletRequest specifically, most JAX-RS implementation have support for this. With Jersey in particular, if this was not possible (meaning the object is not proxiable), then you would get an error message on startup with something like "not within a request scope". The reason is that the ContainerRequestFilter is created on app startup, and all the injections are handled at that time also. If the HttpServletRequest was not proxiable, it would fail to inject because on startup, there is no request scope context.

To confirm that it is not the actual HttpServletRequest and is a proxy, you can log the request.getClass(), and you will see that it is indeed a proxy.

If you are unfamiliar with this pattern, you can see this answer for an idea of how it works.

See Also:

  • Injecting Request Scoped Objects into Singleton Scoped Object with HK2 and Jersey


Related Topics



Leave a reply



Submit