Skip to content
This repository has been archived by the owner on Apr 14, 2022. It is now read-only.

Basic support for Cake pattern based configuration. #17

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
@@ -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()
}

}
Original file line number Diff line number Diff line change
@@ -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
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.springframework.scala.context.function.cake

trait CakeLifecycle { self: Cake =>

protected def startApplicationContext()

}
Original file line number Diff line number Diff line change
@@ -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)
}

}

Original file line number Diff line number Diff line change
@@ -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()
}

}
Original file line number Diff line number Diff line change
@@ -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()
}

}
Original file line number Diff line number Diff line change
@@ -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)

}
Original file line number Diff line number Diff line change
@@ -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)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package org.springframework.scala.context.function.cake

trait Dao
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.springframework.scala.context.function.cake

trait DataAccessComponent extends CakeSupport {

val dao : () => Dao

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.springframework.scala.context.function.cake

trait ProductionDataAccessComponent extends DataAccessComponent {

val dao = singleton(new ProductionDao)

}

class ProductionDao extends Dao
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package org.springframework.scala.context.function.cake

case class Service(dao: Dao)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.springframework.scala.context.function.cake

trait ServiceComponent extends CakeSupport { this: DataAccessComponent =>

val service = singleton(new Service(dao()))

}
Original file line number Diff line number Diff line change
@@ -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"

}