#region License
/*
* Copyright 2002-2005 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.Config{
/// <summary>
/// <see cref="Spring.Objects.Factory.IFactoryObject"/> implementation that
/// evaluates a property path on a given target object.
/// </summary>
/// <remarks>
/// <p>
/// The target object can be specified directly or via an object name (see
/// example below).
/// </p>
/// <p>
/// Please note that the <see cref="Spring.Objects.Factory.Config.PropertyPathFactoryObject"/>
/// is an <see cref="Spring.Objects.Factory.IFactoryObject"/> implementation, and as such has
/// to comply with the contract of the <see cref="Spring.Objects.Factory.IFactoryObject"/>
/// interface; more specifically, this means that the end result of the property lookup path
/// evaluation cannot be <see lang="null"/> (<see cref="Spring.Objects.Factory.IFactoryObject"/>
/// implementations are not permitted to return <see lang="null"/>). If the resut of a
/// property lookup path evaluates to <see lang="null"/>, an exception will be thrown.
/// </p>
/// </remarks>
/// <example>
/// <code escaped="true">
/// <!-- this is the target object -->
/// <object id="foo" type="Whatever.MyClass, MyAssembly" singleton="false">
/// <!-- a System.String typed property -->
/// <property name="name" value="Chinua Achebe"/>
/// </object>
///
/// <!--
/// will result in "Chinua Achebe", which is the value of the 'name' property of the 'foo' object
/// -->
/// <object id="consumer"
/// type="Spring.Objects.Factory.Config.PropertyPathFactoryObject, Spring.Core">
/// <property name="targetObject" ref="foo"/>
/// <property name="propertyPath" value="name"/>
/// </object>
///
/// <!--
/// will result in "Chinua Achebe", which is the value of the 'name' property of the 'foo' object
/// -->
/// <object id="foo.name"
/// type="Spring.Objects.Factory.Config.PropertyPathFactoryObject, Spring.Core"/>
///
/// <!--
/// will result in "Chinua Achebe", which is the value of the 'name' property of the 'foo' object
/// -->
/// <object id="consumer"
/// type="Spring.Objects.Factory.Config.PropertyPathFactoryObject, Spring.Core">
/// <property name="targetObjectName" value="foo"/>
/// <property name="propertyPath" value="name"/>
/// </object>
///
/// <!--
/// will result in (the int value) '13', which is the value of the length property of the 'name'
/// property of the 'foo' object.
///
/// in this case, the first 'part' of the id is taken to be the name of the target object ('foo');
/// the remainder of the name is taken to be the property lookup path
/// -->
/// <object id="foo.name.length"
/// type="Spring.Objects.Factory.Config.PropertyPathFactoryObject, Spring.Core"/>
/// </code>
/// </example>
/// <author>Juergen Hoeller</author>
/// <author>Rick Evans (.NET)</author>
[Serializable]
public class PropertyPathFactoryObject : IFactoryObject, IObjectNameAware, IObjectFactoryAware
{
private IObjectWrapper targetObjectWrapper;
private string targetObjectName;
private string propertyPath;
private Type resultType;
private string objectName;
private IObjectFactory objectFactory;
/// <summary>
/// The target object that the property path lookup is to be applied to.
/// </summary>
/// <remarks>
/// <p>
/// This would most likely be an inner object, but can of course be
/// any object reference.
/// </p>
/// </remarks>
/// <value>
/// The target object that the property path lookup is to be applied to.
/// </value>
/// <seealso cref="Spring.Objects.Factory.Config.PropertyPathFactoryObject.TargetObjectName"/>
public object TargetObject
{
set { this.targetObjectWrapper = new ObjectWrapper(value); }
}
/// <summary>
/// The (object) name of the target object that the property path lookup
/// is to be applied to.
/// </summary>
/// <remarks>
/// <p>
/// Please note that any leading or trailing whitespace <b>will</b> be
/// trimmed from this name prior to resolution. The implication of this is that
/// one cannot use the <see cref="Spring.Objects.Factory.Config.PropertyPathFactoryObject"/>
/// class in conjunction with object names that start or end with whitespace.
/// </p>
/// </remarks>
/// <value>
/// The (object) name of the target object that the property path lookup
/// is to be applied to.
/// </value>
/// <seealso cref="Spring.Objects.Factory.Config.PropertyPathFactoryObject.TargetObject"/>
public string TargetObjectName
{
set
{
if (value != null)
{
value = value.Trim();
}
this.targetObjectName = value;
}
}
/// <summary>
/// The property (lookup) path to be applied to the target object.
/// </summary>
/// <remarks>
/// <p>
/// Please note that any leading or trailing whitespace <b>will</b> be
/// trimmed from this path prior to resolution. Whitespace is not a valid
/// identifier for property names (in part or whole) in CLS-based languages,
/// so this is a not unreasonable action. Please also note that whitespace
/// that is embedded within the property path will be left as-is (which may
/// or may not result in an error being thrown, depending on the context of
/// the whitespace).
/// </p>
/// </remarks>
/// <example>
/// <p>
/// Examples of such property lookup paths can be seen below; note that
/// property lookup paths can be nested to an arbitrary level.
/// </p>
/// <code escaped="true">
/// name.length
/// accountManager.account['the key'].name
/// accounts[0].name
/// </code>
/// </example>
/// <value>
/// The property (lookup) path to be applied to the target object.
/// </value>
public string PropertyPath
{
set
{
if (value != null)
{
value = value.Trim();
}
this.propertyPath = value;
}
}
/// <summary>
/// The 'expected' <see cref="System.Type"/> of the result from evaluating the
/// property path.
/// </summary>
/// <remarks>
/// <p>
/// This is not necessary for directly specified target objects, or
/// singleton target objects, where the <see cref="System.Type"/> can
/// be determined via reflection. Just specify this in case of a
/// prototype target, provided that you need matching by type (for
/// example, for autowiring).
/// </p>
/// <p>
/// It is permissable to set the value of this property to
/// <see lang="null"/> (which in any case is the default value).
/// </p>
/// </remarks>
/// <value>
/// The 'expected' <see cref="System.Type"/> of the result from evaluating the
/// property path.
/// </value>
public Type ResultType
{
set { this.resultType = value; }
}
/// <summary>
/// Return an instance (possibly shared or independent) of the object
/// managed by this factory.
/// </summary>
/// <returns>
/// An instance (possibly shared or independent) of the object managed by
/// this factory.
/// </returns>
/// <see cref="Spring.Objects.Factory.IFactoryObject.GetObject()"/>
public object GetObject()
{
IObjectWrapper target = this.targetObjectWrapper;
if (target == null)
{
// fetch the prototype object object...
target = new ObjectWrapper(this.objectFactory[this.targetObjectName]);
}
object value = target.GetPropertyValue(this.propertyPath);
if (value == null)
{
throw new FatalObjectException("PropertyPathFactoryObject is not allowed to return null, " +
"but property value for path '" + this.propertyPath + "' is null.");
}
return value;
}
/// <summary>
/// Return the <see cref="System.Type"/> of object that this
/// <see cref="Spring.Objects.Factory.IFactoryObject"/> creates, or
/// <see langword="null"/> if not known in advance.
/// </summary>
/// <see cref="Spring.Objects.Factory.IFactoryObject.ObjectType"/>
public Type ObjectType
{
get { return this.resultType; }
}
/// <summary>
/// Is the object managed by this factory a singleton or a prototype?
/// </summary>
/// <see cref="Spring.Objects.Factory.IFactoryObject.IsSingleton"/>
public bool IsSingleton
{
get { return false; }
}
/// <summary>
/// Set the name of the object in the object factory that created this object.
/// </summary>
/// <remarks>
/// <p>
/// The object name of this
/// <see cref="Spring.Objects.Factory.Config.PropertyPathFactoryObject"/>
/// will be interpreted as "objectName.property" pattern, if neither the
/// <see cref="Spring.Objects.Factory.Config.PropertyPathFactoryObject.TargetObjectName"/>
/// <see cref="Spring.Objects.Factory.Config.PropertyPathFactoryObject.TargetObject"/>
/// have been supplied (set).
/// </p>
/// <p>
/// This allows for concise object definitions with just an id or name.
/// </p>
/// </remarks>
/// <value>
/// The name of the object in the factory.
/// </value>
public string ObjectName
{
set { this.objectName = value; }
}
/// <summary>
/// Callback that supplies the owning factory to an object instance.
/// </summary>
/// <value>
/// Owning <see cref="IInitializingObject.AfterPropertiesSet"/>
/// (may not be <see langword="null"/>). The object can immediately
/// call methods on the factory.
/// </value>
/// <exception cref="IInitializingObject">
/// In case of initialization errors.
/// </exception>
public IObjectFactory ObjectFactory
{
set
{
this.objectFactory = value;
if (this.targetObjectWrapper != null && this.targetObjectName != null)
{
throw new ArgumentException("Only one of the TargetObjectName or TargetObject properties can be set, not both.");
}
if (this.targetObjectWrapper == null && this.targetObjectName == null)
{
if (this.propertyPath != null)
{
throw new ArgumentException(
"Specify TargetObject or TargetObjectName property in combination with PropertyPath.");
}
// no other properties specified: check object name...
string strippedObjectname = this.objectName.Trim();
int dotIndex = strippedObjectname.IndexOf('.');
if (dotIndex <= 0)
{
throw new ArgumentException(
"Neither TargetObject nor TargetObjectName specified, and PropertyPathFactoryObject " +
"object name '" + this.objectName + "' does not follow 'objectName.property' syntax.");
}
this.targetObjectName = strippedObjectname.Substring(0, dotIndex);
this.propertyPath = strippedObjectname.Substring(dotIndex + 1);
}
else if (this.propertyPath == null)
{
throw new ArgumentException("The 'PropertyPath' property has not been set.");
}
if (this.targetObjectWrapper == null
&& this.objectFactory.IsSingleton(this.targetObjectName))
{
// eagerly fetch singleton target object, and determine result type...
this.targetObjectWrapper
= new ObjectWrapper(this.objectFactory.GetObject(this.targetObjectName));
this.resultType = this.targetObjectWrapper.GetPropertyType(this.propertyPath);
}
}
}
}
}
|