From 23f6023ae7af99ce7487fe6d60683ac2f3cf37db Mon Sep 17 00:00:00 2001 From: hekonsek Date: Sat, 2 Feb 2013 13:41:25 +0100 Subject: [PATCH] Added pointcut conversions and basic AOP DSL. --- .../scala/aop/AopSupport.scala | 64 +++++++++++++ .../scala/aop/PointcutConversions.scala | 28 ++++++ .../scala/aop/AopSupportTests.scala | 96 +++++++++++++++++++ .../scala/aop/PointcutConversionsTests.scala | 35 +++++++ 4 files changed, 223 insertions(+) create mode 100644 src/main/scala/org/springframework/scala/aop/AopSupport.scala create mode 100644 src/main/scala/org/springframework/scala/aop/PointcutConversions.scala create mode 100644 src/test/scala/org/springframework/scala/aop/AopSupportTests.scala create mode 100644 src/test/scala/org/springframework/scala/aop/PointcutConversionsTests.scala diff --git a/src/main/scala/org/springframework/scala/aop/AopSupport.scala b/src/main/scala/org/springframework/scala/aop/AopSupport.scala new file mode 100644 index 0000000..83e502f --- /dev/null +++ b/src/main/scala/org/springframework/scala/aop/AopSupport.scala @@ -0,0 +1,64 @@ +/* + * 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.aop + +import org.springframework.aop.Pointcut +import org.aopalliance.aop.Advice +import org.springframework.aop.framework.ProxyFactoryBean +import org.springframework.aop.support.DefaultPointcutAdvisor +import org.springframework.scala.context.function.FunctionalConfiguration + +trait AopSupport { + + self: FunctionalConfiguration => + + def advice(beanName: String) = new AdviceDefinition(beanName) + + def advice = new AdviceDefinition("") + + class AdviceDefinition(beanName: String) { + + def targetRef(targetName: String) = new TargetDefinition(targetName = Some(targetName)) + + def target(target: Any) = new TargetDefinition(target = Some(target)) + + class TargetDefinition(targetName: Option[String] = None, target: Option[Any] = None) { + + val proxyFactory = new ProxyFactoryBean + (targetName, target) match { + case (None, Some(t)) => proxyFactory.setTarget(t) + case (Some(tn), None) => proxyFactory.setTargetName(tn) + case _ => throw new IllegalStateException("Either bean reference or embedded bean need to be passed to the AOP proxy builder.") + } + proxyFactory.setProxyTargetClass(true) + bean(name = beanName)(proxyFactory) + + def on(pointcut: Pointcut) = new AdvicePointcutDefinition(pointcut) + + class AdvicePointcutDefinition(pointcut: Pointcut) { + + def using(advice: Advice): TargetDefinition = { + proxyFactory.addAdvisor(new DefaultPointcutAdvisor(pointcut, advice)) + TargetDefinition.this + } + + } + + } + + } + +} \ No newline at end of file diff --git a/src/main/scala/org/springframework/scala/aop/PointcutConversions.scala b/src/main/scala/org/springframework/scala/aop/PointcutConversions.scala new file mode 100644 index 0000000..6b0eba5 --- /dev/null +++ b/src/main/scala/org/springframework/scala/aop/PointcutConversions.scala @@ -0,0 +1,28 @@ +/* + * 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.aop + +import org.springframework.aop.support.StaticMethodMatcherPointcut +import java.lang.reflect.Method +import org.springframework.aop.Pointcut + +object PointcutConversions { + + implicit def asPointcut(matcher: (Method, Class[_]) => Boolean): Pointcut = new StaticMethodMatcherPointcut { + def matches(method: Method, targetClass: Class[_]) = matcher(method, targetClass) + } + +} diff --git a/src/test/scala/org/springframework/scala/aop/AopSupportTests.scala b/src/test/scala/org/springframework/scala/aop/AopSupportTests.scala new file mode 100644 index 0000000..e388e07 --- /dev/null +++ b/src/test/scala/org/springframework/scala/aop/AopSupportTests.scala @@ -0,0 +1,96 @@ +/* + * 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.aop + +import org.springframework.context.support.GenericApplicationContext +import org.scalatest.{BeforeAndAfter, FunSuite} +import org.junit.runner.RunWith +import org.scalatest.junit.JUnitRunner +import org.springframework.scala.context.function.{FunctionalConfiguration, FunctionalConfigApplicationContext} +import PointcutConversions._ +import AdviceConversions._ +import java.lang.reflect.Method +import scala.Array +import java.util.{Calendar, Date} +import org.scalatest.matchers.ShouldMatchers +import org.springframework.aop.support.AopUtils + +@RunWith(classOf[JUnitRunner]) +class AopSupportTests extends FunSuite with BeforeAndAfter with ShouldMatchers { + + val applicationContext: GenericApplicationContext = new FunctionalConfigApplicationContext(classOf[TestConfiguration]) + + before { + val counter = applicationContext.getBean(classOf[Counter]) + counter.reset + } + + test("Should create proxy by bean reference.") { + val dateProxy = applicationContext.getBean("dateRefProxy", classOf[Date]) + AopUtils.isCglibProxy(dateProxy) should equal (true) + } + + test("Should create proxy from embedded bean.") { + val calendarProxy = applicationContext.getBean(classOf[Calendar]) + AopUtils.isCglibProxy(calendarProxy) should equal (true) + } + + test("Should generate name for the anoumous bean.") { + val calendars = applicationContext.getBeansOfType(classOf[Calendar]) + calendars.size should equal (1) + calendars.keySet.iterator.next should not be ('empty) + } + + test("Should call advice.") { + val dateProxy = applicationContext.getBean("dateRefProxy", classOf[Date]) + dateProxy.getTime + val counter = applicationContext.getBean(classOf[Counter]) + counter.count should equal (1) + } + + test("Should call multiple advices.") { + val advisedTwiceDate = applicationContext.getBean("advisedTwiceDate", classOf[Date]) + advisedTwiceDate.getTime + val counter = applicationContext.getBean(classOf[Counter]) + counter.count should equal (2) + } + +} + +class TestConfiguration extends FunctionalConfiguration with AopSupport { + + bean("counter") { + Counter() + } + + val getTimePointcut = (m: Method, c: Class[_]) => m.getName == "getTime" + val beforeAdvice = (m: Method, args: Array[AnyRef], target: Any) => {getBean[Counter]("counter").inc()} + + bean("date")(new Date) + advice(beanName = "dateRefProxy") targetRef "date" on getTimePointcut using beforeAdvice + + advice target Calendar.getInstance() on getTimePointcut using beforeAdvice + + advice(beanName = "advisedTwiceDate").target(new Date). + on(getTimePointcut).using(beforeAdvice). + on(getTimePointcut).using(beforeAdvice) + +} + +case class Counter(var count: Int = 0) { + def inc() {count = count + 1} + def reset() {count = 0} +} \ No newline at end of file diff --git a/src/test/scala/org/springframework/scala/aop/PointcutConversionsTests.scala b/src/test/scala/org/springframework/scala/aop/PointcutConversionsTests.scala new file mode 100644 index 0000000..d75afcd --- /dev/null +++ b/src/test/scala/org/springframework/scala/aop/PointcutConversionsTests.scala @@ -0,0 +1,35 @@ +/* + * 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.aop + +import org.scalatest.FunSuite +import org.junit.runner.RunWith +import org.scalatest.junit.JUnitRunner +import org.springframework.aop.Pointcut +import java.lang.reflect.Method +import org.scalatest.matchers.ShouldMatchers + +@RunWith(classOf[JUnitRunner]) +class PointcutConversionsTests extends FunSuite with ShouldMatchers { + + test("Should convert function to static matcher.") { + import PointcutConversions.asPointcut + val toStringPointcut : Pointcut = (method: Method, targetClass: Class[_]) => method.getName == "toString" + val matches = toStringPointcut.getMethodMatcher.matches(getClass.getMethod("toString"), getClass) + matches should equal (true) + } + +} \ No newline at end of file