Last active
November 18, 2015 18:32
-
-
Save pshirshov/44799f4461c43f8e83c2 to your computer and use it in GitHub Desktop.
OSGi complex config
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// flat <-> hierarchical conversions | |
import java.util | |
import java.util.regex.Pattern | |
import scala.collection.JavaConversions._ | |
import com.typesafe.config.{Config, ConfigFactory, ConfigList, ConfigObject, ConfigUtil} | |
import com.typesafe.scalalogging.StrictLogging | |
/** | |
*/ | |
case class LoadedConfig(effective: Config, reference: Map[String, Any], notPersisted: Set[String]) | |
object ConfigUtils extends StrictLogging { | |
private val listElementPattern = Pattern.compile("^.*\\.\\d+(|\\..*)$") | |
private val digits = Pattern.compile("^[1-9]+$") | |
def loadConfigAndSetDefaultValues(clazz: Class[_], properties: java.util.Map[String, Object]) = { | |
val systemProperties = ConfigFactory.systemProperties() | |
val plainReference = ConfigFactory.parseResources(clazz.getClassLoader, "reference.conf") | |
val refMap = asMap(plainReference) | |
logger.debug(s"Given config::\n$properties") | |
logger.debug(s"Reference:\n$refMap") | |
// ConfigFactory.defaultReference(clazz.getClassLoader) | |
val config = composeConfig(plainReference, ConfigFactory.parseMap(properties), systemProperties) | |
logger.trace(s"Module configuration:\n${config.root().render()}") | |
import scala.collection.JavaConversions._ | |
val diff = refMap.keySet.diff(properties.keySet()) | |
val missingKeys = diff.filter(!listElementPattern.matcher(_).matches()) | |
val missingLists = diff.filter(k => listElementPattern.matcher(k).matches() && isFirstElement(k)) | |
logger.debug(s"""These keys are set in reference but missing in config: $missingKeys""") | |
logger.debug(s"""These lists are set in reference but missing in config: $missingLists""") | |
val allMissing = missingKeys ++ missingLists | |
LoadedConfig(config, refMap, allMissing) | |
} | |
private def isFirstElement(key:String) = { | |
val parts: util.List[String] = ConfigUtil.splitPath(key) | |
parts.forall(!digits.matcher(_).matches()) | |
} | |
def asMap(config:Config) = { | |
toMap(None, config.root()) | |
} | |
private def toMap(top: Option[String], config:ConfigObject): Map[String, Any] = { | |
config.flatMap { | |
case (key, value: ConfigObject) => | |
toMap(Some(key), value).map { | |
case (k1, v1) => | |
addTopKey(Some(key), k1, v1) | |
} | |
case (key, value: ConfigList) => | |
value.zipWithIndex.flatMap { | |
case (lv:ConfigObject, index) => | |
val submap = toMap(None, lv) | |
submap.map { | |
case (skey, svalue) => | |
(s"$key.$index.$skey", svalue) | |
}.toSeq | |
case (lv, index) => | |
Map(s"$key.$index" -> lv.unwrapped()) | |
}.toMap | |
case (key, value) => | |
Map(key -> value.unwrapped()) | |
}.toMap | |
} | |
private def addTopKey(top:Option[String], key:String, value:Any) = { | |
top match { | |
case None => | |
(s"$key", value) | |
case Some(topkey) => | |
(s"$topkey.$key", value) | |
} | |
} | |
def composeConfig(reference: Config, config:Config, systemProperties:Config): Config = { | |
val result: Config = systemProperties | |
.withFallback(config) | |
.withFallback(systemProperties) | |
.withFallback(reference) | |
.resolve() | |
result.checkValid(reference) | |
result | |
} | |
} | |
// OSGi util | |
import scala.collection.JavaConversions._ | |
import aQute.bnd.annotation.component._ | |
import com.typesafe.config.Config | |
import com.typesafe.scalalogging.StrictLogging | |
import org.osgi.framework.{BundleContext, FrameworkUtil} | |
import org.osgi.service.cm.ConfigurationAdmin | |
import org.osgi.service.component.ComponentContext | |
/** | |
*/ | |
trait ConfigurableComponent[T] extends StrictLogging { | |
@Activate | |
final def start(bundleContext: BundleContext, componentContext: ComponentContext, properties: java.util.Map[String, Object]) = { | |
val config = ConfigUtils.loadConfigAndSetDefaultValues(getClass, properties) | |
writeDefaults(config, properties) | |
val tsConfig = transformConfig(config.effective) | |
onStart(bundleContext, componentContext, tsConfig) | |
} | |
@Deactivate | |
final def stop() = { | |
onStop() | |
} | |
protected def writeDefaults(config: LoadedConfig, properties: java.util.Map[String, Object]) = { | |
if (config.notPersisted.nonEmpty) { | |
val pid = if (!getClass.isAnnotationPresent(classOf[Component])) { | |
getClass.getCanonicalName | |
} else { | |
val name = getClass.getAnnotation(classOf[Component]).name() | |
if (name.isEmpty) { | |
getClass.getCanonicalName | |
} else { | |
name | |
} | |
} | |
val notPersistedReferenceValues = if (properties.keySet().forall(_.startsWith("component."))) { | |
logger.info(s"Configuration $pid is empty") | |
config.reference | |
} else { | |
config.notPersisted.map { | |
k => (k, config.reference(k)) | |
}.toMap | |
} | |
logger.info(s"Updating configuration $pid with reference values: $notPersistedReferenceValues") | |
val bundleContext = FrameworkUtil.getBundle(getClass).getBundleContext | |
val configServiceReference = bundleContext.getServiceReference(classOf[ConfigurationAdmin]) | |
val configurationAdmin = bundleContext.getService(configServiceReference) | |
val configuration = configurationAdmin.getConfiguration(pid) | |
configuration.update(asJavaDictionary(scala.collection.mutable.Map(notPersistedReferenceValues.toSeq:_*))) | |
} | |
} | |
protected def onStart(bundleContext: BundleContext, componentContext: ComponentContext, config:T): Unit | |
protected def onStop(): Unit | |
// TODO: default proxy-generating implementation | |
protected def transformConfig(config:Config): T | |
} | |
// sample | |
import java.net.URI | |
import scala.concurrent.duration.Duration | |
import aQute.bnd.annotation.metatype.Meta | |
// TODO: replicate OCD-like service | |
@Meta.OCD | |
trait DNSConfig { | |
def bindTo(): Iterable[URI] | |
def serviceDomain(): String | |
def zoneTtl(): Duration | |
} | |
@Component(immediate = true, configurationPolicy = ConfigurationPolicy.optional) | |
class DNSServerComponent extends StrictLogging with ConfigurableComponent[DNSConfig] { | |
var server: Option[DNSServer] = None | |
var discovery: Option[Discovery] = None | |
@Reference(dynamic = true) | |
def setDiscovery(provider: Discovery): Unit = { | |
logger.debug(s"Cluster state installed: $provider") | |
this.discovery = Option(provider) | |
} | |
def unsetDiscovery(provider: Discovery): Unit = { | |
logger.debug(s"Cluster state removed: $provider") | |
this.discovery = None | |
} | |
override def onStart(bundleContext: BundleContext, componentContext: ComponentContext, dnsConfig: DNSConfig): Unit = { | |
stop() | |
if (discovery.isEmpty) { | |
logger.error("No ClusterState defined") | |
throw new IllegalArgumentException("No ClusterState defined") | |
} | |
server = Some(new DNSServer(discovery.get, dnsConfig)) | |
try { | |
server.foreach(_.start()) | |
} catch { | |
case t:Throwable => | |
logger.error("Can't start DNS server", t) | |
} | |
} | |
override protected def onStop(): Unit = { | |
try { | |
server.foreach(_.stop()) | |
} catch { | |
case t:Throwable => | |
logger.error("Can't stop DNS server", t) | |
} | |
server = None | |
} | |
override protected def transformConfig(config: Config): DNSConfig = { | |
import scala.collection.JavaConversions._ | |
val dnsConfig = new DNSConfig { | |
override def bindTo(): Iterable[URI] = { | |
// scala bug | |
/*import scala.collection.JavaConverters._ | |
val slist = new java.util.ArrayList[String]() | |
slist.asScala.toSet.map(s => new URI(s))*/ | |
val urisSet = config.getStringList("discovery.dns.bind_to").toSet | |
urisSet.map(s => new URI(s)) | |
} | |
override def serviceDomain(): String = | |
config.getString("discovery.dns.domain") | |
override def zoneTtl(): Duration = | |
Duration.fromNanos(config.getDuration("discovery.dns.zone_ttl").toNanos) | |
} | |
dnsConfig | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment