From 773f3146dab86afae11fa359f9682ec4a18a5352 Mon Sep 17 00:00:00 2001 From: Thomas Trageser Date: Thu, 23 Aug 2012 13:14:12 +0100 Subject: [PATCH] Add autowire by attribute for fields, properties, methods and constructors --- .../Spring.Core/Core/MethodParameter.cs | 8 + .../Factory/Attributes/AutowiredAttribute.cs | 74 ++ .../AutowiredAttributeObjectPostProcessor.cs | 639 ++++++++++++++++++ .../Factory/Attributes/InjectionMetadata.cs | 112 +++ .../Factory/Attributes/QualifierAttribute.cs | 32 + .../Factory/Config/DependencyDescriptor.cs | 148 +++- .../Support/DefaultListableObjectFactory.cs | 65 +- .../Spring.Core/Spring.Core.2010.csproj | 4 + .../AutowireAttributeCollectionTests.cs | 160 +++++ .../AutowireAttributeConstructorTest.cs | 343 ++++++++++ .../Attributes/AutowireAttributeFieldTest.cs | 290 ++++++++ .../AutowireAttributeMethodsTests.cs | 371 ++++++++++ .../AutowireAttributePropertyTest.cs | 254 +++++++ .../Spring.Core.Tests.2010.csproj | 5 + 14 files changed, 2501 insertions(+), 4 deletions(-) create mode 100644 src/Spring/Spring.Core/Objects/Factory/Attributes/AutowiredAttribute.cs create mode 100644 src/Spring/Spring.Core/Objects/Factory/Attributes/AutowiredAttributeObjectPostProcessor.cs create mode 100644 src/Spring/Spring.Core/Objects/Factory/Attributes/InjectionMetadata.cs create mode 100644 src/Spring/Spring.Core/Objects/Factory/Attributes/QualifierAttribute.cs create mode 100644 test/Spring/Spring.Core.Tests/Objects/Factory/Attributes/AutowireAttributeCollectionTests.cs create mode 100644 test/Spring/Spring.Core.Tests/Objects/Factory/Attributes/AutowireAttributeConstructorTest.cs create mode 100644 test/Spring/Spring.Core.Tests/Objects/Factory/Attributes/AutowireAttributeFieldTest.cs create mode 100644 test/Spring/Spring.Core.Tests/Objects/Factory/Attributes/AutowireAttributeMethodsTests.cs create mode 100644 test/Spring/Spring.Core.Tests/Objects/Factory/Attributes/AutowireAttributePropertyTest.cs diff --git a/src/Spring/Spring.Core/Core/MethodParameter.cs b/src/Spring/Spring.Core/Core/MethodParameter.cs index 292720f4..1a923378 100644 --- a/src/Spring/Spring.Core/Core/MethodParameter.cs +++ b/src/Spring/Spring.Core/Core/MethodParameter.cs @@ -136,5 +136,13 @@ namespace Spring.Core { get { return constructorInfo; } } + + public Attribute[] GetParameterAttributes() + { + if (methodInfo != null) + return Attribute.GetCustomAttributes(methodInfo.GetParameters()[parameterIndex]); + else + return Attribute.GetCustomAttributes(constructorInfo.GetParameters()[parameterIndex]); + } } } \ No newline at end of file diff --git a/src/Spring/Spring.Core/Objects/Factory/Attributes/AutowiredAttribute.cs b/src/Spring/Spring.Core/Objects/Factory/Attributes/AutowiredAttribute.cs new file mode 100644 index 00000000..9be3154a --- /dev/null +++ b/src/Spring/Spring.Core/Objects/Factory/Attributes/AutowiredAttribute.cs @@ -0,0 +1,74 @@ +#region License + +/* + * Copyright © 2010-2011 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.Objects.Factory.Attributes +{ + /// + /// Marks a constructor, field, propery or config method as to be + /// autowired by Spring's dependency injection facilities. + /// + /// Only one constructor (at max) of any given bean class may carry this + /// annotation, indicating the constructor to autowire when used as a Spring + /// bean. Such a constructor does not have to be public. + /// + /// Fields are injected right after construction of a object, before any + /// config methods are invoked. Such a config field does not have to be public. + /// + /// Config methods may have an arbitrary name and any number of arguments; + /// each of those arguments will be autowired with a matching bean in the + /// Spring container. Object property setter methods are effectively just + /// a special case of such a general config method. Such config methods + /// do not have to be public. + /// + /// In the case of multiple argument methods, the 'required' parameter is + /// applicable for all arguments. + /// + /// In case of a {@link java.util.Collection} or {@link java.util.Map} + /// dependency type, the container will autowire all beans matching the + /// declared value type. In case of a Map, the keys must be declared as + /// type String and will be resolved to the corresponding bean names. + /// + /// Note that actual injection is performed through a + /// {@link org.springframework.beans.factory.config.BeanPostProcessor + /// BeanPostProcessor} which in turn means that you cannot + /// use {@code @Autowired} to inject references into + /// {@link org.springframework.beans.factory.config.BeanPostProcessor + /// BeanPostProcessor} or + /// {@link org.springframework.beans.factory.config.BeanFactoryPostProcessor BeanFactoryPostProcessor} + /// types. Please consult the javadoc for the {@link AutowiredAnnotationBeanPostProcessor} + /// class (which, by default, checks for the presence of this annotation). + /// + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Constructor)] + public class AutowiredAttribute : Attribute + { + private bool _required = true; + + /// + /// Defines it Autowired PostProcessor should fail if object is not set + /// + public bool Required + { + get { return _required; } + set { _required = value; } + } + } +} diff --git a/src/Spring/Spring.Core/Objects/Factory/Attributes/AutowiredAttributeObjectPostProcessor.cs b/src/Spring/Spring.Core/Objects/Factory/Attributes/AutowiredAttributeObjectPostProcessor.cs new file mode 100644 index 00000000..07c74ea8 --- /dev/null +++ b/src/Spring/Spring.Core/Objects/Factory/Attributes/AutowiredAttributeObjectPostProcessor.cs @@ -0,0 +1,639 @@ +#region License + +/* + * Copyright © 2010-2011 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 System.Collections.Generic; +using System.ComponentModel; +using System.Reflection; +using System.Linq; +using Spring.Collections; +using Spring.Core; +using Spring.Objects.Factory.Config; +using Common.Logging; +using Spring.Objects.Factory.Support; + +namespace Spring.Objects.Factory.Attributes +{ + + /// + /// implementation + /// that autowires annotated fields, properties and arbitrary config methods. + /// Such members to be injected are detected through an attribute: by default, + /// Spring's . + /// + /// Only one constructor (at max) of any given bean class may carry this + /// annotation with the 'required' parameter set to true, + /// indicating the constructor to autowire when used as a Spring bean. + /// If multiple non-required constructors carry the annotation, they + /// will be considered as candidates for autowiring. The constructor with + /// the greatest number of dependencies that can be satisfied by matching + /// beans in the Spring container will be chosen. If none of the candidates + /// can be satisfied, then a default constructor (if present) will be used. + /// An annotated constructor does not have to be public. + /// + /// Fields are injected right after construction of a bean, before any + /// config methods are invoked. Such a config field does not have to be public. + /// + /// Config methods may have an arbitrary name and any number of arguments; each of + /// those arguments will be autowired with a matching bean in the Spring container. + /// Bean property setter methods are effectively just a special case of such a + /// general config method. Config methods do not have to be public. + /// + /// Note: A default AutowiredAttributeObjectPostProcessor will be registered + /// by the "context:annotation-config" and "context:component-scan" XML tags. + /// Remove or turn off the default annotation configuration there if you intend + /// to specify a custom AutowiredAnnotationBeanPostProcessor bean definition. + /// NOTE: Annotation injection will be performed before XML injection; + /// thus the latter configuration will override the former for properties wired through + /// both approaches. + /// + public class AutowiredAttributeObjectPostProcessor : InstantiationAwareObjectPostProcessorAdapter, + IObjectFactoryAware, IOrdered + { + private static readonly ILog Logger = LogManager.GetLogger(); + + private int _order = int.MaxValue - 2; + + private static IConfigurableListableObjectFactory _objectFactory; + + private SynchronizedHashtable _candidateConstructorsCache = new SynchronizedHashtable(); + + private readonly IDictionary _injectionMetadataCache = + new Dictionary(); + + private Type _autowiredPropertyType = typeof (AutowiredAttribute); + + /// + /// Return the order value of this object, where a higher value means greater in + /// terms of sorting. + /// + /// + ///

Normally starting with 0 or 1, with indicating + /// greatest. Same order values will result in arbitrary positions for the affected + /// objects. + ///

Higher value can be interpreted as lower priority, consequently the first object + /// has highest priority. + ///

+ ///
+ /// + /// The order value. + /// + public int Order + { + get { return _order; } + private set { _order = 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 { _objectFactory = (IConfigurableListableObjectFactory) value; } + } + + + /// + /// Sets the used AutowiredAttributeType during the scan + /// + public Type AutowiredAttributeType + { + get { return _autowiredPropertyType; } + set { _autowiredPropertyType = value; } + } + + + /// + /// Determines the candidate constructors to use for the given object. + /// + /// The raw Type of the object. + /// Name of the object. + /// The candidate constructors, or null if none specified + /// in case of errors + public override ConstructorInfo[] DetermineCandidateConstructors(Type objectType, string objectName) + { + // Quick check on the concurrent map first, with minimal locking. + ConstructorInfo[] candidateConstructors = _candidateConstructorsCache.ContainsKey(objectType) + ? (ConstructorInfo[])_candidateConstructorsCache[objectType] + : null; + if (candidateConstructors == null) + { + lock (_candidateConstructorsCache) + { + candidateConstructors = _candidateConstructorsCache.ContainsKey(objectType) + ? (ConstructorInfo[])_candidateConstructorsCache[objectType] + : null; + if (candidateConstructors == null) + { + ConstructorInfo[] rawCandidates = objectType.GetConstructors(); + IList candidates = new List(rawCandidates.Length); + ConstructorInfo requiredConstructor = null; + ConstructorInfo defaultConstructor = null; + foreach(var candidate in rawCandidates) + { + AutowiredAttribute attr = + Attribute.GetCustomAttribute(candidate, typeof (AutowiredAttribute)) as AutowiredAttribute; + if (attr != null) + { + if (requiredConstructor != null) { + throw new ObjectCreationException("Invalid autowire-marked constructor: " + candidate + + ". Found another constructor with 'required' Autowired annotation: " + + requiredConstructor); + } + if (candidate.GetParameters().Length == 0) + { + throw new InvalidOperationException("Autowired annotation requires at least one argument: " + candidate); + } + if (attr.Required) + { + if (candidates.Count > 0) + { + throw new ObjectCreationException( + "Invalid autowire-marked constructors: " + candidates + + ". Found another constructor with 'required' Autowired annotation: " + + requiredConstructor); + } + requiredConstructor = candidate; + } + candidates.Add(candidate); + } + else if (candidate.GetParameters().Length == 0) + { + defaultConstructor = candidate; + } + } + if (candidates.Count > 0) + { + // Add default constructor to list of optional constructors, as fallback. + if (requiredConstructor == null && defaultConstructor != null) { + candidates.Add(defaultConstructor); + } + candidateConstructors = candidates.ToArray(); + } + else { + candidateConstructors = new ConstructorInfo[0]; + } + _candidateConstructorsCache.Add(objectType, candidateConstructors); + } + } + } + return (candidateConstructors.Length > 0 ? candidateConstructors : null); + } + + + /// + /// Finds autowire candidates and verifies them + /// + /// + /// The of the target object that is to be + /// instantiated. + /// + /// + /// The name of the target object. + /// + /// + /// The object to expose instead of a default instance of the target + /// object. + /// + /// + /// In the case of any errors. + /// + /// + /// + public override object PostProcessBeforeInstantiation(Type objectType, string objectName) + { + var objectDefinition = _objectFactory.GetObjectDefinition(objectName) as RootObjectDefinition; + if (objectType != null) + { + var metadata = FindAutowiringMetadata(objectType); + metadata.CheckConfigMembers(objectDefinition); + } + return null; + } + + /// + /// Injects autoried annotated properties, fields, methods into objectInstance + /// + /// + /// + /// + /// Name of the object. + /// The actual property values to apply to the given object (can be the + /// passed-in PropertyValues instances0 or null to skip property population. + public override IPropertyValues PostProcessPropertyValues(IPropertyValues pvs, IList pis, + object objectInstance, string objectName) + { + var metadata = FindAutowiringMetadata(objectInstance.GetType()); + try + { + metadata.Inject(objectInstance, objectName, pvs); + } + catch (Exception ex) + { + throw new ObjectCreationException(objectName, "Injection of autowired dependencies failed", ex); + } + return pvs; + } + + + private InjectionMetadata FindAutowiringMetadata(Type objectType) + { + // Quick check on the concurrent map first, with minimal locking. + InjectionMetadata metadata = null; + if (_injectionMetadataCache.ContainsKey(objectType)) + metadata = _injectionMetadataCache[objectType]; + + if (metadata == null) + { + lock (_injectionMetadataCache) + { + if (!_injectionMetadataCache.ContainsKey(objectType)) + { + metadata = BuildAutowiringMetadata(objectType); + _injectionMetadataCache.Add(objectType, metadata); + } + } + } + return metadata; + } + + private InjectionMetadata BuildAutowiringMetadata(Type objectType) + { + var elements = new List(); + + do + { + var currElements = new List(); + foreach ( + var property in + objectType.GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)) + { + var attr = Attribute.GetCustomAttribute(property, _autowiredPropertyType) as AutowiredAttribute; + if (attr != null && property.DeclaringType == objectType) + currElements.Add(new AutowiredPropertyElement(property, attr.Required)); + } + foreach ( + var field in + objectType.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)) + { + var attr = Attribute.GetCustomAttribute(field, _autowiredPropertyType) as AutowiredAttribute; + if (attr != null && field.DeclaringType == objectType) + currElements.Add(new AutowiredFieldElement(field, attr.Required)); + } + foreach ( + var method in + objectType.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)) + { + var attr = Attribute.GetCustomAttribute(method, _autowiredPropertyType) as AutowiredAttribute; + if (attr != null && method.DeclaringType == objectType) + { + if (method.IsStatic) + { + Logger.Warn( + m => m("Autowired annotation is not supported on static methods: " + method.Name)); + continue; + } + if (method.IsGenericMethod) + { + Logger.Warn( + m => m("Autowired annotation is not supported on generic methods: " + method.Name)); + continue; + } + currElements.Add(new AutowiredMethodElement(method, attr.Required)); + } + } + elements.InsertRange(0, currElements); + objectType = objectType.BaseType; + } while (objectType != null && objectType != typeof (Object)); + + return new InjectionMetadata(objectType, elements); + } + + /// + /// Register the specified bean as dependent on the autowired beans. + /// + private static void RegisterDependentObjects(string objectName, IList autowiredObjectNames) + { + if (objectName == null) return; + + var objectDefinition = _objectFactory.GetObjectDefinition(objectName) as RootObjectDefinition; + IList dependsOn = new List(objectDefinition.DependsOn); + foreach (var name in autowiredObjectNames) + { + var autowiredObjectName = name as string; + if (!dependsOn.Contains(autowiredObjectName)) + { + dependsOn.Add(autowiredObjectName); + Logger.Debug( + m => + m("Autowiring by type from object name '{0}' to object named '{1}'", objectName, + autowiredObjectName)); + } + } + objectDefinition.DependsOn = dependsOn; + } + + private static object ResolvedCachedArgument(String objectName, Object cachedArgument) + { + if (cachedArgument is DependencyDescriptor) + { + var descriptor = (DependencyDescriptor) cachedArgument; + return _objectFactory.ResolveDependency(descriptor, objectName, null); + } + else if (cachedArgument is RuntimeObjectReference) + { + return _objectFactory.GetObject(((RuntimeObjectReference) cachedArgument).ObjectName); + } + else + { + return cachedArgument; + } + } + + /// + /// Class representing injection information about an annotated field. + /// + private class AutowiredPropertyElement : InjectionMetadata.InjectedElement + { + + private readonly bool _required; + + private bool _cached = false; + + private Object _cachedFieldValue; + + public AutowiredPropertyElement(PropertyInfo property, bool required) + : base(property) + { + _required = required; + } + + public override void Inject(Object instance, String objectName, IPropertyValues pvs) + { + var property = (PropertyInfo) _member; + try + { + Object value; + if (_cached) + { + value = ResolvedCachedArgument(objectName, _cachedFieldValue); + } + else + { + var descriptor = new DependencyDescriptor(property, _required); + IList autowiredObjectNames = new ArrayList(); + value = _objectFactory.ResolveDependency(descriptor, objectName, autowiredObjectNames); + lock (this) + { + if (!_cached) + { + if (value != null || _required) + { + _cachedFieldValue = descriptor; + RegisterDependentObjects(objectName, autowiredObjectNames); + if (autowiredObjectNames.Count == 1) + { + var autowiredBeanName = autowiredObjectNames[0] as string; + if (_objectFactory.ContainsObject(autowiredBeanName)) + { + if (_objectFactory.IsTypeMatch(autowiredBeanName, property.GetType())) + { + _cachedFieldValue = new RuntimeObjectReference(autowiredBeanName); + } + } + } + } + else + { + _cachedFieldValue = null; + } + _cached = true; + } + } + } + if (value != null) + { + property.SetValue(instance, value, null); + } + } + catch (Exception ex) + { + throw new ObjectCreationException("Could not autowire property: " + property, ex); + } + } + } + + /// + /// Class representing injection information about an annotated field. + /// + private class AutowiredFieldElement : InjectionMetadata.InjectedElement + { + + private readonly bool _required; + + private bool _cached = false; + + private Object _cachedFieldValue; + + public AutowiredFieldElement(FieldInfo field, bool required) + : base(field) + { + _required = required; + } + + public override void Inject(Object instance, String objectName, IPropertyValues pvs) + { + var field = (FieldInfo) _member; + try + { + Object value; + if (_cached) + { + value = ResolvedCachedArgument(objectName, _cachedFieldValue); + } + else + { + var descriptor = new DependencyDescriptor(field, _required); + IList autowiredObjectNames = new ArrayList(); + value = _objectFactory.ResolveDependency(descriptor, objectName, autowiredObjectNames); + lock (this) + { + if (!_cached) + { + if (value != null || _required) + { + _cachedFieldValue = descriptor; + RegisterDependentObjects(objectName, autowiredObjectNames); + if (autowiredObjectNames.Count == 1) + { + var autowiredBeanName = autowiredObjectNames[0] as string; + if (_objectFactory.ContainsObject(autowiredBeanName)) + { + if (_objectFactory.IsTypeMatch(autowiredBeanName, field.GetType())) + { + _cachedFieldValue = new RuntimeObjectReference(autowiredBeanName); + } + } + } + } + else + { + _cachedFieldValue = null; + } + _cached = true; + } + } + } + if (value != null) + { + field.SetValue(instance, value); + } + } + catch (Exception ex) + { + throw new ObjectCreationException("Could not autowire field: " + field, ex); + } + } + } + + /// + /// Class representing injection information about an annotated method. + /// + private class AutowiredMethodElement : InjectionMetadata.InjectedElement + { + private readonly bool _required; + + private bool _cached = false; + + private volatile Object[] _cachedMethodArguments; + + public AutowiredMethodElement(MethodInfo method, bool required) + : base(method) + { + _required = required; + } + + public override void Inject(Object target, string objectName, IPropertyValues pvs) + { + MethodInfo method = _member as MethodInfo; + try + { + Object[] arguments; + if (_cached) + { + arguments = ResolveCachedArguments(objectName); + } + else + { + Type[] paramTypes = method.GetParameters().Select(p => p.ParameterType).ToArray(); + arguments = new Object[paramTypes.Length]; + var descriptors = new DependencyDescriptor[paramTypes.Length]; + IList autowiredBeanNames = new ArrayList(); + for (int i = 0; i < arguments.Length; i++) + { + MethodParameter methodParam = new MethodParameter(method, i); + descriptors[i] = new DependencyDescriptor(methodParam, _required); + arguments[i] = _objectFactory.ResolveDependency(descriptors[i], objectName, + autowiredBeanNames); + if (arguments[i] == null && !_required) + { + arguments = null; + break; + } + } + lock (this) + { + if (!_cached) + { + if (arguments != null) + { + _cachedMethodArguments = new Object[arguments.Length]; + for (int i = 0; i < arguments.Length; i++) + { + _cachedMethodArguments[i] = descriptors[i]; + } + RegisterDependentObjects(objectName, autowiredBeanNames); + if (autowiredBeanNames.Count == paramTypes.Length) + { + for (int i = 0; i < paramTypes.Length; i++) + { + string autowiredBeanName = autowiredBeanNames[i] as string; + if (_objectFactory.ContainsObject(autowiredBeanName)) + { + if (_objectFactory.IsTypeMatch(autowiredBeanName, paramTypes[i])) + { + _cachedMethodArguments[i] = + new RuntimeObjectReference(autowiredBeanName); + } + } + } + } + } + else + { + _cachedMethodArguments = null; + } + _cached = true; + } + } + } + if (arguments != null) + { + method.Invoke(target, arguments); + } + } + catch (Exception ex) + { + throw new ObjectCreationException("Could not autowire method: " + method, ex); + } + } + + private Object[] ResolveCachedArguments(string objectName) + { + if (_cachedMethodArguments == null) + { + return null; + } + Object[] arguments = new Object[_cachedMethodArguments.Length]; + for (int i = 0; i < arguments.Length; i++) + { + arguments[i] = ResolvedCachedArgument(objectName, _cachedMethodArguments[i]); + } + return arguments; + } + } + } +} diff --git a/src/Spring/Spring.Core/Objects/Factory/Attributes/InjectionMetadata.cs b/src/Spring/Spring.Core/Objects/Factory/Attributes/InjectionMetadata.cs new file mode 100644 index 00000000..1df4ffc9 --- /dev/null +++ b/src/Spring/Spring.Core/Objects/Factory/Attributes/InjectionMetadata.cs @@ -0,0 +1,112 @@ +#region License + +/* + * Copyright © 2010-2011 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.Generic; +using System.Reflection; +using Common.Logging; +using Spring.Objects; +using Spring.Objects.Factory.Support; + +namespace Spring.Objects.Factory.Attributes +{ + /// + /// Internal class for managing injection metadata. + /// Not intended for direct use in applications. + /// + public class InjectionMetadata + { + private static readonly ILog Logger = LogManager.GetLogger(); + + private readonly IList _injectedElements; + + + /// + /// + /// + /// + public InjectionMetadata(Type targetType, IList elements) + { + _injectedElements = new List(); + if (elements.Count > 0) + { + foreach (var element in elements) + { + Logger.Debug(m => m("Found injected element on class [" + targetType.Name + "]: " + element)); + _injectedElements.Add(element); + } + } + } + + /// + /// + /// + public void CheckConfigMembers(RootObjectDefinition objectDefinition) + { + } + + + /// + /// Inject values for members into object instance + /// + /// + /// + /// + public void Inject(Object instance, string objectName, IPropertyValues pvs) + { + if (_injectedElements.Count == 0) + return; + + foreach(var element in _injectedElements) + { + Logger.Debug(m => m("Processing injected method of bean '{0}': {1}", objectName, element)); + element.Inject(instance, objectName, pvs); + } + } + + /// + /// Represents an element that needs to be injected + /// + public abstract class InjectedElement + { + /// + /// The Property, field, method or constructor info + /// + protected readonly MemberInfo _member; + + /// + /// Instantiates a new inject element + /// + /// + protected InjectedElement(MemberInfo member) + { + _member = member; + } + + /// + /// Ececuted to inject value to associated memeber info + /// + /// + /// + /// + public abstract void Inject(object target, string requestingObjectName, IPropertyValues pvs); + } + } +} diff --git a/src/Spring/Spring.Core/Objects/Factory/Attributes/QualifierAttribute.cs b/src/Spring/Spring.Core/Objects/Factory/Attributes/QualifierAttribute.cs new file mode 100644 index 00000000..bf2e34ba --- /dev/null +++ b/src/Spring/Spring.Core/Objects/Factory/Attributes/QualifierAttribute.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Spring.Objects.Factory.Attributes +{ + /// + /// This annotation may be used on a field or parameter as a qualifier for + /// candidate beans when autowiring. It may also be used to annotate other + /// custom annotations that can then in turn be used as qualifiers. + /// + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)] + public class QualifierAttribute : Attribute + { + private readonly string _name; + + /// + /// Instantiate a new qualifier type + /// + /// name to use as qualifier + public QualifierAttribute(string name) + { + _name = name; + } + + /// + /// Gets the name associated with this qualifier + /// + public string Name { get { return _name; } } + } +} diff --git a/src/Spring/Spring.Core/Objects/Factory/Config/DependencyDescriptor.cs b/src/Spring/Spring.Core/Objects/Factory/Config/DependencyDescriptor.cs index 39e8358c..cecbebe7 100644 --- a/src/Spring/Spring.Core/Objects/Factory/Config/DependencyDescriptor.cs +++ b/src/Spring/Spring.Core/Objects/Factory/Config/DependencyDescriptor.cs @@ -20,6 +20,8 @@ using System; using Spring.Core; +using System.Reflection; +using Spring.Objects.Factory.Attributes; namespace Spring.Objects.Factory.Config { @@ -34,6 +36,10 @@ namespace Spring.Objects.Factory.Config { private MethodParameter methodParameter; + private PropertyInfo property; + + private FieldInfo field; + private readonly bool required; private readonly bool eager; @@ -45,7 +51,8 @@ namespace Spring.Objects.Factory.Config /// /// The MethodParameter to wrap. /// if set to true if the dependency is required. - public DependencyDescriptor(MethodParameter methodParameter, bool required) : this(methodParameter, required, true) + public DependencyDescriptor(MethodParameter methodParameter, bool required) + : this(methodParameter, required, true) { } @@ -63,6 +70,55 @@ namespace Spring.Objects.Factory.Config this.eager = eager; } + /// + /// Create a new descriptor for a property. + /// Considers the dependency as 'eager'. + /// property to wrap + /// required whether the dependency is required + /// + public DependencyDescriptor(PropertyInfo property, bool required) + : this(property, required, true) + { + } + + /// + /// Create a new descriptor for a property. + /// property to wrap + /// whether the dependency is required + /// whether this dependency is 'eager' in the sense of + /// eagerly resolving potential target beans for type matching + /// + public DependencyDescriptor(PropertyInfo property, bool required, bool eager) + { + this.property = property; + this.required = required; + this.eager = eager; + } + + /// + /// Create a new descriptor for a field. + /// Considers the dependency as 'eager'. + /// field to wrap + /// whether the dependency is required + /// + public DependencyDescriptor(FieldInfo field, bool required) + : this(field, required, true) + { + } + + /// + /// Create a new descriptor for a field. + /// field to wrap + /// whether the dependency is required + /// whether this dependency is 'eager' in the sense of + /// eagerly resolving potential target beans for type matching + /// + public DependencyDescriptor(FieldInfo field, bool required, bool eager) + { + this.field = field; + this.required = required; + this.eager = eager; + } /// /// Gets a value indicating whether this dependency is required. @@ -79,7 +135,17 @@ namespace Spring.Objects.Factory.Config /// The type of the dependency (never null public Type DependencyType { - get { return methodParameter.ParameterType; } + get + { + if (methodParameter != null) + return methodParameter.ParameterType; + if (property != null) + return property.PropertyType; + if (field != null) + return field.FieldType; + + return null; + } } /// @@ -101,5 +167,83 @@ namespace Spring.Objects.Factory.Config { get { return methodParameter; } } + + public string Name + { + get + { + if (methodParameter != null) + return methodParameter.ParameterName(); + if (property != null) + return property.Name; + if (field != null) + return field.Name; + + return ""; + } + } + + /// + /// Determine whether the given dependency carries a value annotation. + /// + public Object GetSuggestedValue() + { + Object value = null; + + if (methodParameter != null) + value = ConvertFieldName(methodParameter.ParameterName()); + if (property != null) + value = property.Name; + if (field != null) + value = ConvertFieldName(field.Name); + + return value; + } + + /// + /// Get the qualifier name if exists + /// + public string GetQualifierName() + { + string value = null; + + if (methodParameter != null) + value = FindValue(methodParameter.GetParameterAttributes()); + if (property != null) + value = FindValue(Attribute.GetCustomAttributes(property)); + if (field != null) + value = FindValue(Attribute.GetCustomAttributes(field)); + + return value; + } + + /** + * Determine a suggested value from any of the given candidate annotations. + */ + + private string FindValue(Attribute[] attributesToSearch) + { + foreach (Attribute attribute in attributesToSearch) + { + if (attribute is QualifierAttribute) + { + var qualifierAttribute = attribute as QualifierAttribute; + return qualifierAttribute.Name; + } + } + return null; + } + + private string ConvertFieldName(string fieldName) + { + if (string.IsNullOrEmpty(fieldName)) + return string.Empty; + + char[] letters = fieldName.TrimStart('_').ToCharArray(); + letters[0] = char.ToUpper(letters[0]); + + return new string(letters); + } + } } \ No newline at end of file diff --git a/src/Spring/Spring.Core/Objects/Factory/Support/DefaultListableObjectFactory.cs b/src/Spring/Spring.Core/Objects/Factory/Support/DefaultListableObjectFactory.cs index dbd12dea..f9b33de6 100644 --- a/src/Spring/Spring.Core/Objects/Factory/Support/DefaultListableObjectFactory.cs +++ b/src/Spring/Spring.Core/Objects/Factory/Support/DefaultListableObjectFactory.cs @@ -1111,6 +1111,21 @@ namespace Spring.Objects.Factory.Support public override object ResolveDependency(DependencyDescriptor descriptor, string objectName, IList autowiredObjectNames) { + string qualifierName = descriptor.GetQualifierName(); + if (!string.IsNullOrEmpty(qualifierName)) + { + if (ContainsObject(qualifierName)) + { + autowiredObjectNames.Add(qualifierName); + return GetObject(qualifierName); + } + else + { + if (descriptor.Required) + throw new NoSuchObjectDefinitionException(qualifierName, "no object found with this name"); + return null; + } + } Type type = descriptor.DependencyType; if (type.IsArray) { @@ -1133,6 +1148,38 @@ namespace Spring.Objects.Factory.Support } return TypeConversionUtils.ConvertValueIfNecessary(type, matchingObjects.Values, null); } + else if (type.IsGenericType && + (type.GetGenericTypeDefinition() == typeof(IList<>) || type.GetGenericTypeDefinition() == typeof(Spring.Collections.Generic.ISet<>) || + type.GetGenericTypeDefinition() == typeof(IDictionary<,>))) + { + var isDictionary = (type.GetGenericTypeDefinition() == typeof (IDictionary<,>)); + var elementType = isDictionary ? type.GetGenericArguments()[1] : type.GetGenericArguments()[0]; + + if (isDictionary && type.GetGenericArguments()[0] != typeof(string)) + throw new NoSuchObjectDefinitionException(type, + "expected first generic to be a string but is " + type.GetGenericArguments()[0]); + + IDictionary matchingObjects = FindAutowireCandidates(objectName, elementType, descriptor); + if (matchingObjects.Count == 0) + { + if (descriptor.Required) + { + RaiseNoSuchObjectDefinitionException(elementType, "dictionary/list/set of " + elementType.FullName, descriptor); + } + return null; + } + if (autowiredObjectNames != null) + { + foreach (DictionaryEntry matchingObject in matchingObjects) + { + autowiredObjectNames.Add(matchingObject.Key); + } + } + + return isDictionary + ? TypeConversionUtils.ConvertValueIfNecessary(type, matchingObjects, null) + : TypeConversionUtils.ConvertValueIfNecessary(type, matchingObjects.Values, null); + } else if (typeof(ICollection).IsAssignableFrom(type) && type.IsInterface) { //TODO - handle generic types. @@ -1142,6 +1189,20 @@ namespace Spring.Objects.Factory.Support else { IDictionary matchingObjects = FindAutowireCandidates(objectName, type, descriptor); + if (matchingObjects.Count == 0 || matchingObjects.Count > 1) + { + Object value = descriptor.GetSuggestedValue(); + if (value is string) + { + string matchingObject = value as string; + if (ContainsObject(matchingObject)) + { + matchingObjects.Clear(); + matchingObjects.Add(matchingObject, GetObject(matchingObject)); + } + } + } + if (matchingObjects.Count == 0) { if (descriptor.Required) @@ -1155,9 +1216,8 @@ namespace Spring.Objects.Factory.Support } if (matchingObjects.Count > 1) { - throw new NoSuchObjectDefinitionException(type, - "expected single matching object but found " + matchingObjects.Count + ": " + matchingObjects); + "expected single matching object but found " + matchingObjects.Count + ": " + matchingObjects); } DictionaryEntry entry = (DictionaryEntry)ObjectUtils.EnumerateFirstElement(matchingObjects); if (autowiredObjectNames != null) @@ -1201,6 +1261,7 @@ namespace Spring.Objects.Factory.Support } } } + for (int i = 0; i < candidateNames.Count; i++) { string candidateName = candidateNames[i]; diff --git a/src/Spring/Spring.Core/Spring.Core.2010.csproj b/src/Spring/Spring.Core/Spring.Core.2010.csproj index 18b82f56..ced462ac 100644 --- a/src/Spring/Spring.Core/Spring.Core.2010.csproj +++ b/src/Spring/Spring.Core/Spring.Core.2010.csproj @@ -675,6 +675,10 @@ Code + + + + diff --git a/test/Spring/Spring.Core.Tests/Objects/Factory/Attributes/AutowireAttributeCollectionTests.cs b/test/Spring/Spring.Core.Tests/Objects/Factory/Attributes/AutowireAttributeCollectionTests.cs new file mode 100644 index 00000000..41a87e30 --- /dev/null +++ b/test/Spring/Spring.Core.Tests/Objects/Factory/Attributes/AutowireAttributeCollectionTests.cs @@ -0,0 +1,160 @@ +#region License + +/* + * Copyright © 2002-2011 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.Collections.Generic; +using NUnit.Framework; +using Spring.Context.Support; +using Spring.Objects.Factory.Config; +using Spring.Objects.Factory.Support; + +namespace Spring.Objects.Factory.Attributes +{ + [TestFixture] + public class AutowireAttributeCollectionTests + { + private GenericApplicationContext _applicationContext; + + [SetUp] + public void Setup() + { + _applicationContext = new GenericApplicationContext(); + + var objDef = new RootObjectDefinition(typeof(AutowiredAttributeObjectPostProcessor)); + objDef.Role = ObjectRole.ROLE_INFRASTRUCTURE; + _applicationContext.RegisterObjectDefinition("AutowiredAttributeObjectPostProcessor", objDef); + + objDef = new RootObjectDefinition(typeof(ColFoo1)); + _applicationContext.RegisterObjectDefinition("Foo1", objDef); + + objDef = new RootObjectDefinition(typeof(ColFoo2)); + _applicationContext.RegisterObjectDefinition("Foo2", objDef); + + objDef = new RootObjectDefinition(typeof(ColTestObject1)); + objDef.Scope = "prototype"; + _applicationContext.RegisterObjectDefinition("ColTestObject1", objDef); + + objDef = new RootObjectDefinition(typeof(ColTestObject2)); + objDef.Scope = "prototype"; + _applicationContext.RegisterObjectDefinition("ColTestObject2", objDef); + + objDef = new RootObjectDefinition(typeof(ColTestObject3)); + objDef.Scope = "prototype"; + _applicationContext.RegisterObjectDefinition("ColTestObject3", objDef); + + objDef = new RootObjectDefinition(typeof(ColTestObject4)); + objDef.Scope = "prototype"; + _applicationContext.RegisterObjectDefinition("ColTestObject4", objDef); + + _applicationContext.Refresh(); + } + + [Test] + public void InjectAListOfObjects() + { + var testObj = (ColTestObject1)_applicationContext.GetObject("ColTestObject1"); + var objDef = _applicationContext.ObjectFactory.GetObjectDefinition("ColTestObject1"); + + Assert.That(testObj.Count, Is.EqualTo(2)); + Assert.That(objDef.DependsOn.Count, Is.EqualTo(2)); + } + + [Test] + public void InjectASetOfObjects() + { + var testObj = (ColTestObject2)_applicationContext.GetObject("ColTestObject2"); + + Assert.That(testObj.Count, Is.EqualTo(2)); + } + + [Test] + public void InjectADictionaryOfObjects() + { + var testObj = (ColTestObject3)_applicationContext.GetObject("ColTestObject3"); + + Assert.That(testObj.Count, Is.EqualTo(2)); + } + + [Test] + public void InjectAnArrayOfObjects() + { + var testObj = (ColTestObject4)_applicationContext.GetObject("ColTestObject4"); + + Assert.That(testObj.Count, Is.EqualTo(2)); + } + } + + #region Test Objects + + public interface IColFoo + { + string Name(); + } + + public class ColFoo1 : IColFoo + { + public string Name() + { + return "Foo1"; + } + } + + public class ColFoo2 : IColFoo + { + public string Name() + { + return "Foo2"; + } + } + + public class ColTestObject1 + { + [Autowired] + private IList _col; + + public int Count { get { return _col.Count; } } + } + + public class ColTestObject2 + { + [Autowired] + private Spring.Collections.Generic.ISet _col; + + public int Count { get { return _col.Count; } } + } + + public class ColTestObject3 + { + [Autowired] + private IDictionary _col; + + public int Count { get { return _col.Count; } } + } + + public class ColTestObject4 + { + [Autowired] + private IColFoo[] _col; + + public int Count { get { return _col.Length; } } + } + + #endregion + +} \ No newline at end of file diff --git a/test/Spring/Spring.Core.Tests/Objects/Factory/Attributes/AutowireAttributeConstructorTest.cs b/test/Spring/Spring.Core.Tests/Objects/Factory/Attributes/AutowireAttributeConstructorTest.cs new file mode 100644 index 00000000..f745c501 --- /dev/null +++ b/test/Spring/Spring.Core.Tests/Objects/Factory/Attributes/AutowireAttributeConstructorTest.cs @@ -0,0 +1,343 @@ +#region License + +/* + * Copyright © 2002-2011 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.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using Spring.Context.Support; +using Spring.Objects.Factory.Config; +using Spring.Objects.Factory.Support; + +namespace Spring.Objects.Factory.Attributes +{ + [TestFixture] + public class AutowireAttributeConstructorTest + { + private GenericApplicationContext _applicationContext; + + [SetUp] + public void Setup() + { + _applicationContext = new GenericApplicationContext(); + + var objDef = new RootObjectDefinition(typeof(AutowiredAttributeObjectPostProcessor)); + objDef.Role = ObjectRole.ROLE_INFRASTRUCTURE; + _applicationContext.RegisterObjectDefinition("AutowiredAttributeObjectPostProcessor", objDef); + + objDef = new RootObjectDefinition(typeof(ConsSimple)); + _applicationContext.ObjectFactory.RegisterObjectDefinition("ConsSimple", objDef); + + objDef = new RootObjectDefinition(typeof(ConsAdvanced)); + _applicationContext.ObjectFactory.RegisterObjectDefinition("ConsAdvanced", objDef); + + objDef = new RootObjectDefinition(typeof(ConsHello)); + _applicationContext.ObjectFactory.RegisterObjectDefinition("ConsHello", objDef); + + objDef = new RootObjectDefinition(typeof(ConsCiao)); + _applicationContext.ObjectFactory.RegisterObjectDefinition("ConsCiao", objDef); + + objDef = new RootObjectDefinition(typeof(ConsTestObject1)); + objDef.Scope = "prototype"; + _applicationContext.ObjectFactory.RegisterObjectDefinition("ConsTestObject1", objDef); + + objDef = new RootObjectDefinition(typeof(ConsTestObject2)); + objDef.Scope = "prototype"; + _applicationContext.ObjectFactory.RegisterObjectDefinition("ConsTestObject2", objDef); + + objDef = new RootObjectDefinition(typeof(ConsTestObject3)); + objDef.Scope = "prototype"; + _applicationContext.ObjectFactory.RegisterObjectDefinition("ConsTestObject3", objDef); + + objDef = new RootObjectDefinition(typeof(ConsTestObject4)); + objDef.Scope = "prototype"; + _applicationContext.ObjectFactory.RegisterObjectDefinition("ConsTestObject4", objDef); + + objDef = new RootObjectDefinition(typeof(ConsTestObject5)); + objDef.Scope = "prototype"; + _applicationContext.ObjectFactory.RegisterObjectDefinition("ConsTestObject5", objDef); + + objDef = new RootObjectDefinition(typeof(ConsTestObject6)); + objDef.Scope = "prototype"; + _applicationContext.ObjectFactory.RegisterObjectDefinition("ConsTestObject6", objDef); + + objDef = new RootObjectDefinition(typeof(ConsTestObject7)); + objDef.Scope = "prototype"; + _applicationContext.ObjectFactory.RegisterObjectDefinition("ConsTestObject7", objDef); + + _applicationContext.Refresh(); + } + + [Test] + public void ConstructorWithInjectParameter() + { + var testObj = (ConsTestObject1)_applicationContext.GetObject("ConsTestObject1"); + + Assert.That(testObj.IsSet(), Is.True); + } + + [Test] + public void SelectInjectConstructorOverDefault() + { + var testObj = (ConsTestObject2)_applicationContext.GetObject("ConsTestObject2"); + + Assert.That(testObj.IsSet(), Is.True); + } + + [Test] + public void SelectConstructorWithMostParamters() + { + var testObj = (ConsTestObject3)_applicationContext.GetObject("ConsTestObject3"); + + Assert.That(testObj.IsSet(), Is.True); + } + + [Test] + public void FailIfParameterAreRequired() + { + Exception ex = null; + try + { + var testObj = (ConsTestObject4)_applicationContext.GetObject("ConsTestObject4"); + } + catch (Exception e) { ex = e; } + + Assert.That(ex, Is.Not.Null, "Exception should be thrown"); + Assert.That(ex.Message, Is.StringContaining("Unsatisfied dependency expressed")); + } + + [Test] + public void InjectCollection() + { + var testObj = (ConsTestObject5)_applicationContext.GetObject("ConsTestObject5"); + + Assert.That(testObj.IsSet(), Is.True); + Assert.That(testObj.ObjectCount(), Is.EqualTo(2)); + } + + + [Test] + public void SetParameterByParameterName() + { + var testObj = (ConsTestObject6)_applicationContext.GetObject("ConsTestObject6"); + + Assert.That(testObj.IsSet(), Is.True); + } + + [Test] + public void SetParameterByQualifier() + { + var testObj = (ConsTestObject7)_applicationContext.GetObject("ConsTestObject7"); + + Assert.That(testObj.IsSet(), Is.True); + Assert.That(testObj.CorrectObject(), Is.True); + } + } + + #region Test Objects + + public interface IConsSimple + { + string Message(); + } + + public class ConsSimple : IConsSimple + { + public string Message() + { + return "ok"; + } + } + + public interface IConsAdvanced + { + string Message(); + } + + public class ConsAdvanced : IConsAdvanced + { + public string Message() + { + return "ok"; + } + } + + public interface INotSet + { + } + + public interface IConsCol + { + string Message(); + } + + public class ConsHello : IConsCol + { + public string Message() + { + return "hello"; + } + } + + public class ConsCiao : IConsCol + { + public string Message() + { + return "ciao"; + } + } + + public class ConsTestObject1 + { + private IConsSimple _consSimple; + + [Autowired] + public ConsTestObject1(IConsSimple consSimple) + { + _consSimple = consSimple; + } + + public bool IsSet() + { + return _consSimple != null; + } + } + + public class ConsTestObject2 + { + private IConsSimple _consSimple; + + public ConsTestObject2() + { + } + + [Autowired] + public ConsTestObject2(IConsSimple consSimple) + { + _consSimple = consSimple; + } + + public bool IsSet() + { + return _consSimple != null; + } + } + + public class ConsTestObject3 + { + private IConsSimple _consSimple; + private IConsAdvanced _consAdvanced; + + [Autowired(Required = false)] + public ConsTestObject3(IConsSimple consSimple) + { + _consSimple = consSimple; + } + + [Autowired(Required = false)] + public ConsTestObject3(IConsSimple consSimple, IConsAdvanced consAdvanced) + { + _consSimple = consSimple; + _consAdvanced = consAdvanced; + } + + public bool IsSet() + { + return _consSimple != null && _consAdvanced != null; + } + } + + public class ConsTestObject4 + { + private INotSet _notSet; + + [Autowired] + public ConsTestObject4(INotSet notSet) + { + _notSet = notSet; + } + + public bool IsSet() + { + return _notSet != null; + } + } + + public class ConsTestObject5 + { + private IDictionary _consCol; + + [Autowired] + public ConsTestObject5(IDictionary consCol) + { + _consCol = consCol; + } + + public bool IsSet() + { + return _consCol != null; + } + + public int ObjectCount() + { + return _consCol.Count; + } + } + + public class ConsTestObject6 + { + private IConsCol _consHello; + + [Autowired] + public ConsTestObject6(IConsCol consHello) + { + _consHello = consHello; + } + + public bool IsSet() + { + return _consHello != null; + } + } + + public class ConsTestObject7 + { + private IConsCol _consHello; + + [Autowired] + public ConsTestObject7([Qualifier("ConsCiao")] IConsCol consHello) + { + _consHello = consHello; + } + + public bool IsSet() + { + return _consHello != null; + } + + public bool CorrectObject() + { + return _consHello.Message() == "ciao"; + } + } + + #endregion +} diff --git a/test/Spring/Spring.Core.Tests/Objects/Factory/Attributes/AutowireAttributeFieldTest.cs b/test/Spring/Spring.Core.Tests/Objects/Factory/Attributes/AutowireAttributeFieldTest.cs new file mode 100644 index 00000000..b9fc2c29 --- /dev/null +++ b/test/Spring/Spring.Core.Tests/Objects/Factory/Attributes/AutowireAttributeFieldTest.cs @@ -0,0 +1,290 @@ +#region License + +/* + * Copyright 2002-2010 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 NUnit.Framework; +using Spring.Context.Support; +using Spring.Objects.Factory.Config; +using Spring.Objects.Factory.Support; + +namespace Spring.Objects.Factory.Attributes +{ + [TestFixture] + public class AutowireAttributeFieldTest + { + private GenericApplicationContext _applicationContext; + + [SetUp] + public void Setup() + { + _applicationContext = new GenericApplicationContext(); + + var objDef = new RootObjectDefinition(typeof(AutowiredAttributeObjectPostProcessor)); + objDef.Role = ObjectRole.ROLE_INFRASTRUCTURE; + _applicationContext.RegisterObjectDefinition("AutowiredAttributeObjectPostProcessor", objDef); + + + objDef = new RootObjectDefinition(typeof(FldFooImpl)); + _applicationContext.RegisterObjectDefinition("FldFoo", objDef); + + objDef = new RootObjectDefinition(typeof(FldHello)); + _applicationContext.RegisterObjectDefinition("FldHello", objDef); + + objDef = new RootObjectDefinition(typeof(FldCioa)); + _applicationContext.RegisterObjectDefinition("FldCioa", objDef); + + objDef = new RootObjectDefinition(typeof(FldHola)); + _applicationContext.RegisterObjectDefinition("FldHola", objDef); + + objDef = new RootObjectDefinition(typeof(FldTestObject1)); + objDef.Scope = "prototype"; + _applicationContext.RegisterObjectDefinition("FldTestObject1", objDef); + + objDef = new RootObjectDefinition(typeof(FldTestObject2)); + objDef.Scope = "prototype"; + _applicationContext.RegisterObjectDefinition("FldTestObject2", objDef); + + objDef = new RootObjectDefinition(typeof(FldTestObject3)); + objDef.Scope = "prototype"; + _applicationContext.RegisterObjectDefinition("FldTestObject3", objDef); + + objDef = new RootObjectDefinition(typeof(FldTestObject4)); + objDef.Scope = "prototype"; + _applicationContext.RegisterObjectDefinition("FldTestObject4", objDef); + + objDef = new RootObjectDefinition(typeof(FldTestObject5)); + objDef.Scope = "prototype"; + _applicationContext.RegisterObjectDefinition("FldTestObject5", objDef); + + objDef = new RootObjectDefinition(typeof(FldTestObject6)); + objDef.Scope = "prototype"; + _applicationContext.RegisterObjectDefinition("FldTestObject6", objDef); + + objDef = new RootObjectDefinition(typeof(FldTestObject7)); + objDef.Scope = "prototype"; + _applicationContext.RegisterObjectDefinition("FldTestObject7", objDef); + + _applicationContext.Refresh(); + } + + [Test] + public void InjectPropertyBasedOnFieldType() + { + var testObj = (FldTestObject1)_applicationContext.GetObject("FldTestObject1"); + var objectDefinition = _applicationContext.ObjectFactory.GetObjectDefinition("FldTestObject1"); + + Assert.That(testObj.Test(), Is.EqualTo("foo")); + Assert.That(objectDefinition.DependsOn.Count, Is.EqualTo(1)); + } + + [Test] + public void WithTwoTypesRegisteredAndNoNameShouldFail() + { + Assert.That(delegate { var testObj = (FldTestObject2)_applicationContext.GetObject("FldTestObject2"); }, Throws.Exception.TypeOf()); + } + + [Test] + public void WithQualifierName() + { + var testObj = (FldTestObject3)_applicationContext.GetObject("FldTestObject3"); + + Assert.That(testObj.Say(), Is.EqualTo("cioa")); + } + + [Test] + public void WithTwoTypesAndNoQualifierUsePopertyName() + { + var testObj = (FldTestObject4)_applicationContext.GetObject("FldTestObject4"); + + Assert.That(testObj.Say(), Is.EqualTo("cioa")); + } + + [Test] + public void FailIfTypeCantBeResolved() + { + Exception ex = null; + try + { + var testObj = (FldTestObject5)_applicationContext.GetObject("FldTestObject5"); + } + catch (Exception e) { ex = e; } + + Assert.That(ex, Is.Not.Null, "Should throw an exception"); + Assert.That(ex.Message, Is.StringContaining("Injection of autowired dependencies failed")); + } + + [Test] + public void InjectedObjectAssignedToTwoInterfaces() + { + var testObj = (FldTestObject6)_applicationContext.GetObject("FldTestObject6"); + + Assert.That(testObj.Test(), Is.EqualTo("test")); + } + + [Test] + public void IsNotRequired() + { + var testObj = (FldTestObject7)_applicationContext.GetObject("FldTestObject7"); + + Assert.That(testObj.IsNull(), Is.True); + } + + } + + + #region Test Objects + + public interface IFldNotAnObject + { + + } + + public interface IFldAnotherOne + { + string Quite(); + } + + public interface IFldFoo + { + string Test(); + } + + public class FldFooImpl : IFldFoo + { + public string Test() + { + return "foo"; + } + } + + public interface IFldSay + { + string Say(); + } + + public class FldHello : IFldSay + { + public string Say() + { + return "hello"; + } + } + + public class FldCioa : IFldSay + { + public string Say() + { + return "cioa"; + } + } + + public class FldHola : IFldFoo, IFldAnotherOne + { + public string Quite() + { + return "test"; + } + + public string Test() + { + return "test"; + } + } + + public class FldTestObject1 + { + [Autowired] + private IFldFoo _fldFoo; + + public string Test() + { + return _fldFoo.Test(); + } + } + + // object with 2 possibilities should fail + public class FldTestObject2 + { + [Autowired] + private IFldSay _wrongName; + + public string Say() + { + return _wrongName.Say(); + } + } + + // should not fail but inject Cioa object + public class FldTestObject3 + { + [Autowired] + [Qualifier("FldCioa")] + private IFldSay _fldCioa; + + public string Say() + { + return _fldCioa.Say(); + } + } + + // should not fail but inject Hello via Propertyname + public class FldTestObject4 + { + [Autowired] + private IFldSay _fldCioa; + + public string Say() + { + return _fldCioa.Say(); + } + } + + // should not fail but inject Hello via Propertyname + public class FldTestObject5 + { + [Autowired] + private IFldNotAnObject _ohhh; + } + + public class FldTestObject6 + { + [Autowired] + private IFldAnotherOne _hola; + + public string Test() + { + return _hola.Quite(); + } + } + + public class FldTestObject7 + { + [Autowired(Required = false)] + private IFldNotAnObject _nono; + + public bool IsNull() + { + return (_nono == null); + } + } + + #endregion + +} diff --git a/test/Spring/Spring.Core.Tests/Objects/Factory/Attributes/AutowireAttributeMethodsTests.cs b/test/Spring/Spring.Core.Tests/Objects/Factory/Attributes/AutowireAttributeMethodsTests.cs new file mode 100644 index 00000000..8d0bd0b5 --- /dev/null +++ b/test/Spring/Spring.Core.Tests/Objects/Factory/Attributes/AutowireAttributeMethodsTests.cs @@ -0,0 +1,371 @@ +#region License + +/* + * Copyright © 2002-2011 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.Generic; +using NUnit.Framework; +using Spring.Context.Support; +using Spring.Objects.Factory.Config; +using Spring.Objects.Factory.Support; + +namespace Spring.Objects.Factory.Attributes +{ + [TestFixture] + public class AutowireAttributeMethodsTests + { + private GenericApplicationContext _applicationContext; + + [SetUp] + public void Setup() + { + _applicationContext = new GenericApplicationContext(); + + var objDef = new RootObjectDefinition(typeof(AutowiredAttributeObjectPostProcessor)); + objDef.Role = ObjectRole.ROLE_INFRASTRUCTURE; + _applicationContext.RegisterObjectDefinition("AutowiredAttributeObjectPostProcessor", objDef); + + objDef = new RootObjectDefinition(typeof(Simple)); + _applicationContext.ObjectFactory.RegisterObjectDefinition("Simple", objDef); + + objDef = new RootObjectDefinition(typeof(MethodHello)); + _applicationContext.ObjectFactory.RegisterObjectDefinition("MethodHello", objDef); + + objDef = new RootObjectDefinition(typeof(MethodCiao)); + _applicationContext.ObjectFactory.RegisterObjectDefinition("MethodCiao", objDef); + + objDef = new RootObjectDefinition(typeof(MethodTestObject1)); + objDef.Scope = "prototype"; + _applicationContext.ObjectFactory.RegisterObjectDefinition("MethodTestObject1", objDef); + + objDef = new RootObjectDefinition(typeof(MethodTestObject2)); + objDef.Scope = "prototype"; + _applicationContext.ObjectFactory.RegisterObjectDefinition("MethodTestObject2", objDef); + + objDef = new RootObjectDefinition(typeof(MethodTestObject3)); + objDef.Scope = "prototype"; + _applicationContext.ObjectFactory.RegisterObjectDefinition("MethodTestObject3", objDef); + + objDef = new RootObjectDefinition(typeof(MethodTestObject4)); + objDef.Scope = "prototype"; + _applicationContext.ObjectFactory.RegisterObjectDefinition("MethodTestObject4", objDef); + + objDef = new RootObjectDefinition(typeof(MethodTestObject5)); + objDef.Scope = "prototype"; + _applicationContext.ObjectFactory.RegisterObjectDefinition("MethodTestObject5", objDef); + + objDef = new RootObjectDefinition(typeof(MethodTestObject6)); + objDef.Scope = "prototype"; + _applicationContext.ObjectFactory.RegisterObjectDefinition("MethodTestObject6", objDef); + + objDef = new RootObjectDefinition(typeof(MethodTestObject7)); + objDef.Scope = "prototype"; + _applicationContext.ObjectFactory.RegisterObjectDefinition("MethodTestObject7", objDef); + + objDef = new RootObjectDefinition(typeof(MethodTestObject8)); + objDef.Scope = "prototype"; + _applicationContext.ObjectFactory.RegisterObjectDefinition("MethodTestObject8", objDef); + + objDef = new RootObjectDefinition(typeof(MethodTestObject9)); + objDef.Scope = "prototype"; + _applicationContext.ObjectFactory.RegisterObjectDefinition("MethodTestObject9", objDef); + + _applicationContext.Refresh(); + } + + [Test] + public void InObjectByType() + { + var testObj = (MethodTestObject1)_applicationContext.GetObject("MethodTestObject1"); + var objectDefinition = _applicationContext.ObjectFactory.GetObjectDefinition("MethodTestObject1"); + + Assert.That(testObj.GetObject(), Is.Not.Null); + Assert.That(objectDefinition.DependsOn.Count, Is.EqualTo(1)); + } + + [Test] + public void InjectByParamterName() + { + var testObj = (MethodTestObject2)_applicationContext.GetObject("MethodTestObject2"); + + Assert.That(testObj.GetObject(), Is.Not.Null); + } + + [Test] + public void InjectByQualifier() + { + var testObj = (MethodTestObject3)_applicationContext.GetObject("MethodTestObject3"); + + Assert.That(testObj.GetObject(), Is.Not.Null); + } + + [Test] + public void FailIfTypeCantBeResolved() + { + Exception ex = null; + try + { + var testObj = (FldTestObject5)_applicationContext.GetObject("MethodTestObject4"); + } + catch (Exception e) { ex = e; } + + Assert.That(ex, Is.Not.Null, "Should throw an exception"); + Assert.That(ex.Message, Is.StringContaining("Injection of autowired dependencies failed")); + } + + [Test] + public void InjectListOfObjects() + { + var testObj = (MethodTestObject5)_applicationContext.GetObject("MethodTestObject5"); + + Assert.That(testObj.GetObject(), Is.Not.Null); + Assert.That(testObj.GetObject().Count, Is.EqualTo(2)); + } + + [Test] + public void InjectSeveralParameters() + { + var testObj = (MethodTestObject6)_applicationContext.GetObject("MethodTestObject6"); + var objectDefinition = _applicationContext.ObjectFactory.GetObjectDefinition("MethodTestObject6"); + + Assert.That(testObj.GetObject(), Is.Not.Null); + Assert.That(testObj.GetSimple(), Is.Not.Null); + Assert.That(objectDefinition.DependsOn.Count, Is.EqualTo(2)); + } + + [Test] + public void FailIfObjecNotAvailable() + { + Assert.That(delegate { var testObj = (MethodTestObject7)_applicationContext.GetObject("MethodTestObject7"); }, + Throws.Exception.TypeOf()); + } + + [Test] + public void PassIfObjectNotavailableButNotRequired() + { + var testObj1 = (MethodTestObject8)_applicationContext.GetObject("MethodTestObject8"); + var objectDefinition = _applicationContext.ObjectFactory.GetObjectDefinition("MethodTestObject8"); + + Assert.That(testObj1.GetSimple(), Is.Null); + Assert.That(objectDefinition.DependsOn.Count, Is.EqualTo(0)); + + var testObj2 = (MethodTestObject9)_applicationContext.GetObject("MethodTestObject9"); + objectDefinition = _applicationContext.ObjectFactory.GetObjectDefinition("MethodTestObject9"); + + Assert.That(testObj2.GetrAdvanced(), Is.Null); + Assert.That(objectDefinition.DependsOn.Count, Is.EqualTo(0)); + } + + } + + + #region Test Objects + + public interface ISimple + { + string Foo(); + } + + public class Simple : ISimple + { + public string Foo() + { + return "simple"; + } + } + + public interface IAdvanced + { + + } + + public interface IMethodFoo + { + string Foo(); + } + + public class MethodHello : IMethodFoo + { + public string Foo() + { + return "hello"; + } + } + + public class MethodCiao : IMethodFoo + { + public string Foo() + { + return "ciao"; + } + } + + public class MethodTestObject1 + { + private ISimple _impl; + + [Autowired] + public void Prepare(ISimple impl) + { + _impl = impl; + } + + public object GetObject() + { + return _impl; + } + } + + public class MethodTestObject2 + { + private IMethodFoo _methodCiao; + + [Autowired] + public void Prepare(IMethodFoo methodCiao) + { + _methodCiao = methodCiao; + } + + public object GetObject() + { + return _methodCiao; + } + } + + public class MethodTestObject3 + { + private IMethodFoo _impl; + + [Autowired] + public void Prepare([Qualifier("MethodHello")] IMethodFoo impl) + { + _impl = impl; + } + + public object GetObject() + { + return _impl; + } + } + + public class MethodTestObject4 + { + private IMethodFoo _impl; + + [Autowired] + public void Prepare([Qualifier("MethodHello")] IMethodFoo impl, string test) + { + _impl = impl; + } + + public object GetObject() + { + return _impl; + } + } + + public class MethodTestObject5 + { + private IList _impl; + + [Autowired] + public void Prepare(IList impl) + { + _impl = impl; + } + + public IList GetObject() + { + return _impl; + } + } + + public class MethodTestObject6 + { + private IMethodFoo _impl; + private ISimple _simple; + + [Autowired] + public void Prepare(IMethodFoo methodHello, ISimple simple) + { + _impl = methodHello; + _simple = simple; + } + + public object GetObject() + { + return _impl; + } + + public object GetSimple() + { + return _simple; + } + } + + public class MethodTestObject7 + { + private ISimple _simple; + + [Autowired] + public void Prepare([Qualifier("NotAvailable")] ISimple simple) + { + _simple = simple; + } + + public object GetSimple() + { + return _simple; + } + } + + public class MethodTestObject8 + { + private ISimple _simple; + + [Autowired(Required = false)] + public void Prepare([Qualifier("NotAvailable")] ISimple simple) + { + _simple = simple; + } + + public object GetSimple() + { + return _simple; + } + } + + public class MethodTestObject9 + { + private IAdvanced _advanced; + + [Autowired(Required = false)] + public void Prepare(IAdvanced advanced) + { + _advanced = advanced; + } + + public object GetrAdvanced() + { + return _advanced; + } + } + + #endregion +} diff --git a/test/Spring/Spring.Core.Tests/Objects/Factory/Attributes/AutowireAttributePropertyTest.cs b/test/Spring/Spring.Core.Tests/Objects/Factory/Attributes/AutowireAttributePropertyTest.cs new file mode 100644 index 00000000..eec2e459 --- /dev/null +++ b/test/Spring/Spring.Core.Tests/Objects/Factory/Attributes/AutowireAttributePropertyTest.cs @@ -0,0 +1,254 @@ +#region License + +/* + * Copyright © 2002-2011 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 NUnit.Framework; +using Spring.Context.Support; +using Spring.Objects.Factory.Config; +using Spring.Objects.Factory.Support; + +namespace Spring.Objects.Factory.Attributes +{ + [TestFixture] + public class AutowireAttributePropertyTest + { + private GenericApplicationContext _applicationContext; + + [SetUp] + public void Setup() + { + _applicationContext = new GenericApplicationContext(); + + var objDef = new RootObjectDefinition(typeof(AutowiredAttributeObjectPostProcessor)); + objDef.Role = ObjectRole.ROLE_INFRASTRUCTURE; + _applicationContext.RegisterObjectDefinition("AutowiredAttributeObjectPostProcessor", objDef); + + + objDef = new RootObjectDefinition(typeof(PropFooImpl)); + _applicationContext.RegisterObjectDefinition("PropFoo", objDef); + + objDef = new RootObjectDefinition(typeof(PropHello)); + _applicationContext.RegisterObjectDefinition("PropHello", objDef); + + objDef = new RootObjectDefinition(typeof(PropCioa)); + _applicationContext.RegisterObjectDefinition("PropCioa", objDef); + + objDef = new RootObjectDefinition(typeof(PropTestObject1)); + objDef.Scope = "prototype"; + _applicationContext.RegisterObjectDefinition("PropTestObject1", objDef); + + objDef = new RootObjectDefinition(typeof(PropTestObject2)); + objDef.Scope = "prototype"; + _applicationContext.RegisterObjectDefinition("PropTestObject2", objDef); + + objDef = new RootObjectDefinition(typeof(PropTestObject3)); + objDef.Scope = "prototype"; + _applicationContext.RegisterObjectDefinition("PropTestObject3", objDef); + + objDef = new RootObjectDefinition(typeof(PropTestObject4)); + objDef.Scope = "prototype"; + _applicationContext.RegisterObjectDefinition("PropTestObject4", objDef); + + objDef = new RootObjectDefinition(typeof(PropTestObject5)); + objDef.Scope = "prototype"; + _applicationContext.RegisterObjectDefinition("PropTestObject5", objDef); + + _applicationContext.Refresh(); + } + + [Test] + public void InjectPropertyBasedOnPropertyType() + { + var testObj = (PropTestObject1)_applicationContext.GetObject("PropTestObject1"); + var objectDefinition = _applicationContext.ObjectFactory.GetObjectDefinition("PropTestObject1"); + + Assert.That(testObj.Foo, Is.Not.Null); + Assert.That(testObj.Test(), Is.EqualTo("foo")); + Assert.That(objectDefinition.DependsOn.Count, Is.EqualTo(1), "Should have one Dependant Object"); + } + + [Test] + public void WithTwoTypesRegisteredAndNoNameShouldFail() + { + Assert.That(delegate { var testObj = (PropTestObject2)_applicationContext.GetObject("PropTestObject2"); }, Throws.Exception.TypeOf()); + } + + [Test] + public void WithQualifierName() + { + var testObj = (PropTestObject3)_applicationContext.GetObject("PropTestObject3"); + + Assert.That(testObj.Cioa, Is.Not.Null); + Assert.That(testObj.Say(), Is.EqualTo("cioa")); + } + + [Test] + public void WithTwoTypesAndNoQualifierUsePopertyName() + { + var testObj = (PropTestObject4)_applicationContext.GetObject("PropTestObject4"); + + Assert.That(testObj.PropCioa, Is.Not.Null); + Assert.That(testObj.Say(), Is.EqualTo("cioa")); + } + + [Test] + public void FailIfTypeCantBeResolved() + { + Exception ex = null; + try + { + var testObj = (FldTestObject5)_applicationContext.GetObject("PropTestObject5"); + } + catch (Exception e) { ex = e; } + + Assert.That(ex, Is.Not.Null, "Should throw an exception"); + Assert.That(ex.Message, Is.StringContaining("Injection of autowired dependencies failed")); + } + + } + + #region Test Objects + + public interface IPropNotAnObject + { + + } + + public interface IPropFoo + { + string Test(); + } + + public class PropFooImpl : IPropFoo + { + public string Test() + { + return "foo"; + } + } + + public interface IPropSay + { + string PropSay(); + } + + public class PropHello : IPropSay + { + public string PropSay() + { + return "hello"; + } + } + + public class PropCioa : IPropSay + { + public string PropSay() + { + return "cioa"; + } + } + + public class PropTestObject1 + { + private IPropFoo _foo; + + [Autowired] + public IPropFoo Foo + { + get { return _foo; } + set { _foo = value; } + } + + public string Test() + { + return _foo.Test(); + } + } + + public class PropTestObject2 + { + private IPropSay _hello; + + [Autowired] + public IPropSay WrongName + { + get { return _hello; } + set { _hello = value; } + } + + public string Say() + { + return _hello.PropSay(); + } + } + + // should not fail but inject Cioa object + public class PropTestObject3 + { + private IPropSay _cioa; + + [Autowired] + [Qualifier("PropCioa")] + public IPropSay Cioa + { + get { return _cioa; } + set { _cioa = value; } + } + + public string Say() + { + return _cioa.PropSay(); + } + } + + // should not fail but inject Hello via Propertyname + public class PropTestObject4 + { + private IPropSay _obj; + + [Autowired] + public IPropSay PropCioa + { + get { return _obj; } + set { _obj = value; } + } + + public string Say() + { + return _obj.PropSay(); + } + } + + // should not fail but inject Hello via Propertyname + public class PropTestObject5 + { + private IPropNotAnObject _obj; + + [Autowired] + public IPropNotAnObject Ohhh + { + get { return _obj; } + set { _obj = value; } + } + } + + #endregion + +} diff --git a/test/Spring/Spring.Core.Tests/Spring.Core.Tests.2010.csproj b/test/Spring/Spring.Core.Tests/Spring.Core.Tests.2010.csproj index d9fbeafe..66411318 100644 --- a/test/Spring/Spring.Core.Tests/Spring.Core.Tests.2010.csproj +++ b/test/Spring/Spring.Core.Tests/Spring.Core.Tests.2010.csproj @@ -318,6 +318,11 @@ + + + + +