17

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!

Community
  • 1
  • 1
yzandrew
  • 785
  • 1
  • 11
  • 25

4 Answers4

15

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.

Edward Samson
  • 2,395
  • 2
  • 26
  • 39
lpiepiora
  • 13,659
  • 1
  • 35
  • 47
  • Does this work when you mix with other existing annotations say @PathParam in same method? – Dhana Krishnasamy May 01 '14 at 06:35
  • @DhanaKrishnasamy well you cannot have annotation on the same method argument obviously, but you can have a method like `@Path("{id}") public String getResult(@UserAuthHeaderParam String user, @PathParam("id") Integer id)`, if that's what you're asking for? – lpiepiora May 01 '14 at 06:53
  • @lpiepiora Thank you for your solution! What I actually need is to binding all the HTTP headers into one object. So I have to create a custom binding. And I am sorry about the late reply. I was working on several projects so I haven't have a chance to try your solution. I will reply once I verify it. It should be early next week! Thanks again! – yzandrew May 02 '14 at 01:18
  • @lpiepiora Your solution works for me! It's exactly what I need. – yzandrew May 08 '14 at 21:21
  • Thanks, very useful post! Only one question, I can't find `AbstractHttpContextValueFactory` on Jersey2.6. Do you know what we can use in place? Thanks again! – Christian Sisti Jun 02 '14 at 10:15
  • @ChristianSisti Thanks, it was actually an internal class of Jersey, so maybe it wasn't the wisest idea to use it in my example code. You can check the [original Jersey code](https://raw.githubusercontent.com/jersey/jersey/2.0.x/core-server/src/main/java/org/glassfish/jersey/server/internal/inject/AbstractHttpContextValueFactory.java) and write such class yourself - it's rather simple. – lpiepiora Jun 02 '14 at 10:27
  • @lpiepiora I see, thanks. I did manage to make my code work using [AbstractContainerRequestValueFactory](https://github.com/jersey/jersey/blob/master/core-server/src/main/java/org/glassfish/jersey/server/internal/inject/AbstractContainerRequestValueFactory.java), but again, it is an internal Jersey class. Much better to provide our own implementation. – Christian Sisti Jun 03 '14 at 15:48
  • @ChristianSisti I meant to provide your own implementation, I only linked the other class, so you can see how it's done. – lpiepiora Jun 03 '14 at 15:59
  • what are the import packages of the class UserAuthHeaderParamValueFactoryProvider ? – Ahmet Karakaya Jan 07 '16 at 19:55
2

I don't know how to resolve your exception. However, may I propose you a different way to do the same thing. I hope it helps.

I've faced exactly the same problem: I need extra parameters in the http header (btw, also related to authentication). Besides, I need to send them in every call, since I want to do a "typical" rest implementation, without maintaining a session.

I'm using Jersey 2.7 - but I'd say it should work in 2.0. I've followed their documentation https://jersey.java.net/documentation/2.0/filters-and-interceptors.html

It's quite clear there, but anyway I copy-paste my implementation below. It works fine. True there are some other ways to secure a rest service, for example this is a good one: http://www.objecthunter.net/tinybo/blog/articles/89

But they depend on the application server implementation and the database you use. The filter, in my opinion, is more flexible and easier to implement.

The copy-paste: I've defined a filter for authentication, which applies to every call and it is executed before the service (thanks to @PreMatching).

@PreMatching
public class AuthenticationRequestFilter implements ContainerRequestFilter {

    @Override
    public void filter(final ContainerRequestContext requestContext) throws IOException {
        final MultivaluedMap<String, String> headers = requestContext.getHeaders();
        if (headers == null) {
            throw new...
        }

        // here I get parameters from the header, via headers.get("parameter_name")
        // In particular, I get the profile, which I plan to use as a Jersey role
        // then I authenticate
        // finally, I inform the Principal and the role in the SecurityContext object, so that I can use @RolesAllowed later
        requestContext.setSecurityContext(new SecurityContext() {

            @Override
            public boolean isUserInRole(final String arg0) {
                //...
            }

            @Override
            public boolean isSecure() {
                //...
            }

            @Override
            public Principal getUserPrincipal() {
                //...
            }

            @Override
            public String getAuthenticationScheme() {
                //...
            }
        });

    }

}

You have to include this filter class in your implementation of ResourceConfig,

public class MyResourceConfig extends ResourceConfig {

    public MyResourceConfig() {

        // my init
        // my packages
        register(AuthenticationRequestFilter.class); // filtro de autenticación
        // other register

    }

}

Hope it helps!

lpiepiora
  • 13,659
  • 1
  • 35
  • 47
lrnzcig
  • 3,868
  • 4
  • 36
  • 50
  • Thank you for your solution! I have to stick on custom binding. Please see my comments to lpiepiora for the detail reason. However, your solution might help other people with similar problem. – yzandrew May 02 '14 at 01:14
1

If your need is to retrieve all the http headers binding into one object, a solution could be to use the @Context annotation to get javax.ws.rs.core.HttpHeaders; which contains the list of all request headers.

@POST
@Path("/user/{userId}/orders")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public MyResult foo(@PathParam("userId") String someString, @Context HttpHeaders headers){
 // You can list all available HTTP request headers via following code :
   for(String header : headers.getRequestHeaders().keySet()){
     System.out.println(header);
   }
}
herau
  • 1,466
  • 2
  • 18
  • 36
  • Thank you for you reply. I think custom binding is a better way for me. I can bind the http headers/query param/path param into a protobuff object, which is then used as part of model object in my business logic. – yzandrew May 08 '14 at 19:00
0

here is my actual implementatipn of UserAuthHeaderParamValueFactoryProvider class

import javax.inject.Inject;
import javax.inject.Singleton;

import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory;
import org.glassfish.jersey.server.internal.inject.AbstractValueFactoryProvider;
import org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractorProvider;

    import org.glassfish.jersey.server.model.Parameter;

    @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 AbstractContainerRequestValueFactory<String>() {
                @Override
                public String provide() {
                    //you can use get any header value.
                    return getContainerRequest().getHeaderString("Authorization");
                }

            };
        }
Ahmet Karakaya
  • 9,899
  • 23
  • 86
  • 141