No object available for injection error when using bindFactory

4.4k views Asked by At

I have the following implementation for a Jersey (2.18) application:

public class RootApplication extends ResourceConfig {

    public RootApplication() {
        packages("com.foo.bar");

        register(new AbstractBinder() {

            @Override
            protected void configure() {       
                bindFactory(RepositoryFactory.class).to(Repository.class);

                // if I use following line instead of bindFactory it works
                // bind(OracleRepository.class).to(Repository.class);
            }
        });
    }

    public class RepositoryFactory implements Factory<Repository> {

        private final Repository repo;

        public RepositoryFactory() {
            this.repo = new OracleRepository();
        }

        @Override
        public Repository provide() {
            return repo;
        }

        @Override
        public void dispose(Repository repo) {
        }
    }
}

and get the exception below when hitting a service that injects Repository

javax.servlet.ServletException: A MultiException has 3 exceptions.  They are:
1. org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl(requiredType=Repository,parent=MeasureService,qualifiers={},position=-1,optional=false,self=false,unqualified=null,56464420)
2. java.lang.IllegalArgumentException: While attempting to resolve the dependencies of com.fidelity.pi.dashboard.rest.MeasureService errors were found
3. java.lang.IllegalStateException: Unable to perform operation: resolve on com.fidelity.pi.dashboard.rest.MeasureService

It all works if I comment out the bindFactory and use the commented-out bind. Am I missing something in terms of the Factory implementation? The exception seems to happen even before the RepositoryFactory constructor is hit. I need the factory since I have some other initialization to do on the OracleRepository instance.

1

There are 1 answers

2
Paul Samsotha On BEST ANSWER

The only way I was able to reproduce the problem (with your incomplete information - i.e. missing injection point) was to try and inject OracleRepository instead of Repository. I don't have the exact reason why the injection fails, but I guess it's because you're binding Repository and not OracleRepository. If this is the problem, the simplest fix would be to bind the factory to OracleRepository or instead, simply inject Repository.

For injection of Repository, if you want to qualify different implementations you can do so by chaining named or qaulifiedBy to the binding, as in the example below (where I used named and annotate the injection point with @Named).

In the example I used the Jersey Test Framework

<dependency>
    <groupId>org.glassfish.jersey.test-framework.providers</groupId>
    <artifactId>jersey-test-framework-provider-inmemory</artifactId>
    <version>${jersey2.version}</version>
    <scope>test</scope>
</dependency>

Here is the complete test. You can change the @Named between "Sql" and "Oracle" to see the difference.

import javax.inject.Inject;
import javax.inject.Named;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;
import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Assert;
import org.junit.Test;

public class InjectionTest extends JerseyTest {

    @Path("test")
    public static class SimpleResource {

        @Inject
        @Named("Oracle")
        private Repository repo;

        @GET
        public String getRepoName() {
            return repo.getClass().getSimpleName();
        }
    }

    @Override
    public Application configure() {
        ResourceConfig config = new ResourceConfig();
        config.register(SimpleResource.class);
        config.register(new AbstractBinder(){
            @Override
            protected void configure() {
                bindFactory(SqlFactory.class)
                        .to(Repository.class).named("Sql");
                bindFactory(OracleFactory.class)
                        .to(Repository.class).named("Oracle");
            }
        });
        return config;       
    }


    public static interface Repository {}

    public static class OracleRepository implements Repository {}

    public static class SqlRepository implements Repository {}

    public static class SqlFactory implements Factory<Repository> {

        @Override
        public Repository provide() {
            return new SqlRepository();
        }

        @Override
        public void dispose(Repository t) {}
    }

    public static class OracleFactory implements Factory<Repository> {

        @Override
        public Repository provide() {
            return new OracleRepository();
        }

        @Override
        public void dispose(Repository t) {}
    }

    /**
     * Change the `Assert` from "OracleRepository" to "SqlRepository"
     * when switching the `@Named` on the injection point.
     */
    @Test
    public void testInjectOk() {
        Response response = target("test").request().get();
        String respString = response.readEntity(String.class);
        Assert.assertEquals("OracleRepository", respString);
        System.out.println(respString);
    }
}

If you still have a problem, please post a complete single class test case like I have above that demonstrates the problem.