franck102
franck102

Reputation: 341

JAX-RS @Context usage in Jersey 2 compared to Jersey 1

[EDIT] The problem is with the register(new ServiceBinder<>(MyService.class)); Jersey generates a warning and ignores the registration for all but the first one (Existing previous registration found for the type); it only considers the type-erased ServiceBinder class to decide there is a conflict. It looks like I need to use a more sophisticated version of register to get past that issue. [/EDIT]

In Jersey 1 I was able to use custom injectable providers to inject my objects into both class fields and method parameters, by extending LazySingletonInjectableProvider

I can't figure out how to port that pattern to Jersey 2 (with hk2 on Tomcat 7). I have read everything I could find on the topic, including Jersey custom method parameter injection with inbuild injection - but I don't want to use a custom annotation, and I am not trying to inject a request parameter.

[EDIT] I made the wrong assumption regarding what works and what doesn't:

[EDIT 2]: The InjectionResolver as described below actually doesn't work at all, I have removed it. Jersey already has a ContextInjectionResolver which presumably should take care of the @Context annotation.

I have created and registered an AbstractBinder, and with that class field injection works fine; however method parameter injection doesn't (the binder never gets invoked and the parameter remains null). I have tried to bind an InjectionResolver but that didn't help either.

Any suggestion on how to make this work would be greatly appreciated... here is the current code:

The HK2 binder:

public class ServiceBinder<T> extends AbstractBinder
{
    private final Factory<T> _factory;
    private final Class<? extends T> _clazz;

    public OsgiServiceBinder(Class<T> clazz)
    {
        _factory = new ServiceFactory<>(clazz);
        _clazz = clazz;
    }

    protected void configure()
    {
        bindFactory(_factory).to(_clazz); //.in(RequestScoped.class);
        bind(ServiceInjectionResolver.class)
            .to(new TypeLiteral<InjectionResolver<Context>>() { })
            .in(PerLookup.class);
    }
}

The injection resolver:

public class ServiceInjectionResolver<T> implements InjectionResolver<Context>
{
    private Class<T> _clazz;

    public OsgiServiceInjectionResolver(Class<T> clazz)
    {
        _clazz = clazz;
    }

    public Object resolve(Injectee injectee, ServiceHandle<?> root)
    {
        if (_clazz.getCanonicalName().equals(injectee.getRequiredType().getTypeName())) {
            return Framework.getService(_clazz);
        }
        return null;
    }

    public boolean isConstructorParameterIndicator()
    {
        return false;
    }

    public boolean isMethodParameterIndicator()
    {
        return true;
    }
}

The JAX-RS registration:

public class MyApplication extends Application
{
    public MyApplication()
    {
        registerClasses(<resource classes>);
        register(new ServiceBinder<>(MyService.class));
    }
}

The resource class:

   @Path("/schedules")
public class SchedulesResource
{
    @Context UriInfo _uriInfo;

    // This injection works fine, _service1 is properly initialized
    @Context MyService _service1;

    @PUT
    @Consumes({MediaType.APPLICATION_JSON})
    @Path("{jobGroup}/{jobName}")
    public Response putSchedule(@Context MyService service2,
                                ...)
    {
        // The injection of service2 doesn't work...
    }
}

The Factory class:

public class ServiceFactory<T> implements Factory<T>
{
    private Class<T> _clazz;

    protected ServiceFactory(Class<T> clazz)
    {
       _clazz = clazz;
    }

    public T provide()
    {
        return Framework.getService(_clazz);
    }
}

public void dispose(T t)
{
}

}

pok

Upvotes: 0

Views: 857

Answers (1)

franck102
franck102

Reputation: 341

The problem was actually with Jersey component registrations. Even though I was registering binder instances, Jersey was checking the class (ServiceBinder) and discarding all but the first registration (WARN: existing registration found for the type).

This seems a bit bogus given I am registering instances, and I wish Jersey would fail with an error rather than log a warning when failing to register a component, but the solution is to simply change the registration pattern slightly:

// Doesn't work
 register(new ServiceBinder<>(MyService1.class));
 register(new ServiceBinder<>(MyService2.class));

// Works like a charm
register(new ServiceBinder(MyService1.class, MyService2.class));

where obviously the ServiceBinder is adjusted to call bindFactory for each supplied service.

Upvotes: 0

Related Questions