From e6940ef66ff69a530f9d552fe54b3356d98ced7f Mon Sep 17 00:00:00 2001 From: Ramnivas Laddad Date: Sat, 27 Jul 2013 10:39:35 -0700 Subject: [PATCH 1/4] Removed conflicting overloaded methods from RestTemplate RestTemplate consisted of methods of the following form (getFor..., postFor..., etc.) def m(...) and def m(..., uriVariables: Any*) This made it impossible to call methods without passing any uriVariables, since the compiler cannot determine which of the overloaded method is intended. In any case, there is no need for the non-varargs methods; users can just call the varargs version passing no arguments for the uriVariables parameter. --- .../scala/web/client/RestTemplate.scala | 139 +----------------- 1 file changed, 1 insertion(+), 138 deletions(-) diff --git a/src/main/scala/org/springframework/scala/web/client/RestTemplate.scala b/src/main/scala/org/springframework/scala/web/client/RestTemplate.scala index 68beb10..10b868e 100644 --- a/src/main/scala/org/springframework/scala/web/client/RestTemplate.scala +++ b/src/main/scala/org/springframework/scala/web/client/RestTemplate.scala @@ -30,6 +30,7 @@ import scala.reflect.ClassTag * advantage of functions and Scala types. * * @author Arjen Poutsma + * @author Ramnivas Laddad * @since 1.0 * @constructor Creates a `RestTemplate` that wraps the given Java template, defaulting to the standard `RestTemplate` * @param javaTemplate the Java `RestTemplate` to wrap @@ -75,17 +76,6 @@ class RestTemplate(val javaTemplate: org.springframework.web.client.RestOperatio Option(javaTemplate.getForObject(url, typeToClass[T], uriVariables.asJava)) } - /** - * Retrieve a representation by doing a GET on the URL. - * The response (if any) is converted and returned. - * - * @param url the URL - * @return the converted object - */ - def getForAny[T: ClassTag](url: URI): Option[T] = { - Option(javaTemplate.getForObject(url, typeToClass[T])) - } - /** * Retrieve an entity by doing a GET on the specified URL. * The response is converted and stored in an `ResponseEntity`. @@ -112,15 +102,6 @@ class RestTemplate(val javaTemplate: org.springframework.web.client.RestOperatio javaTemplate.getForEntity(url, typeToClass[T], uriVariables.asJava) } - /** - * Retrieve a representation by doing a GET on the URL . - * The response is converted and stored in an [[org.springframework.http.ResponseEntity]]}. - * @param url the URL - * @return the converted object - */ - def getForEntity[T: ClassTag](url: URI): ResponseEntity[T] = { - javaTemplate.getForEntity(url, typeToClass[T]) - } // HEAD /** @@ -149,14 +130,6 @@ class RestTemplate(val javaTemplate: org.springframework.web.client.RestOperatio javaTemplate.headForHeaders(url, uriVariables.asJava) } - /** - * Retrieve all headers of the resource specified by the URL. - * @param url the URL - * @return all HTTP headers of that resource - */ - def headForHeaders(url: URI): HttpHeaders = { - javaTemplate.headForHeaders(url) - } // POST /** @@ -199,22 +172,6 @@ class RestTemplate(val javaTemplate: org.springframework.web.client.RestOperatio javaTemplate.postForLocation(url, request.orNull, uriVariables.asJava) } - /** - * Create a new resource by POSTing the given object to the URL, and returns the value of the `Location` header. - * This header typically indicates where the new resource is stored. - * - * The `request` parameter can be a [[org.springframework.http.HttpEntity]] in order to add additional HTTP headers - * to the request. - * - * @param url the URL - * @param request the Object to be POSTed - * @return the value for the `Location` header - * @see HttpEntity - */ - def postForLocation(url: URI, request: Option[Any]): URI = { - javaTemplate.postForLocation(url, request.orNull) - } - /** * Create a new resource by POSTing the given object to the URI template, and returns the representation found in * the response. @@ -253,22 +210,6 @@ class RestTemplate(val javaTemplate: org.springframework.web.client.RestOperatio Option(javaTemplate.postForObject(url, request.orNull, typeToClass[T], uriVariables.asJava)) } - /** - * Create a new resource by POSTing the given object to the URL, and returns the representation found in the - * response. - * - * The `request` parameter can be a [[org.springframework.http.HttpEntity]] in order to add additional HTTP headers - * to the request. - * - * @param url the URL - * @param request the Object to be POSTed - * @return the converted object - * @see HttpEntity - */ - def postForObject[T: ClassTag](url: URI, request: Option[Any]): Option[T] = { - Option(javaTemplate.postForObject(url, request.orNull, typeToClass[T])) - } - /** * Create a new resource by POSTing the given object to the URI template, and returns the response as * [[org.springframework.http.ResponseEntity]]. @@ -307,23 +248,6 @@ class RestTemplate(val javaTemplate: org.springframework.web.client.RestOperatio javaTemplate.postForEntity(url, request.orNull, typeToClass[T], uriVariables.asJava) } - /** - * Create a new resource by POSTing the given object to the URL, and returns the response as - * [[org.springframework.http.ResponseEntity]]. - * - * The `request` parameter can be a [[org.springframework.http.HttpEntity]] in order to add additional HTTP headers - * to the request. - * - * @param url the URL - * @param request the Object to be POSTed, may be `null` - * @return the converted object - * @see HttpEntity - * @since 3.0.2 - */ - def postForEntity[T: ClassTag](url: URI, request: Option[Any]): ResponseEntity[T] = { - javaTemplate.postForEntity(url, request.orNull, typeToClass[T]) - } - // PUT /** * Create or update a resource by PUTting the given object to the URI. @@ -359,19 +283,6 @@ class RestTemplate(val javaTemplate: org.springframework.web.client.RestOperatio javaTemplate.put(url, request.orNull, uriVariables.asJava) } - /** - * Creates a new resource by PUTting the given object to URL. - * - * The `request` parameter can be a [[org.springframework.http.HttpEntity]] in order to add additional HTTP headers - * to the request. - * - * @param url the URL - * @param request the Object to be PUT - * @see HttpEntity - */ - def put(url: URI, request: Option[Any]) { - javaTemplate.put(url, request.orNull) - } // DELETE /** @@ -397,14 +308,6 @@ class RestTemplate(val javaTemplate: org.springframework.web.client.RestOperatio javaTemplate.delete(url, uriVariables.asJava) } - /** - * Delete the resources at the specified URL. - * - * @param url the URL - */ - def delete(url: URI) { - javaTemplate.delete(url) - } // OPTIONS /** @@ -433,16 +336,6 @@ class RestTemplate(val javaTemplate: org.springframework.web.client.RestOperatio javaTemplate.optionsForAllow(url, uriVariables.asJava).asScala } - /** - * Return the value of the Allow header for the given URL. - * - * @param url the URL - * @return the value of the allow header - */ - def optionsForAllow(url: URI): Set[HttpMethod] = { - javaTemplate.optionsForAllow(url).asScala - } - // exchange /** * Execute the HTTP method to the given URI template, writing the given request entity to the request, and @@ -477,18 +370,6 @@ class RestTemplate(val javaTemplate: org.springframework.web.client.RestOperatio javaTemplate.exchange(url, method, requestEntity.orNull, typeToClass[T], uriVariables.asJava) } - /** - * Execute the HTTP method to the given URI template, writing the given request entity to the request, and - * returns the response as `ResponseEntity`. - * - * @param url the URL - * @param method the HTTP method (GET, POST, etc) - * @param requestEntity the entity (headers and/or body) to write to the request - * @return the response as entity - */ - def exchange[T: ClassTag](url: URI, method: HttpMethod, requestEntity: Option[HttpEntity[_]]): ResponseEntity[T] = { - javaTemplate.exchange(url, method, requestEntity.orNull, typeToClass[T]) - } // general execution /** @@ -533,24 +414,6 @@ class RestTemplate(val javaTemplate: org.springframework.web.client.RestOperatio uriVariables.asJava)) } - /** - * Execute the HTTP method to the given URL, preparing the request with the given function, and reading the response - * with a function. - * - * @param url the URL - * @param method the HTTP method (GET, POST, etc) - * @param requestFunction function that prepares the request - * @param responseFunction function that extracts the return value from the response - * @return an arbitrary object, as returned by the response object - */ - def execute[T](url: URI, method: HttpMethod) - (requestFunction: ClientHttpRequest => Unit) - (responseFunction: ClientHttpResponse => T): Option[T] = { - Option(javaTemplate.execute(url, method, functionToRequestCallback(requestFunction), - functionToResponseExtractor(responseFunction))) - - } - private def asInstanceOfAnyRef(seq: Seq[Any]) = { seq.map(_.asInstanceOf[AnyRef]) } From 88a2e41c996f271b6f108495c70b47d7c909ae66 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Mon, 29 Jul 2013 10:42:29 +0200 Subject: [PATCH 2/4] Revert "Merge pull request #28 from ramnivas/fix-RestTemple" This reverts commit e3586d8379cfa3b2b9f44f84150751eca587d124, reversing changes made to fbfc885a794e621f56ab2ccca41b7fe8e00eeeaf. --- .../scala/web/client/RestTemplate.scala | 139 +++++++++++++++++- 1 file changed, 138 insertions(+), 1 deletion(-) diff --git a/src/main/scala/org/springframework/scala/web/client/RestTemplate.scala b/src/main/scala/org/springframework/scala/web/client/RestTemplate.scala index 10b868e..68beb10 100644 --- a/src/main/scala/org/springframework/scala/web/client/RestTemplate.scala +++ b/src/main/scala/org/springframework/scala/web/client/RestTemplate.scala @@ -30,7 +30,6 @@ import scala.reflect.ClassTag * advantage of functions and Scala types. * * @author Arjen Poutsma - * @author Ramnivas Laddad * @since 1.0 * @constructor Creates a `RestTemplate` that wraps the given Java template, defaulting to the standard `RestTemplate` * @param javaTemplate the Java `RestTemplate` to wrap @@ -76,6 +75,17 @@ class RestTemplate(val javaTemplate: org.springframework.web.client.RestOperatio Option(javaTemplate.getForObject(url, typeToClass[T], uriVariables.asJava)) } + /** + * Retrieve a representation by doing a GET on the URL. + * The response (if any) is converted and returned. + * + * @param url the URL + * @return the converted object + */ + def getForAny[T: ClassTag](url: URI): Option[T] = { + Option(javaTemplate.getForObject(url, typeToClass[T])) + } + /** * Retrieve an entity by doing a GET on the specified URL. * The response is converted and stored in an `ResponseEntity`. @@ -102,6 +112,15 @@ class RestTemplate(val javaTemplate: org.springframework.web.client.RestOperatio javaTemplate.getForEntity(url, typeToClass[T], uriVariables.asJava) } + /** + * Retrieve a representation by doing a GET on the URL . + * The response is converted and stored in an [[org.springframework.http.ResponseEntity]]}. + * @param url the URL + * @return the converted object + */ + def getForEntity[T: ClassTag](url: URI): ResponseEntity[T] = { + javaTemplate.getForEntity(url, typeToClass[T]) + } // HEAD /** @@ -130,6 +149,14 @@ class RestTemplate(val javaTemplate: org.springframework.web.client.RestOperatio javaTemplate.headForHeaders(url, uriVariables.asJava) } + /** + * Retrieve all headers of the resource specified by the URL. + * @param url the URL + * @return all HTTP headers of that resource + */ + def headForHeaders(url: URI): HttpHeaders = { + javaTemplate.headForHeaders(url) + } // POST /** @@ -172,6 +199,22 @@ class RestTemplate(val javaTemplate: org.springframework.web.client.RestOperatio javaTemplate.postForLocation(url, request.orNull, uriVariables.asJava) } + /** + * Create a new resource by POSTing the given object to the URL, and returns the value of the `Location` header. + * This header typically indicates where the new resource is stored. + * + * The `request` parameter can be a [[org.springframework.http.HttpEntity]] in order to add additional HTTP headers + * to the request. + * + * @param url the URL + * @param request the Object to be POSTed + * @return the value for the `Location` header + * @see HttpEntity + */ + def postForLocation(url: URI, request: Option[Any]): URI = { + javaTemplate.postForLocation(url, request.orNull) + } + /** * Create a new resource by POSTing the given object to the URI template, and returns the representation found in * the response. @@ -210,6 +253,22 @@ class RestTemplate(val javaTemplate: org.springframework.web.client.RestOperatio Option(javaTemplate.postForObject(url, request.orNull, typeToClass[T], uriVariables.asJava)) } + /** + * Create a new resource by POSTing the given object to the URL, and returns the representation found in the + * response. + * + * The `request` parameter can be a [[org.springframework.http.HttpEntity]] in order to add additional HTTP headers + * to the request. + * + * @param url the URL + * @param request the Object to be POSTed + * @return the converted object + * @see HttpEntity + */ + def postForObject[T: ClassTag](url: URI, request: Option[Any]): Option[T] = { + Option(javaTemplate.postForObject(url, request.orNull, typeToClass[T])) + } + /** * Create a new resource by POSTing the given object to the URI template, and returns the response as * [[org.springframework.http.ResponseEntity]]. @@ -248,6 +307,23 @@ class RestTemplate(val javaTemplate: org.springframework.web.client.RestOperatio javaTemplate.postForEntity(url, request.orNull, typeToClass[T], uriVariables.asJava) } + /** + * Create a new resource by POSTing the given object to the URL, and returns the response as + * [[org.springframework.http.ResponseEntity]]. + * + * The `request` parameter can be a [[org.springframework.http.HttpEntity]] in order to add additional HTTP headers + * to the request. + * + * @param url the URL + * @param request the Object to be POSTed, may be `null` + * @return the converted object + * @see HttpEntity + * @since 3.0.2 + */ + def postForEntity[T: ClassTag](url: URI, request: Option[Any]): ResponseEntity[T] = { + javaTemplate.postForEntity(url, request.orNull, typeToClass[T]) + } + // PUT /** * Create or update a resource by PUTting the given object to the URI. @@ -283,6 +359,19 @@ class RestTemplate(val javaTemplate: org.springframework.web.client.RestOperatio javaTemplate.put(url, request.orNull, uriVariables.asJava) } + /** + * Creates a new resource by PUTting the given object to URL. + * + * The `request` parameter can be a [[org.springframework.http.HttpEntity]] in order to add additional HTTP headers + * to the request. + * + * @param url the URL + * @param request the Object to be PUT + * @see HttpEntity + */ + def put(url: URI, request: Option[Any]) { + javaTemplate.put(url, request.orNull) + } // DELETE /** @@ -308,6 +397,14 @@ class RestTemplate(val javaTemplate: org.springframework.web.client.RestOperatio javaTemplate.delete(url, uriVariables.asJava) } + /** + * Delete the resources at the specified URL. + * + * @param url the URL + */ + def delete(url: URI) { + javaTemplate.delete(url) + } // OPTIONS /** @@ -336,6 +433,16 @@ class RestTemplate(val javaTemplate: org.springframework.web.client.RestOperatio javaTemplate.optionsForAllow(url, uriVariables.asJava).asScala } + /** + * Return the value of the Allow header for the given URL. + * + * @param url the URL + * @return the value of the allow header + */ + def optionsForAllow(url: URI): Set[HttpMethod] = { + javaTemplate.optionsForAllow(url).asScala + } + // exchange /** * Execute the HTTP method to the given URI template, writing the given request entity to the request, and @@ -370,6 +477,18 @@ class RestTemplate(val javaTemplate: org.springframework.web.client.RestOperatio javaTemplate.exchange(url, method, requestEntity.orNull, typeToClass[T], uriVariables.asJava) } + /** + * Execute the HTTP method to the given URI template, writing the given request entity to the request, and + * returns the response as `ResponseEntity`. + * + * @param url the URL + * @param method the HTTP method (GET, POST, etc) + * @param requestEntity the entity (headers and/or body) to write to the request + * @return the response as entity + */ + def exchange[T: ClassTag](url: URI, method: HttpMethod, requestEntity: Option[HttpEntity[_]]): ResponseEntity[T] = { + javaTemplate.exchange(url, method, requestEntity.orNull, typeToClass[T]) + } // general execution /** @@ -414,6 +533,24 @@ class RestTemplate(val javaTemplate: org.springframework.web.client.RestOperatio uriVariables.asJava)) } + /** + * Execute the HTTP method to the given URL, preparing the request with the given function, and reading the response + * with a function. + * + * @param url the URL + * @param method the HTTP method (GET, POST, etc) + * @param requestFunction function that prepares the request + * @param responseFunction function that extracts the return value from the response + * @return an arbitrary object, as returned by the response object + */ + def execute[T](url: URI, method: HttpMethod) + (requestFunction: ClientHttpRequest => Unit) + (responseFunction: ClientHttpResponse => T): Option[T] = { + Option(javaTemplate.execute(url, method, functionToRequestCallback(requestFunction), + functionToResponseExtractor(responseFunction))) + + } + private def asInstanceOfAnyRef(seq: Seq[Any]) = { seq.map(_.asInstanceOf[AnyRef]) } From ccb8fdbb6911272ce9b4841eb4d37547ca9d1708 Mon Sep 17 00:00:00 2001 From: Maciej Zientarski Date: Wed, 17 Jul 2013 23:03:00 +0200 Subject: [PATCH 3/4] Added TransactionSupport trait Added TransactionSupport trait that can be used to extend FunctionalConfiguration. enableTransactionManagement(...) function is equivalent to traditional . --- build.gradle | 1 + .../function/TransactionMode.scala | 50 ++++++ .../function/TransactionSupport.scala | 153 ++++++++++++++++ .../function/TransactionSupportTests.scala | 165 ++++++++++++++++++ 4 files changed, 369 insertions(+) create mode 100644 src/main/scala/org/springframework/scala/transaction/function/TransactionMode.scala create mode 100644 src/main/scala/org/springframework/scala/transaction/function/TransactionSupport.scala create mode 100644 src/test/scala/org/springframework/scala/transaction/function/TransactionSupportTests.scala diff --git a/build.gradle b/build.gradle index c65016c..17e2c11 100644 --- a/build.gradle +++ b/build.gradle @@ -44,6 +44,7 @@ dependencies { testCompile("junit:junit:4.10") testRuntime("org.hsqldb:hsqldb-j5:2.2.4") testRuntime("log4j:log4j:1.2.16") + testCompile("org.springframework:spring-aspects:$springVersion") } tasks.withType(ScalaCompile) { diff --git a/src/main/scala/org/springframework/scala/transaction/function/TransactionMode.scala b/src/main/scala/org/springframework/scala/transaction/function/TransactionMode.scala new file mode 100644 index 0000000..822a92c --- /dev/null +++ b/src/main/scala/org/springframework/scala/transaction/function/TransactionMode.scala @@ -0,0 +1,50 @@ +/* + * 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.transaction.function + +/** + * Base class for annotation-driven transaction manager modes: + * [[org.springframework.scala.transaction.function.AspectJTransactionMode]] + * and + * [[org.springframework.scala.transaction.function.ProxyTransactionMode]] + * + * @author Maciej Zientarski + * @since 1.0 + */ +abstract class TransactionMode + +/** + * AspectJ annotation-driven transaction manager mode. To be used as parameter of + * `org.springframework.scala.context.function.TransactionSupport.enableTransactionManagement()` + * + * @author Maciej Zientarski + * @since 1.0 + */ +case class AspectJTransactionMode() extends TransactionMode + +/** + * Spring's AOP framework proxy transaction mode. To be used as + * parameter of [[org.springframework.scala.transaction.function.TransactionSupport.enableTransactionManagement( )]] + * Equivalent to `mode="proxy"` attribute of ``. + * + * @param proxyTargetClass equivalent to `proxy-target-class="true|false"` attribute of + * ``. If set to true, then class based proxies are used. + * False means that standard JDK interface-based proxies should be created. + * @author Maciej Zientarski + * @since 1.0 + */ +case class ProxyTransactionMode(proxyTargetClass: Boolean = false) extends TransactionMode \ No newline at end of file diff --git a/src/main/scala/org/springframework/scala/transaction/function/TransactionSupport.scala b/src/main/scala/org/springframework/scala/transaction/function/TransactionSupport.scala new file mode 100644 index 0000000..93af360 --- /dev/null +++ b/src/main/scala/org/springframework/scala/transaction/function/TransactionSupport.scala @@ -0,0 +1,153 @@ +/* + * 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.transaction.function + +import org.springframework.scala.context.function.FunctionalConfiguration +import org.springframework.transaction.config.TransactionManagementConfigUtils +import org.springframework.beans.factory.support.{BeanNameGenerator, BeanDefinitionReaderUtils, RootBeanDefinition} +import org.springframework.beans.factory.parsing.BeanComponentDefinition +import org.springframework.beans.factory.config.{RuntimeBeanReference, BeanDefinition} +import org.springframework.aop.config.AopConfigUtils +import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource +import org.springframework.transaction.interceptor.{BeanFactoryTransactionAttributeSourceAdvisor, TransactionInterceptor} +import org.springframework.context.support.GenericApplicationContext + +/** + * Defines additional FunctionalConfiguration elements for transaction support . + * + * @author Maciej Zientarski + * @since 1.0 + * @see FunctionalConfiguration + */ +trait TransactionSupport { + self: FunctionalConfiguration => + + /** + * Enables annotation-driven transaction management. Equivalent to `` + * and [[org.springframework.transaction.annotation.EnableTransactionManagement]] annotation. + * Adds transactions around calls to methods annotated with @Transactional. + * Should be used as follows: + * {{{ + * class Config extends FunctionalConfiguration with TransactionSupport { + * enableTransactionManagement() + * } + * }}} + * which is equivalent to + * {{{ + * + * + * + * }}} + * and + * {{{ + * @Configuration + * @EnableTransactionManagement + * public class Config {} + * }}} + * + * @param transactionMode one of [[org.springframework.scala.context.function.AspectJTransactionMode]] + * or [[org.springframework.scala.context.function.ProxyTransactionMode]]. Defaults to + * `ProxyTransactionMode` + * @param transactionManagerName transaction manager bean name. + * Defaults to `TransactionSupport.DEFAULT_TRANSACTION_MANAGER_NAME` + * @param order order of transaction advice applied to `@Transactional` methods + */ + def enableTransactionManagement( + transactionMode: TransactionMode = ProxyTransactionMode(), + transactionManagerName: String = TransactionSupport.DEFAULT_TRANSACTION_MANAGER_NAME, + order: Int = org.springframework.core.Ordered.LOWEST_PRECEDENCE + ) { + + def setupAspectJTransactions() { + val txAspectBeanName = TransactionManagementConfigUtils.TRANSACTION_ASPECT_BEAN_NAME + val txAspectClassName = TransactionManagementConfigUtils.TRANSACTION_ASPECT_CLASS_NAME + + if (!beanRegistry.containsBeanDefinition(txAspectBeanName)) { + val rootBeanDefinition = new RootBeanDefinition() + rootBeanDefinition.setBeanClassName(txAspectClassName) + rootBeanDefinition.setFactoryMethodName("aspectOf") + rootBeanDefinition.getPropertyValues.add("transactionManagerBeanName", transactionManagerName) + BeanDefinitionReaderUtils.registerBeanDefinition( + new BeanComponentDefinition(rootBeanDefinition, txAspectBeanName), + beanRegistry + ) + } + } + + def setupProxyTransactions(proxyTargetClass: Boolean, beanNameGenerator: BeanNameGenerator) { + + def registerWithGeneratedName(beanDefinition: BeanDefinition) = { + val generatedName = beanNameGenerator.generateBeanName(beanDefinition, beanRegistry) + beanRegistry.registerBeanDefinition(generatedName, beanDefinition) + generatedName + } + + AopConfigUtils.registerAutoProxyCreatorIfNecessary(beanRegistry, null) + if (proxyTargetClass) { + AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(beanRegistry) + } + + if (!beanRegistry.containsBeanDefinition( + TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)) { + + // Create the TransactionAttributeSource definition. + val sourceDef = new RootBeanDefinition(Predef.classOf[AnnotationTransactionAttributeSource]) + sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE) + val sourceName = registerWithGeneratedName(sourceDef) + + // Create the TransactionInterceptor definition. + val interceptorDef = new RootBeanDefinition(Predef.classOf[TransactionInterceptor]) + interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE) + interceptorDef.getPropertyValues.add("transactionManagerBeanName", + transactionManagerName) + interceptorDef.getPropertyValues.add("transactionAttributeSource", + new RuntimeBeanReference(sourceName)) + val interceptorName = registerWithGeneratedName(interceptorDef) + + // Create the TransactionAttributeSourceAdvisor definition. + val advisorDef = new RootBeanDefinition(Predef.classOf[BeanFactoryTransactionAttributeSourceAdvisor]) + advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE) + advisorDef.getPropertyValues.add("transactionAttributeSource", + new RuntimeBeanReference(sourceName)) + advisorDef.getPropertyValues.add("advice", new RuntimeBeanReference(interceptorName)) + advisorDef.getPropertyValues.add("adviceBeanName", interceptorName) + advisorDef.getPropertyValues.add("order", order.toString) + beanRegistry.registerBeanDefinition( + TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME, advisorDef) + } + } + + onRegister((applicationContext: GenericApplicationContext, + beanNameGenerator: BeanNameGenerator) => + transactionMode match { + case ProxyTransactionMode(proxyTargetClass) => + setupProxyTransactions(proxyTargetClass, beanNameGenerator) + case _ => + setupAspectJTransactions() + }) + } +} + +/** + * Used to store default transaction manager name. + * + * @author Maciej Zientarski + * @since 1.0 + */ +object TransactionSupport { + val DEFAULT_TRANSACTION_MANAGER_NAME = "transactionManager" +} \ No newline at end of file diff --git a/src/test/scala/org/springframework/scala/transaction/function/TransactionSupportTests.scala b/src/test/scala/org/springframework/scala/transaction/function/TransactionSupportTests.scala new file mode 100644 index 0000000..80a96d8 --- /dev/null +++ b/src/test/scala/org/springframework/scala/transaction/function/TransactionSupportTests.scala @@ -0,0 +1,165 @@ +/* + * 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.transaction.function + +import org.junit.runner.RunWith +import org.scalatest.junit.JUnitRunner +import org.scalatest.{BeforeAndAfterEach, FunSuite} +import org.springframework.transaction.support.{DefaultTransactionStatus, AbstractPlatformTransactionManager, TransactionSynchronizationManager} +import org.springframework.transaction.annotation.Transactional +import org.springframework.transaction.TransactionDefinition +import org.springframework.aop.config.AopConfigUtils +import org.springframework.aop.framework.autoproxy.InfrastructureAdvisorAutoProxyCreator +import org.springframework.transaction.config.TransactionManagementConfigUtils +import org.springframework.scala.context.function.{FunctionalConfigApplicationContext, FunctionalConfiguration} + +/** + * @author Maciej Zientarski + */ +@RunWith(classOf[JUnitRunner]) +class TransactionSupportTests extends FunSuite with BeforeAndAfterEach { + var applicationContext: FunctionalConfigApplicationContext = _ + + override protected def beforeEach() { + applicationContext = new FunctionalConfigApplicationContext() + } + + test("enableTransactionManagement() default values") { + //given + val config = new FunctionalConfiguration with TransactionSupport { + + enableTransactionManagement() + + bean(TransactionSupport.DEFAULT_TRANSACTION_MANAGER_NAME) { + new DummyTransactionManager + } + } + + applicationContext.registerConfigurations(config) + + //then + val infrastructureAdvisorAutoProxyCreator = applicationContext.getBean( + AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME, + classOf[InfrastructureAdvisorAutoProxyCreator] + ) + assert(!infrastructureAdvisorAutoProxyCreator.isProxyTargetClass) + + assert(!applicationContext.containsBean( + TransactionManagementConfigUtils.TRANSACTION_ASPECT_BEAN_NAME)) + } + + test("enableTransactionManagement() makes @Transactional methods run in transaction") { + //given + val config = new FunctionalConfiguration with TransactionSupport { + + enableTransactionManagement() + + bean("frog") { + new Frog + } + bean(TransactionSupport.DEFAULT_TRANSACTION_MANAGER_NAME) { + new DummyTransactionManager + } + } + + applicationContext.registerConfigurations(config) + applicationContext.refresh() + + //when + val frog = applicationContext.getBean("frog", classOf[Frog]) + + //then + assert(frog.hasTransaction === true) + } + + test("enableTransactionManagement() with custom transaction manager name") { + val config = new FunctionalConfiguration with TransactionSupport { + val transactionManagerName = "myCustomTransactionManagerName" + + enableTransactionManagement(transactionManagerName = transactionManagerName) + + bean("frog") { + new Frog + } + bean(transactionManagerName) { + new DummyTransactionManager + } + } + + applicationContext.registerConfigurations(config) + applicationContext.refresh() + + val frog = applicationContext.getBean("frog", classOf[Frog]) + assert(frog.hasTransaction === true) + } + + test("enableTransactionManagement() CGLIB proxy") { + val config = new FunctionalConfiguration with TransactionSupport { + + enableTransactionManagement(ProxyTransactionMode(proxyTargetClass = true)) + + bean(TransactionSupport.DEFAULT_TRANSACTION_MANAGER_NAME) { + new DummyTransactionManager + } + } + + applicationContext.registerConfigurations(config) + applicationContext.refresh() + + val infrastructureAdvisorAutoProxyCreator = applicationContext.getBean( + AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME, + classOf[InfrastructureAdvisorAutoProxyCreator] + ) + assert(infrastructureAdvisorAutoProxyCreator.isProxyTargetClass) + } + + test("enableTransactionManagement() AspectJ mode") { + val config = new FunctionalConfiguration with TransactionSupport { + + enableTransactionManagement(AspectJTransactionMode()) + + bean(TransactionSupport.DEFAULT_TRANSACTION_MANAGER_NAME) { + new DummyTransactionManager + } + } + + applicationContext.registerConfigurations(config) + applicationContext.refresh() + + assert(applicationContext.containsBean( + TransactionManagementConfigUtils.TRANSACTION_ASPECT_BEAN_NAME)) + } +} + +class DummyTransactionManager extends AbstractPlatformTransactionManager { + def doGetTransaction(): AnyRef = { + Unit + } + + def doBegin(transaction: Any, definition: TransactionDefinition) {} + + def doCommit(status: DefaultTransactionStatus) {} + + def doRollback(status: DefaultTransactionStatus) {} +} + +class Frog { + @Transactional + def hasTransaction: Boolean = { + TransactionSynchronizationManager.isActualTransactionActive + } +} From 0672dcfe04132c91b3f12e885d97ac002d9eb172 Mon Sep 17 00:00:00 2001 From: Maciej Zientarski Date: Sun, 11 Aug 2013 22:10:09 +0200 Subject: [PATCH 4/4] PropertiesResolver and Properties Aware traits PropertiesResolver - to ease access to application properties when configuring Spring beans PropertiesAware - to ease access to application properties from the Spring beans --- .../factory/config/PropertiesAware.scala | 128 ++++++++++++ .../factory/config/PropertiesResolver.scala | 55 +++++ .../factory/config/PropertiesAwareTest.scala | 196 ++++++++++++++++++ .../config/PropertiesResolverTest.scala | 160 ++++++++++++++ .../factory/config/PropertiesTestUtils.scala | 66 ++++++ 5 files changed, 605 insertions(+) create mode 100644 src/main/scala/org/springframework/scala/beans/factory/config/PropertiesAware.scala create mode 100644 src/main/scala/org/springframework/scala/beans/factory/config/PropertiesResolver.scala create mode 100644 src/test/scala/org/springframework/scala/beans/factory/config/PropertiesAwareTest.scala create mode 100644 src/test/scala/org/springframework/scala/beans/factory/config/PropertiesResolverTest.scala create mode 100644 src/test/scala/org/springframework/scala/beans/factory/config/PropertiesTestUtils.scala diff --git a/src/main/scala/org/springframework/scala/beans/factory/config/PropertiesAware.scala b/src/main/scala/org/springframework/scala/beans/factory/config/PropertiesAware.scala new file mode 100644 index 0000000..f9ad741 --- /dev/null +++ b/src/main/scala/org/springframework/scala/beans/factory/config/PropertiesAware.scala @@ -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) +} \ No newline at end of file diff --git a/src/main/scala/org/springframework/scala/beans/factory/config/PropertiesResolver.scala b/src/main/scala/org/springframework/scala/beans/factory/config/PropertiesResolver.scala new file mode 100644 index 0000000..ad929f7 --- /dev/null +++ b/src/main/scala/org/springframework/scala/beans/factory/config/PropertiesResolver.scala @@ -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"))().$ +} diff --git a/src/test/scala/org/springframework/scala/beans/factory/config/PropertiesAwareTest.scala b/src/test/scala/org/springframework/scala/beans/factory/config/PropertiesAwareTest.scala new file mode 100644 index 0000000..ef54025 --- /dev/null +++ b/src/test/scala/org/springframework/scala/beans/factory/config/PropertiesAwareTest.scala @@ -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) + } + } +} + diff --git a/src/test/scala/org/springframework/scala/beans/factory/config/PropertiesResolverTest.scala b/src/test/scala/org/springframework/scala/beans/factory/config/PropertiesResolverTest.scala new file mode 100644 index 0000000..5812c1a --- /dev/null +++ b/src/test/scala/org/springframework/scala/beans/factory/config/PropertiesResolverTest.scala @@ -0,0 +1,160 @@ +/* + * 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.{BeforeAndAfterEach, FunSuite} +import org.springframework.scala.context.function.{FunctionalConfiguration, FunctionalConfigApplicationContext} +import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer + +/** + * @author Maciej Zientarski + * @since 1.0 + */ +class PropertiesResolverTest extends FunSuite with PropertiesTestUtils { + + test("nested placeholder") { + //given + val context = new TestConfig { + withProperties("subject" -> "Frog", "predicate" -> "croaks", "sentence" -> "${subject} ${predicate}") + withPropertyValueDefinition { + $.sentence + } + } + + //then + val propertyValue: String = registerContext(context).propertyValue + + assert("Frog croaks".equals(propertyValue)) + } + + test("nested path") { + //given + val context = new TestConfig { + withProperties("dot.net" -> "sucks") + withPropertyValueDefinition { + $.dot.net + } + } + + //then + val propertyValue: String = registerContext(context).propertyValue + + assert("sucks".equals(propertyValue)) + } + + test("nested path with intermediate value available") { + //given + val context = new TestConfig { + withProperties("dot" -> "department of transportation", "dot.net" -> "sucks") + withPropertyValueDefinition { + $.dot.net + } + } + + //then + val propertyValue: String = registerContext(context).propertyValue + + assert("sucks".equals(propertyValue)) + } + + test("as string") { + //given + val context = new TestConfig { + withProperties("throw.new" -> "exception") + withPropertyValueDefinition { + $("throw.new") + } + } + + //then + val propertyValue: String = registerContext(context).propertyValue + + assert("exception".equals(propertyValue)) + } + + test("scala keywords") { + //given + val context = new TestConfig { + withProperties("throw.new" -> "exception") + withPropertyValueDefinition { + $.`throw`.`new` + } + } + + //then + val propertyValue: String = registerContext(context).propertyValue + + assert("exception".equals(propertyValue)) + } + + test("scala keyword in the middle of the path") { + //given + val context = new TestConfig { + withProperties("dont.throw.that" -> "exception") + withPropertyValueDefinition { + $.dont.`throw`.that + } + } + + //then + val propertyValue: String = registerContext(context).propertyValue + + assert("exception".equals(propertyValue)) + } + + + test("nonexistent property") { + //given + val context = new TestConfig { + withProperties("dont" -> "touch me") + withPropertyValueDefinition { + $.ok + } + } + + intercept[IllegalArgumentException] { + registerContext(context).propertyValue + } + } + + test("more nested nonexistent property") { + //given + val context = new TestConfig { + withProperties("im" -> "having a good time") + withPropertyValueDefinition { + $.dont.stop.me.now + } + } + + intercept[IllegalArgumentException] { + registerContext(context).propertyValue + } + } + + test("no property resolver defined") { + //given + val context = new TestConfig { + withPropertyValueDefinition { + $.dont.stop.me.now + } + } + + val propertyValue: String = registerContext(context).propertyValue + + assert("${dont.stop.me.now}" === propertyValue) + } +} diff --git a/src/test/scala/org/springframework/scala/beans/factory/config/PropertiesTestUtils.scala b/src/test/scala/org/springframework/scala/beans/factory/config/PropertiesTestUtils.scala new file mode 100644 index 0000000..c511d19 --- /dev/null +++ b/src/test/scala/org/springframework/scala/beans/factory/config/PropertiesTestUtils.scala @@ -0,0 +1,66 @@ +/* + * 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.{FunctionalConfigApplicationContext, FunctionalConfiguration} +import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer +import org.scalatest.{BeforeAndAfterEach, FunSuite} + +/** + * @author Maciej Zientarski + * @since 1.0 + */ +private[config] trait PropertiesTestUtils extends FunSuite with BeforeAndAfterEach { + var applicationContext: FunctionalConfigApplicationContext = _ + + override protected def beforeEach() { + applicationContext = new FunctionalConfigApplicationContext() + } + + def registerContext(context: TestConfig): TestConfig = { + applicationContext.registerConfigurations(context) + applicationContext.refresh() + context + } + + implicit def map2Properties(map: Map[String, String]): java.util.Properties = { + val props = new java.util.Properties() + map foreach { + case (key, value) => props.put(key, value) + } + props + } + + class TestConfig extends FunctionalConfiguration with PropertiesResolver { + def withProperties(properties: (String, String)*) { + bean("propertiesConfig") { + new PropertyPlaceholderConfigurer { + setProperties(Map(properties: _*)) + } + } + } + + def withPropertyValueDefinition(function: => Any) { + bean("propertyValue") { + function + } + } + + def propertyValue: String = applicationContext.getBean("propertyValue", classOf[Property]) + } + +}