From 72722094e1247b89294fe54aef7431b1fdaea2cd Mon Sep 17 00:00:00 2001 From: markpollack Date: Thu, 9 Oct 2008 01:37:16 +0000 Subject: [PATCH] SPRNET-883 - In Process - Add support for translation of exceptions using PersistenceExceptionTranslationInterceptor --- .../Support/AttributeMatchMethodPointcut.cs | 2 +- .../Aop/Support/AttributeMatchingPointcut.cs | 121 +++++++++++ .../Aop/Support/AttributeMethodMatcher.cs | 109 ++++++++++ .../Aop/Support/AttributeTypeFilter.cs | 111 ++++++++++ src/Spring/Spring.Aop/Spring.Aop.2005.csproj | 3 + .../Spring.Core/Spring.Core.2005.csproj | 3 + .../Stereotype/ComponentAttribute.cs | 54 +++++ .../Stereotype/RepositoryAttribute.cs | 60 ++++++ .../Stereotype/ServiceAttribute.cs | 54 +++++ .../PersistenceExceptionTranslationAdvisor.cs | 96 +++++++++ ...stenceExceptionTranslationPostProcessor.cs | 176 ++++++++++++++++ .../ChainedPersistenceExceptionTranslator.cs | 100 +++++++++ .../IPersistenceExceptionTranslator.cs | 71 +++++++ ...sistenceExceptionTranslationInterceptor.cs | 195 ++++++++++++++++++ .../Spring.Data/Spring.Data.2005.csproj | 5 + ...istenceExceptionTranslationAdvisorTests.cs | 83 ++++++++ ...eExceptionTranslationPostProcessorTests.cs | 102 +++++++++ .../Spring.Data.Tests.2005.csproj | 2 + 18 files changed, 1346 insertions(+), 1 deletion(-) create mode 100644 src/Spring/Spring.Aop/Aop/Support/AttributeMatchingPointcut.cs create mode 100644 src/Spring/Spring.Aop/Aop/Support/AttributeMethodMatcher.cs create mode 100644 src/Spring/Spring.Aop/Aop/Support/AttributeTypeFilter.cs create mode 100644 src/Spring/Spring.Core/Stereotype/ComponentAttribute.cs create mode 100644 src/Spring/Spring.Core/Stereotype/RepositoryAttribute.cs create mode 100644 src/Spring/Spring.Core/Stereotype/ServiceAttribute.cs create mode 100644 src/Spring/Spring.Data/Dao/Attributes/PersistenceExceptionTranslationAdvisor.cs create mode 100644 src/Spring/Spring.Data/Dao/Attributes/PersistenceExceptionTranslationPostProcessor.cs create mode 100644 src/Spring/Spring.Data/Dao/Support/ChainedPersistenceExceptionTranslator.cs create mode 100644 src/Spring/Spring.Data/Dao/Support/IPersistenceExceptionTranslator.cs create mode 100644 src/Spring/Spring.Data/Dao/Support/PersistenceExceptionTranslationInterceptor.cs create mode 100644 test/Spring/Spring.Data.Tests/Dao/Attributes/PersistenceExceptionTranslationAdvisorTests.cs create mode 100644 test/Spring/Spring.Data.Tests/Dao/Attributes/PersistenceExceptionTranslationPostProcessorTests.cs diff --git a/src/Spring/Spring.Aop/Aop/Support/AttributeMatchMethodPointcut.cs b/src/Spring/Spring.Aop/Aop/Support/AttributeMatchMethodPointcut.cs index 254bb009..2c4999ba 100644 --- a/src/Spring/Spring.Aop/Aop/Support/AttributeMatchMethodPointcut.cs +++ b/src/Spring/Spring.Aop/Aop/Support/AttributeMatchMethodPointcut.cs @@ -143,7 +143,7 @@ namespace Spring.Aop.Support } /// - /// Is the interfaces attributes of the method to be included in the search for the + /// Is the interfaces attributes of the method to be included in the search for theg /// ? /// /// diff --git a/src/Spring/Spring.Aop/Aop/Support/AttributeMatchingPointcut.cs b/src/Spring/Spring.Aop/Aop/Support/AttributeMatchingPointcut.cs new file mode 100644 index 00000000..12940a80 --- /dev/null +++ b/src/Spring/Spring.Aop/Aop/Support/AttributeMatchingPointcut.cs @@ -0,0 +1,121 @@ +#region License + +/* + * Copyright 2002-2008 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 Spring.Util; + +namespace Spring.Aop.Support +{ + /// + /// Pointcut that looks for a specific attribute being present on a class or + /// method. + /// + /// Juergen Hoeller + /// Mark Pollack (.NET) + public class AttributeMatchingPointcut : IPointcut + { + private readonly ITypeFilter typeFilter; + private readonly IMethodMatcher methodMatcher; + + + /// + /// Initializes a new instance of the class for the + /// given attribute type. + /// + /// Type of the attribute to look for at the class level. + public AttributeMatchingPointcut(Type attributeType) + { + ValidateAttributeTypeArgument(attributeType); + this.typeFilter = new AttributeTypeFilter(attributeType); + this.methodMatcher = TrueMethodMatcher.True; + } + + /// + /// Initializes a new instance of the class for the + /// given attribute type + /// + /// Type of the attribute. + /// if set to true [check inherited]. + public AttributeMatchingPointcut(Type attributeType, bool checkInherited) + { + ValidateAttributeTypeArgument(attributeType); + this.typeFilter = new AttributeTypeFilter(attributeType, checkInherited); + this.methodMatcher = TrueMethodMatcher.True; + } + + /// + /// Initializes a new instance of the class for the given + /// attribute type + /// + /// The attribute type to look for at the class level. + /// The attribute type to look for at the method attribute. + public AttributeMatchingPointcut(Type classAttributeType, Type methodAttributeType) + { + AssertUtils.IsTrue(classAttributeType != null || methodAttributeType != null, + "Either Type attribute type or Method attribute type needs to be specified (or both)"); + + if (classAttributeType != null) + { + this.typeFilter = new AttributeTypeFilter(classAttributeType); + } else + { + this.typeFilter = TrueTypeFilter.True; + } + + if (methodAttributeType != null) + { + this.methodMatcher = new AttributeMethodMatcher(methodAttributeType); + } else + { + this.methodMatcher = TrueMethodMatcher.True; + } + } + + /// + /// The for this pointcut. + /// + /// The current . + public ITypeFilter TypeFilter + { + get { return this.typeFilter; } + } + + /// + /// The for this pointcut. + /// + /// The current . + public IMethodMatcher MethodMatcher + { + get { return this.methodMatcher; } + } + + private static void ValidateAttributeTypeArgument(Type attributeType) + { + AssertUtils.ArgumentNotNull(attributeType, "attributeType"); + if (!typeof(Attribute).IsAssignableFrom(attributeType)) + { + throw new ArgumentException( + string.Format( + "The [{0}] Type must be derived from the [System.Attribute] class.", + attributeType)); + } + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Aop/Aop/Support/AttributeMethodMatcher.cs b/src/Spring/Spring.Aop/Aop/Support/AttributeMethodMatcher.cs new file mode 100644 index 00000000..989ae74c --- /dev/null +++ b/src/Spring/Spring.Aop/Aop/Support/AttributeMethodMatcher.cs @@ -0,0 +1,109 @@ +#region License + +/* + * Copyright 2002-2008 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 System.Reflection; +using Spring.Util; + +namespace Spring.Aop.Support +{ + /// + /// MethodMatcher that looks for a specific attribute being present on the + /// method (checking both the method on the onviked interface, if any and the corresponding + /// method on the target class + /// + /// Juergen hoeller + /// Mark Pollack + /// + public class AttributeMethodMatcher : StaticMethodMatcher + { + private readonly Type attributeType; + + + /// + /// Initializes a new instance of the class for the + /// given atribute type. + /// + /// Type of the attribute to look for. + public AttributeMethodMatcher(Type attributeType) + { + ValidateAttributeTypeArgument(attributeType); + this.attributeType = attributeType; + } + + /// + /// Does the supplied satisfy this matcher? + /// + /// The candidate method. + /// The target (may be , + /// in which case the candidate must be taken + /// to be the 's declaring class). + /// + /// if this this method matches statically. + /// + /// + ///

+ /// Must be implemented by a derived class in order to specify matching + /// rules. + ///

+ ///
+ public override bool Matches(MethodInfo method, Type targetType) + { + if (method.IsDefined(attributeType, true)) + { + // Checks whether the attribute is defined on the method or a super definition of the method + // but does not check attributes on implemented interfaces. + return true; + } + else + { + + Type[] parameterTypes = ReflectionUtils.GetParameterTypes(method); + + // Also check whether the attribute is defined on a method implemented from an interface. + // First find all interfaces for the type that contains the method. + // Next, check each interface for the presence of the attribute on the corresponding + // method from the interface. + foreach (Type interfaceType in method.DeclaringType.GetInterfaces()) + { + MethodInfo intfMethod = interfaceType.GetMethod(method.Name, parameterTypes); + if (intfMethod != null && intfMethod.IsDefined(attributeType, true)) + { + return true; + } + } + + return false; + } + } + + private static void ValidateAttributeTypeArgument(Type attributeType) + { + AssertUtils.ArgumentNotNull(attributeType, "attributeType"); + if (!typeof(Attribute).IsAssignableFrom(attributeType)) + { + throw new ArgumentException( + string.Format( + "The [{0}] Type must be derived from the [System.Attribute] class.", + attributeType)); + } + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Aop/Aop/Support/AttributeTypeFilter.cs b/src/Spring/Spring.Aop/Aop/Support/AttributeTypeFilter.cs new file mode 100644 index 00000000..9a2d6f71 --- /dev/null +++ b/src/Spring/Spring.Aop/Aop/Support/AttributeTypeFilter.cs @@ -0,0 +1,111 @@ +#region License + +/* + * Copyright 2002-2008 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 Spring.Util; + +namespace Spring.Aop.Support +{ + /// + /// ITypeFilter that looks for a specific attribute being present on a class + /// + /// Juergen Hoeller + /// Mark Pollack (.NET) + public class AttributeTypeFilter : ITypeFilter + { + private readonly Type attributeType; + + private readonly bool checkInherited; + + /// + /// Initializes a new instance of the class for the + /// given attribute type. + /// + /// Type of the attribute to look for. + public AttributeTypeFilter(Type attributeType) : this(attributeType, false) + { + + } + + /// + /// Initializes a new instance of the class for the + /// given attribute type. + /// + /// Type of the attribute. + /// if set to true [check inherited]. + public AttributeTypeFilter(Type attributeType, bool checkInherited) + { + #region parameter validation + AssertUtils.ArgumentNotNull(attributeType, "attributeType"); + if (!typeof(Attribute).IsAssignableFrom(attributeType)) + { + throw new ArgumentException( + string.Format( + "The [{0}] Type must be derived from the [System.Attribute] class.", + attributeType)); + } + #endregion + this.attributeType = attributeType; + this.checkInherited = checkInherited; + } + + + /// + /// Should the pointcut apply to the supplied ? + /// + /// The candidate . + /// + /// if the advice should apply to the supplied + /// + /// + public bool Matches(Type type) + { + if (checkInherited) + { + return FindAttribute(type, attributeType) != null; + } else + { + return Attribute.GetCustomAttributes(type, attributeType, false) != null; + } + } + + private Attribute FindAttribute(Type type, Type attribType) + { + Attribute[] attributes = Attribute.GetCustomAttributes(type, attributeType, false); // we will traverse hierarchy ourselves. + if (attributes.Length > 0) + { + return attributes[0]; + } + foreach (Type interfaceType in type.GetInterfaces()) + { + Attribute attrib = FindAttribute(interfaceType, attribType); + if (attrib != null) + { + return attrib; + } + } + if (type.BaseType == null) + { + return null; + } + return FindAttribute(type.BaseType, attribType); + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Aop/Spring.Aop.2005.csproj b/src/Spring/Spring.Aop/Spring.Aop.2005.csproj index 59be97d4..5aa4e8ad 100644 --- a/src/Spring/Spring.Aop/Spring.Aop.2005.csproj +++ b/src/Spring/Spring.Aop/Spring.Aop.2005.csproj @@ -281,12 +281,15 @@ Code + Code Code + + Code diff --git a/src/Spring/Spring.Core/Spring.Core.2005.csproj b/src/Spring/Spring.Core/Spring.Core.2005.csproj index ea4ae66b..10620982 100644 --- a/src/Spring/Spring.Core/Spring.Core.2005.csproj +++ b/src/Spring/Spring.Core/Spring.Core.2005.csproj @@ -956,6 +956,9 @@ + + + diff --git a/src/Spring/Spring.Core/Stereotype/ComponentAttribute.cs b/src/Spring/Spring.Core/Stereotype/ComponentAttribute.cs new file mode 100644 index 00000000..0b8c4d29 --- /dev/null +++ b/src/Spring/Spring.Core/Stereotype/ComponentAttribute.cs @@ -0,0 +1,54 @@ + + +using System; + +namespace Spring.Stereotype { + + /// + /// Indicates that an annotated class is a "component". + /// Such classes are considered as candidates for future features such + /// as auto-detection when using attribute-based configuration and assembly scanning. + /// + /// Other class-level annotations may be considered as identifying + /// a component as well, typically a special kind of component: + /// e.g. the Repository attribute. + /// + /// Mark Fisher + /// Mark Pollack (.NET) + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, Inherited = true)] + [Serializable] + public class ComponentAttribute : Attribute + { + private string name = ""; + + + /// + /// Initializes a new instance of the class. + /// + public ComponentAttribute() + { + } + + + /// + /// Initializes a new instance of the class. + /// + /// The name of the component. + public ComponentAttribute(string name) + { + this.name = name; + } + + + /// + /// Gets or sets the name of the component + /// + /// The name of the component. + public string Name + { + get { return name; } + set { name = value; } + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Core/Stereotype/RepositoryAttribute.cs b/src/Spring/Spring.Core/Stereotype/RepositoryAttribute.cs new file mode 100644 index 00000000..89dc4fbf --- /dev/null +++ b/src/Spring/Spring.Core/Stereotype/RepositoryAttribute.cs @@ -0,0 +1,60 @@ +#region License + +/* + * Copyright 2002-2008 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; + +namespace Spring.Stereotype +{ + /// + /// Indicates that an annotated class is a "Repository" (or "DAO"). + /// + /// + /// A class with this attribute is eligible for Spring DataAccessException translation. A class + /// with the Repository attribute is also clarified as to its role in the overall application + /// architecture for the purpose of tools, aspects, etc. + /// + /// This attribute also serves as a specialization of the ComponentAttribute, allowing implementation + /// classes to be autodetected in future releases through assembly scanning. + /// + /// + /// Rod Johnson + /// Jueren Hoeller + /// Mark Pollack (.NET) + /// + public class RepositoryAttribute : ComponentAttribute + { + /// + /// Initializes a new instance of the class. + /// + public RepositoryAttribute() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The name of the repository. + public RepositoryAttribute(string name) : base(name) + { + } + + + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Core/Stereotype/ServiceAttribute.cs b/src/Spring/Spring.Core/Stereotype/ServiceAttribute.cs new file mode 100644 index 00000000..3dcd334d --- /dev/null +++ b/src/Spring/Spring.Core/Stereotype/ServiceAttribute.cs @@ -0,0 +1,54 @@ +#region License + +/* + * Copyright 2002-2008 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 + +namespace Spring.Stereotype +{ + /// + /// Indicates that an annotated class is a "Service" (e.g. a business service facade). + /// + /// + /// + /// This attribute also serves as a specialization of the ComponentAttribute, allowing implementation + /// classes to be autodetected in future releases through assembly scanning. + /// + /// + /// Juergen Hoeller + /// Mark Pollack (.NET) + public class ServiceAttribute : ComponentAttribute + { + + /// + /// Initializes a new instance of the class. + /// + public ServiceAttribute() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The name. + public ServiceAttribute(string name) : base(name) + { + } + + + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Data/Dao/Attributes/PersistenceExceptionTranslationAdvisor.cs b/src/Spring/Spring.Data/Dao/Attributes/PersistenceExceptionTranslationAdvisor.cs new file mode 100644 index 00000000..25ce1e35 --- /dev/null +++ b/src/Spring/Spring.Data/Dao/Attributes/PersistenceExceptionTranslationAdvisor.cs @@ -0,0 +1,96 @@ +#region License + +/* + * Copyright 2002-2008 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 Spring.Aop; +using Spring.Aop.Support; +using Spring.Dao.Support; +using Spring.Objects.Factory; + +namespace Spring.Dao.Attributes +{ + /// + /// Spring AOP exception translation aspect for use at Repository or DAO layer level. + /// Translates native persistence exceptions into Spring's DataAccessException hierarchy, + /// based on a given PersistenceExceptionTranslator. + /// + /// Rod Johnson + /// Juergen Hoeller + /// Mark Pollack (.NET) + public class PersistenceExceptionTranslationAdvisor : AbstractPointcutAdvisor + { + private PersistenceExceptionTranslationInterceptor advice; + + private AttributeMatchingPointcut pointcut; + + /// + /// Initializes a new instance of the class. + /// + /// The persistence exception translator to use. + /// Type of the repository attribute to check for. + public PersistenceExceptionTranslationAdvisor(IPersistenceExceptionTranslator persistenceExceptionTranslator, + Type repositoryAttributeType) + { + this.advice = new PersistenceExceptionTranslationInterceptor(persistenceExceptionTranslator); + this.pointcut = new AttributeMatchingPointcut(repositoryAttributeType, true); + } + + /// + /// Initializes a new instance of the class. + /// + /// The object factory to obtain all IPersistenceExceptionTranslators from. + /// Type of the repository attribute to check for. + public PersistenceExceptionTranslationAdvisor(IListableObjectFactory objectFactory, Type repositoryAttributeType) + { + this.advice = new PersistenceExceptionTranslationInterceptor(objectFactory); + this.pointcut = new AttributeMatchingPointcut(repositoryAttributeType, true); + } + + /// + /// Return the advice part of this aspect. + /// + /// + /// + ///

+ /// An advice may be an interceptor, a throws advice, before advice, + /// introduction etc. + ///

+ ///
+ /// + /// The advice that should apply if the pointcut matches. + /// + public override IAdvice Advice + { + get { return this.advice; } + set { } + } + + /// + /// The that drives this advisor. + /// + /// + public override IPointcut Pointcut + { + get { return this.pointcut; } + set { } + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Data/Dao/Attributes/PersistenceExceptionTranslationPostProcessor.cs b/src/Spring/Spring.Data/Dao/Attributes/PersistenceExceptionTranslationPostProcessor.cs new file mode 100644 index 00000000..7e589b1e --- /dev/null +++ b/src/Spring/Spring.Data/Dao/Attributes/PersistenceExceptionTranslationPostProcessor.cs @@ -0,0 +1,176 @@ +#region License + +/* + * Copyright 2002-2008 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 Spring.Aop.Framework; +using Spring.Core; +using Spring.Dao; +using Spring.Dao.Support; +using Spring.Objects.Factory; +using Spring.Objects.Factory.Config; +using Spring.Stereotype; +using Spring.Util; + +namespace Spring.Dao.Attributes +{ + /// + /// Object post-processor that automatically applies persistence exception + /// translation to any bean that carries the + /// attribute, adding a corresponding + /// to the xposed proxy (either an existing AOP proxy or a newly generated + /// proxy that implements all of the target's interfaces). + /// + /// + /// Translates native resource exceptions to Spring's + /// hierarchy. Autodetects object that implement the + /// interface, which are subsequently asked to translate candidate exceptions. + /// + /// All of Spring's applicable resource factories implement the + /// IPersistenceExceptionTranslator interface out of the box. + /// As a consequence, all that is usually needed to enable automatic exception + /// translation is marking all affected objects (such as DAOs) with the + /// Repository annotation, along with defining this post-processor + /// in the application context. + /// + /// + /// Rod Johnson + /// Juergen Hoeller + /// Mark Pollack (.NET) + /// + /// + /// + /// + public class PersistenceExceptionTranslationPostProcessor : ProxyConfig, IObjectPostProcessor, IObjectFactoryAware, IOrdered + { + private Type repositoryAttributeType = typeof(RepositoryAttribute); + + private PersistenceExceptionTranslationAdvisor persistenceExceptionTranslationAdvisor; + + + /// + /// Sets the type of the repository attribute. The default required attribute type is the + /// attirbute. This setter property exists so that developers + /// can provide their own (non-Spring-specific) attribute type to indicate that a class has a + /// repository role. + /// + /// The desitred type of the repository attribute. + public Type RepositoryAttributeType + { + set + { + AssertUtils.ArgumentNotNull(value, "'RepositoryAttributeType' must not be null"); + repositoryAttributeType = value; + } + } + + + + + public IObjectFactory ObjectFactory + { + set + { + IListableObjectFactory lof = value as IListableObjectFactory; + if (lof == null) + { + throw new ArgumentException("Cannot use PersistenceExceptionTranslator autodetection without IListableObjectFactory"); + } + this.persistenceExceptionTranslationAdvisor = + new PersistenceExceptionTranslationAdvisor(lof, this.repositoryAttributeType); + } + } + + public int Order + { + get { + return Int32.MaxValue; + // lowest precidence value + // This should run after all other post-processors, so that it can just add + // an advisor to existing proxies rather than double-proxy. + } + } + + /// + /// Just return the passed in object instance + /// + /// The new object instance. + /// The name of the object. + /// + /// The passed in object instance + /// + /// + /// In case of errors. + /// + public object PostProcessBeforeInitialization(object instance, string name) + { + return instance; + } + + /// + /// Add PersistenceExceptionTranslationAdvice to candidate object if it is a match. + /// Create AOP proxy if necessary or add advice to existing advice chain. + /// + /// The new object instance. + /// The name of the object. + /// + /// The object instance to use, wrapped with either the original or a wrapped one. + /// + /// + /// In case of errors. + /// + public object PostProcessAfterInitialization(object instance, string objectName) + { + IAdvised advised = instance as IAdvised; + Type targetType; + if (advised != null) + { + targetType = advised.TargetSource.TargetType; + } else + { + targetType = instance.GetType(); + } + if (targetType == null) + { + // Can't do much here + return instance; + } + + if (AopUtils.CanApply(this.persistenceExceptionTranslationAdvisor, targetType, ReflectionUtils.GetInterfaces(targetType))) + { + if (advised != null) + { + advised.AddAdvisor(this.persistenceExceptionTranslationAdvisor); + return instance; + } + else + { + ProxyFactory proxyFactory = new ProxyFactory(instance); + // copy our properties inherited from ProxyConfig + proxyFactory.CopyFrom(this); + proxyFactory.AddAdvisor(this.persistenceExceptionTranslationAdvisor); + return proxyFactory.GetProxy(); + } + } else + { + return instance; + } + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Data/Dao/Support/ChainedPersistenceExceptionTranslator.cs b/src/Spring/Spring.Data/Dao/Support/ChainedPersistenceExceptionTranslator.cs new file mode 100644 index 00000000..76b5c74c --- /dev/null +++ b/src/Spring/Spring.Data/Dao/Support/ChainedPersistenceExceptionTranslator.cs @@ -0,0 +1,100 @@ +#region License + +/* + * Copyright 2002-2008 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 System.Collections; +using Spring.Data.Support; +using Spring.Util; + +namespace Spring.Dao.Support +{ + /// + /// Implementation of PersistenceExceptionTranslator that supports chaining, + /// allowing the addition of PersistenceExceptionTranslator instances in order. + /// Returns non-null on the first (if any) match. + /// + /// Rod Johnson + /// Juergen Hoeller + /// Mark Pollack (.NET) + public class ChainedPersistenceExceptionTranslator : IPersistenceExceptionTranslator + { + private readonly ArrayList translatorList = new ArrayList(4); + + /// + /// Adds the translator to the translator list. + /// + /// The translator. + public void AddTranslator(IPersistenceExceptionTranslator translator) + { + AssertUtils.ArgumentNotNull(translator, "PersistenceExceptionTranslator must not be null"); + this.translatorList.Add(translator); + } + + + /// + /// Gets all registered IPersistenceExceptionTranslator as an array. + /// + /// The IPersistenceExceptionTranslators. + public IPersistenceExceptionTranslator[] Translators + { + get + { + return (IPersistenceExceptionTranslator[]) translatorList.ToArray(typeof (IPersistenceExceptionTranslator)); + } + } + + /// + /// Translate the given exception thrown by a persistence framework to a + /// corresponding exception from Spring's generic DataAccessException hierarchy, + /// if possible. + /// + /// The exception thrown. + /// + /// the corresponding DataAccessException (or null if the + /// exception could not be translated, as in this case it may result from + /// user code rather than an actual persistence problem) + /// + /// + /// + /// Do not translate exceptions that are not understand by this translator: + /// for example, if coming from another persistence framework, or resulting + /// from user code and unrelated to persistence. + /// + /// + /// Of particular importance is the correct translation to + /// for example on constraint violation. Implementations may use Spring ADO.NET Framework's + /// sophisticated exception translation to provide further information in the event of SQLException as a root cause. + /// + /// + /// + /// + public DataAccessException TranslateExceptionIfPossible(Exception ex) + { + DataAccessException translatedDex = null; + foreach (IPersistenceExceptionTranslator pet in translatorList) + { + translatedDex = pet.TranslateExceptionIfPossible(ex); + } + return translatedDex; + } + + + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Data/Dao/Support/IPersistenceExceptionTranslator.cs b/src/Spring/Spring.Data/Dao/Support/IPersistenceExceptionTranslator.cs new file mode 100644 index 00000000..67921199 --- /dev/null +++ b/src/Spring/Spring.Data/Dao/Support/IPersistenceExceptionTranslator.cs @@ -0,0 +1,71 @@ + + +#region License + +/* + * Copyright 2002-2008 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 Spring.Dao; +using Spring.Data.Support; + +namespace Spring.Dao.Support +{ + /// + /// Interface implemented by Spring integrations with data access technologies + /// that throw exceptions. + /// + /// + /// This allows consistent usage of combined exception translation functionality, + /// without forcing a single translator to understand every single possible type + /// of exception. + /// + /// Rod Johnson + /// Mark Pollack (.NET) + public interface IPersistenceExceptionTranslator + { + /// + /// Translate the given exception thrown by a persistence framework to a + /// corresponding exception from Spring's generic DataAccessException hierarchy, + /// if possible. + /// + /// + /// + /// Do not translate exceptions that are not understand by this translator: + /// for example, if coming from another persistence framework, or resulting + /// from user code and unrelated to persistence. + /// + /// + /// Of particular importance is the correct translation to + /// for example on constraint violation. Implementations may use Spring ADO.NET Framework's + /// sophisticated exception translation to provide further information in the event of SQLException as a root cause. + /// + /// + /// The exception thrown. + /// the corresponding DataAccessException (or null if the + /// exception could not be translated, as in this case it may result from + /// user code rather than an actual persistence problem) + /// + /// + /// + /// Rod Johnson + /// Juergen Hoeller + /// Mark Pollack (.NET) + DataAccessException TranslateExceptionIfPossible(Exception ex); + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Data/Dao/Support/PersistenceExceptionTranslationInterceptor.cs b/src/Spring/Spring.Data/Dao/Support/PersistenceExceptionTranslationInterceptor.cs new file mode 100644 index 00000000..ffcc949c --- /dev/null +++ b/src/Spring/Spring.Data/Dao/Support/PersistenceExceptionTranslationInterceptor.cs @@ -0,0 +1,195 @@ +#region License + +/* + * Copyright 2002-2008 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 System.Collections; +using AopAlliance.Intercept; +using Spring.Objects.Factory; +using Spring.Objects.Factory.Config; +using Spring.Util; + +namespace Spring.Dao.Support +{ + /// + /// AOP MethodInterceptor that provides persistence exception translation + /// based on a given PersistenceExceptionTranslator. + /// + /// + /// Delegates to the given to translate + /// an Exception thrown into Spring's DataAccessException hierarchy + /// (if appropriate). + /// + /// Rod Johnson + /// Juergen Hoeller + /// Mark Pollack (.NET) + public class PersistenceExceptionTranslationInterceptor : IMethodInterceptor, IObjectFactoryAware, IInitializingObject + { + private IPersistenceExceptionTranslator persistenceExceptionTranslator; + + + /// + /// Initializes a new instance of the class. + /// Needs to be configured with a PersistenceExceptionTranslator afterwards. + /// + public PersistenceExceptionTranslationInterceptor() + { + } + + + /// + /// Initializes a new instance of the class for the + /// given IPersistenceExceptionTranslator + /// + /// The persistence exception translator to use. + public PersistenceExceptionTranslationInterceptor(IPersistenceExceptionTranslator persistenceExceptionTranslator) + { + this.persistenceExceptionTranslator = persistenceExceptionTranslator; + } + + /// + /// Initializes a new instance of the class, autodetecting + /// IPersistenceExceptionTranslators in the given object factory. + /// + /// The object factory to obtain all IPersistenceExceptionTranslators from. + public PersistenceExceptionTranslationInterceptor(IListableObjectFactory objectFactory) + { + this.persistenceExceptionTranslator = DetectPersistenceExceptionTranslators(objectFactory); + } + + + /// + /// Sets the persistence exception translator. The default is to autodetect all IPersistenceExceptionTranslators + /// in the containing object factory, using them in a chain. + /// + /// The persistence exception translator. + public IPersistenceExceptionTranslator PersistenceExceptionTranslator + { + set + { + AssertUtils.ArgumentNotNull(value, "IPersistenceExceptionTranslator must not be null"); + persistenceExceptionTranslator = value; + } + } + + + /// + /// Callback that supplies the owning factory to an object instance. + /// + /// + /// Owning + /// (may not be ). The object can immediately + /// call methods on the factory. + /// + /// + ///

+ /// Invoked after population of normal object properties but before an init + /// callback like 's + /// + /// method or a custom init-method. + ///

+ ///
+ /// + /// In case of initialization errors. + /// + public IObjectFactory ObjectFactory + { + set + { + if (this.persistenceExceptionTranslator == null) + { + // No explicit exception translator specified - perform autodetection. + IListableObjectFactory owningFactory = value as IListableObjectFactory; + if (owningFactory == null) + { + throw new ArgumentException("Cannot use IPersistenceExceptionTranslator autodetection without IListableBeanFactory"); + } + this.persistenceExceptionTranslator = DetectPersistenceExceptionTranslators(owningFactory); + } + } + } + + /// + /// Ensures that the property PersistenceExceptionTranslator has been set. + /// Invoked by an + /// after it has injected all of an object's dependencies. + /// + /// + /// In the event of misconfiguration (such as the failure to set a + /// required property) or if initialization fails. + /// + public void AfterPropertiesSet() + { + if (this.persistenceExceptionTranslator == null) + { + throw new ArgumentException("Property 'PersistenceExceptionTranslator' is required"); + } + } + + /// + /// Detects the petsistence exception translators in the given object factory. + /// + /// The object factory for obtaining all IPersistenceExceptionTranslators. + /// A chained IPersistenceExceptionTranslator, combining all PersistenceExceptionTranslators found in the factory + /// + /// + protected IPersistenceExceptionTranslator DetectPersistenceExceptionTranslators(IListableObjectFactory objectFactory) + { + // Find all translators, being careful not to activate FactoryObjects. + IDictionary pets = + ObjectFactoryUtils.ObjectsOfTypeIncludingAncestors(objectFactory, + typeof (IPersistenceExceptionTranslator), false, + false); + if (pets.Count == 0) + { + throw new InvalidOperationException("No persistence exception translators found in container. Cannot perform exception translation."); + } + + ChainedPersistenceExceptionTranslator cpet = new ChainedPersistenceExceptionTranslator(); + foreach (DictionaryEntry pet in pets) + { + cpet.AddTranslator((IPersistenceExceptionTranslator)pet.Value); + } + return cpet; + } + + /// + /// Return a translated exception if this is appropriate, otherwise rethrow the original exception. + /// + /// + /// + public object Invoke(IMethodInvocation invocation) + { + try + { + return invocation.Proceed(); + } catch (Exception ex) + { + DataAccessException dex = this.persistenceExceptionTranslator.TranslateExceptionIfPossible(ex); + if (dex == null) + { + throw; + } else + { + throw dex; + } + } + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Data/Spring.Data.2005.csproj b/src/Spring/Spring.Data/Spring.Data.2005.csproj index d205235d..20050270 100644 --- a/src/Spring/Spring.Data/Spring.Data.2005.csproj +++ b/src/Spring/Spring.Data/Spring.Data.2005.csproj @@ -97,6 +97,8 @@ Code
+ + @@ -116,9 +118,12 @@ + + + diff --git a/test/Spring/Spring.Data.Tests/Dao/Attributes/PersistenceExceptionTranslationAdvisorTests.cs b/test/Spring/Spring.Data.Tests/Dao/Attributes/PersistenceExceptionTranslationAdvisorTests.cs new file mode 100644 index 00000000..c2fc83f8 --- /dev/null +++ b/test/Spring/Spring.Data.Tests/Dao/Attributes/PersistenceExceptionTranslationAdvisorTests.cs @@ -0,0 +1,83 @@ +#region License + +/* + * Copyright © 2002-2007 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 + +#region Imports + +using System; +using NUnit.Framework; +using Spring.Stereotype; + +#endregion + +namespace Spring.Dao.Attributes +{ + /// + /// This class contains tests for + /// + /// Mark Pollack + /// $Id:$ + [TestFixture] + public class PersistenceExceptionTranslationAdvisorTests + { + [SetUp] + public void Setup() + { + } + + [Test] + public void Test() + { + } + + } +#if !NET_1_0 + [Repository] + public class StereotypedRepositoryInterfaceImpl : RepositoryInterfaceImpl + { + // Extends above class just to add repository annotation + } + + + public class RepositoryInterfaceImpl : IRepositoryInterface + { + private Exception ex; + + + public Exception Behavior + { + set { ex = value; } + } + + public void Throws() + { + if (ex != null) + { + throw ex; + } + } + } +#endif + + public interface IRepositoryInterface + { + void Throws(); + } + +} \ No newline at end of file diff --git a/test/Spring/Spring.Data.Tests/Dao/Attributes/PersistenceExceptionTranslationPostProcessorTests.cs b/test/Spring/Spring.Data.Tests/Dao/Attributes/PersistenceExceptionTranslationPostProcessorTests.cs new file mode 100644 index 00000000..539e9ad1 --- /dev/null +++ b/test/Spring/Spring.Data.Tests/Dao/Attributes/PersistenceExceptionTranslationPostProcessorTests.cs @@ -0,0 +1,102 @@ +#region License + +/* + * Copyright © 2002-2007 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 +#if !NET_1_0 +#region Imports + +using NUnit.Framework; +using Spring.Aop; +using Spring.Aop.Framework; +using Spring.Context.Support; +using Spring.Dao.Support; +using Spring.Objects; +using Spring.Objects.Factory.Support; + +#endregion + +namespace Spring.Dao.Attributes +{ + /// + /// Unit tests for PersistenceExceptionTranslationPostProcessor. Does not test + /// translation; there are separate unit tests for the Spring AOP Advisor. + /// Just checks whether proxying occurs correctly, + /// + /// Rod Johnson + /// Mark Pollack (.NET) + /// $Id:$ + [TestFixture] + public class PersistenceExceptionTranslationPostProcessorTests + { + [SetUp] + public void Setup() + { + } + + [Test] + public void FailsWithNoPersistenceExceptionTranslators() + { + GenericApplicationContext gac = new GenericApplicationContext(); + gac.RegisterObjectDefinition("translator", new RootObjectDefinition(typeof(PersistenceExceptionTranslationPostProcessor))); + gac.RegisterObjectDefinition("proxied", new RootObjectDefinition(typeof(StereotypedRepositoryInterfaceImpl))); + try + { + gac.Refresh(); + Assert.Fail("Should fail with no translators"); + } catch (ObjectsException) + { + // expected. + } + } + + [Test] + public void ProxiesCorrectly() + { + GenericApplicationContext gac = new GenericApplicationContext(); + gac.RegisterObjectDefinition("translator", new RootObjectDefinition(typeof(PersistenceExceptionTranslationPostProcessor))); + gac.RegisterObjectDefinition("notProxied", new RootObjectDefinition(typeof(RepositoryInterfaceImpl))); + gac.RegisterObjectDefinition("proxied", + new RootObjectDefinition(typeof (StereotypedRepositoryInterfaceImpl))); + gac.RegisterObjectDefinition("chainedTranslator", + new RootObjectDefinition(typeof (ChainedPersistenceExceptionTranslator))); + gac.Refresh(); + + IRepositoryInterface shouldNotBeProxied = (IRepositoryInterface) gac.GetObject("notProxied"); + Assert.IsFalse(AopUtils.IsAopProxy(shouldNotBeProxied)); + + IRepositoryInterface shouldBeProxied = (IRepositoryInterface) gac.GetObject("proxied"); + Assert.IsTrue(AopUtils.IsAopProxy(shouldBeProxied)); + + CheckWillTranslateExceptions(shouldBeProxied); + + } + + private void CheckWillTranslateExceptions(object o) + { + IAdvised advised = o as IAdvised; + Assert.IsNotNull(advised); + foreach (IAdvisor advisor in advised.Advisors) + { + PersistenceExceptionTranslationAdvisor peta = advisor as PersistenceExceptionTranslationAdvisor; + if (peta != null) return; + } + Assert.Fail("No translation"); + } + } +} +#endif \ No newline at end of file diff --git a/test/Spring/Spring.Data.Tests/Spring.Data.Tests.2005.csproj b/test/Spring/Spring.Data.Tests/Spring.Data.Tests.2005.csproj index 13c80e7d..6fc19ae9 100644 --- a/test/Spring/Spring.Data.Tests/Spring.Data.Tests.2005.csproj +++ b/test/Spring/Spring.Data.Tests/Spring.Data.Tests.2005.csproj @@ -170,6 +170,8 @@ + +