diff --git a/doc/reference/src/objects.xml b/doc/reference/src/objects.xml index c49b20ca..9f6b8d4b 100644 --- a/doc/reference/src/objects.xml +++ b/doc/reference/src/objects.xml @@ -199,7 +199,7 @@ <!-- additional collaborators and configuration for this object go here --> </object> - <!-- more bean definitions for services go here --> + <!-- more object definitions for services go here --> </objects>The following example shows the data access objects (daos.xml) configuration file: @@ -207,14 +207,14 @@ <objects xmlns="http://www.springframework.net"> <object id="AccountDao" type="Petstore.Dao.HibernateAccountDao, PetStore"> - <!-- additional collaborators and configuration for this bean go here --> + <!-- additional collaborators and configuration for this object go here --> </object> <object id="ItemDao" type="Petstore.Dao.HibernateItemDao, PetStore"> - <!-- additional collaborators and configuration for this bean go here --> + <!-- additional collaborators and configuration for this object go here --> </object> - <!-- more bean definitions for data access objects go here --> + <!-- more object definitions for data access objects go here --> </objects> In the preceeding example, the service layer consists of the class @@ -436,7 +436,7 @@ that these paths are relative, it is better form not to use the slash at all. The contents of the files being imported, including the top level <objects/> element, must be valid XML - bean definitions according to the Spring Schema. + object definitions according to the Spring Schema. @@ -453,7 +453,7 @@ The IApplicationContext enables you to read object definitions and access them as follows: - // create and configure beans + // create and configure objects IApplicationContext context = new XmlApplicationContext("services.xml", "daos.xml"); // retrieve configured instance @@ -1037,8 +1037,8 @@ public class TestGenericObjectFactory instance after it is constructed. (Factory methods may be considered a special case of providing constructor arguments for the purposes of this description). The container injects these dependencies when it creates - the bean. This process is fundamentally the inverse to the case when the - bean itself is controlling the instantiation or location of its + the object. This process is fundamentally the inverse to the case when + the object itself is controlling the instantiation or location of its dependencies by using direct construction of classes, or the Service Locator pattern. The inverting of this responsibility is why the name Inversion of Control (IoC) is used to describe the container's @@ -1725,9 +1725,9 @@ public class MixedIocObject no validation is performed on the value that is passed to the targetName property of the client object. Typos are only discovered (with ost mikely fatal results) when the - 'client' object is actually instantiated. If the 'client' bean is a - prototype bean, this typo and the resulting exception may only be - discovered long after the container is deployed. + 'client' object is actually instantiated. If the 'client' object is + a prototype object, this typo and the resulting exception may only + be discovered long after the container is deployed. Additionally, if the reference object is in the same XML unit, and the object name is the object id, you can @@ -1770,7 +1770,7 @@ public class MixedIocObject collaborator is a singleton object it may be initialized already by the container.) All references are ultimately just a reference to another object. Scoping and validation depend on whether you specify - the id/name of the object through the bean, + the id/name of the object through the object, local, or parent attributes. @@ -1802,16 +1802,16 @@ public class MixedIocObject the values in the 'name' attribute of the target object, and the target object must be in a parent container to the current one. You us ethis object reference variant mainly when you - have a hierarchy of containers and you want to wrap an existing bean + have a hierarchy of containers and you want to wrap an existing object in a parent container with some sort of proxy which will have the same - name as the parent bean.<!-- in the parent context --> + name as the parent object.<!-- in the parent context --> <object id="AccountService" type="MyApp.SimpleAccountService, MyApp"> -<!-- insert dependencies as required as here --> + <!-- insert dependencies as required as here --> </object><!-- in the child (descendant) context --> -<object id="AccountService" <-- notice that the name of this bean is the same as the name of the 'parent' bean - type="Spring.Aop.Framework.ProxyFactoryBean, Spring.Aop"> +<object id="AccountService" <-- notice that the name of this object is the same as the name of the 'parent' object + type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop"> <property name="target"> - <ref parent="AccountService"/> <-- notice how we refer to the parent bean --> + <ref parent="AccountService"/> <-- notice how we refer to the parent object --> </property> <!-- insert other configuration and dependencies as required as here --> </object> @@ -1841,7 +1841,7 @@ public class MixedIocObject scope flag. Inner object are always anonymous and they are always scoped as prototypes. It is not possible - to inject inner objects into collaborating beans other than into the + to inject inner objects into collaborating objects other than into the enclosing object. @@ -2032,6 +2032,85 @@ public class MixedIocObject </object> + + Collection Merging + + As of Spring 1.3, the container supports the merging of + collections. An application developer can define a parent-style + <list/>, + <dictionary/>, + <set/> or + <name-value/> element, and have child-style + <list/>, + <dictionary/>, + <set/> or + <name-value/> elements inherit and override + values from the parent collection. That is, the child collection's + values are the result of merging the elements of the parent and child + collections, with the child's collection elements overriding values + specified in the parent collection. + + This section on merging discusses the parent-child + object mechanism. Readers unfamiliar with parent and child object + definitions may wish to read the relevant section before + continuing. + + The following example demonstrates collection merging: + + <object id="parent" abstract="true" type="Example.ComplexObject, Examples"> + <property name="AdminEmails"> + <name-values> + <add key="administrator" value="administrator@example.com"/> + <add key="support" value="support@example.com"/> + </name-values> + </property> + </object> + + <object id="child" parent="parent" > + <property name="AdminEmails"> + <!-- the merge is specified on the *child* collection definition --> + <name-values merge="true"> + <add key="sales" value="sales@example.com"/> + <add key="support" value="support@example.co.uk"/> + </name-values> + </property> + </object> + + Notice the use of the merge=true attribute on + the <name-values/> element of the + AdminEmails property of the child object + definition. When the child object is resolved and instantiated by the + container, the resulting instance has an + AdminEmails Properties collection that contains the + result of the merging of the child's AdminEmails + collection with the parent's AdminEmails + collection. + + administrator=administrator@example.com +sales=sales@example.com +support=support@example.co.uk + + The child Properties collection's value set inherits all + property elements from the parent + <name-values/>, and the child's value for the + support value overrides the value in the parent collection. This + merging behavior applies similarly to the + <list/>, + <dictionary/>, and + <set/> collection types. In the specific case + of the <list/> element, the semantics + associated with the IList collection type, that is, + the notion of an ordered collection of values, is maintained; the + parent's values precede all of the child list's values. In the case of + the IDictionary, ISet, and + NameValue collection types, no ordering exists. + Hence no ordering semantics are in effect for the collection types + that underlie the associated IDictionary, + ISet, and NameValueCollection + implementation types that the container uses internally. + + Null and empty values @@ -2388,11 +2467,11 @@ source.OnClick(); // First eventListener1.HandleEvent is invoked, then eventList If an object is a dependency of another that usually means that one object is set as a property of another. Typically you accomplish this with the <ref/> element in XML-based - configuration metadata. However, sometimes dependencies between beans + configuration metadata. However, sometimes dependencies between objects are less direct; for example, a static initializer in a class needs to be triggered, such as device driver registration. The depends-on attribute can explicitly force one or more - beans to be initialized before the bean using this element is + objects to be initialized before the object using this element is initialized. The following example uses the depends-on attribute to express a dependency on a single object: <object id="objectOne" type="Examples.ExampleObject, ExamplesLibrary" depends-on="manager"> @@ -2452,13 +2531,13 @@ source.OnClick(); // First eventListener1.HandleEvent is invoked, then eventList IApplicationContext is starting up, whereas the not.lazy object is eagerly pre-instantiated. - However, when a lazy-initialized bean is a dependency of a + However, when a lazy-initialized object is a dependency of a singleton object that is not lazy-initialized, the IApplicationContext creates the lazy-initialized - bean at startup, because it must satisfy the singleton's dependencies. - The lazy-initialized bean is injected into a singleton object elsewhere - that is not lazy-initialized. + object at startup, because it must satisfy the singleton's dependencies. + The lazy-initialized object is injected into a singleton object + elsewhere that is not lazy-initialized. You can also control lazy-initialization at the container level by using the default-lazy-init attribute on the @@ -3460,7 +3539,7 @@ public class MyClassFactory prototype objects, be aware that dependencies are resolved at instantiation time. Thus if you dependency-inject a prototype-scoped objects into a singleton-scoped object, a new prototype - bean is instantiated and then dependency-injected into the singleton + object is instantiated and then dependency-injected into the singleton object. The prototype instance is the sole instance that is ever supplied to the singleton-scoped object. diff --git a/src/Spring/Spring.Core/Objects/Factory/Config/ConstructorArgumentValues.cs b/src/Spring/Spring.Core/Objects/Factory/Config/ConstructorArgumentValues.cs index 7e585b42..28e8198e 100644 --- a/src/Spring/Spring.Core/Objects/Factory/Config/ConstructorArgumentValues.cs +++ b/src/Spring/Spring.Core/Objects/Factory/Config/ConstructorArgumentValues.cs @@ -180,17 +180,35 @@ namespace Spring.Objects.Factory.Config GenericArgumentValues.Add(o); } foreach (DictionaryEntry entry in other.IndexedArgumentValues) - { - IndexedArgumentValues.Add(entry.Key, entry.Value); + { + ValueHolder vh = entry.Value as ValueHolder; + if (vh != null) + { + AddOrMergeIndexedArgumentValues( (int) entry.Key, vh.Copy()); + } } foreach (DictionaryEntry entry in other.NamedArgumentValues) { NamedArgumentValues.Add(entry.Key, entry.Value); } } - } + } + + private void AddOrMergeIndexedArgumentValues(int key, ValueHolder newValue) + { + ValueHolder currentValue = _indexedArgumentValues[key] as ValueHolder; + IMergable mergable = newValue.Value as IMergable; + if (currentValue != null && mergable != null ) + { + if (mergable.MergeEnabled) + { + newValue.Value = mergable.Merge(currentValue.Value); + } + } + _indexedArgumentValues[key] = newValue; + } - /// + /// /// Add argument value for the given index in the constructor argument list. /// /// @@ -599,7 +617,13 @@ namespace Spring.Objects.Factory.Config #endregion - #region Methods + #region Methods + + public ValueHolder Copy() + { + ValueHolder copy = new ValueHolder(this._ctorValue, this.typeName); + return copy; + } /// /// A that represents the current @@ -650,7 +674,7 @@ namespace Spring.Objects.Factory.Config private object _ctorValue; private string typeName; - #endregion + #endregion } #endregion diff --git a/test/Spring/Spring.Core.Tests/Data/Spring/Objects/Factory/Xml/collectionMerging.xml b/test/Spring/Spring.Core.Tests/Data/Spring/Objects/Factory/Xml/collectionMerging.xml index 910c627c..09cb6556 100644 --- a/test/Spring/Spring.Core.Tests/Data/Spring/Objects/Factory/Xml/collectionMerging.xml +++ b/test/Spring/Spring.Core.Tests/Data/Spring/Objects/Factory/Xml/collectionMerging.xml @@ -3,7 +3,7 @@ - + Rob Harrop Rod Johnson @@ -12,13 +12,22 @@ - + Juergen Hoeller + + + + + + + + + @@ -36,6 +45,17 @@ + + + + + + + + + + + @@ -55,6 +75,19 @@ + + + + + + + + + + + + + @@ -72,4 +105,106 @@ + + + + + + Rob Harrop + Rod Johnson + + + + + + + + + Juergen Hoeller + + + + + + + + + + + + + + + + Rob Harrop + + + + + + + + Sally Greenwood + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/Spring/Spring.Core.Tests/Data/Spring/Objects/Factory/Xml/collectionMergingGen.xml b/test/Spring/Spring.Core.Tests/Data/Spring/Objects/Factory/Xml/collectionMergingGen.xml new file mode 100644 index 00000000..d0b5941f --- /dev/null +++ b/test/Spring/Spring.Core.Tests/Data/Spring/Objects/Factory/Xml/collectionMergingGen.xml @@ -0,0 +1,23 @@ + + + + + + + + Rob Harrop + Rod Johnson + + + + + + + + Juergen Hoeller + + + + + + diff --git a/test/Spring/Spring.Core.Tests/Objects/Factory/Xml/CollectionMergingGenericTests.cs b/test/Spring/Spring.Core.Tests/Objects/Factory/Xml/CollectionMergingGenericTests.cs new file mode 100644 index 00000000..130c9bd5 --- /dev/null +++ b/test/Spring/Spring.Core.Tests/Objects/Factory/Xml/CollectionMergingGenericTests.cs @@ -0,0 +1,68 @@ +#region License + +/* + * Copyright © 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +#if !NET_1_1 +#region Imports + +using System; +using System.Collections; +using System.Collections.Specialized; +using NUnit.Framework; +using Spring.Collections; +using Spring.Objects.Factory.Support; + +#endregion + +namespace Spring.Objects.Factory.Xml +{ + /// + /// Unit and integration tests for the collection merging support + /// + /// Rod Johnson + /// Rick Evans + /// Mark Pollack (.NET) + [TestFixture] + [Ignore("SPRNET-1242 Support for collection merging with generic collections")] + public class CollectionMergingGenericTests + { + private DefaultListableObjectFactory objectFactory; + + [SetUp] + public void SetUp() + { + this.objectFactory = new DefaultListableObjectFactory(); + IObjectDefinitionReader reader = new XmlObjectDefinitionReader(this.objectFactory); + reader.LoadObjectDefinitions(new ReadOnlyXmlTestResource("collectionMergingGeneric.xml", GetType())); + } + + [Test] + public void MergeList() + { + TestObject to = (TestObject) this.objectFactory.GetObject("childWithList"); + System.Collections.Generic.List list = to.SomeGenericStringList; + Assert.That(3, Is.EqualTo(list.Count)); + Assert.That("Rob Harrop", Is.EqualTo(list[0])); + Assert.That("Rod Johnson", Is.EqualTo(list[1])); + Assert.That("Juergen Hoeller", Is.EqualTo(list[2])); + } + + } +} +#endif \ No newline at end of file diff --git a/test/Spring/Spring.Core.Tests/Objects/Factory/Xml/CollectionMergingTests.cs b/test/Spring/Spring.Core.Tests/Objects/Factory/Xml/CollectionMergingTests.cs index 5c8e4395..95af297d 100644 --- a/test/Spring/Spring.Core.Tests/Objects/Factory/Xml/CollectionMergingTests.cs +++ b/test/Spring/Spring.Core.Tests/Objects/Factory/Xml/CollectionMergingTests.cs @@ -61,6 +61,17 @@ namespace Spring.Objects.Factory.Xml Assert.That("Juergen Hoeller", Is.EqualTo(list[2])); } + [Test] + public void MergListWithInnerObjectAsListElement() + { + TestObject to = (TestObject)this.objectFactory.GetObject("childWithListOfRefs"); + IList list = to.SomeList; + Assert.IsNotNull(list); + Assert.AreEqual(3, list.Count); + Assert.IsNotNull(list[2]); + Assert.That(list[2], Is.InstanceOf(typeof (TestObject))); + } + [Test] public void MergeSet() { @@ -71,6 +82,22 @@ namespace Spring.Objects.Factory.Xml Assert.IsTrue(set.Contains("Sally Greenwood")); } + [Test] + public void MergeSetWithInnerObjectAsSetElement() + { + TestObject to = (TestObject)this.objectFactory.GetObject("childWithSetOfRefs"); + ISet set = to.SomeSet; + Assert.IsNotNull(set); + Assert.AreEqual(2, set.Count); + IEnumerator enumerator = set.GetEnumerator(); + enumerator.MoveNext(); + enumerator.MoveNext(); + object o = enumerator.Current; + Assert.IsNotNull(o); + Assert.That(o, Is.InstanceOf(typeof(TestObject))); + Assert.AreEqual("Sally", ((TestObject) o).Name); + } + [Test] public void MergeDictionary() { @@ -82,6 +109,18 @@ namespace Spring.Objects.Factory.Xml Assert.AreEqual("Eva", map["Juergen"]); } + [Test] + public void MergeMapWithInnerObjectAsMapEntryValue() + { + TestObject to = (TestObject)this.objectFactory.GetObject("childWithMapOfRefs"); + IDictionary map = to.SomeMap; + Assert.NotNull(map); + Assert.AreEqual(2, map.Count); + Assert.NotNull(map["Rob"]); + Assert.That(map["Rob"], Is.InstanceOf(typeof(TestObject))); + Assert.AreEqual("Sally", ((TestObject) map["Rob"]).Name); + } + [Test] public void MergeNameValueCollection() { @@ -92,5 +131,89 @@ namespace Spring.Objects.Factory.Xml Assert.AreEqual("Kerry", map["Rod"]); Assert.AreEqual("Eva", map["Juergen"]); } + + [Test] + public void MergeListInConstructor() + { + TestObject to = (TestObject)this.objectFactory.GetObject("childWithListInConstructor"); + IList list = to.SomeList; + Assert.AreEqual(3, list.Count); + Assert.AreEqual("Rob Harrop", list[0]); + Assert.AreEqual("Rod Johnson", list[1]); + Assert.AreEqual("Juergen Hoeller", list[2]); + } + + [Test] + public void MergeListWithInnerObjectAsListElementInConstructor() + { + TestObject to = (TestObject)this.objectFactory.GetObject("childWithListOfRefsInConstructor"); + IList list = to.SomeList; + Assert.IsNotNull(list); + Assert.AreEqual(3, list.Count); + Assert.IsNotNull(list[2]); + Assert.That(list[2], Is.InstanceOf(typeof(TestObject))); + } + + [Test] + public void MergeSetInConstructor() + { + TestObject to = (TestObject)this.objectFactory.GetObject("childWithSetInConstructor"); + ISet set = to.SomeSet; + Assert.AreEqual(2, set.Count); + Assert.IsTrue(set.Contains("Rob Harrop")); + Assert.IsTrue(set.Contains("Sally Greenwood")); + } + + [Test] + public void MergeSetWithInnerObjectAsSetElementInConstructor() + { + TestObject to = (TestObject)this.objectFactory.GetObject("childWithSetOfRefsInConstructor"); + ISet set = to.SomeSet; + Assert.IsNotNull(set); + Assert.AreEqual(2, set.Count); + IEnumerator enumerator = set.GetEnumerator(); + enumerator.MoveNext(); + enumerator.MoveNext(); + object o = enumerator.Current; + Assert.IsNotNull(o); + Assert.That(o, Is.InstanceOf(typeof(TestObject))); + Assert.AreEqual("Sally", ((TestObject)o).Name); + } + + [Test] + public void MergeMapInConstructor() + { + TestObject to = (TestObject)this.objectFactory.GetObject("childWithMapInConstructor"); + IDictionary map = to.SomeMap; + Assert.AreEqual(3, map.Count); + Assert.AreEqual("Sally", map["Rob"]); + Assert.AreEqual("Kerry", map["Rod"]); + Assert.AreEqual("Eva", map["Juergen"]); + } + + [Test] + public void MergeMapWithInnerObjectgAsMapEntryValueInConstructor() + { + TestObject to = (TestObject)this.objectFactory.GetObject("childWithMapOfRefsInConstructor"); + IDictionary map = to.SomeMap; + Assert.IsNotNull(map); + Assert.AreEqual(2, map.Count); + Assert.IsNotNull(map["Rob"]); + Assert.That(map["Rob"], Is.InstanceOf(typeof(TestObject))); + Assert.AreEqual("Sally", ((TestObject) map["Rob"]).Name); + + } + + [Test] + public void MergeNameValueCollectionInConstructor() + { + TestObject to = (TestObject)this.objectFactory.GetObject("childWithPropsInConstructor"); + NameValueCollection props = to.SomeNameValueCollection; + Assert.AreEqual(3, props.Count); + Assert.AreEqual("Sally", props["Rob"]); + Assert.AreEqual("Kerry", props["Rod"]); + Assert.AreEqual("Eva", props["Juergen"]); + + } } } \ No newline at end of file diff --git a/test/Spring/Spring.Core.Tests/Objects/TestObject.cs b/test/Spring/Spring.Core.Tests/Objects/TestObject.cs index 5731c6b9..1ccf64e0 100644 --- a/test/Spring/Spring.Core.Tests/Objects/TestObject.cs +++ b/test/Spring/Spring.Core.Tests/Objects/TestObject.cs @@ -22,7 +22,10 @@ using System; using System.Collections; +using System.Collections.Generic; +#if !NET_1_1 using System.Collections.Specialized; +#endif using System.ComponentModel; using System.ComponentModel.Design.Serialization; using System.Drawing; @@ -242,7 +245,7 @@ namespace Spring.Objects get { return computers; } } - public virtual Set SomeSet + public virtual ISet SomeSet { get { return someSet; } set { this.someSet = value; } @@ -266,6 +269,14 @@ namespace Spring.Objects set { this.someList = value;} } +#if !NET_1_1 + public virtual List SomeGenericStringList + { + get { return someGenericStringList; } + set { this.someGenericStringList = value; } + } +#endif + public virtual NameValueCollection SomeNameValueCollection { get { return someNameValueCollection; } @@ -370,8 +381,8 @@ namespace Spring.Objects }; private Type classProperty; - private Set computers = new HybridSet(); - private Set someSet = new HybridSet(); + private ISet computers = new HybridSet(); + private ISet someSet = new HybridSet(); private IDictionary someMap = new Hashtable(); private IList someList = new ArrayList(); private DateTime date = DateTime.Now; @@ -397,6 +408,7 @@ namespace Spring.Objects private IDictionary sharedState; private NameValueCollection someNameValueCollection; + private List someGenericStringList; #endregion @@ -432,6 +444,26 @@ namespace Spring.Objects this.spouse = spouse; } + public TestObject(IList someList) + { + this.someList = someList; + } + + public TestObject(ISet someSet) + { + this.someSet = someSet; + } + + public TestObject(IDictionary someMap) + { + this.someMap = someMap; + } + + public TestObject(NameValueCollection someProps) + { + this.someNameValueCollection = someProps; + } + #endregion #region Static Methods diff --git a/test/Spring/Spring.Core.Tests/Spring.Core.Tests.2008.csproj b/test/Spring/Spring.Core.Tests/Spring.Core.Tests.2008.csproj index a242f049..5ca5a1ed 100644 --- a/test/Spring/Spring.Core.Tests/Spring.Core.Tests.2008.csproj +++ b/test/Spring/Spring.Core.Tests/Spring.Core.Tests.2008.csproj @@ -338,6 +338,7 @@ + @@ -811,6 +812,7 @@ + diff --git a/test/Spring/Spring.Web.Tests/Spring.Web.Tests.2003.csproj b/test/Spring/Spring.Web.Tests/Spring.Web.Tests.2003.csproj index 85f925e0..3a2a5f69 100644 --- a/test/Spring/Spring.Web.Tests/Spring.Web.Tests.2003.csproj +++ b/test/Spring/Spring.Web.Tests/Spring.Web.Tests.2003.csproj @@ -202,7 +202,7 @@