Search code examples
javajersey-2.0grizzly

jersey 2: How to create custom HTTP param binding


I am trying to create a custom http param binding for my restful service. Please see the example below.

@POST
@Path("/user/{userId}/orders")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public MyResult foo(@PathParam("userId") String someString, @UserAuthHeaderParam String authString){

}

You can see that there is a UserAuthHeaderParam annotation in the function signature. What I want to do is have a custom http param binding other than the standard javax.ws.rs.*Param .

I have try to implement org.glassfish.hk2.api.InjectionResolver which basically extract the value from http header:

public class ProtoInjectionResolver implements InjectionResolver<UserAuthHeaderParam>{
...
@Override
public Object resolve(Injectee injectee, ServiceHandle< ? > root)
{

    return "Hello World";
}
...

}

When I call the restful service, the server get below exceptions. It indicates that the framework fails to resolve the param in the function signature:

org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at Injectee(requiredType=String,parent=MyResource,qualifiers={}),position=0,optional=false,self=false,unqualified=null,2136594195), 

java.lang.IllegalArgumentException: While attempting to resolve the dependencies of rs.server.MyResource errors were found

Please help. Any advise is appreciated. I do make a lot of search on google but fails to make it work. Jersey 2.*. How to replace InjectableProvider and AbstractHttpContextInjectable of Jersey 1.* might be the similar question.

-- UPDATES: I use AbstractBinder to bind my resolver to UserAuthHeaderParam:

public class MyApplication extends ResourceConfig
{

public MyApplication()
{
    register(new AbstractBinder()
    {
        @Override
        protected void configure()
        {
            // bindFactory(UrlStringFactory.class).to(String.class);
            bind(UrlStringInjectResolver.class).to(new TypeLiteral<InjectionResolver<UrlInject>>()
            {
            }).in(Singleton.class);
        }
    });
    packages("rs");

}

}

Thank you!


Solution

  • If all you want is to pass value directly from the header to the method you don't need to create custom annotations. Let's say you have a header Authorization, then you can easily access it by declaring your method like this:

    @GET
    public String authFromHeader(@HeaderParam("Authorization") String authorization) {
        return "Header Value: " + authorization + "\n";
    }
    

    You can test it by calling curl, e.g.

    $ curl --header "Authorization: 1234" http://localhost:8080/rest/resource
    Header Value: 1234
    

    Given that the answer to your question, how to create custom binding is as follows.

    First you have to declare your annotation like this:

    @java.lang.annotation.Target(PARAMETER)
    @java.lang.annotation.Retention(RUNTIME)
    @java.lang.annotation.Documented
    public @interface UserAuthHeaderParam {
    }
    

    Having your annotation declared you have to define how it will be resolved. Declare the Value Factory Provider (this is where you'll have access to the header parameters - see my comment):

    @Singleton
    public class UserAuthHeaderParamValueFactoryProvider extends AbstractValueFactoryProvider {
    
        @Inject
        protected UserAuthHeaderParamValueFactoryProvider(MultivaluedParameterExtractorProvider mpep, ServiceLocator locator) {
            super(mpep, locator, Parameter.Source.UNKNOWN);
        }
    
        @Override
        protected Factory<?> createValueFactory(Parameter parameter) {
            Class<?> classType = parameter.getRawType();
    
            if (classType == null || (!classType.equals(String.class))) {
                return null;
            }
    
            return new AbstractHttpContextValueFactory<String>() {
                @Override
                protected String get(HttpContext httpContext) {
                    // you can get the header value here
                    return "testString";
                }
            };
        }
    }
    

    Now declare an injection resolver

    public class UserAuthHeaderParamResolver extends ParamInjectionResolver<UserAuthHeaderParam> {
        public UserAuthHeaderParamResolver() {
            super(UserAuthHeaderParamValueFactoryProvider.class);
        }
    }
    

    and a Binder for your configuration

    public class HeaderParamResolverBinder extends AbstractBinder {
    
        @Override
        protected void configure() {
            bind(UserAuthHeaderParamValueFactoryProvider.class)
                    .to(ValueFactoryProvider.class)
                    .in(Singleton.class);
    
            bind(UserAuthHeaderParamResolver.class)
                    .to(new TypeLiteral<InjectionResolver<UserAuthHeaderParam>>() {})
                    .in(Singleton.class);
        }
    }
    

    now the last thing, in your ResourceConfig add register(new HeaderParamResolverBinder()), like this

    @ApplicationPath("rest")
    public class MyApplication extends ResourceConfig {
        public MyApplication() {
            register(new HeaderParamResolverBinder());
            packages("your.packages");
        }
    }
    

    Given that, you should be now able to use the value as you wanted:

    @GET
    public String getResult(@UserAuthHeaderParam String param) {
        return "RESULT: " + param;
    }
    

    I hope this helps.