Yuri Geinish
Yuri Geinish

Reputation: 17224

Typesafe Config under OSGi

Libraries using Typesafe Config usually depend on one big configuration merged from /reference.conf files on the classpath.

For example, Spray expects to find its config sections in the ActorSystem's Typesafe Config instance, but they aren't available unless the bundle where I create the ActorSystem imports Spray packages. In my application there isn't such an import, because I've got a dedicated bundle whose sole purpose is publishing ActorSystem as a service. Other bundles use that, and some of them depend on Spray but not the bundle that just exports the AS.

This led me to the general problem of Typesafe Config finding /reference.conf files in an OSGi environment. I know akka-osgi's BundleDelegatingClassLoader looks up resources down a chain of bundle dependencies so I thought why not just look through all bundles in the system to be in line with Typesafe Config's merging idiology?

What is the correct way to work with Typesafe Config under OSGi? I'll present my generic solution in an answer but I'm not an OSGi expert and would like to hear if that's wrong and why and what would be a better way to handle the merge.

Upvotes: 0

Views: 376

Answers (1)

Yuri Geinish
Yuri Geinish

Reputation: 17224

This makes Typesafe Config's include to look for the given resource in all installed bundles:

// create Typesafe Config

val myConfig = ConfigFactory.parseFile(
  new File("myconfig.conf"),
  ConfigParseOptions.defaults().setClassLoader(new ClassLoader() {
    override def getResources(name: String): util.Enumeration[URL] = {
      val resources = context.getBundles.flatMap { bundle =>
        val found = JavaConversions.enumerationAsScalaIterator(
          Option(bundle.getResources(name)).getOrElse(Collections.emptyEnumeration())
        )
        found
      }
      JavaConversions.asJavaEnumeration(resources.toIterator)
    }
  })
).resolve()

// create ActorSystem

/* This could've been myconfig.getConfig("myconfig").withOnlyPath("akka") but
 * like I said, Spray expects to find "spray.*" section in the ActorSystem's config.
 */
val factory = OsgiActorSystemFactory(context, myconfig.getConfig("myconfig"))
val as = factory.createActorSystem("blah")

In the config itself I'm using:

myconfig {

    // this will include reference.conf from every installed bundle in the container
    include classpath("reference.conf")

    // overrides
    akka.loglevel = INFO
    spray.version = "1.3.2"

    // etc ...
    other.stuff.for.my.app = 1
}

Having -Dconfig.trace=loads shows:

karaf@root()> feature:install myfeature
Loading config from a file: C:\Users\YUUshakov\p\myconfig.conf
Loading config from URL bundleresource://925.fwk875016237/reference.conf from class loader mytest.ConfigActivator$$anon$1@1131e4e4
Loading config from URL bundleresource://931.fwk875016237/reference.conf from class loader mytest.ConfigActivator$$anon$1@1131e4e4
Loading config from URL bundleresource://933.fwk875016237/reference.conf from class loader mytest.ConfigActivator$$anon$1@1131e4e4
Loading config from URL bundleresource://934.fwk875016237/reference.conf from class loader mytest.ConfigActivator$$anon$1@1131e4e4
Loading config from URL bundleresource://946.fwk875016237/reference.conf from class loader mytest.ConfigActivator$$anon$1@1131e4e4

And resulting config instance got all the reference sections like myconfig.akka, myconfig.spray, etc.

Upvotes: 1

Related Questions