Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conditional beans include support in commons-spring #432

Merged
merged 5 commits into from
Mar 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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
Expand All @@ -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"
}
}
6 changes: 6 additions & 0 deletions commons-spring/src/test/resources/conditionalInclude.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
beans {
beanFromConditional = {
%class = com.avsystem.commons.spring.ConditionalTestBean, %construct = true
int = 100
}
}
7 changes: 7 additions & 0 deletions commons-spring/src/test/resources/conditionalsDisabled.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
featureFlag.enabled = false

beans.beanFromConditional = null

conditionals = [
{condition: ${featureFlag.enabled}, config: {include "conditionalInclude.conf"}},
]
7 changes: 7 additions & 0 deletions commons-spring/src/test/resources/conditionalsEnabled.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
featureFlag.enabled = true

beans.beanFromConditional = null

conditionals = [
{condition: ${featureFlag.enabled}, config: {include "conditionalInclude.conf"}},
]
20 changes: 20 additions & 0 deletions commons-spring/src/test/resources/conditionalsNested.conf
Original file line number Diff line number Diff line change
@@ -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}},
]
Original file line number Diff line number Diff line change
@@ -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") {
Expand All @@ -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)
Expand All @@ -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")

Expand Down Expand Up @@ -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)
}
}