From adb63f8e6da6e12a03c23e5205db708ac9bc6f8b Mon Sep 17 00:00:00 2001 From: eeichinger Date: Thu, 25 Jun 2009 10:17:11 +0000 Subject: [PATCH] partial fix for SPRNET-1221 (avoid duplicate advice invocations for nested proxies) --- .../Spring.Aop/Aop/Framework/AopUtils.cs | 44 +++- .../DynamicProxy/DefaultAopProxyFactory.cs | 216 ++++++++++-------- .../Support/AbstractGenericPointcutAdvisor.cs | 20 +- .../Aop/Support/AbstractPointcutAdvisor.cs | 15 +- .../Aop/Support/DefaultIntroductionAdvisor.cs | 44 +++- .../Aop/Support/DefaultPointcutAdvisor.cs | 29 ++- .../ObjectNameAutoProxyCreatorTests.cs | 6 +- .../Aop/Framework/ProxyFactoryTests.cs | 130 ++++++++--- .../DefaultIntroductionAdvisorTests.cs | 93 ++++++++ .../Spring.Aop.Tests.2008.csproj | 1 + 10 files changed, 452 insertions(+), 146 deletions(-) create mode 100644 test/Spring/Spring.Aop.Tests/Aop/Support/DefaultIntroductionAdvisorTests.cs diff --git a/src/Spring/Spring.Aop/Aop/Framework/AopUtils.cs b/src/Spring/Spring.Aop/Aop/Framework/AopUtils.cs index e089ed1b..7bb2d969 100644 --- a/src/Spring/Spring.Aop/Aop/Framework/AopUtils.cs +++ b/src/Spring/Spring.Aop/Aop/Framework/AopUtils.cs @@ -42,12 +42,24 @@ namespace Spring.Aop.Framework /// Aleksandar Seovic (.NET) public sealed class AopUtils { - // This is a leaky abstraction as we have hardcoded known IAopProxyFactory implementations. private const string COMPOSITION_PROXY_TYPE_NAME = "CompositionAopProxy"; private const string DECORATOR_PROXY_TYPE_NAME = "DecoratorAopProxy"; + /// + /// Is the supplied an AOP proxy? + /// + /// + /// Return whether the given type is either a composition-based or a decorator-based proxy type. + /// + /// The type to be checked. + /// if the supplied is an AOP proxy type. + public static bool IsAopProxyType(Type objectType) + { + return IsCompositionAopProxyType(objectType) || IsDecoratorAopProxyType(objectType); + } + /// /// Is the supplied an AOP proxy? /// @@ -75,7 +87,20 @@ namespace Spring.Aop.Framework /// public static bool IsCompositionAopProxy(Object instance) { - return ((instance != null) && instance.GetType().FullName.StartsWith(COMPOSITION_PROXY_TYPE_NAME)); + return ((instance != null) && IsCompositionAopProxyType(instance.GetType())); + } + + /// + /// Is the supplied a composition based AOP proxy type? + /// + /// + /// Return whether the given type is a composition-based proxy type. + /// + /// The type to be checked. + /// if the supplied is a composition based AOP proxy type. + public static bool IsCompositionAopProxyType(Type objectType) + { + return ((objectType != null) && objectType.FullName.StartsWith(COMPOSITION_PROXY_TYPE_NAME)); } /// @@ -88,7 +113,20 @@ namespace Spring.Aop.Framework /// public static bool IsDecoratorAopProxy(Object instance) { - return ((instance != null) && instance.GetType().FullName.StartsWith(DECORATOR_PROXY_TYPE_NAME)); + return ((instance != null) && IsDecoratorAopProxyType(instance.GetType())); + } + + /// + /// Is the supplied a composition based AOP proxy type? + /// + /// + /// Return whether the given type is a composition-based proxy type. + /// + /// The type to be checked. + /// if the supplied is a composition based AOP proxy type. + public static bool IsDecoratorAopProxyType(Type objectType) + { + return ((objectType != null) && objectType.FullName.StartsWith(DECORATOR_PROXY_TYPE_NAME)); } /// diff --git a/src/Spring/Spring.Aop/Aop/Framework/DynamicProxy/DefaultAopProxyFactory.cs b/src/Spring/Spring.Aop/Aop/Framework/DynamicProxy/DefaultAopProxyFactory.cs index d5a36e24..9c7b8d8c 100644 --- a/src/Spring/Spring.Aop/Aop/Framework/DynamicProxy/DefaultAopProxyFactory.cs +++ b/src/Spring/Spring.Aop/Aop/Framework/DynamicProxy/DefaultAopProxyFactory.cs @@ -1,7 +1,7 @@ -#region License - +#region License + /* - * Copyright © 2002-2007 the original author or authors. + * Copyright © 2002-2009 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. @@ -14,104 +14,128 @@ * 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. - */ - -#endregion - -#region Imports - + */ + +#endregion + using System; +using System.Collections; using System.Reflection; -using Spring.Proxy; +using Spring.Aop.Support; +using Spring.Proxy; using Spring.Aop.Target; -using Spring.Util; - -#endregion - -namespace Spring.Aop.Framework.DynamicProxy -{ - /// - /// Default implementation of the - /// interface, - /// either creating a decorator-based dynamic proxy or - /// a composition-based dynamic proxy. - /// - /// - ///

- /// Creates a decorator-base proxy if one the following is true : - /// - the "ProxyTargetType" property is set - /// - no interfaces have been specified - ///

- ///

- /// In general, specify "ProxyTargetType" to enforce a decorator-based proxy, - /// or specify one or more interfaces to use a composition-based proxy. - ///

- ///
- /// Rod Johnson - /// Aleksandar Seovic (.NET) - /// Bruno Baia (.NET) - /// - [Serializable] - public class DefaultAopProxyFactory : IAopProxyFactory - { +using Spring.Util; + +namespace Spring.Aop.Framework.DynamicProxy +{ + /// + /// Default implementation of the + /// interface, + /// either creating a decorator-based dynamic proxy or + /// a composition-based dynamic proxy. + /// + /// + ///

+ /// Creates a decorator-base proxy if one the following is true : + /// - the "ProxyTargetType" property is set + /// - no interfaces have been specified + ///

+ ///

+ /// In general, specify "ProxyTargetType" to enforce a decorator-based proxy, + /// or specify one or more interfaces to use a composition-based proxy. + ///

+ ///
+ /// Rod Johnson + /// Aleksandar Seovic (.NET) + /// Bruno Baia (.NET) + /// Erich Eichinger (.NET) + /// + [Serializable] + public class DefaultAopProxyFactory : IAopProxyFactory + { /// /// Force transient assemblies to be resolvable by . - /// - static DefaultAopProxyFactory() + ///
+ static DefaultAopProxyFactory() { SystemUtils.RegisterLoadedAssemblyResolver(); - } - - /// - /// Creates an for the - /// supplied configuration. - /// - /// The AOP configuration. - /// An . - /// - /// If the supplied configuration is - /// invalid. - /// - /// - public virtual IAopProxy CreateAopProxy(AdvisedSupport advisedSupport) - { - if (advisedSupport == null) - { - throw new AopConfigException("Cannot create IAopProxy with null ProxyConfig"); - } - if (advisedSupport.Advisors.Length == 0 && advisedSupport.TargetSource == EmptyTargetSource.Empty) - { - throw new AopConfigException("Cannot create IAopProxy with no advisors and no target source"); - } - if (advisedSupport.ProxyType == null) - { - IProxyTypeBuilder typeBuilder; - if ((advisedSupport.ProxyTargetType) || - (advisedSupport.Interfaces.Length == 0)) - { - typeBuilder = new DecoratorAopProxyTypeBuilder(advisedSupport); - } - else - { - typeBuilder = new CompositionAopProxyTypeBuilder(advisedSupport); - } - advisedSupport.ProxyType = BuildProxyType(typeBuilder); - advisedSupport.ProxyConstructor = advisedSupport.ProxyType.GetConstructor(new Type[] { typeof(IAdvised) }); - } - - return (IAopProxy)advisedSupport.ProxyConstructor.Invoke(new object[] { advisedSupport }); - } - - /// - /// Generates the proxy type. - /// - /// - /// The to use - /// - /// The generated proxy class. - protected virtual Type BuildProxyType(IProxyTypeBuilder typeBuilder) - { - return typeBuilder.BuildProxyType(); - } - } + } + + /// + /// Creates an for the + /// supplied configuration. + /// + /// The AOP configuration. + /// An . + /// + /// If the supplied configuration is + /// invalid. + /// + /// + public virtual IAopProxy CreateAopProxy(AdvisedSupport advisedSupport) + { + if (advisedSupport == null) + { + throw new AopConfigException("Cannot create IAopProxy with null ProxyConfig"); + } + if (advisedSupport.Advisors.Length == 0 && advisedSupport.TargetSource == EmptyTargetSource.Empty) + { + throw new AopConfigException("Cannot create IAopProxy with no advisors and no target source"); + } + if (advisedSupport.ProxyType == null) + { + IProxyTypeBuilder typeBuilder; + if ((advisedSupport.ProxyTargetType) || + (advisedSupport.Interfaces.Length == 0)) + { + typeBuilder = new DecoratorAopProxyTypeBuilder(advisedSupport); + } + else + { + typeBuilder = new CompositionAopProxyTypeBuilder(advisedSupport); + } + advisedSupport.ProxyType = BuildProxyType(typeBuilder); + advisedSupport.ProxyConstructor = advisedSupport.ProxyType.GetConstructor(new Type[] { typeof(IAdvised) }); + } + + if (advisedSupport.TargetSource is SingletonTargetSource + && AopUtils.IsAopProxyType(advisedSupport.TargetSource.TargetType)) + { + IAdvised innerProxy = (IAdvised)advisedSupport.TargetSource.GetTarget(); + // eliminate duplicate advisors + ArrayList thisAdvisors = new ArrayList(advisedSupport.Advisors); + foreach (IAdvisor innerAdvisor in innerProxy.Advisors) + { + foreach (IAdvisor thisAdvisor in thisAdvisors) + { + if (ReferenceEquals(thisAdvisor, innerAdvisor) + || (thisAdvisor.GetType() == typeof(DefaultPointcutAdvisor) + && ((DefaultPointcutAdvisor)thisAdvisor).Equals(innerAdvisor) + ) + ) + { + advisedSupport.RemoveAdvisor(thisAdvisor); + } + } + } + + // elimination of duplicate introductions is not necessary + // since they do not propagate to nested proxy anyway + } + + return (IAopProxy)advisedSupport.ProxyConstructor.Invoke(new object[] { advisedSupport }); + } + + /// + /// Generates the proxy type. + /// + /// + /// The to use + /// + /// The generated proxy class. + protected virtual Type BuildProxyType(IProxyTypeBuilder typeBuilder) + { + return typeBuilder.BuildProxyType(); + } + } } \ No newline at end of file diff --git a/src/Spring/Spring.Aop/Aop/Support/AbstractGenericPointcutAdvisor.cs b/src/Spring/Spring.Aop/Aop/Support/AbstractGenericPointcutAdvisor.cs index bbaf9b2a..dead61e7 100644 --- a/src/Spring/Spring.Aop/Aop/Support/AbstractGenericPointcutAdvisor.cs +++ b/src/Spring/Spring.Aop/Aop/Support/AbstractGenericPointcutAdvisor.cs @@ -33,7 +33,6 @@ namespace Spring.Aop.Support { private IAdvice advice; - /// /// Return the advice part of this advisor. /// @@ -47,7 +46,24 @@ namespace Spring.Aop.Support set { this.advice = value; } } - + /// + /// 2 s are considered equals, if + /// a) their pointcuts are equal + /// b) their advices are equal + /// + public override bool Equals(object obj) + { + return base.Equals(obj as AbstractGenericPointcutAdvisor); + } + + /// + /// Calculates a unique hashcode based on advice + pointcut + /// + public override int GetHashCode() + { + return base.GetHashCode(); + } + /// /// Returns a that represents the current /// . diff --git a/src/Spring/Spring.Aop/Aop/Support/AbstractPointcutAdvisor.cs b/src/Spring/Spring.Aop/Aop/Support/AbstractPointcutAdvisor.cs index 1997c7ae..4dc6be91 100644 --- a/src/Spring/Spring.Aop/Aop/Support/AbstractPointcutAdvisor.cs +++ b/src/Spring/Spring.Aop/Aop/Support/AbstractPointcutAdvisor.cs @@ -126,8 +126,18 @@ namespace Spring.Aop.Support if (!(o is AbstractPointcutAdvisor)) { return false; + } + + if (ReferenceEquals(this, o)) + { + return true; + } + + AbstractPointcutAdvisor otherAdvisor = (AbstractPointcutAdvisor)o; + if (this.Order != otherAdvisor.Order) + { + return false; } - IPointcutAdvisor otherAdvisor = (IPointcutAdvisor)o; if (otherAdvisor.Advice == null && otherAdvisor.Pointcut == null) { return (this.Advice == null && this.Pointcut == null); @@ -157,7 +167,8 @@ namespace Spring.Aop.Support { return 0 // (SPRNET-847) base.GetHashCode() + 13 * (Pointcut == null ? 0 : Pointcut.GetHashCode()) - + 27 * (Advice == null ? 0 : Advice.GetHashCode()); + + 27 * (Advice == null ? 0 : Advice.GetHashCode()) + + 31 * Order.GetHashCode(); } #endregion diff --git a/src/Spring/Spring.Aop/Aop/Support/DefaultIntroductionAdvisor.cs b/src/Spring/Spring.Aop/Aop/Support/DefaultIntroductionAdvisor.cs index 3f94ced9..cc59caae 100644 --- a/src/Spring/Spring.Aop/Aop/Support/DefaultIntroductionAdvisor.cs +++ b/src/Spring/Spring.Aop/Aop/Support/DefaultIntroductionAdvisor.cs @@ -37,8 +37,8 @@ namespace Spring.Aop.Support [Serializable] public class DefaultIntroductionAdvisor : IIntroductionAdvisor, ITypeFilter { - private IAdvice _introduction; - private ISet _interfaces = new HybridSet(); + private readonly IAdvice _introduction; + private readonly ISet _interfaces = new HybridSet(); /// /// Creates a new instance of the @@ -236,5 +236,45 @@ namespace Spring.Aop.Support throw new ArgumentException("Type [" + intf.FullName + "] is not an interface; cannot be used in an introduction."); } } + + /// + /// 2 IntroductionAdvisors are considered equal if + /// a) they are of the same type + /// b) their introduction advices are equal + /// c) they introduce the same interfaces + /// + public bool Equals(DefaultIntroductionAdvisor other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + if (other.GetType() != this.GetType()) + return false; + return Equals(other._introduction, _introduction) && Equals(other._interfaces, _interfaces); + } + + /// + /// 2 IntroductionAdvisors are considered equal if + /// a) they are of the same type + /// b) their introduction advices are equal + /// c) they introduce the same interfaces + /// + public override bool Equals(object obj) + { + return Equals(obj as DefaultIntroductionAdvisor); + } + + /// + /// 2 IntroductionAdvisors are considered equal if + /// a) they are of the same type + /// b) their introduction advices are equal + /// c) they introduce the same interfaces + /// + public override int GetHashCode() + { + unchecked + { + return ((_introduction != null ? _introduction.GetHashCode() : 0)*397) ^ (_interfaces != null ? _interfaces.GetHashCode() : 0); + } + } } } \ No newline at end of file diff --git a/src/Spring/Spring.Aop/Aop/Support/DefaultPointcutAdvisor.cs b/src/Spring/Spring.Aop/Aop/Support/DefaultPointcutAdvisor.cs index 42ceb535..2368fab9 100644 --- a/src/Spring/Spring.Aop/Aop/Support/DefaultPointcutAdvisor.cs +++ b/src/Spring/Spring.Aop/Aop/Support/DefaultPointcutAdvisor.cs @@ -41,7 +41,6 @@ namespace Spring.Aop.Support [Serializable] public class DefaultPointcutAdvisor : AbstractGenericPointcutAdvisor { - private IPointcut pointcut = TruePointcut.True; /// @@ -90,12 +89,28 @@ namespace Spring.Aop.Support { get { return pointcut; } set { pointcut = value;} - } - - - - - /// + } + + + /// + /// 2 s are considered equal, if + /// a) their pointcuts are equal + /// b) their advices are equal + /// + public override bool Equals(object obj) + { + return base.Equals(obj as DefaultPointcutAdvisor); + } + + /// + /// Calculates a unique hashcode based on advice + pointcut + /// + public override int GetHashCode() + { + return base.GetHashCode(); + } + + /// /// Returns a that represents the current /// . /// diff --git a/test/Spring/Spring.Aop.Tests/Aop/Framework/AutoProxy/ObjectNameAutoProxyCreatorTests.cs b/test/Spring/Spring.Aop.Tests/Aop/Framework/AutoProxy/ObjectNameAutoProxyCreatorTests.cs index e85b48da..4433e4ac 100644 --- a/test/Spring/Spring.Aop.Tests/Aop/Framework/AutoProxy/ObjectNameAutoProxyCreatorTests.cs +++ b/test/Spring/Spring.Aop.Tests/Aop/Framework/AutoProxy/ObjectNameAutoProxyCreatorTests.cs @@ -66,10 +66,10 @@ namespace Spring.Aop.Framework.AutoProxy } [Test] - public void ProxyWithDoubleProxying() + public void ProxyWithDoubleProxyingInvokesInterceptorsOnceOnly() { ITestObject testObject = (ITestObject)ctx.GetObject("doubleProxy"); - ProxyAssertions(testObject, 2); + ProxyAssertions(testObject, 1); Assert.AreEqual("doubleProxy", testObject.Name); } @@ -138,7 +138,7 @@ namespace Spring.Aop.Framework.AutoProxy int age = 5; testObject.Age = age; Assert.AreEqual(age, testObject.Age); - Assert.AreEqual(2 * nopInterceptorCount, nop.Count); + Assert.AreEqual(2*nopInterceptorCount, nop.Count); } private void DecoratorProxyAssertions(ITestObject testObject) diff --git a/test/Spring/Spring.Aop.Tests/Aop/Framework/ProxyFactoryTests.cs b/test/Spring/Spring.Aop.Tests/Aop/Framework/ProxyFactoryTests.cs index 2b899fb8..74cf433f 100644 --- a/test/Spring/Spring.Aop.Tests/Aop/Framework/ProxyFactoryTests.cs +++ b/test/Spring/Spring.Aop.Tests/Aop/Framework/ProxyFactoryTests.cs @@ -211,27 +211,27 @@ namespace Spring.Aop.Framework pf.AddAdvice(nopInterceptor); pf.AddAdvisor(new DefaultPointcutAdvisor(countingBeforeAdvice)); object proxy = pf.GetProxy(); - ITestObject to = (ITestObject) proxy; + ITestObject to = (ITestObject)proxy; Assert.AreEqual("Adam", to.Name); Assert.AreEqual(1, countingBeforeAdvice.GetCalls()); } [Test] - [ExpectedException(typeof (AopConfigException))] + [ExpectedException(typeof(AopConfigException))] public void InstantiateWithNullTarget() { - new ProxyFactory((object) null); + new ProxyFactory((object)null); } [Test] - [ExpectedException(typeof (ArgumentNullException))] + [ExpectedException(typeof(ArgumentNullException))] public void AddNullInterface() { new ProxyFactory().AddInterface(null); } [Test] - [ExpectedException(typeof (AopConfigException))] + [ExpectedException(typeof(AopConfigException))] public void AddInterfaceWhenConfigurationIsFrozen() { ProxyFactory factory = new ProxyFactory(); @@ -246,16 +246,16 @@ namespace Spring.Aop.Framework ProxyFactory pf = new ProxyFactory(target); NopInterceptor nop = new NopInterceptor(); IAdvisor advisor = new DefaultPointcutAdvisor(new CountingBeforeAdvice()); - IAdvised advised = (IAdvised) pf.GetProxy(); + IAdvised advised = (IAdvised)pf.GetProxy(); // Can use advised and ProxyFactory interchangeably advised.AddAdvice(nop); pf.AddAdvisor(advisor); - Assert.AreEqual(- 1, pf.IndexOf((IInterceptor) null)); - Assert.AreEqual(- 1, pf.IndexOf(new NopInterceptor())); + Assert.AreEqual(-1, pf.IndexOf((IInterceptor)null)); + Assert.AreEqual(-1, pf.IndexOf(new NopInterceptor())); Assert.AreEqual(0, pf.IndexOf(nop)); - Assert.AreEqual(- 1, advised.IndexOf((IAdvisor) null)); + Assert.AreEqual(-1, advised.IndexOf((IAdvisor)null)); Assert.AreEqual(1, pf.IndexOf(advisor)); - Assert.AreEqual(- 1, advised.IndexOf(new DefaultPointcutAdvisor(null))); + Assert.AreEqual(-1, advised.IndexOf(new DefaultPointcutAdvisor(null))); } [Test] @@ -268,7 +268,7 @@ namespace Spring.Aop.Framework IAdvisor advisor = new DefaultPointcutAdvisor(cba); pf.AddAdvice(nop); pf.AddAdvisor(advisor); - ITestObject proxied = (ITestObject) pf.GetProxy(); + ITestObject proxied = (ITestObject)pf.GetProxy(); proxied.Age = 5; Assert.AreEqual(1, cba.GetCalls()); Assert.AreEqual(1, nop.Count); @@ -292,7 +292,7 @@ namespace Spring.Aop.Framework pf.AddAdvisor(advisor); NopInterceptor nop2 = new NopInterceptor(2); // make instance unique (see SPRNET-847) pf.AddAdvice(nop2); - ITestObject proxied = (ITestObject) pf.GetProxy(); + ITestObject proxied = (ITestObject)pf.GetProxy(); proxied.Age = 5; Assert.AreEqual(1, cba.GetCalls()); Assert.AreEqual(1, nop.Count); @@ -313,7 +313,7 @@ namespace Spring.Aop.Framework // Check out of bounds try { - pf.RemoveAdvisor(- 1); + pf.RemoveAdvisor(-1); Assert.Fail("Supposed to throw exception"); } catch (AopConfigException) @@ -338,14 +338,14 @@ namespace Spring.Aop.Framework [Test] public void TryRemoveNonProxiedInterface() { - ProxyFactory factory = new ProxyFactory(new TestObject ()); + ProxyFactory factory = new ProxyFactory(new TestObject()); Assert.IsFalse(factory.RemoveInterface(typeof(IServiceProvider))); } [Test] public void RemoveProxiedInterface() { - ProxyFactory factory = new ProxyFactory(new TestObject ()); + ProxyFactory factory = new ProxyFactory(new TestObject()); Assert.IsTrue(factory.RemoveInterface(typeof(ITestObject))); } @@ -361,10 +361,10 @@ namespace Spring.Aop.Framework IAdvisor advisor2 = new DefaultPointcutAdvisor(cba2); pf.AddAdvisor(advisor1); pf.AddAdvice(nop); - ITestObject proxied = (ITestObject) pf.GetProxy(); + ITestObject proxied = (ITestObject)pf.GetProxy(); // Use the type cast feature // Replace etc methods on advised should be same as on ProxyFactory - IAdvised advised = (IAdvised) proxied; + IAdvised advised = (IAdvised)proxied; proxied.Age = 5; Assert.AreEqual(1, cba1.GetCalls()); Assert.AreEqual(0, cba2.GetCalls()); @@ -427,9 +427,9 @@ namespace Spring.Aop.Framework ProxyFactory pf = new ProxyFactory(tst); // We've already implicitly added this interface. // This call should be ignored without error - pf.AddInterface(typeof (ITimeStamped)); + pf.AddInterface(typeof(ITimeStamped)); // All cool - ITimeStamped ts = (ITimeStamped) pf.GetProxy(); + ITimeStamped ts = (ITimeStamped)pf.GetProxy(); } internal class TestObjectSubclass : TestObject, IComparable @@ -448,7 +448,7 @@ namespace Spring.Aop.Framework ProxyFactory factory = new ProxyFactory(raw); Assert.AreEqual(8, factory.Interfaces.Length, "Found correct number of interfaces"); //System.out.println("Proxied interfaces are " + StringUtils.arrayToDelimitedString(factory.getProxiedInterfaces(), ",")); - ITestObject tb = (ITestObject) factory.GetProxy(); + ITestObject tb = (ITestObject)factory.GetProxy(); Assert.IsTrue(tb is IOther, "Picked up secondary interface"); raw.Age = 25; @@ -461,15 +461,15 @@ namespace Spring.Aop.Framework //factory.addAdvisor(0, new DefaultIntroductionAdvisor(ti, typeof(ITimeStamped))); factory.AddIntroduction( - new DefaultIntroductionAdvisor(ti, typeof (ITimeStamped)) + new DefaultIntroductionAdvisor(ti, typeof(ITimeStamped)) ); Console.WriteLine(StringUtils.ArrayToDelimitedString(factory.Interfaces, "/")); - ITimeStamped ts = (ITimeStamped) factory.GetProxy(); + ITimeStamped ts = (ITimeStamped)factory.GetProxy(); Assert.IsTrue(ts.TimeStamp == t); // Shouldn't fail; - ((IOther) ts).Absquatulate(); + ((IOther)ts).Absquatulate(); } private class AnonymousClassInterceptor : IInterceptor @@ -491,7 +491,7 @@ namespace Spring.Aop.Framework } // Check we can still use it - IOther other = (IOther) factory.GetProxy(); + IOther other = (IOther)factory.GetProxy(); other.Absquatulate(); } @@ -502,21 +502,21 @@ namespace Spring.Aop.Framework NopInterceptor diUnused = new NopInterceptor(1); // // make instance unique (see SPRNET-847) ProxyFactory factory = new ProxyFactory(new TestObject()); factory.AddAdvice(0, di); - ITestObject tb = (ITestObject) factory.GetProxy(); + ITestObject tb = (ITestObject)factory.GetProxy(); Assert.IsTrue(factory.AdviceIncluded(di)); Assert.IsTrue(!factory.AdviceIncluded(diUnused)); - Assert.IsTrue(factory.CountAdviceOfType(typeof (NopInterceptor)) == 1); + Assert.IsTrue(factory.CountAdviceOfType(typeof(NopInterceptor)) == 1); factory.AddAdvice(0, diUnused); Assert.IsTrue(factory.AdviceIncluded(diUnused)); - Assert.IsTrue(factory.CountAdviceOfType(typeof (NopInterceptor)) == 2); + Assert.IsTrue(factory.CountAdviceOfType(typeof(NopInterceptor)) == 2); } [Test] public void AddAdvisedSupportListener() { IDynamicMock mock = new DynamicMock(typeof(IAdvisedSupportListener)); - IAdvisedSupportListener listener = (IAdvisedSupportListener) mock.Object; + IAdvisedSupportListener listener = (IAdvisedSupportListener)mock.Object; mock.Expect("Activated"); ProxyFactory factory = new ProxyFactory(new TestObject()); @@ -529,7 +529,7 @@ namespace Spring.Aop.Framework public void AdvisedSupportListenerMethodsAreCalledAppropriately() { IDynamicMock mock = new DynamicMock(typeof(IAdvisedSupportListener)); - IAdvisedSupportListener listener = (IAdvisedSupportListener) mock.Object; + IAdvisedSupportListener listener = (IAdvisedSupportListener)mock.Object; mock.Expect("Activated"); mock.Expect("AdviceChanged"); @@ -552,7 +552,7 @@ namespace Spring.Aop.Framework public void AdvisedSupportListenerMethodsAre_NOT_CalledIfProxyHasNotBeenCreated() { IDynamicMock mock = new DynamicMock(typeof(IAdvisedSupportListener)); - IAdvisedSupportListener listener = (IAdvisedSupportListener) mock.Object; + IAdvisedSupportListener listener = (IAdvisedSupportListener)mock.Object; ProxyFactory factory = new ProxyFactory(new TestObject()); factory.AddListener(listener); @@ -583,7 +583,7 @@ namespace Spring.Aop.Framework public void RemoveAdvisedSupportListener() { IDynamicMock mock = new DynamicMock(typeof(IAdvisedSupportListener)); - IAdvisedSupportListener listener = (IAdvisedSupportListener) mock.Object; + IAdvisedSupportListener listener = (IAdvisedSupportListener)mock.Object; ProxyFactory factory = new ProxyFactory(new TestObject()); factory.AddListener(listener); @@ -603,5 +603,73 @@ namespace Spring.Aop.Framework factory.IsFrozen = true; factory.RemoveAdvisor(null); } + + public interface IMultiProxyingTestInterface + { + string TestMethod(string arg); + } + + public interface IMultiProxyingTestInterface2 : IMultiProxyingTestInterface { } + + public class MultiProxyingTestClass : IMultiProxyingTestInterface2 + { + public int InvocationCounter; + + public string TestMethod(string arg) + { + InvocationCounter++; + return arg + "|" + arg; + } + } + + public interface ICountingIntroduction + { + void Inc(); + } + + public class TestCountingIntroduction : ICountingIntroduction, IAdvice + { + public int Counter; + + public void Inc() + { + Counter++; + } + } + + [Test] + public void NestedProxiesDontInvokeSameAdviceOrIntroductionTwice() + { + MultiProxyingTestClass testObj = new MultiProxyingTestClass(); + ProxyFactory pf1 = new ProxyFactory(); + pf1.Target = testObj; + + NopInterceptor di = new NopInterceptor(); + NopInterceptor diUnused = new NopInterceptor(1); // // make instance unique (see SPRNET-847) + TestCountingIntroduction countingMixin = new TestCountingIntroduction(); + + pf1.AddAdvice(diUnused); + pf1.AddAdvisor(new DefaultPointcutAdvisor(di)); + pf1.AddIntroduction(new DefaultIntroductionAdvisor(countingMixin)); + + object innerProxy = pf1.GetProxy(); + ProxyFactory pf2 = new ProxyFactory(); + pf2.Target = innerProxy; + pf2.AddAdvice(diUnused); + pf2.AddAdvisor(new DefaultPointcutAdvisor(di)); + pf2.AddIntroduction(new DefaultIntroductionAdvisor(countingMixin)); + + object outerProxy = pf2.GetProxy(); + + // any advice instance is invoked once only + string result = ((IMultiProxyingTestInterface)outerProxy).TestMethod("arg"); + Assert.AreEqual(1, testObj.InvocationCounter); + Assert.AreEqual("arg|arg", result); + Assert.AreEqual(1, di.Count); + + // any introduction instance is invoked once only + ((ICountingIntroduction)outerProxy).Inc(); + Assert.AreEqual(1, countingMixin.Counter); + } } } \ No newline at end of file diff --git a/test/Spring/Spring.Aop.Tests/Aop/Support/DefaultIntroductionAdvisorTests.cs b/test/Spring/Spring.Aop.Tests/Aop/Support/DefaultIntroductionAdvisorTests.cs new file mode 100644 index 00000000..d0652f1a --- /dev/null +++ b/test/Spring/Spring.Aop.Tests/Aop/Support/DefaultIntroductionAdvisorTests.cs @@ -0,0 +1,93 @@ +#region License + +/* + * Copyright 2002-2009 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. + */ + +#endregion + +using System; +using AopAlliance.Aop; +using NUnit.Framework; + +namespace Spring.Aop.Support +{ + /// + /// + /// Erich Eichinger + [TestFixture] + public class DefaultIntroductionAdvisorTests + { + public interface IBaseInterface { } + public interface IDerivedInterface : IBaseInterface { } + + private class TestIntroductionAdvice : IDerivedInterface, IAdvice + { + private object equalsToObject; + + public void SetEqualsToObject(object other) + { + equalsToObject = other; + } + + public override bool Equals(object obj) + { + return object.Equals(equalsToObject, obj); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + } + + [Test] + public void EqualsOnAdviceEqualAndInterfacesEqual() + { + TestIntroductionAdvice to1 = new TestIntroductionAdvice(); + TestIntroductionAdvice to2 = new TestIntroductionAdvice(); + to1.SetEqualsToObject(to2); + to2.SetEqualsToObject(to1); + + DefaultIntroductionAdvisor a1 = new DefaultIntroductionAdvisor(to1); + DefaultIntroductionAdvisor a2 = new DefaultIntroductionAdvisor(to2); + bool result = a1.Equals(a2); + + Assert.IsTrue(result); + } + + [Test] + [ExpectedException(typeof(ArgumentException), ExpectedMessage = "Type [Spring.Aop.Support.DefaultIntroductionAdvisorTests] is not an interface; cannot be used in an introduction.")] + public void BailsIfInterfaceTypeIsNotAnInterface() + { + DefaultIntroductionAdvisor a = new DefaultIntroductionAdvisor(new TestIntroductionAdvice(), this.GetType()); + } + + [Test] + [ExpectedException(typeof(ArgumentException), ExpectedMessage = "Introduction [Spring.Aop.Support.DefaultIntroductionAdvisorTests+TestIntroductionAdvice] does not implement interface 'System.ICloneable' specified in introduction advice.")] + public void IntroductionMustImplementIntroducedInterfaces() + { + DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor(new TestIntroductionAdvice(), typeof(ICloneable)); + advisor.ValidateInterfaces(); + } + + [Test] + public void BaseInterfacesAreValid() + { + DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor(new TestIntroductionAdvice(), typeof(IBaseInterface)); + advisor.ValidateInterfaces(); + } + } +} \ No newline at end of file diff --git a/test/Spring/Spring.Aop.Tests/Spring.Aop.Tests.2008.csproj b/test/Spring/Spring.Aop.Tests/Spring.Aop.Tests.2008.csproj index ae656671..970bb77a 100644 --- a/test/Spring/Spring.Aop.Tests/Spring.Aop.Tests.2008.csproj +++ b/test/Spring/Spring.Aop.Tests/Spring.Aop.Tests.2008.csproj @@ -210,6 +210,7 @@ Code + Code