Jersey 2.X Custom Injection Annotation with Attributes

Jersey 2.x Custom Injection Annotation With Attributes

Yeah Jersey made the creation of custom injections a bit more complicated in 2.x. There are a few main components to custom injection you need to know about with Jersey 2.x

  • org.glassfish.hk2.api.Factory - Creates injectable objects/services
  • org.glassfish.hk2.api.InjectionResolver - Used to create injection points for your own annotations.
  • org.glassfish.jersey.server.spi.internal.ValueFactoryProvider - To provide parameter value injections.

You can read more about custom injection in Custom Injection and Lifecycle Management. One shortcoming of the documentation is the lack of explanation of how to inject parameter values. You could get away with simply implementing the InjectResolver, and you would be able to inject into fields with your custom annotation, but in order to inject into method parameters, we need to ValueFactoryProvider.

Luckily there are some abstract classes we can extend (which the documentation also fails to mention) that will make life a little easier. I has to scour the source code of the org.glassfish.jersey.server.internal.inject package for a bit to try and figure it all out.

Here's a full example to help get you started.

Token (injectable object)

public class Token {
private final String token;
public Token(String token) { this.token = token; }
public String getToken() { return token; }
}

@TokenParam (our injection annotation)

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.FIELD})
public @interface TokenParam {
boolean someAttribute() default true;
}

TokenFactory (implements Factory per the first bullet point, but we just extend the AbstractContainerRequestValueFactory. There we'll have access to the ContainerRequestContext. Note, that all these HK2 components, we can inject other dependencies into them, for example the TokenAuthenticator, which we will bind to HK2 later.

import javax.inject.Inject;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory;

public class TokenFactory extends AbstractContainerRequestValueFactory<Token> {

private final TokenAuthenticator tokenAuthenticator;

@Inject
public TokenFactory(TokenAuthenticator tokenAuthenticator) {
this.tokenAuthenticator = tokenAuthenticator;
}

@Override
public Token provide() {
String auth = getContainerRequest().getHeaderString(HttpHeaders.AUTHORIZATION);
try {
if (tokenAuthenticator.authenticate(auth).get() == null) {
throw new WebApplicationException(Response.Status.FORBIDDEN);
}
} catch (AuthenticationException ex) {
Logger.getLogger(TokenFactory.class.getName()).log(Level.SEVERE, null, ex);
}

return new Token("New Token");
}
}

TokenParamInjectionResolver (implements the InjectResolver per bullet point two. I simply extend ParamInjectionResolver. If your interested in what's going on under the hood, you can find the class in the source code I linked to)

import org.glassfish.jersey.server.internal.inject.ParamInjectionResolver;

public class TokenParamInjectionResolver extends ParamInjectionResolver {
public TokenParamInjectionResolver() {
super(TokenFactoryProvider.class);
}
}

TokenFactoryProvider (implements the ValueFactoryProvider per the third bullet point. I simply extend AbstractValueFactoryProvider. Again, you can look at the source for the under the hood details)

import javax.inject.Inject;
import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.jersey.server.internal.inject.AbstractValueFactoryProvider;
import org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractorProvider;
import org.glassfish.jersey.server.model.Parameter;

public class TokenFactoryProvider extends AbstractValueFactoryProvider {

private final TokenFactory tokenFactory;

@Inject
public TokenFactoryProvider(
final MultivaluedParameterExtractorProvider extractorProvider,
ServiceLocator locator,
TokenFactory tokenFactory) {

super(extractorProvider, locator, Parameter.Source.UNKNOWN);
this.tokenFactory = tokenFactory;
}

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

TokenFeature (Here we bind all the components seen above, even the TokenAuthentictor, which I have left out, but if your usual Dropwizard Authenticator. I also made use of a Feature. I tend to do this to wrap components of a custom feature. This is also where you can decide all the scoping. Just note some components are required to be in Singleton scope)

import javax.inject.Singleton;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.TypeLiteral;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.server.spi.internal.ValueFactoryProvider;

public class TokenFeature implements Feature {

@Override
public boolean configure(FeatureContext context) {
context.register(new AbstractBinder(){
@Override
public void configure() {
bind(TokenAuthenticator.class)
.to(TokenAuthenticator.class)
.in(Singleton.class);
bind(TokenFactory.class).to(TokenFactory.class)
.in(Singleton.class);
bind(TokenFactoryProvider.class)
.to(ValueFactoryProvider.class)
.in(Singleton.class);
bind(TokenParamInjectionResolver.class)
.to(new TypeLiteral<InjectionResolver<TokenParam>>(){})
.in(Singleton.class);
}
});
return true;
}
}

And finally simply register the feature

register(TokenFeature.class);

Now you should be able to inject the Token with @TokenParam, as well as your usual entity bodies (which would not be possible if we didn't implement the ValueFactoryProvider

@POST
@Consumes(MediaType.APPLICATION_JSON)
public String postToken(@TokenParam Token token, User user) {

}


UPDATE

It's kind of a half-@$$ example for your particular use case. A better approach would probably have a clone method in your Factory class and create a new TokenFactory with some parameters (maybe that you get from your annotation. For example, in the TokenFactory` you can have something like

public class TokenFactory extends AbstractContainerRequestValueFactory<Token> {

public TokenFactory clone(boolean someAttribute) {
return new TokenFactory(authenticator, someAttribute);
}

In the TokenFactoryProvider ine createValueFactory method, you then call the clone method

TokenParam annotation = parameter.getAnnotation(TokenParam.class);

if (annotation != null && paramType.isAssignableFrom(Token.class)) {
return tokenFactory.clone(annotation.someAttribute());
}

Or you could actually create the factory inside the method. you have options.

UPDATE 2

See Also

  • jersey 2 context injection based upon HttpRequest without singleton

UPDATE 3

Starting Jersey 2.26, the dependency injection has changed. You will want to look at this post for example of how the code has changed in implementing this same injection.

Jersey 2.26: Defining Custom Injection Annotation

Thanks to Paul's comment about using GenericType, here is one solution that works for me on Jersey 2.26 again. It's using the org.glassfish.hk2.api.* classes.

AbstractBinder

.... 

@Override
protected void configure() {
/*
Adds binding for @CurrentUser.
By default, factories are being injected with PerLookup scope.
*/
bindFactory(CurrentUserSupplier.class)
.to(User.class)
.proxy(true)
.proxyForSameScope(false)
.in(RequestScoped.class);
bind(CurrentUserResolver.class)
.to(new GenericType<InjectionResolver<CurrentUser>>(){})
.in(Singleton.class);
}
....

CurrentUserSupplier

public class CurrentUserSupplier implements Supplier<User> {

// inject what is required

@Override
public User get() {
// do what is necessary to obtain User
// and return it
}

}

CurrentUserResolver

import org.glassfish.hk2.api.Injectee;
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.ServiceHandle;

public class CurrentUserResolver implements InjectionResolver<CurrentUser> {

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

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

return null;
}

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

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

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

Jersey and HK2 - Injecting current user

What you're looking for is not trivially done. One way you could handle this is setting the SecurityContext inside the ContainerRequestFilter, as seen here. This doesn't involve any direct interaction with HK2. You could then inject the SecurityContext in your resource class. And get the user by

securityContext.getUserPrincipal().getName();

If you really want to go with injecting the username with a custom annotation, you will need to create a InjectionResolver (See Defining Custom Injection Annotation. You could inject ContainerRequestContext (the same one passed to the filter method in the ContainerRequestFilter) or the SecurityContext into the InjectionResolver. For example

Filter

@Provider
@PreMatching
public class UserFilter implements ContainerRequestFilter {

public static final String USER_PROP = "user";

@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
requestContext.setProperty(USER_PROP, new User("peeskillet"));
}
}

Annotation

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface CurrentUser {
}

InjectionResolver

public class CurrentUserInjectionResolver implements InjectionResolver<CurrentUser> {

javax.inject.Provider<ContainerRequestContext> requestContext;

@Inject
public CurrentUserInjectionResolver(
javax.inject.Provider<ContainerRequestContext> requestContext) {
this.requestContext = requestContext;
}

@Override
public Object resolve(Injectee injectee, ServiceHandle<?> sh) {
if (User.class == injectee.getRequiredType()) {
return requestContext.get().getProperty(UserFilter.USER_PROP);
}
return null;
}

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

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

Bind the InjectionResolver

@Provider
public class UserFeature implements Feature {

@Override
public boolean configure(FeatureContext context) {
context.register(new AbstractBinder(){
@Override
public void configure() {

bind(CurrentUserInjectionResolver.class)
.to(new TypeLiteral<InjectionResolver<CurrentUser>>(){})
.in(Singleton.class);
}
});
return true;
}
}

Resource

@Path("user")
public class UserResource {

@CurrentUser
private User user;

@GET
public Response getCurrentUser() {
return Response.ok(user.getUsername()).build();
}
}

Now I'm not quite sure about this second approach, at least the part about the filter being a @PreMatching filter. If I don't make it a pre-matching, the User will be null. It seems the ContainerRequestContext does not yet have the property we set, meaning what appears to be happening is the the the InjectResolver is being called before the filter. I will need to look into this. Making it a pre-matching, IMO should not be required.

Personally though, I would go with the first approach, just using the SecurityContext. A full example is in the link I provided above. With this approach, you can take advantage of Jersey's RolesAllowedDynamicFeature if needed.

Dependency injection with Jersey 2.0

You need to define an AbstractBinder and register it in your JAX-RS application. The binder specifies how the dependency injection should create your classes.

public class MyApplicationBinder extends AbstractBinder {
@Override
protected void configure() {
bind(MyService.class).to(MyService.class);
}
}

When @Inject is detected on a parameter or field of type MyService.class it is instantiated using the class MyService. To use this binder, it need to be registered with the JAX-RS application. In your web.xml, define a JAX-RS application like this:

<servlet>
<servlet-name>MyApplication</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.mypackage.MyApplication</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>MyApplication</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>

Implement the MyApplication class (specified above in the init-param).

public class MyApplication extends ResourceConfig {
public MyApplication() {
register(new MyApplicationBinder());
packages(true, "com.mypackage.rest");
}
}

The binder specifying dependency injection is registered in the constructor of the class, and we also tell the application where to find the REST resources (in your case, MyResource) using the packages() method call.



Related Topics



Leave a reply



Submit