#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
#region Imports
using System;
using System.Collections;
using System.Configuration;
using System.Reflection;
using System.Xml;
using Common.Logging;
using Spring.Core;
using Spring.Core.TypeResolution;
using Spring.Objects;
using Spring.Reflection.Dynamic;
using Spring.Util;
#endregion
namespace Spring.Context.Support{
/// <summary>
/// Creates an <see cref="Spring.Context.IApplicationContext"/> instance
/// using context definitions supplied in a custom configuration and
/// configures the <see cref="ContextRegistry"/> with that instance.
/// </summary>
/// <remarks>
/// Implementations of the <see cref="Spring.Context.IApplicationContext"/>
/// interface <b>must</b> provide the following two constructors:
/// <list type="number">
/// <item>
/// <description>
/// A constructor that takes a string array of resource locations.
/// </description>
/// </item>
/// <item>
/// <description>
/// A constructor that takes a reference to a parent application context
/// and a string array of resource locations (and in that order).
/// </description>
/// </item>
/// </list>
/// <p>
/// Note that if the <c>type</c> attribute is not present in the declaration
/// of a particular context, then a default
/// <see cref="Spring.Context.IApplicationContext"/> <see cref="System.Type"/>
/// is assumed. This default
/// <see cref="Spring.Context.IApplicationContext"/> <see cref="System.Type"/>
/// is currently the <see cref="Spring.Context.Support.XmlApplicationContext"/>
/// <see cref="System.Type"/>; please note the exact <see cref="System.Type"/>
/// of this default <see cref="Spring.Context.IApplicationContext"/> is an
/// implementation detail, that, while unlikely, may do so in the future.
/// to
/// </p>
/// </remarks>
/// <example>
/// <p>
/// This is an example of specifying a context that reads its resources from
/// an embedded Spring.NET XML object configuration file...
/// </p>
/// <code escaped="true">
/// <configuration>
/// <configSections>
/// <sectionGroup name="spring">
/// <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/>
/// </sectionGroup>
/// </configSections>
/// <spring>
/// <context>
/// <resource uri="assembly://MyAssemblyName/MyResourceNamespace/MyObjects.xml"/>
/// </context>
/// </spring>
/// </configuration>
/// </code>
/// <p>
/// This is an example of specifying a context that reads its resources from
/// a custom configuration section within the same application / web
/// configuration file and uses case insensitive object lookups.
/// </p>
/// <p>
/// Please note that you <b>must</b> adhere to the naming
/// of the various sections (i.e. '<sectionGroup name="spring">' and
/// '<section name="context">'.
/// </p>
/// <code escaped="true">
/// <configuration>
/// <configSections>
/// <sectionGroup name="spring">
/// <section name="context"
/// type="Spring.Context.Support.ContextHandler, Spring.Core"/>
/// <section name="objects"
/// type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
/// </sectionGroup>
/// </configSections>
/// <spring>
/// <context
/// caseSensitive="false"
/// type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
/// <resource uri="config://spring/objects"/>
/// </context>
/// <objects xmlns="http://www.springframework.net">
/// <!-- object definitions... -->
/// </objects>
/// </spring>
/// </configuration>
/// </code>
/// <p>
/// And this is an example of specifying a hierarchy of contexts. The
/// hierarchy in this case is only a simple parent->child hierarchy, but
/// hopefully it illustrates the nesting of context configurations. This
/// nesting of contexts can be arbitrarily deep, and is one way... child
/// contexts know about their parent contexts, but parent contexts do not
/// know how many child contexts they have (if any), or have references
/// to any such child contexts.
/// </p>
/// <code escaped="true">
/// <configuration>
/// <configSections>
/// <sectionGroup name='spring'>
/// <section name='context' type='Spring.Context.Support.ContextHandler, Spring.Core'/>
/// <section name='objects' type='Spring.Context.Support.DefaultSectionHandler, Spring.Core' />
/// <sectionGroup name="child">
/// <section name='objects' type='Spring.Context.Support.DefaultSectionHandler, Spring.Core' />
/// </sectionGroup>
/// </sectionGroup>
/// </configSections>
///
/// <spring>
/// <context name='Parent'>
/// <resource uri='config://spring/objects'/>
/// <context name='Child'>
/// <resource uri='config://spring/childObjects'/>
/// </context>
/// </context>
/// <!-- parent context's objects -->
/// <objects xmlns='http://www.springframework.net'>
/// <object id='Parent' type='Spring.Objects.TestObject,Spring.Core.Tests'>
/// <property name='name' value='Parent'/>
/// </object>
/// </objects>
/// <!-- child context's objects -->
/// <child>
/// <objects xmlns='http://www.springframework.net'>
/// <object id='Child' type='Spring.Objects.TestObject,Spring.Core.Tests'>
/// <property name='name' value='Child'/>
/// </object>
/// </objects>
/// </child>
/// </spring>
/// </configuration>
/// </code>
/// </example>
/// <author>Mark Pollack</author>
/// <author>Aleksandar Seovic</author>
/// <author>Rick Evans</author>
/// <seealso cref="ContextRegistry"/>
public class ContextHandler : IConfigurationSectionHandler
{
private readonly ILog Log = LogManager.GetLogger(typeof(ContextHandler));
/// <summary>
/// The <see cref="System.Type"/> of <see cref="Spring.Context.IApplicationContext"/>
/// created if no <c>type</c> attribute is specified on a <c>context</c> element.
/// </summary>
/// <seealso cref="GetContextType"/>
protected virtual Type DefaultApplicationContextType
{
get { return typeof (XmlApplicationContext); }
}
/// <summary>
/// Get the context's case-sensitivity to use if none is specified
/// </summary>
/// <remarks>
/// <p>
/// Derived handlers may override this property to change their default case-sensitivity.
/// </p>
/// <p>
/// Defaults to 'true'.
/// </p>
/// </remarks>
protected virtual bool DefaultCaseSensitivity
{
get { return true; }
}
/// <summary>
/// Creates an <see cref="Spring.Context.IApplicationContext"/> instance
/// using the context definitions supplied in a custom
/// configuration section.
/// </summary>
/// <remarks>
/// <p>
/// This <see cref="Spring.Context.IApplicationContext"/> instance is
/// also used to configure the <see cref="ContextRegistry"/>.
/// </p>
/// </remarks>
/// <param name="parent">
/// The configuration settings in a corresponding parent
/// configuration section.
/// </param>
/// <param name="configContext">
/// The configuration context when called from the ASP.NET
/// configuration system. Otherwise, this parameter is reserved and
/// is <see langword="null"/>.
/// </param>
/// <param name="section">
/// The <see cref="System.Xml.XmlNode"/> for the section.
/// </param>
/// <returns>
/// An <see cref="Spring.Context.IApplicationContext"/> instance
/// populated with the object definitions supplied in the configuration
/// section.
/// </returns>
public object Create(object parent, object configContext, XmlNode section)
{
XmlElement contextElement = section as XmlElement;
#region Sanity Checks
if (contextElement == null)
{
throw ConfigurationUtils.CreateConfigurationException(
"Context configuration section must be an XmlElement.");
}
// sanity check on parent
if ( (parent != null) && !(parent is IApplicationContext) )
{
throw ConfigurationUtils.CreateConfigurationException(
String.Format("Parent context must be of type IApplicationContext, but was '{0}'", parent.GetType().FullName));
}
#endregion
// determine name of context to be created
string contextName = GetContextName(configContext, contextElement);
if (!StringUtils.HasLength(contextName))
{
contextName = AbstractApplicationContext.DefaultRootContextName;
}
#region Instrumentation
if (Log.IsDebugEnabled) Log.Debug(string.Format("creating context '{0}'", contextName ) );
#endregion
IApplicationContext context = null;
try
{
IApplicationContext parentContext = parent as IApplicationContext;
// determine context type
Type contextType = GetContextType(contextElement, parentContext);
// determine case-sensitivity
bool caseSensitive = GetCaseSensitivity(contextElement);
// get resource-list
string[] resources = GetResources(contextElement);
// finally create the context instance
context = InstantiateContext(parentContext, configContext, contextName, contextType, caseSensitive, resources);
// get and create child context definitions
XmlNode[] childContexts = GetChildContexts(contextElement);
CreateChildContexts(context, configContext, childContexts);
if (Log.IsDebugEnabled) Log.Debug( string.Format("context '{0}' created for name '{1}'", context, contextName) );
}
catch (Exception ex)
{
if (!ConfigurationUtils.IsConfigurationException(ex))
{
throw ConfigurationUtils.CreateConfigurationException(
String.Format("Error creating context '{0}': {1}",
contextName, ReflectionUtils.GetExplicitBaseException(ex).Message), ex);
}
throw;
}
return context;
}
/// <summary>
/// Create all child-contexts in the given <see cref="XmlNodeList"/> for the given context.
/// </summary>
/// <param name="parentContext">The parent context to use</param>
/// <param name="configContext">The current configContext <see cref="IConfigurationSectionHandler.Create"/></param>
/// <param name="childContexts">The list of child context elements</param>
protected virtual void CreateChildContexts(IApplicationContext parentContext, object configContext, XmlNode[] childContexts)
{
// create child contexts for 'the most recently created context'...
foreach (XmlNode childContext in childContexts)
{
this.Create(parentContext, configContext, childContext);
}
}
/// <summary>
/// Instantiates a new context.
/// </summary>
protected virtual IApplicationContext InstantiateContext(IApplicationContext parentContext, object configContext, string contextName, Type contextType, bool caseSensitive, string[] resources)
{
IApplicationContext context;
ContextInstantiator instantiator;
if (parentContext == null)
{
instantiator = new RootContextInstantiator(contextType, contextName, caseSensitive, resources);
}
else
{
instantiator = new DescendantContextInstantiator(parentContext, contextType, contextName, caseSensitive, resources);
}
if (IsLazy)
{
// TODO
}
context = instantiator.InstantiateContext();
return context;
}
/// <summary>
/// Gets the context's name specified in the name attribute of the context element.
/// </summary>
/// <param name="configContext">The current configContext <see cref="IConfigurationSectionHandler.Create"/></param>
/// <param name="contextElement">The context element</param>
protected virtual string GetContextName(object configContext, XmlElement contextElement)
{
string contextName;
contextName = contextElement.GetAttribute(ContextSchema.NameAttribute);
return contextName;
}
/// <summary>
/// Extracts the context-type from the context element.
/// If none is specified, returns the parent's type.
/// </summary>
private Type GetContextType(XmlElement contextElement, IApplicationContext parentContext)
{
Type contextType;
if (parentContext != null)
{
// set default context type to parent's type (allows for type inheritance)
contextType = GetConfiguredContextType(contextElement, parentContext.GetType());
}
else
{
contextType = GetConfiguredContextType(contextElement, this.DefaultApplicationContextType);
}
return contextType;
}
/// <summary>
/// Extracts the case-sensitivity attribute from the context element
/// </summary>
private bool GetCaseSensitivity(XmlElement contextElement)
{
bool caseSensitive = DefaultCaseSensitivity;
string caseSensitiveAttr = contextElement.GetAttribute(ContextSchema.CaseSensitiveAttribute);
if (StringUtils.HasText(caseSensitiveAttr))
{
caseSensitive = Boolean.Parse(caseSensitiveAttr);
}
return caseSensitive;
}
/// <summary>
/// Gets the context <see cref="System.Type"/> specified in the type
/// attribute of the context element.
/// </summary>
/// <remarks>
/// <p>
/// If this attribute is not defined it defaults to the
/// <see cref="Spring.Context.Support.XmlApplicationContext"/> type.
/// </p>
/// </remarks>
/// <exception cref="TypeMismatchException">
/// If the context type does not implement the
/// <see cref="Spring.Context.IApplicationContext"/> interface.
/// </exception>
private Type GetConfiguredContextType(XmlElement contextElement, Type defaultContextType)
{
string typeName = contextElement.GetAttribute(ContextSchema.TypeAttribute);
if (StringUtils.IsNullOrEmpty(typeName))
{
return defaultContextType;
}
else
{
Type type = TypeResolutionUtils.ResolveType(typeName);
if (typeof(IApplicationContext).IsAssignableFrom(type))
{
return type;
}
else
{
throw new TypeMismatchException( type.Name + " does not implement IApplicationContext.");
}
}
}
/// <summary>
/// Returns <see langword="true"/> if the context should be lazily
/// initialized.
/// </summary>
private bool IsLazy
{
get { return false; }
}
/// <summary>
/// Returns the array of resources containing object definitions for
/// this context.
/// </summary>
private string[] GetResources( XmlElement contextElement )
{
ArrayList resourceNodes = new ArrayList(contextElement.ChildNodes.Count);
foreach (XmlNode possibleResourceNode in contextElement.ChildNodes)
{
XmlElement possibleResourceElement = possibleResourceNode as XmlElement;
if(possibleResourceElement != null &&
possibleResourceElement.LocalName == ContextSchema.ResourceElement)
{
string resourceName = possibleResourceElement.GetAttribute(ContextSchema.URIAttribute);
if(StringUtils.HasText(resourceName))
{
resourceNodes.Add(resourceName);
}
}
}
return (string[]) resourceNodes.ToArray(typeof(string));
}
/// <summary>
/// Returns the array of child contexts for this context.
/// </summary>
private XmlNode[] GetChildContexts(XmlElement contextElement)
{
ArrayList contextNodes = new ArrayList(contextElement.ChildNodes.Count);
foreach (XmlNode possibleContextNode in contextElement.ChildNodes)
{
XmlElement possibleContextElement = possibleContextNode as XmlElement;
if (possibleContextElement != null &&
possibleContextElement.LocalName == ContextSchema.ContextElement)
{
contextNodes.Add(possibleContextElement);
}
}
return (XmlNode[])contextNodes.ToArray(typeof(XmlNode));
}
#region Inner Class : ContextInstantiator
private abstract class ContextInstantiator
{
protected ContextInstantiator(
Type contextType, string contextName, bool caseSensitive, string[] resources)
{
_contextType = contextType;
_contextName = contextName;
_caseSensitive = caseSensitive;
_resources = resources;
}
public IApplicationContext InstantiateContext()
{
ConstructorInfo ctor = GetContextConstructor();
if (ctor == null)
{
string errorMessage = "No constructor with string[] argument found for context type [" + ContextType.Name + "]";
throw ConfigurationUtils.CreateConfigurationException(errorMessage);
}
IApplicationContext context = InvokeContextConstructor(ctor);
ContextRegistry.RegisterContext(context);
return context;
}
protected abstract ConstructorInfo GetContextConstructor();
protected abstract IApplicationContext InvokeContextConstructor(
ConstructorInfo ctor);
protected Type ContextType
{
get { return _contextType; }
}
protected string ContextName
{
get { return _contextName; }
}
protected bool CaseSensitive
{
get { return _caseSensitive; }
}
protected string[] Resources
{
get { return _resources; }
}
private Type _contextType;
private string _contextName;
private bool _caseSensitive;
private string[] _resources;
}
#endregion
#region Inner Class : RootContextInstantiator
private sealed class RootContextInstantiator : ContextInstantiator
{
public RootContextInstantiator(
Type contextType, string contextName, bool caseSensitive, string[] resources)
: base(contextType, contextName, caseSensitive, resources)
{
}
protected override ConstructorInfo GetContextConstructor()
{
return ContextType.GetConstructor(new Type[] {typeof(string), typeof(bool), typeof(string[])});
}
protected override IApplicationContext InvokeContextConstructor(
ConstructorInfo ctor)
{
return (IApplicationContext)(new SafeConstructor(ctor).Invoke(new object[] {ContextName, CaseSensitive, Resources}));
}
}
#endregion
#region Inner Class : DescendantContextInstantiator
private sealed class DescendantContextInstantiator : ContextInstantiator
{
public DescendantContextInstantiator(
IApplicationContext parentContext, Type contextType,
string contextName, bool caseSensitive, string[] resources)
: base(contextType, contextName, caseSensitive, resources)
{
this.parentContext = parentContext;
}
protected override ConstructorInfo GetContextConstructor()
{
return ContextType.GetConstructor(
new Type[] {typeof(string), typeof(bool), typeof(IApplicationContext), typeof(string[])});
}
protected override IApplicationContext InvokeContextConstructor(
ConstructorInfo ctor)
{
return (IApplicationContext)(new SafeConstructor(ctor).Invoke(new object[] {ContextName, CaseSensitive, this.parentContext, Resources}));
}
private IApplicationContext parentContext;
}
#endregion
#region Context Schema Constants
/// <summary>
/// Constants defining the structure and values associated with the
/// schema for laying out Spring.NET contexts in XML.
/// </summary>
private sealed class ContextSchema
{
/// <summary>
/// Defines a single
/// <see cref="Spring.Context.IApplicationContext"/>.
/// </summary>
public const string ContextElement = "context";
/// <summary>
/// Specifies a context name.
/// </summary>
public const string NameAttribute = "name";
/// <summary>
/// Specifies if context should be case sensitive or not. Default is <c>true</c>.
/// </summary>
public const string CaseSensitiveAttribute = "caseSensitive";
/// <summary>
/// Specifies a <see cref="System.Type"/>.
/// </summary>
/// <remarks>
/// <p>
/// Does not have to be fully assembly qualified, but its generally regarded
/// as better form if the <see cref="System.Type"/> names of one's objects
/// are specified explicitly.
/// </p>
/// </remarks>
public const string TypeAttribute = "type";
/// <summary>
/// Specifies whether context should be lazy initialized.
/// </summary>
public const string LazyAttribute = "lazy";
/// <summary>
/// Defines an <see cref="Spring.Core.IO.IResource"/>
/// </summary>
public const string ResourceElement = "resource";
/// <summary>
/// Specifies the URI for an
/// <see cref="Spring.Core.IO.IResource"/>.
/// </summary>
public const string URIAttribute = "uri";
}
#endregion
}
}
|