Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I pass parameters into a Provider with Guice?

Let's say I have this interface:

public interface DbMapper{
}

And then this implementation:

public interface NameDbMapper extends DbMapper {

    @SqlUpdate("insert into names (name) values (:name)")
    void insert(@Bind("name") String name);
}

This implementation exists in a module, so I don't know all DbMappers at compile time. I discover DbMappers through reflection:

public class GuiceModule extends AbstractModule{

    @Override
    protected void configure() {
        Reflections reflections = new Reflections("com.company");
        Set<Class<? extends DbMapper>> dbMappers = reflections.getSubTypesOf(DbMapper.class);

        for (Class<? extends DbMapper> dbMapper : dbMappers) {
            Class<DbMapper> db = (Class<DbMapper>) dbMapper;
            binder().bind(db).toProvider(DbMapperProvider.class);
        }
    }

Then I instansiate mappers in my provider:

public class DbMapperProvider implements Provider<DbMapper> {

    private final User user;

    @Inject
    public DbMapperProvider(User user) {
        this.user = user;
    }

    @Override
    public DbMapper get() {
        String jdbc = user.getJdbc();

        DBI userSpecificDatabase = new DBI(jdbc, "user", "password");
        //How to replace NameDbMapper.class here with the db variable in GuiceModule?
        DbMapper dbMapper = userSpecificDatabase.onDemand(NameDbMapper.class);
        return dbMapper;
    }
}

User is a @RequestScoped instance, so I can't create providers regularly in GuiceModule. Injecting User works but how do I pass which Class DBI should use instead of hardcode NameDbMapper in DbMapperProvider here?

I've tried the approach suggested in http://google-guice.googlecode.com/git/javadoc/com/google/inject/assistedinject/FactoryModuleBuilder.html but couldn't get it to work.

The goal here is that modules shouldn't have to write their own providers, is this achievable?

like image 713
jontejj Avatar asked Jun 04 '14 06:06

jontejj


1 Answers

You can bind to a provider instance, like

for (Class<? extends DbMapper> dbMapper : dbMappers) {
    bind(dbMapper).toProvider(new DbMapperProvider<>(db));
}

Then modify your provider like this:

public class DbMapperProvider<T extends DbMapper> implements Provider<T> {
    // Use field or method injection
    @Inject
    private Provider<User> user;

    private final Class<T> type;

    public DbMapperProvider(Class<T> type) {
        this.type = type;
    }

    @Override
    public T get() {
        String jdbc = user.get().getJdbc();

        DBI userSpecificDatabase = new DBI(jdbc, "user", "password");
        DbMapper dbMapper = userSpecificDatabase.onDemand(type);
        return dbMapper;
    }
}
like image 167
Tavian Barnes Avatar answered Sep 22 '22 04:09

Tavian Barnes