kwogger
kwogger

Reputation: 1422

Jersey: PerRequestTypeInjectableProvider not picked up

Using Jersey 1.17.1, I have implemented a basic RESTful API and deployed it as a servlet in Google App Engine 1.7.5. I am authenticating users with a token that is transmitted in a HTTP request header parameter. I was successful using @HeaderParam to extract the token and authenticate the user with the token, but I want to create a custom annotation to handle the authentication for me. However, the provider does not seem to be picked up by Jersey.

I define an annotation:

package com.kwogger.api.web.rs.ext;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

and a provider:

package com.kwogger.api.web.rs.ext;

import com.googlecode.objectify.Key;
import com.sun.jersey.api.core.HttpContext;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.server.impl.inject.AbstractHttpContextInjectable;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.PerRequestTypeInjectableProvider;
import com.kwogger.om.User;

@Provider
public class AuthUserProvider extends PerRequestTypeInjectableProvider<AuthUser, Key<User>> {
  public AuthUserProvider() {
    super(Key.class);
  }

  @Override
  public Injectable<Key<User>> getInjectable(ComponentContext ic, final AuthUser a) {
    return new AbstractHttpContextInjectable<Key<User>>() {
      @Override
      public Key<User> getValue(HttpContext ctxt) {
        // ... authenticate user here ...
      }
    };
  }
}

and in my resource, I modify the endpoint from this:

  @GET
  public JsonNode getVault() {
    Key<User> user = authUser();
  }

  private String userToken;

  @HeaderParam(Constants.HEADER_USER_TOKEN)
  public void setUserToken(String token) {
    if (token != null) {
      userToken = token;
    }
  }

  @CookieParam(Constants.COOKIE_USER_TOKEN)
  public void setUserTokenCookie(String cookie) {
    if (cookie != null) {
      userToken = cookie;
    }
  }

  private Key<User> authUser() {
    // ... authenticate user here ...
  }

to this:

  @GET
  public JsonNode getItem(@AuthUser Key<User> user) {
    // do stuff...
  }

However, when I try to access the endpoint, I get a HTTP 415 with the following error message in my logs:

com.sun.jersey.spi.container.ContainerRequest getEntity: A message body reader for Java class com.googlecode.objectify.Key, and Java type com.googlecode.objectify.Key<com.kwogger.om.User>, and MIME media type application/octet-stream was not found.
The registered message body readers compatible with the MIME media type are:
application/octet-stream ->
  com.sun.jersey.core.impl.provider.entity.ByteArrayProvider
  com.sun.jersey.core.impl.provider.entity.FileProvider
  com.sun.jersey.core.impl.provider.entity.InputStreamProvider
  com.sun.jersey.core.impl.provider.entity.DataSourceProvider
  com.sun.jersey.core.impl.provider.entity.RenderedImageProvider
*/* ->
  com.sun.jersey.core.impl.provider.entity.FormProvider
  com.sun.jersey.core.impl.provider.entity.MimeMultipartProvider
  com.sun.jersey.json.impl.provider.entity.JSONJAXBElementProvider$General
  com.sun.jersey.json.impl.provider.entity.JSONArrayProvider$General
  com.sun.jersey.json.impl.provider.entity.JSONObjectProvider$General
  com.sun.jersey.core.impl.provider.entity.StringProvider
  com.sun.jersey.core.impl.provider.entity.ByteArrayProvider
  com.sun.jersey.core.impl.provider.entity.FileProvider
  com.sun.jersey.core.impl.provider.entity.InputStreamProvider
  com.sun.jersey.core.impl.provider.entity.DataSourceProvider
  com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$General
  com.sun.jersey.core.impl.provider.entity.ReaderProvider
  com.sun.jersey.core.impl.provider.entity.DocumentProvider
  com.sun.jersey.core.impl.provider.entity.SourceProvider$StreamSourceReader
  com.sun.jersey.core.impl.provider.entity.SourceProvider$SAXSourceReader
  com.sun.jersey.core.impl.provider.entity.SourceProvider$DOMSourceReader
  com.sun.jersey.json.impl.provider.entity.JSONRootElementProvider$General
  com.sun.jersey.json.impl.provider.entity.JSONListElementProvider$General
  com.sun.jersey.json.impl.provider.entity.JacksonProviderProxy
  com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$General
  com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$General
  com.sun.jersey.core.impl.provider.entity.XMLRootObjectProvider$General
  com.sun.jersey.core.impl.provider.entity.EntityHolderReader

Every other endpoint in the same Resource file works fine. I have specified the package in the com.sun.jersey.config.property.packages init-param in the servlet definition and an ExceptionMapper located in the same package as the custom provider works. I am not using Spring or Guice.

What am I missing? It appears that Jersey is trying to read Key<User> user from the body, which makes me think the Provider isn't getting picked up at all, but I can't seem to figure out how to get it working.

Upvotes: 1

Views: 1130

Answers (1)

Perception
Perception

Reputation: 80633

Your Key<User> class is not intercepted because the provider registered with the Jersey runtime is expecting a raw type (Key), as specified in your providers constructor:

public AuthUserProvider() {
    super(Key.class);
}

But you are trying to inject a parameterized type, so you need to invoke the superclass constructor appropriately. The easiest (and possibly only) way to get the actual type needed is via a supertype token. Below is an example using the Jackson TypeReference class as a helper:

public AuthUserProvider() {
    super(new TypeReference<Key<User>>() {
    }.getType());
}

This line:

new TypeReference<Key<User>>() {}.getType()

Basically allows you to create a type that retains its generic information (in this case it represents a Key<User>.

Alternatively to the Jackson TypeReference class, you could use the Jersey Client GenericType, or Gson TypeToken, to accomplish the same thing.

Upvotes: 2

Related Questions