diff --git a/commons-spring/src/main/scala/com/avsystem/commons/spring/HoconBeanDefinitionReader.scala b/commons-spring/src/main/scala/com/avsystem/commons/spring/HoconBeanDefinitionReader.scala index 606e87c6e..057ecfe38 100644 --- a/commons-spring/src/main/scala/com/avsystem/commons/spring/HoconBeanDefinitionReader.scala +++ b/commons-spring/src/main/scala/com/avsystem/commons/spring/HoconBeanDefinitionReader.scala @@ -1,10 +1,7 @@ package com.avsystem.commons package spring -import java.{util => ju} - import com.avsystem.commons.spring.AttrNames._ -import scala.annotation.nowarn import com.typesafe.config._ import org.springframework.beans.factory.annotation.Qualifier import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder @@ -13,9 +10,13 @@ import org.springframework.beans.factory.support._ import org.springframework.beans.{MutablePropertyValues, PropertyValue} import org.springframework.core.io.Resource +import java.{util => ju} +import scala.annotation.nowarn + class HoconBeanDefinitionReader(registry: BeanDefinitionRegistry) extends AbstractBeanDefinitionReader(registry) { + import com.avsystem.commons.spring.HoconBeanDefinitionReader.Keys._ import com.typesafe.config.ConfigValueType._ private implicit class ConfigValueExtensions(value: ConfigValue) { @@ -344,9 +345,22 @@ class HoconBeanDefinitionReader(registry: BeanDefinitionRegistry) } } - def loadBeanDefinitions(config: Config): Int = { - val beans = if (config.hasPath("beans")) config.getObject("beans") else ConfigFactory.empty.root - val aliases = if (config.hasPath("aliases")) config.getObject("aliases") else ConfigFactory.empty.root + private def readConditionals(config: Config): Config = { + if (!config.hasPath(Conditionals)) config + else config.getList(Conditionals).asScala.foldLeft(config.withoutPath(Conditionals)) { (currentConfig, conditionalObject) => + val props = getProps(conditionalObject.as[ConfigObject]) + + if (props(Condition).as[Boolean]) + readConditionals(props(Config).as[Config]).withFallback(currentConfig) + else + currentConfig + } + } + + def loadBeanDefinitions(resourceConfig: Config): Int = { + val config = readConditionals(resourceConfig) + val beans = if (config.hasPath(Beans)) config.getObject(Beans) else ConfigFactory.empty.root + val aliases = if (config.hasPath(Aliases)) config.getObject(Aliases) else ConfigFactory.empty.root val result = readBeans(beans) readAliases(aliases) result @@ -355,3 +369,12 @@ class HoconBeanDefinitionReader(registry: BeanDefinitionRegistry) def loadBeanDefinitions(resource: Resource): Int = loadBeanDefinitions(ConfigFactory.parseURL(resource.getURL).resolve) } +object HoconBeanDefinitionReader { + object Keys { + final val Conditionals = "conditionals" + final val Condition = "condition" + final val Config = "config" + final val Beans = "beans" + final val Aliases = "aliases" + } +} diff --git a/commons-spring/src/test/resources/conditionalInclude.conf b/commons-spring/src/test/resources/conditionalInclude.conf new file mode 100644 index 000000000..e58e6926f --- /dev/null +++ b/commons-spring/src/test/resources/conditionalInclude.conf @@ -0,0 +1,6 @@ +beans { + beanFromConditional = { + %class = com.avsystem.commons.spring.ConditionalTestBean, %construct = true + int = 100 + } +} \ No newline at end of file diff --git a/commons-spring/src/test/resources/conditionalsDisabled.conf b/commons-spring/src/test/resources/conditionalsDisabled.conf new file mode 100644 index 000000000..b7ad168aa --- /dev/null +++ b/commons-spring/src/test/resources/conditionalsDisabled.conf @@ -0,0 +1,7 @@ +featureFlag.enabled = false + +beans.beanFromConditional = null + +conditionals = [ + {condition: ${featureFlag.enabled}, config: {include "conditionalInclude.conf"}}, +] diff --git a/commons-spring/src/test/resources/conditionalsEnabled.conf b/commons-spring/src/test/resources/conditionalsEnabled.conf new file mode 100644 index 000000000..956e192ff --- /dev/null +++ b/commons-spring/src/test/resources/conditionalsEnabled.conf @@ -0,0 +1,7 @@ +featureFlag.enabled = true + +beans.beanFromConditional = null + +conditionals = [ + {condition: ${featureFlag.enabled}, config: {include "conditionalInclude.conf"}}, +] diff --git a/commons-spring/src/test/resources/conditionalsNested.conf b/commons-spring/src/test/resources/conditionalsNested.conf new file mode 100644 index 000000000..c925c0512 --- /dev/null +++ b/commons-spring/src/test/resources/conditionalsNested.conf @@ -0,0 +1,20 @@ +featureFlag.enabled = true + +beans { + testBean { + %class = com.avsystem.commons.spring.TestBean + } +} + +conditionals = [ + {condition: ${featureFlag.enabled}, config: {beans.testBean.int = 0}}, + {condition: ${featureFlag.enabled}, config: {beans.testBean.int = 1}}, + { + condition: ${featureFlag.enabled}, config: { + conditionals = [ + {condition: true, config: {beans.testBean.int = 2}} + ] + } + }, + {condition: false, config: {beans.testBean.int = 3}}, +] \ No newline at end of file diff --git a/commons-spring/src/test/scala/com/avsystem/commons/spring/HoconBeanDefinitionReaderTest.scala b/commons-spring/src/test/scala/com/avsystem/commons/spring/HoconBeanDefinitionReaderTest.scala index f0fe3b58d..64abe1f06 100644 --- a/commons-spring/src/test/scala/com/avsystem/commons/spring/HoconBeanDefinitionReaderTest.scala +++ b/commons-spring/src/test/scala/com/avsystem/commons/spring/HoconBeanDefinitionReaderTest.scala @@ -1,14 +1,14 @@ package com.avsystem.commons package spring -import java.{util => ju} - import com.typesafe.config.{Config, ConfigFactory} +import org.scalatest.BeforeAndAfterEach import org.scalatest.funsuite.AnyFunSuite import org.springframework.beans.factory.support.DefaultListableBeanFactory import org.springframework.context.support.GenericApplicationContext import org.springframework.core.StandardReflectionParameterNameDiscoverer +import java.{util => ju} import scala.beans.BeanProperty class TestBean(val constrInt: Int = 1, val constrString: String = "constrDefault") { @@ -25,7 +25,17 @@ object TestBean { new TestBean(theInt, theString) } -class HoconBeanDefinitionReaderTest extends AnyFunSuite { +class ConditionalTestBean(int: Int) { + + import ConditionalTestBean.initializedCount + + initializedCount += 1 +} +object ConditionalTestBean { + var initializedCount = 0 +} + +class HoconBeanDefinitionReaderTest extends AnyFunSuite with BeforeAndAfterEach { def createContext(resource: String): GenericApplicationContext = { val beanFactory = new DefaultListableBeanFactory beanFactory.setParameterNameDiscoverer(new StandardReflectionParameterNameDiscoverer) @@ -40,6 +50,10 @@ class HoconBeanDefinitionReaderTest extends AnyFunSuite { ctx } + override def beforeEach(): Unit = { + ConditionalTestBean.initializedCount = 0 + } + test("hocon bean definition reader should work") { val ctx = createContext("testBean.conf") @@ -82,4 +96,23 @@ class HoconBeanDefinitionReaderTest extends AnyFunSuite { assert(testBeanFMDefAll.constrInt == -1) assert(testBeanFMDefAll.constrString == "factoryDefault") } + + test("file should be included with true condition") { + val ctx = createContext("conditionalsEnabled.conf") + val testBean = ctx.getBean("beanFromConditional", classOf[ConditionalTestBean]) + assert(testBean != null) + assertResult(1)(ConditionalTestBean.initializedCount) + } + + test("file should not be included with false condition") { + val ctx = createContext("conditionalsDisabled.conf") + assert(!ctx.containsBean("beanFromConditional")) + assertResult(0)(ConditionalTestBean.initializedCount) + } + + test("hocon bean definition with nested conditionals should work") { + val ctx = createContext("conditionalsNested.conf") + val testBean = ctx.getBean("testBean", classOf[TestBean]) + assert(testBean.int == 2) + } }