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

Reading application properties #29

Open
wants to merge 6 commits 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,128 @@
/*
* 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.beans.factory.config

import org.springframework.context.EmbeddedValueResolverAware
import org.springframework.util.StringValueResolver
import scala.collection.immutable.StringLike
import scala.language.dynamics

/**
* Beans extending this trait have automatic access to application's properties.
* Trait introduces variable `$` of type [[org.springframework.scala.beans.factory.config.DynamicPropertyResource]]
* which allows easy access to the properties.
*
* __''Important notice:''__ Because of the fact that [[http://static.springsource.org/spring/docs/3.2.x/javadoc-api/org/springframework/util/StringValueResolver.html StringValueResolver]]
* is injected to the trait after the bean is constructed, references to properties can
* only be used in lazy initialized value definitions or in methods.
*
* == Referencing properties ==
*
* There are two ways of reading property value:
*
* 1. by string representation passed to `$` - for example `$("my.property.name")`
* 1. by dynamic properties of `$` - for example `$.my.property.name` (which is equivalent to point 1.)
*
* Examples:
* {{{
* lazy val version:String = $.app.version
* lazy val revision:Int = $("app.revision").toInt
* lazy val debugMode:Boolean = $.`throw`.spear.toBoolean
* }}}
* In both cases, the value of the expression is of type [[org.springframework.scala.beans.factory.config.Property]]
* which, when needed, is implicitly converted to [[http://docs.oracle.com/javase/7/docs/api/java/lang/String.html String]].
* What's more, [[org.springframework.scala.beans.factory.config.Property]]
* extends [[http://www.scala-lang.org/api/current/#scala.collection.immutable.StringLike StringLike]]
* trait giving it additional type conversion capabilities by `toBoolean()`, `toByte()`, `toShort()`,
* `toInt()`, `toLong()`, `toFloat()` and `toDouble()` methods.
*
* __''Important notice:''__ When property key contains scala keywords, they should be
* surrounded with backticks when using second access method mentioned above. Example:
* `$.`throw`.`new`.exception` which is equivalent to `$("throw.new.exception")`.
*
* When property is not found, default Spring action that occurs when accessing nonexistent
* properties is triggered (for example may throw [[http://docs.oracle.com/javase/7/docs/api/java/lang/IllegalArgumentException.html IllegalArgumentException]]
* or return unescaped value like `${my.property.name}`).
*
* @author Maciej Zientarski
* @since 1.0
*/
trait PropertiesAware extends EmbeddedValueResolverAware {
protected[config] var $: DynamicPropertyResource = new NotConfiguredDynamicPropertyResource

implicit def propertyToString(property: Property) = property.toString

override def setEmbeddedValueResolver(resolver: StringValueResolver) {
$ = new DynamicPropertyResource(resolver)
}
}

/**
* Represents `key` and `value` of application property. Returned by
* [[org.springframework.scala.beans.factory.config.DynamicPropertyResource]].
* Provides utility methods to convert `value` `toBoolean()`, `toByte()`, `toShort()`,
* `toInt()`, `toLong()`, `toFloat()`, `toDouble()` and `toString()`.
*
* If `value` for the `key` is not defined then [[http://docs.oracle.com/javase/7/docs/api/java/lang/IllegalArgumentException.html IllegalArgumentException]]
* is thrown on type conversion attempt.
*
* @author Maciej Zientarski
* @since 1.0
*/
class Property(key: String, val value: Option[String], tryToResolve: (String => Property)) extends Dynamic with StringLike[Property] {

def selectDynamic(subKey: String) = tryToResolve(s"$key.$subKey")

override protected[this] def newBuilder = null

override def seq: IndexedSeq[Char] = toString

override def slice(from: Int, until: Int) = new Property(key, Option(toString.slice(from, until)), tryToResolve)

override def toString = value.getOrElse(throw new IllegalArgumentException(s"Could not resolve placeholder '$key'"))
}

/**
* @author Maciej Zientarski
* @since 1.0
*/
private[config] class DynamicPropertyResource(resolver: StringValueResolver) extends Dynamic {
private def tryToResolve(name: String): Property = {
val resolved = Option(
try {
resolver.resolveStringValue("${%s}".format(name))
} catch {
case iae: IllegalArgumentException => null
case e: Exception => throw e
}
)

new Property(name, resolved, tryToResolve)
}

def selectDynamic(path: String) = tryToResolve(path)

def apply(path: String) = tryToResolve(path)
}

private[config] class NotConfiguredDynamicPropertyResource extends DynamicPropertyResource(null) {
private val message = "StringValueResolver was not injected yet. You should reference properties by lazy-initialized value definitions or inside functions."

override def selectDynamic(path: String) = throw new IllegalStateException(message)

override def apply(path: String) = throw new IllegalStateException(message)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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.beans.factory.config

import org.springframework.scala.context.function.FunctionalConfiguration
import org.springframework.context.support.GenericApplicationContext
import org.springframework.beans.factory.support.BeanNameGenerator

/**
* Defines additional [[org.springframework.scala.context.function.FunctionalConfiguration]]
* elements to simplify access to application properties.
*
* Introduces variable `$` of type [[org.springframework.scala.beans.factory.config.DynamicPropertyResource]]
* which can be used to read application properties in one of the following ways:
*
* 1. referencing by string - for example `$("logs.folder")`
* 1. referencing by dynamic properties of [[org.springframework.scala.beans.factory.config.DynamicPropertyResource]]
* and [[org.springframework.scala.beans.factory.config.Property]] - for example `$.logs.folder`.
*
* For more information see `Referencing properties` section of [[org.springframework.scala.beans.factory.config.PropertiesAware]] scaladoc.
*
* Note that this trait does not configure properties source, it only provides convenient
* way for accessing them.
*
* @author Maciej Zientarski
* @since 1.0
*/
trait PropertiesResolver {
this: FunctionalConfiguration =>

implicit def propertyToString(property: Property) = property.toString

onRegister((applicationContext: GenericApplicationContext,
beanNameGenerator: BeanNameGenerator) => {
bean("dynamicPropertyResolver") {
new PropertiesAware() {}
}
})

lazy val $ = (() => getBean[PropertiesAware]("dynamicPropertyResolver"))().$
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/*
* 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.beans.factory.config

import org.scalatest.FunSuite
import org.springframework.beans.factory.BeanCreationException

/**
* @author Maciej Zientarski
* @since 1.0
*/
class PropertiesAwareTest extends FunSuite with PropertiesTestUtils {
test("get property by string") {
//given test class
class TestedBean extends PropertiesAware {
def getProperty(path: String): String = $(path);
}

//and context that initializes the class
val context = new TestConfig {
withProperties("we.are.the" -> "champions")

bean("testedBean") {
new TestedBean()
}
}

//when context is registered
registerContext(context)

//and bean is found
val testedBean = applicationContext.getBean("testedBean", classOf[TestedBean])

//then it is possible to read properties
assert("champions".equals(testedBean.getProperty("we.are.the")))
}

test("get property by dynamic properties") {
//given test class
class TestedBean extends PropertiesAware {
lazy val theProperty: String = $.easy.come
}

//and context that initializes the class
val context = new TestConfig {
withProperties("easy.come" -> "easy go")

bean("testedBean") {
new TestedBean()
}
}

//when context is registered
registerContext(context)

//and bean is found
val testedBean = applicationContext.getBean("testedBean", classOf[TestedBean])

//then it is possible to read properties
assert("easy go".equals(testedBean.theProperty))
}

test("dynamic properties casting") {
//given test class
class TestedBean extends PropertiesAware {
lazy val int: Int = $.easy.int.toInt

lazy val float: Float = $.easy.float.toFloat

lazy val boolean: Boolean = $.easy.boolean.toBoolean
}

//and context that initializes the class
val context = new TestConfig {
withProperties(
"easy.int" -> "17",
"easy.float" -> "1.3",
"easy.boolean" -> "true"
)

bean("testedBean") {
new TestedBean()
}
}

//when context is registered
registerContext(context)

//and bean is found
val testedBean = applicationContext.getBean("testedBean", classOf[TestedBean])

//then it is possible to read properties
assert(17 === testedBean.int)
assert(true === testedBean.boolean)
assert(1.3f === testedBean.float)
}

test("get nonexistent property") {
//given test class
class TestedBean extends PropertiesAware {
lazy val someInt = $.i.am.not.here.toInt
}

//and context that initializes the class
val context = new TestConfig {
withProperties("we.are.the" -> "champions")

bean("testedBean") {
new TestedBean()
}
}

//when context is registered
registerContext(context)

//and bean is found
val testedBean = applicationContext.getBean("testedBean", classOf[TestedBean])
intercept[IllegalArgumentException] {
testedBean.someInt
}
}

test("no property resolver defined") {
//given test class
class TestedBean extends PropertiesAware {
lazy val someProperty: String = $.i.am.not.here
}

//and context that initializes the class
val context = new TestConfig {
bean("testedBean") {
new TestedBean()
}
}

//when context is registered
registerContext(context)

//and bean is found
val testedBean = applicationContext.getBean("testedBean", classOf[TestedBean])
assert("${i.am.not.here}".equals(testedBean.someProperty))
}

test("val instead of lazy val - by dynamic properties") {
//given test class
class TestedBean extends PropertiesAware {
val someProperty: String = $.i.am.not.here
}

//and context that initializes the class
val context = new TestConfig {
bean("testedBean") {
new TestedBean()
}
}

//then
intercept[BeanCreationException] {
registerContext(context)
}
}

test("val instead of lazy val - by string") {
//given test class
class TestedBean extends PropertiesAware {
val someProperty: String = $("i.am.not.here")
}

//and context that initializes the class
val context = new TestConfig {
bean("testedBean") {
new TestedBean()
}
}

//then
intercept[BeanCreationException] {
registerContext(context)
}
}
}

Loading