Ernest Sadykov
Ernest Sadykov

Reputation: 831

Inject configuration value in play framework using Guice

I have play web application with conf/application.conf (nothing unusual). Guice is used for dependency injection. How can property value be injected in class constructor? The code is below.

class MyController @Inject() (private val foo: Foo) extends Controller {
    ...
}

@ImplementedBy(classOf[FooImpl])
trait Foo { 
    def bar: String
}

class FooImpl extends Foo {
    override val bar = current.configuration.getString("my.bar").get
    ...
}

In the current configuration FooImpl can't be tested without running application. I want to be able instantiate FooImpl in unit tests. The perfect solution [from my point of view] should look like that:

class FooImpl @Inject() (@Named("my.bar") override val bar: String) extends Foo {
    ...
}

Unfortunately, this code doesn't work, because Guice doesn't have 'my.bar' binding:

No implementation for java.lang.String annotated with @com.google.inject.name.Named(value=my.bar) was bound.

The only solution that I came up with is writing my own module, which iterates through configuration properties and binds them as named dependencies (a variation of the example from this doc). But I believe that a better approach exists.

Upvotes: 1

Views: 4002

Answers (4)

Ernest Sadykov
Ernest Sadykov

Reputation: 831

I encountered the same problem after about a year, and this time come up with the following solution (very similar to the one proposed by @stranger-in-the-q and @droidman):

class InjectionModule extends AbstractModule {

  override def configure(): Unit = {

    val config: Config = TypesafeConfigReader.config
    config.entrySet().asScala.foreach { entry =>
      val path = entry.getKey
      entry.getValue.valueType() match {
        case ConfigValueType.NUMBER =>
          bind(classOf[Int])
            .annotatedWith(Names.named(path))
            .toInstance(config.getInt(path))
        case ConfigValueType.BOOLEAN =>
           bind(classOf[Boolean])
             .annotatedWith(Names.named(path))
             .toInstance(config.getBoolean(path))
        case ConfigValueType.STRING =>
           bind(classOf[String])
             .annotatedWith(Names.named(path))
             .toInstance(config.getString(path))
        case _ =>
      }
    }
  }
}

Also, this approach can be extended by appending prefixes to system properties (which key-value pairs are part of the loaded config):

private def getPrefix(configValue: ConfigValue): String = {
  val description = configValue.origin().description()
  if (description.contains("system properties")) {
    "sys."
  } else {
    ""
  }
}

In this case instead of writing Names.named(path) one should use Names.named(getPrefix(entry.getValue) + path).

Upvotes: 1

Droidman
Droidman

Reputation: 540

To inject multiple properties from a play configuration you could do this way. Create a map out of Play configuration and pass it as Properties to Guice binder.

public class Module extends AbstractModule {

    private Environment environment;
    private Configuration configuration;

    public Module(Environment environment,Configuration configuration){
        this.environment = environment;
        this.configuration = configuration;
    }

    @Override
    public void configure() {
        Configuration helloConf = configuration.getConfig("myconfig");
        Map<String, Object> map = helloConf.asMap();
        Properties properties = new Properties();
        properties.putAll(map);        
        Names.bindProperties(binder(), properties);
    }
}

Upvotes: 0

JonDoe297
JonDoe297

Reputation: 1721

I implemented that using Java. I hope you can use it as reference for your Scala implementation.

At first, I created a Module:

public class MainModule extends AbstractModule {
    public final static String TIMEOUT_IN_MILLISECONDS_ANNOTATION = "timeout-promise";
    private final Configuration configuration;

    public MainModule(@SuppressWarnings("unused") Environment environment, Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    protected void configure() {
        long timeoutInMilliseconds = configuration.getLong("application.promise.timeout.in.milliseconds", 0L);
        bindConstant().annotatedWith(Names.named(TIMEOUT_IN_MILLISECONDS_ANNOTATION)).to(timeoutInMilliseconds);
    }
}

After that, I just used the annotation on different places:

class Service {

    @Inject
    @Named(MainModule.TIMEOUT_IN_MILLISECONDS_ANNOTATION)
    protected long timeoutInMilliseconds;

}

Hope this helps.

Upvotes: 2

Stranger in the Q
Stranger in the Q

Reputation: 3898

Some time ago i was developed small guice extention for simple injection configuration variables mapped on Enum

guice-config

Upvotes: 1

Related Questions