#region License
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#endregion
using System;
using System.Collections;
using System.Reflection;
using System.Text;
using Spring.Collections;
using Spring.Objects;
using Spring.Objects.Factory;
using Spring.Objects.Factory.Attributes;
using Spring.Objects.Factory.Config;
using Spring.Util;
namespace Spring.Objects.Factory.Attributes{
/// <summary>
/// A <see cref="IObjectPostProcessor"/> implementation that enforces required properties to have been configured.
/// Required properties are detected through an attribute, by default, Spring's <see cref="RequiredAttribute"/>
/// attribute.
/// </summary>
/// <remarks>
/// <para>The motivation for the existence of this IObjectPostProcessor is to allow
/// developers to annotate the setter properties of their own classes with an
/// arbitrary attribute to indicate that the container must check
/// for the configuration of a dependency injected value. This neatly pushes
/// responsibility for such checking onto the container (where it arguably belongs),
/// and obviates the need (<b>in part</b>) for a developer to code a method that
/// simply checks that all required properties have actually been set.
/// </para>
/// <para>Please note that an 'init' method may still need to implemented (and may
/// still be desirable), because all that this class does is enforce that a
/// 'required' property has actually been configured with a value. It does
/// <b>not</b> check anything else... In particular, it does not check that a
/// configured value is not <code>null</code>.
/// </para>
/// </remarks>
/// <author>Rob Harrop</author>
/// <author>Juergen Hoeller</author>
/// <author>Mark Pollack (.NET)</author>
public class RequiredAttributeObjectPostProcessor : InstantiationAwareObjectPostProcessorAdapter
{
private Type requiredAttributeType = typeof (RequiredAttribute);
/// <summary>
/// Cache for validated object names, skipping re-validation for the same object
/// </summary>
private ISet validatedObjectNames = new SynchronizedSet(new HashedSet());
/// <summary>
/// Sets the type of the required attribute, to be used on a property setter
/// </summary>
/// <remarks>
/// The default required attribute type is the Spring-provided <see cref="RequiredAttribute"/> attribute.
/// This setter property exists so that developers can provide their own
/// (non-Spring-specific) annotation type to indicate that a property value is required.
/// </remarks>
/// <value>The type of the required attribute.</value>
public Type RequiredAttributeType
{
set
{
AssertUtils.ArgumentNotNull(value, "RequiredAttributeType", "RequiredAttributeType property must not be null");
if (!typeof(Attribute).IsAssignableFrom(value))
{
throw new ArgumentException(
string.Format("[{0}] does not inherit from System.Attribute (it must).", value.FullName));
}
requiredAttributeType = value;
}
get
{
return requiredAttributeType;
}
}
/// <summary>
/// Post-process the given property values before the factory applies them
/// to the given object. Checks for the attribute specified by this PostProcessor's RequiredAttributeType.
/// </summary>
/// <param name="pvs">The property values that the factory is about to apply (never <code>null</code>).</param>
/// <param name="pis">The relevant property infos for the target object (with ignored
/// dependency types - which the factory handles specifically - already filtered out)</param>
/// <param name="objectInstance">The object instance created, but whose properties have not yet
/// been set.</param>
/// <param name="objectName">Name of the object.</param>
/// <returns>
/// The actual property values to apply to the given object (can be the
/// passed-in PropertyValues instances or null to skip property population.
/// </returns>
/// <exception cref="ObjectInitializationException">If a required property value has not been specified
/// in the configuration metadata.</exception>
public override IPropertyValues PostProcessPropertyValues(IPropertyValues pvs, PropertyInfo[] pis, object objectInstance,
string objectName)
{
if (!validatedObjectNames.Contains(objectName))
{
ArrayList invalidProperties = new ArrayList();
foreach (PropertyInfo pi in pis)
{
if (IsRequiredProperty(pi) && !pvs.Contains(pi.Name))
{
invalidProperties.Add(pi.Name);
}
}
if (invalidProperties.Count != 0)
{
throw new ObjectInitializationException(
BuildExceptionMessage((string[]) invalidProperties.ToArray(typeof (string)), objectName));
}
validatedObjectNames.Add(objectName);
}
return pvs;
}
/// <summary>
/// Determines whether the supplied property is required to have a value, that is to be dependency injected.
/// </summary>
/// <remarks>
/// This implementation looks for the existence of a "required" attribute on the supplied PropertyInfo and that
/// the property has a setter method.
/// </remarks>
/// <param name="pi">The target PropertyInfo</param>
/// <returns>
/// <c>true</c> if the supplied property has been marked as being required;; otherwise, <c>false</c> if
/// not or if the supplied property does not have a setter method
/// </returns>
protected virtual bool IsRequiredProperty(PropertyInfo pi)
{
return (pi.GetSetMethod() != null && pi.GetCustomAttributes(RequiredAttributeType, true).Length > 0);
}
/// <summary>
/// Builds an exception message for the given list of invalid properties.
/// </summary>
/// <param name="invalidProperties">The list of names of invalid properties.</param>
/// <param name="objectName">Name of the object.</param>
/// <returns>The exception message</returns>
private string BuildExceptionMessage(string[] invalidProperties, ICloneable objectName)
{
int size = invalidProperties.Length;
StringBuilder sb = new StringBuilder();
sb.Append(size == 1 ? "Property" : "Properties");
for (int i=0; i < size; i++)
{
string propName = invalidProperties[i];
if (i > 0)
{
if (i == (size -1 ))
{
sb.Append(" and");
}
else
{
sb.Append(", ");
}
}
sb.Append(" '").Append(propName).Append("'");
}
sb.Append(" required for object '").Append(objectName).Append("'");
return sb.ToString();
}
}
}
|