diff --git a/src/main/scala/org/springframework/scala/context/function/cake/Cake.scala b/src/main/scala/org/springframework/scala/context/function/cake/Cake.scala new file mode 100644 index 0000000..4b8aa7b --- /dev/null +++ b/src/main/scala/org/springframework/scala/context/function/cake/Cake.scala @@ -0,0 +1,24 @@ +package org.springframework.scala.context.function.cake + +import org.springframework.scala.context.function.FunctionalConfiguration +import scala.collection.mutable.ListBuffer + +class Cake(initialConfigurationClasses: Class[_ <: FunctionalConfiguration]*) extends CakeApplicationContext with CakeLifecycle { + + // Members + + private[cake] val _configurationClasses = new ListBuffer[Class[_ <: FunctionalConfiguration]]() + + // Initialization + + _configurationClasses ++= initialConfigurationClasses + startApplicationContext() + + // Lifecycle routines + + protected def startApplicationContext() { + _configurationClasses.foreach(applicationContext.registerClasses(_)) + applicationContext.refresh() + } + +} diff --git a/src/main/scala/org/springframework/scala/context/function/cake/CakeApplicationContext.scala b/src/main/scala/org/springframework/scala/context/function/cake/CakeApplicationContext.scala new file mode 100644 index 0000000..7b1f499 --- /dev/null +++ b/src/main/scala/org/springframework/scala/context/function/cake/CakeApplicationContext.scala @@ -0,0 +1,35 @@ +package org.springframework.scala.context.function.cake + +import org.springframework.context.ApplicationContext +import org.springframework.scala.context.function.{FunctionalConfiguration, FunctionalConfigApplicationContext} +import org.springframework.beans.factory.config.ConfigurableBeanFactory +import org.springframework.scala.util.ManifestUtils._ +import scala.collection.mutable.ListBuffer + +trait CakeApplicationContext { + + // Public API + + def context: ApplicationContext = applicationContext + + // Internal Cake API + + private[cake] val configuration = new FunctionalConfiguration {} + + private[cake] val applicationContext = new FunctionalConfigApplicationContext() + applicationContext.registerConfigurations(configuration) + + private[cake] val beanFunctions = ListBuffer[() => _ <: Any]() + + private[cake] def registerFunctionalBeanDefinition[T](beanName: String, aliases: Seq[String], + scope: String, lazyInit: Boolean) + (beanFunction: () => T) + (implicit manifest: Manifest[T]): () => T = { + configuration.registerBean(beanName, aliases, scope, lazyInit, beanFunction, manifest) + if (beanName.isEmpty) + () => applicationContext.getBean(manifestToClass(manifest)) + else + () => applicationContext.bean(beanName).get + } + +} \ No newline at end of file diff --git a/src/main/scala/org/springframework/scala/context/function/cake/CakeLifecycle.scala b/src/main/scala/org/springframework/scala/context/function/cake/CakeLifecycle.scala new file mode 100644 index 0000000..bb70848 --- /dev/null +++ b/src/main/scala/org/springframework/scala/context/function/cake/CakeLifecycle.scala @@ -0,0 +1,7 @@ +package org.springframework.scala.context.function.cake + +trait CakeLifecycle { self: Cake => + + protected def startApplicationContext() + +} diff --git a/src/main/scala/org/springframework/scala/context/function/cake/CakeSupport.scala b/src/main/scala/org/springframework/scala/context/function/cake/CakeSupport.scala new file mode 100644 index 0000000..3eebc12 --- /dev/null +++ b/src/main/scala/org/springframework/scala/context/function/cake/CakeSupport.scala @@ -0,0 +1,19 @@ +package org.springframework.scala.context.function.cake + +import org.springframework.beans.factory.config.ConfigurableBeanFactory + + +trait CakeSupport extends CakeApplicationContext { + + protected def bean[T](beanName: String = "", aliases: Seq[String] = Seq(), + scope: String = ConfigurableBeanFactory.SCOPE_SINGLETON, lazyInit: Boolean = true) + (beanFunction: => T)(implicit manifest: Manifest[T]): () => T = { + registerFunctionalBeanDefinition(beanName, aliases, scope, lazyInit)(beanFunction _) + } + + protected def singleton[T](beanFunction: => T)(implicit manifest: Manifest[T]): () => T = { + bean()(beanFunction) + } + +} + diff --git a/src/main/scala/org/springframework/scala/context/function/cake/FunctionalConfigurationSupport.scala b/src/main/scala/org/springframework/scala/context/function/cake/FunctionalConfigurationSupport.scala new file mode 100644 index 0000000..e0ffa2f --- /dev/null +++ b/src/main/scala/org/springframework/scala/context/function/cake/FunctionalConfigurationSupport.scala @@ -0,0 +1,14 @@ +package org.springframework.scala.context.function.cake + +import org.springframework.scala.context.function.FunctionalConfiguration + +trait FunctionalConfigurationSupport extends CakeLifecycle { self: Cake => + + def configurationClass : Class[_ <: FunctionalConfiguration] + + abstract override protected def startApplicationContext() { + _configurationClasses += configurationClass + super.startApplicationContext() + } + +} diff --git a/src/main/scala/org/springframework/scala/context/function/cake/FunctionalConfigurationsSupport.scala b/src/main/scala/org/springframework/scala/context/function/cake/FunctionalConfigurationsSupport.scala new file mode 100644 index 0000000..23b35db --- /dev/null +++ b/src/main/scala/org/springframework/scala/context/function/cake/FunctionalConfigurationsSupport.scala @@ -0,0 +1,14 @@ +package org.springframework.scala.context.function.cake + +import org.springframework.scala.context.function.FunctionalConfiguration + +trait FunctionalConfigurationsSupport extends CakeLifecycle { self: Cake => + + def configurationClasses : Seq[Class[_ <: FunctionalConfiguration]] + + abstract override protected def startApplicationContext() { + _configurationClasses ++= configurationClasses + super.startApplicationContext() + } + +} diff --git a/src/test/scala/org/springframework/scala/context/function/cake/CakeObject.scala b/src/test/scala/org/springframework/scala/context/function/cake/CakeObject.scala new file mode 100644 index 0000000..387515f --- /dev/null +++ b/src/test/scala/org/springframework/scala/context/function/cake/CakeObject.scala @@ -0,0 +1,10 @@ +package org.springframework.scala.context.function.cake + +object CakeObject extends Cake with FunctionalConfigurationSupport + with ServiceComponent with DataAccessComponent { + + def configurationClass = classOf[TestFunctionalConfiguration] + + val dao = singleton(new ProductionDao) + +} diff --git a/src/test/scala/org/springframework/scala/context/function/cake/CakeTests.scala b/src/test/scala/org/springframework/scala/context/function/cake/CakeTests.scala new file mode 100644 index 0000000..aff5607 --- /dev/null +++ b/src/test/scala/org/springframework/scala/context/function/cake/CakeTests.scala @@ -0,0 +1,113 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.scala.context.function.cake + +import org.scalatest.{GivenWhenThen, FunSuite} +import org.junit.runner.RunWith +import org.scalatest.junit.JUnitRunner +import org.scalatest.matchers.ShouldMatchers +import org.springframework.scala.beans.factory.BeanFactoryConversions._ +import org.springframework.scala.beans.factory.RichListableBeanFactory + +@RunWith(classOf[JUnitRunner]) +class CakeTests extends FunSuite with GivenWhenThen with ShouldMatchers { + + test("should access dependencies via cake") { + Given("cake configuration") + val cake = new Cake with ServiceComponent with ProductionDataAccessComponent + + When("dependencies are fetched from cake") + val service = cake.service() + val dao = cake.dao() + + Then("cake should provide all dependencies") + service should not be (null) + dao should not be (null) + } + + test("should access dependencies via ApplicationContext") { + Given("cake configuration") + val cake = new Cake with ServiceComponent with ProductionDataAccessComponent + + When("dependencies are fetched from ApplicationContext") + val context: RichListableBeanFactory = cake.context + val service = context[Service] + val dao = context[Dao] + + Then("context should provide all dependencies") + service should not be (null) + dao should not be (null) + } + + test("cake and applicationContext should return the same singletons") { + Given("cake configuration") + val cake = new Cake with ServiceComponent with ProductionDataAccessComponent + + When("dependencies are fetched") + val context: RichListableBeanFactory = cake.context + val serviceFromContext = context[Service] + val daoFromContext = context[Dao] + val serviceFromCake = cake.service() + val daoFromCake = cake.dao() + + Then("dependencies from cake and applicationContext should be the same") + serviceFromContext should be theSameInstanceAs (serviceFromCake) + daoFromContext should be theSameInstanceAs (daoFromCake) + } + + test("the same DAO should be injected and provided as top level bean") { + Given("cake configuration") + val cake = new Cake with ServiceComponent with ProductionDataAccessComponent + + When("dependencies are fetched") + val topLevelDao = cake.dao() + val injectedDao = cake.service().dao + + Then("injected DAO should be the same as top level bean") + topLevelDao should be theSameInstanceAs (injectedDao) + } + + test("should inject dependencies into global configuration") { + When("dependencies are fetched") + val dao = CakeObject.dao() + val service = CakeObject.service() + + Then("dependencies should not be injected") + dao should not be (null) + service should not be (null) + } + + test("should include additional configuration in the global cake") { + Given("global application context has been created") + val context: RichListableBeanFactory = CakeObject.context + + When("String bean is fetched") + val fooString = context[String] + + Then("String bean should not be null") + fooString should equal(TestFunctionalConfiguration.fooString) + } + + test("(cake object) the same DAO should be injected and provided as top level bean") { + When("dependencies are fetched") + val topLevelDao = CakeObject.dao() + val injectedDao = CakeObject.service().dao + + Then("injected DAO should be the same as top level bean") + topLevelDao should be theSameInstanceAs (injectedDao) + } + +} \ No newline at end of file diff --git a/src/test/scala/org/springframework/scala/context/function/cake/Dao.scala b/src/test/scala/org/springframework/scala/context/function/cake/Dao.scala new file mode 100644 index 0000000..3ea7c79 --- /dev/null +++ b/src/test/scala/org/springframework/scala/context/function/cake/Dao.scala @@ -0,0 +1,3 @@ +package org.springframework.scala.context.function.cake + +trait Dao diff --git a/src/test/scala/org/springframework/scala/context/function/cake/DataAccessComponent.scala b/src/test/scala/org/springframework/scala/context/function/cake/DataAccessComponent.scala new file mode 100644 index 0000000..1911d5f --- /dev/null +++ b/src/test/scala/org/springframework/scala/context/function/cake/DataAccessComponent.scala @@ -0,0 +1,7 @@ +package org.springframework.scala.context.function.cake + +trait DataAccessComponent extends CakeSupport { + + val dao : () => Dao + +} \ No newline at end of file diff --git a/src/test/scala/org/springframework/scala/context/function/cake/ProductionDataAccessComponent.scala b/src/test/scala/org/springframework/scala/context/function/cake/ProductionDataAccessComponent.scala new file mode 100644 index 0000000..cedd1ed --- /dev/null +++ b/src/test/scala/org/springframework/scala/context/function/cake/ProductionDataAccessComponent.scala @@ -0,0 +1,9 @@ +package org.springframework.scala.context.function.cake + +trait ProductionDataAccessComponent extends DataAccessComponent { + + val dao = singleton(new ProductionDao) + +} + +class ProductionDao extends Dao \ No newline at end of file diff --git a/src/test/scala/org/springframework/scala/context/function/cake/Service.scala b/src/test/scala/org/springframework/scala/context/function/cake/Service.scala new file mode 100644 index 0000000..978910d --- /dev/null +++ b/src/test/scala/org/springframework/scala/context/function/cake/Service.scala @@ -0,0 +1,3 @@ +package org.springframework.scala.context.function.cake + +case class Service(dao: Dao) \ No newline at end of file diff --git a/src/test/scala/org/springframework/scala/context/function/cake/ServiceComponent.scala b/src/test/scala/org/springframework/scala/context/function/cake/ServiceComponent.scala new file mode 100644 index 0000000..e503657 --- /dev/null +++ b/src/test/scala/org/springframework/scala/context/function/cake/ServiceComponent.scala @@ -0,0 +1,7 @@ +package org.springframework.scala.context.function.cake + +trait ServiceComponent extends CakeSupport { this: DataAccessComponent => + + val service = singleton(new Service(dao())) + +} diff --git a/src/test/scala/org/springframework/scala/context/function/cake/TestFunctionalConfiguration.scala b/src/test/scala/org/springframework/scala/context/function/cake/TestFunctionalConfiguration.scala new file mode 100644 index 0000000..9212c00 --- /dev/null +++ b/src/test/scala/org/springframework/scala/context/function/cake/TestFunctionalConfiguration.scala @@ -0,0 +1,15 @@ +package org.springframework.scala.context.function.cake + +import org.springframework.scala.context.function.FunctionalConfiguration + +class TestFunctionalConfiguration extends FunctionalConfiguration { + + bean("fooString")("fooString") + +} + +object TestFunctionalConfiguration { + + val fooString = "fooString" + +}