#region Apache License
|
//
|
// Licensed to the Apache Software Foundation (ASF) under one or more
|
// contributor license agreements. See the NOTICE file distributed with
|
// this work for additional information regarding copyright ownership.
|
// The ASF licenses this file to you 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.Globalization;
|
using System.Reflection;
|
using System.Xml;
|
|
using log4net.Appender;
|
using log4net.Util;
|
using log4net.Core;
|
using log4net.ObjectRenderer;
|
|
namespace log4net.Repository.Hierarchy
|
{
|
/// <summary>
|
/// Initializes the log4net environment using an XML DOM.
|
/// </summary>
|
/// <remarks>
|
/// <para>
|
/// Configures a <see cref="Hierarchy"/> using an XML DOM.
|
/// </para>
|
/// </remarks>
|
/// <author>Nicko Cadell</author>
|
/// <author>Gert Driesen</author>
|
public class XmlHierarchyConfigurator
|
{
|
private enum ConfigUpdateMode
|
{
|
Merge,
|
Overwrite
|
}
|
|
#region Public Instance Constructors
|
|
/// <summary>
|
/// Construct the configurator for a hierarchy
|
/// </summary>
|
/// <param name="hierarchy">The hierarchy to build.</param>
|
/// <remarks>
|
/// <para>
|
/// Initializes a new instance of the <see cref="XmlHierarchyConfigurator" /> class
|
/// with the specified <see cref="Hierarchy" />.
|
/// </para>
|
/// </remarks>
|
public XmlHierarchyConfigurator(Hierarchy hierarchy)
|
{
|
m_hierarchy = hierarchy;
|
m_appenderBag = new Hashtable();
|
}
|
|
#endregion Public Instance Constructors
|
|
#region Public Instance Methods
|
|
/// <summary>
|
/// Configure the hierarchy by parsing a DOM tree of XML elements.
|
/// </summary>
|
/// <param name="element">The root element to parse.</param>
|
/// <remarks>
|
/// <para>
|
/// Configure the hierarchy by parsing a DOM tree of XML elements.
|
/// </para>
|
/// </remarks>
|
public void Configure(XmlElement element)
|
{
|
if (element == null || m_hierarchy == null)
|
{
|
return;
|
}
|
|
string rootElementName = element.LocalName;
|
|
if (rootElementName != CONFIGURATION_TAG)
|
{
|
LogLog.Error(declaringType, "Xml element is - not a <" + CONFIGURATION_TAG + "> element.");
|
return;
|
}
|
|
if (!LogLog.EmitInternalMessages)
|
{
|
// Look for a emitDebug attribute to enable internal debug
|
string emitDebugAttribute = element.GetAttribute(EMIT_INTERNAL_DEBUG_ATTR);
|
LogLog.Debug(declaringType, EMIT_INTERNAL_DEBUG_ATTR + " attribute [" + emitDebugAttribute + "].");
|
|
if (emitDebugAttribute.Length > 0 && emitDebugAttribute != "null")
|
{
|
LogLog.EmitInternalMessages = OptionConverter.ToBoolean(emitDebugAttribute, true);
|
}
|
else
|
{
|
LogLog.Debug(declaringType, "Ignoring " + EMIT_INTERNAL_DEBUG_ATTR + " attribute.");
|
}
|
}
|
|
if (!LogLog.InternalDebugging)
|
{
|
// Look for a debug attribute to enable internal debug
|
string debugAttribute = element.GetAttribute(INTERNAL_DEBUG_ATTR);
|
LogLog.Debug(declaringType, INTERNAL_DEBUG_ATTR+" attribute [" + debugAttribute + "].");
|
|
if (debugAttribute.Length>0 && debugAttribute != "null")
|
{
|
LogLog.InternalDebugging = OptionConverter.ToBoolean(debugAttribute, true);
|
}
|
else
|
{
|
LogLog.Debug(declaringType, "Ignoring " + INTERNAL_DEBUG_ATTR + " attribute.");
|
}
|
|
string confDebug = element.GetAttribute(CONFIG_DEBUG_ATTR);
|
if (confDebug.Length>0 && confDebug != "null")
|
{
|
LogLog.Warn(declaringType, "The \"" + CONFIG_DEBUG_ATTR + "\" attribute is deprecated.");
|
LogLog.Warn(declaringType, "Use the \"" + INTERNAL_DEBUG_ATTR + "\" attribute instead.");
|
LogLog.InternalDebugging = OptionConverter.ToBoolean(confDebug, true);
|
}
|
}
|
|
// Default mode is merge
|
ConfigUpdateMode configUpdateMode = ConfigUpdateMode.Merge;
|
|
// Look for the config update attribute
|
string configUpdateModeAttribute = element.GetAttribute(CONFIG_UPDATE_MODE_ATTR);
|
if (configUpdateModeAttribute != null && configUpdateModeAttribute.Length > 0)
|
{
|
// Parse the attribute
|
try
|
{
|
configUpdateMode = (ConfigUpdateMode)OptionConverter.ConvertStringTo(typeof(ConfigUpdateMode), configUpdateModeAttribute);
|
}
|
catch
|
{
|
LogLog.Error(declaringType, "Invalid " + CONFIG_UPDATE_MODE_ATTR + " attribute value [" + configUpdateModeAttribute + "]");
|
}
|
}
|
|
// IMPL: The IFormatProvider argument to Enum.ToString() is deprecated in .NET 2.0
|
LogLog.Debug(declaringType, "Configuration update mode [" + configUpdateMode.ToString() + "].");
|
|
// Only reset configuration if overwrite flag specified
|
if (configUpdateMode == ConfigUpdateMode.Overwrite)
|
{
|
// Reset to original unset configuration
|
m_hierarchy.ResetConfiguration();
|
LogLog.Debug(declaringType, "Configuration reset before reading config.");
|
}
|
|
/* Building Appender objects, placing them in a local namespace
|
for future reference */
|
|
/* Process all the top level elements */
|
|
foreach (XmlNode currentNode in element.ChildNodes)
|
{
|
if (currentNode.NodeType == XmlNodeType.Element)
|
{
|
XmlElement currentElement = (XmlElement)currentNode;
|
|
if (currentElement.LocalName == LOGGER_TAG)
|
{
|
ParseLogger(currentElement);
|
}
|
else if (currentElement.LocalName == CATEGORY_TAG)
|
{
|
// TODO: deprecated use of category
|
ParseLogger(currentElement);
|
}
|
else if (currentElement.LocalName == ROOT_TAG)
|
{
|
ParseRoot(currentElement);
|
}
|
else if (currentElement.LocalName == RENDERER_TAG)
|
{
|
ParseRenderer(currentElement);
|
}
|
else if (currentElement.LocalName == APPENDER_TAG)
|
{
|
// We ignore appenders in this pass. They will
|
// be found and loaded if they are referenced.
|
}
|
else
|
{
|
// Read the param tags and set properties on the hierarchy
|
SetParameter(currentElement, m_hierarchy);
|
}
|
}
|
}
|
|
// Lastly set the hierarchy threshold
|
string thresholdStr = element.GetAttribute(THRESHOLD_ATTR);
|
LogLog.Debug(declaringType, "Hierarchy Threshold [" + thresholdStr + "]");
|
if (thresholdStr.Length > 0 && thresholdStr != "null")
|
{
|
Level thresholdLevel = (Level) ConvertStringTo(typeof(Level), thresholdStr);
|
if (thresholdLevel != null)
|
{
|
m_hierarchy.Threshold = thresholdLevel;
|
}
|
else
|
{
|
LogLog.Warn(declaringType, "Unable to set hierarchy threshold using value [" + thresholdStr + "] (with acceptable conversion types)");
|
}
|
}
|
|
// Done reading config
|
}
|
|
#endregion Public Instance Methods
|
|
#region Protected Instance Methods
|
|
/// <summary>
|
/// Parse appenders by IDREF.
|
/// </summary>
|
/// <param name="appenderRef">The appender ref element.</param>
|
/// <returns>The instance of the appender that the ref refers to.</returns>
|
/// <remarks>
|
/// <para>
|
/// Parse an XML element that represents an appender and return
|
/// the appender.
|
/// </para>
|
/// </remarks>
|
protected IAppender FindAppenderByReference(XmlElement appenderRef)
|
{
|
string appenderName = appenderRef.GetAttribute(REF_ATTR);
|
|
IAppender appender = (IAppender)m_appenderBag[appenderName];
|
if (appender != null)
|
{
|
return appender;
|
}
|
else
|
{
|
// Find the element with that id
|
XmlElement element = null;
|
|
if (appenderName != null && appenderName.Length > 0)
|
{
|
foreach (XmlElement curAppenderElement in appenderRef.OwnerDocument.GetElementsByTagName(APPENDER_TAG))
|
{
|
if (curAppenderElement.GetAttribute("name") == appenderName)
|
{
|
element = curAppenderElement;
|
break;
|
}
|
}
|
}
|
|
if (element == null)
|
{
|
LogLog.Error(declaringType, "XmlHierarchyConfigurator: No appender named [" + appenderName + "] could be found.");
|
return null;
|
}
|
else
|
{
|
appender = ParseAppender(element);
|
if (appender != null)
|
{
|
m_appenderBag[appenderName] = appender;
|
}
|
return appender;
|
}
|
}
|
}
|
|
/// <summary>
|
/// Parses an appender element.
|
/// </summary>
|
/// <param name="appenderElement">The appender element.</param>
|
/// <returns>The appender instance or <c>null</c> when parsing failed.</returns>
|
/// <remarks>
|
/// <para>
|
/// Parse an XML element that represents an appender and return
|
/// the appender instance.
|
/// </para>
|
/// </remarks>
|
protected IAppender ParseAppender(XmlElement appenderElement)
|
{
|
string appenderName = appenderElement.GetAttribute(NAME_ATTR);
|
string typeName = appenderElement.GetAttribute(TYPE_ATTR);
|
|
LogLog.Debug(declaringType, "Loading Appender [" + appenderName + "] type: [" + typeName + "]");
|
try
|
{
|
IAppender appender = (IAppender)Activator.CreateInstance(SystemInfo.GetTypeFromString(typeName, true, true));
|
appender.Name = appenderName;
|
|
foreach (XmlNode currentNode in appenderElement.ChildNodes)
|
{
|
/* We're only interested in Elements */
|
if (currentNode.NodeType == XmlNodeType.Element)
|
{
|
XmlElement currentElement = (XmlElement)currentNode;
|
|
// Look for the appender ref tag
|
if (currentElement.LocalName == APPENDER_REF_TAG)
|
{
|
string refName = currentElement.GetAttribute(REF_ATTR);
|
|
IAppenderAttachable appenderContainer = appender as IAppenderAttachable;
|
if (appenderContainer != null)
|
{
|
LogLog.Debug(declaringType, "Attaching appender named [" + refName + "] to appender named [" + appender.Name + "].");
|
|
IAppender referencedAppender = FindAppenderByReference(currentElement);
|
if (referencedAppender != null)
|
{
|
appenderContainer.AddAppender(referencedAppender);
|
}
|
}
|
else
|
{
|
LogLog.Error(declaringType, "Requesting attachment of appender named ["+refName+ "] to appender named [" + appender.Name + "] which does not implement log4net.Core.IAppenderAttachable.");
|
}
|
}
|
else
|
{
|
// For all other tags we use standard set param method
|
SetParameter(currentElement, appender);
|
}
|
}
|
}
|
|
IOptionHandler optionHandler = appender as IOptionHandler;
|
if (optionHandler != null)
|
{
|
optionHandler.ActivateOptions();
|
}
|
|
LogLog.Debug(declaringType, "reated Appender [" + appenderName + "]");
|
return appender;
|
}
|
catch (Exception ex)
|
{
|
// Yes, it's ugly. But all exceptions point to the same problem: we can't create an Appender
|
|
LogLog.Error(declaringType, "Could not create Appender [" + appenderName + "] of type [" + typeName + "]. Reported error follows.", ex);
|
return null;
|
}
|
}
|
|
/// <summary>
|
/// Parses a logger element.
|
/// </summary>
|
/// <param name="loggerElement">The logger element.</param>
|
/// <remarks>
|
/// <para>
|
/// Parse an XML element that represents a logger.
|
/// </para>
|
/// </remarks>
|
protected void ParseLogger(XmlElement loggerElement)
|
{
|
// Create a new log4net.Logger object from the <logger> element.
|
string loggerName = loggerElement.GetAttribute(NAME_ATTR);
|
|
LogLog.Debug(declaringType, "Retrieving an instance of log4net.Repository.Logger for logger [" + loggerName + "].");
|
Logger log = m_hierarchy.GetLogger(loggerName) as Logger;
|
|
// Setting up a logger needs to be an atomic operation, in order
|
// to protect potential log operations while logger
|
// configuration is in progress.
|
lock(log)
|
{
|
bool additivity = OptionConverter.ToBoolean(loggerElement.GetAttribute(ADDITIVITY_ATTR), true);
|
|
LogLog.Debug(declaringType, "Setting [" + log.Name + "] additivity to [" + additivity + "].");
|
log.Additivity = additivity;
|
ParseChildrenOfLoggerElement(loggerElement, log, false);
|
}
|
}
|
|
/// <summary>
|
/// Parses the root logger element.
|
/// </summary>
|
/// <param name="rootElement">The root element.</param>
|
/// <remarks>
|
/// <para>
|
/// Parse an XML element that represents the root logger.
|
/// </para>
|
/// </remarks>
|
protected void ParseRoot(XmlElement rootElement)
|
{
|
Logger root = m_hierarchy.Root;
|
// logger configuration needs to be atomic
|
lock(root)
|
{
|
ParseChildrenOfLoggerElement(rootElement, root, true);
|
}
|
}
|
|
/// <summary>
|
/// Parses the children of a logger element.
|
/// </summary>
|
/// <param name="catElement">The category element.</param>
|
/// <param name="log">The logger instance.</param>
|
/// <param name="isRoot">Flag to indicate if the logger is the root logger.</param>
|
/// <remarks>
|
/// <para>
|
/// Parse the child elements of a <logger> element.
|
/// </para>
|
/// </remarks>
|
protected void ParseChildrenOfLoggerElement(XmlElement catElement, Logger log, bool isRoot)
|
{
|
// Remove all existing appenders from log. They will be
|
// reconstructed if need be.
|
log.RemoveAllAppenders();
|
|
foreach (XmlNode currentNode in catElement.ChildNodes)
|
{
|
if (currentNode.NodeType == XmlNodeType.Element)
|
{
|
XmlElement currentElement = (XmlElement) currentNode;
|
|
if (currentElement.LocalName == APPENDER_REF_TAG)
|
{
|
IAppender appender = FindAppenderByReference(currentElement);
|
string refName = currentElement.GetAttribute(REF_ATTR);
|
if (appender != null)
|
{
|
LogLog.Debug(declaringType, "Adding appender named [" + refName + "] to logger [" + log.Name + "].");
|
log.AddAppender(appender);
|
}
|
else
|
{
|
LogLog.Error(declaringType, "Appender named [" + refName + "] not found.");
|
}
|
}
|
else if (currentElement.LocalName == LEVEL_TAG || currentElement.LocalName == PRIORITY_TAG)
|
{
|
ParseLevel(currentElement, log, isRoot);
|
}
|
else
|
{
|
SetParameter(currentElement, log);
|
}
|
}
|
}
|
|
IOptionHandler optionHandler = log as IOptionHandler;
|
if (optionHandler != null)
|
{
|
optionHandler.ActivateOptions();
|
}
|
}
|
|
/// <summary>
|
/// Parses an object renderer.
|
/// </summary>
|
/// <param name="element">The renderer element.</param>
|
/// <remarks>
|
/// <para>
|
/// Parse an XML element that represents a renderer.
|
/// </para>
|
/// </remarks>
|
protected void ParseRenderer(XmlElement element)
|
{
|
string renderingClassName = element.GetAttribute(RENDERING_TYPE_ATTR);
|
string renderedClassName = element.GetAttribute(RENDERED_TYPE_ATTR);
|
|
LogLog.Debug(declaringType, "Rendering class [" + renderingClassName + "], Rendered class [" + renderedClassName + "].");
|
IObjectRenderer renderer = (IObjectRenderer)OptionConverter.InstantiateByClassName(renderingClassName, typeof(IObjectRenderer), null);
|
if (renderer == null)
|
{
|
LogLog.Error(declaringType, "Could not instantiate renderer [" + renderingClassName + "].");
|
return;
|
}
|
else
|
{
|
try
|
{
|
m_hierarchy.RendererMap.Put(SystemInfo.GetTypeFromString(renderedClassName, true, true), renderer);
|
}
|
catch(Exception e)
|
{
|
LogLog.Error(declaringType, "Could not find class [" + renderedClassName + "].", e);
|
}
|
}
|
}
|
|
/// <summary>
|
/// Parses a level element.
|
/// </summary>
|
/// <param name="element">The level element.</param>
|
/// <param name="log">The logger object to set the level on.</param>
|
/// <param name="isRoot">Flag to indicate if the logger is the root logger.</param>
|
/// <remarks>
|
/// <para>
|
/// Parse an XML element that represents a level.
|
/// </para>
|
/// </remarks>
|
protected void ParseLevel(XmlElement element, Logger log, bool isRoot)
|
{
|
string loggerName = log.Name;
|
if (isRoot)
|
{
|
loggerName = "root";
|
}
|
|
string levelStr = element.GetAttribute(VALUE_ATTR);
|
LogLog.Debug(declaringType, "Logger [" + loggerName + "] Level string is [" + levelStr + "].");
|
|
if (INHERITED == levelStr)
|
{
|
if (isRoot)
|
{
|
LogLog.Error(declaringType, "Root level cannot be inherited. Ignoring directive.");
|
}
|
else
|
{
|
LogLog.Debug(declaringType, "Logger [" + loggerName + "] level set to inherit from parent.");
|
log.Level = null;
|
}
|
}
|
else
|
{
|
log.Level = log.Hierarchy.LevelMap[levelStr];
|
if (log.Level == null)
|
{
|
LogLog.Error(declaringType, "Undefined level [" + levelStr + "] on Logger [" + loggerName + "].");
|
}
|
else
|
{
|
LogLog.Debug(declaringType, "Logger [" + loggerName + "] level set to [name=\"" + log.Level.Name + "\",value=" + log.Level.Value + "].");
|
}
|
}
|
}
|
|
/// <summary>
|
/// Sets a parameter on an object.
|
/// </summary>
|
/// <param name="element">The parameter element.</param>
|
/// <param name="target">The object to set the parameter on.</param>
|
/// <remarks>
|
/// The parameter name must correspond to a writable property
|
/// on the object. The value of the parameter is a string,
|
/// therefore this function will attempt to set a string
|
/// property first. If unable to set a string property it
|
/// will inspect the property and its argument type. It will
|
/// attempt to call a static method called <c>Parse</c> on the
|
/// type of the property. This method will take a single
|
/// string argument and return a value that can be used to
|
/// set the property.
|
/// </remarks>
|
protected void SetParameter(XmlElement element, object target)
|
{
|
// Get the property name
|
string name = element.GetAttribute(NAME_ATTR);
|
|
// If the name attribute does not exist then use the name of the element
|
if (element.LocalName != PARAM_TAG || name == null || name.Length == 0)
|
{
|
name = element.LocalName;
|
}
|
|
// Look for the property on the target object
|
Type targetType = target.GetType();
|
Type propertyType = null;
|
|
PropertyInfo propInfo = null;
|
MethodInfo methInfo = null;
|
|
// Try to find a writable property
|
propInfo = targetType.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.IgnoreCase);
|
if (propInfo != null && propInfo.CanWrite)
|
{
|
// found a property
|
propertyType = propInfo.PropertyType;
|
}
|
else
|
{
|
propInfo = null;
|
|
// look for a method with the signature Add<property>(type)
|
methInfo = FindMethodInfo(targetType, name);
|
|
if (methInfo != null)
|
{
|
propertyType = methInfo.GetParameters()[0].ParameterType;
|
}
|
}
|
|
if (propertyType == null)
|
{
|
LogLog.Error(declaringType, "XmlHierarchyConfigurator: Cannot find Property [" + name + "] to set object on [" + target.ToString() + "]");
|
}
|
else
|
{
|
string propertyValue = null;
|
|
if (element.GetAttributeNode(VALUE_ATTR) != null)
|
{
|
propertyValue = element.GetAttribute(VALUE_ATTR);
|
}
|
else if (element.HasChildNodes)
|
{
|
// Concatenate the CDATA and Text nodes together
|
foreach(XmlNode childNode in element.ChildNodes)
|
{
|
if (childNode.NodeType == XmlNodeType.CDATA || childNode.NodeType == XmlNodeType.Text)
|
{
|
if (propertyValue == null)
|
{
|
propertyValue = childNode.InnerText;
|
}
|
else
|
{
|
propertyValue += childNode.InnerText;
|
}
|
}
|
}
|
}
|
|
if(propertyValue != null)
|
{
|
#if !NETCF
|
try
|
{
|
// Expand environment variables in the string.
|
propertyValue = OptionConverter.SubstituteVariables(propertyValue, Environment.GetEnvironmentVariables());
|
}
|
catch(System.Security.SecurityException)
|
{
|
// This security exception will occur if the caller does not have
|
// unrestricted environment permission. If this occurs the expansion
|
// will be skipped with the following warning message.
|
LogLog.Debug(declaringType, "Security exception while trying to expand environment variables. Error Ignored. No Expansion.");
|
}
|
#endif
|
|
Type parsedObjectConversionTargetType = null;
|
|
// Check if a specific subtype is specified on the element using the 'type' attribute
|
string subTypeString = element.GetAttribute(TYPE_ATTR);
|
if (subTypeString != null && subTypeString.Length > 0)
|
{
|
// Read the explicit subtype
|
try
|
{
|
Type subType = SystemInfo.GetTypeFromString(subTypeString, true, true);
|
|
LogLog.Debug(declaringType, "Parameter ["+name+"] specified subtype ["+subType.FullName+"]");
|
|
if (!propertyType.IsAssignableFrom(subType))
|
{
|
// Check if there is an appropriate type converter
|
if (OptionConverter.CanConvertTypeTo(subType, propertyType))
|
{
|
// Must re-convert to the real property type
|
parsedObjectConversionTargetType = propertyType;
|
|
// Use sub type as intermediary type
|
propertyType = subType;
|
}
|
else
|
{
|
LogLog.Error(declaringType, "subtype ["+subType.FullName+"] set on ["+name+"] is not a subclass of property type ["+propertyType.FullName+"] and there are no acceptable type conversions.");
|
}
|
}
|
else
|
{
|
// The subtype specified is found and is actually a subtype of the property
|
// type, therefore we can switch to using this type.
|
propertyType = subType;
|
}
|
}
|
catch(Exception ex)
|
{
|
LogLog.Error(declaringType, "Failed to find type ["+subTypeString+"] set on ["+name+"]", ex);
|
}
|
}
|
|
// Now try to convert the string value to an acceptable type
|
// to pass to this property.
|
|
object convertedValue = ConvertStringTo(propertyType, propertyValue);
|
|
// Check if we need to do an additional conversion
|
if (convertedValue != null && parsedObjectConversionTargetType != null)
|
{
|
LogLog.Debug(declaringType, "Performing additional conversion of value from [" + convertedValue.GetType().Name + "] to [" + parsedObjectConversionTargetType.Name + "]");
|
convertedValue = OptionConverter.ConvertTypeTo(convertedValue, parsedObjectConversionTargetType);
|
}
|
|
if (convertedValue != null)
|
{
|
if (propInfo != null)
|
{
|
// Got a converted result
|
LogLog.Debug(declaringType, "Setting Property [" + propInfo.Name + "] to " + convertedValue.GetType().Name + " value [" + convertedValue.ToString() + "]");
|
|
try
|
{
|
// Pass to the property
|
propInfo.SetValue(target, convertedValue, BindingFlags.SetProperty, null, null, CultureInfo.InvariantCulture);
|
}
|
catch(TargetInvocationException targetInvocationEx)
|
{
|
LogLog.Error(declaringType, "Failed to set parameter [" + propInfo.Name + "] on object [" + target + "] using value [" + convertedValue + "]", targetInvocationEx.InnerException);
|
}
|
}
|
else if (methInfo != null)
|
{
|
// Got a converted result
|
LogLog.Debug(declaringType, "Setting Collection Property [" + methInfo.Name + "] to " + convertedValue.GetType().Name + " value [" + convertedValue.ToString() + "]");
|
|
try
|
{
|
// Pass to the property
|
methInfo.Invoke(target, BindingFlags.InvokeMethod, null, new object[] {convertedValue}, CultureInfo.InvariantCulture);
|
}
|
catch(TargetInvocationException targetInvocationEx)
|
{
|
LogLog.Error(declaringType, "Failed to set parameter [" + name + "] on object [" + target + "] using value [" + convertedValue + "]", targetInvocationEx.InnerException);
|
}
|
}
|
}
|
else
|
{
|
LogLog.Warn(declaringType, "Unable to set property [" + name + "] on object [" + target + "] using value [" + propertyValue + "] (with acceptable conversion types)");
|
}
|
}
|
else
|
{
|
object createdObject = null;
|
|
if (propertyType == typeof(string) && !HasAttributesOrElements(element))
|
{
|
// If the property is a string and the element is empty (no attributes
|
// or child elements) then we special case the object value to an empty string.
|
// This is necessary because while the String is a class it does not have
|
// a default constructor that creates an empty string, which is the behavior
|
// we are trying to simulate and would be expected from CreateObjectFromXml
|
createdObject = "";
|
}
|
else
|
{
|
// No value specified
|
Type defaultObjectType = null;
|
if (IsTypeConstructible(propertyType))
|
{
|
defaultObjectType = propertyType;
|
}
|
|
createdObject = CreateObjectFromXml(element, defaultObjectType, propertyType);
|
}
|
|
if (createdObject == null)
|
{
|
LogLog.Error(declaringType, "Failed to create object to set param: "+name);
|
}
|
else
|
{
|
if (propInfo != null)
|
{
|
// Got a converted result
|
LogLog.Debug(declaringType, "Setting Property ["+ propInfo.Name +"] to object ["+ createdObject +"]");
|
|
try
|
{
|
// Pass to the property
|
propInfo.SetValue(target, createdObject, BindingFlags.SetProperty, null, null, CultureInfo.InvariantCulture);
|
}
|
catch(TargetInvocationException targetInvocationEx)
|
{
|
LogLog.Error(declaringType, "Failed to set parameter [" + propInfo.Name + "] on object [" + target + "] using value [" + createdObject + "]", targetInvocationEx.InnerException);
|
}
|
}
|
else if (methInfo != null)
|
{
|
// Got a converted result
|
LogLog.Debug(declaringType, "Setting Collection Property ["+ methInfo.Name +"] to object ["+ createdObject +"]");
|
|
try
|
{
|
// Pass to the property
|
methInfo.Invoke(target, BindingFlags.InvokeMethod, null, new object[] {createdObject}, CultureInfo.InvariantCulture);
|
}
|
catch(TargetInvocationException targetInvocationEx)
|
{
|
LogLog.Error(declaringType, "Failed to set parameter [" + methInfo.Name + "] on object [" + target + "] using value [" + createdObject + "]", targetInvocationEx.InnerException);
|
}
|
}
|
}
|
}
|
}
|
}
|
|
/// <summary>
|
/// Test if an element has no attributes or child elements
|
/// </summary>
|
/// <param name="element">the element to inspect</param>
|
/// <returns><c>true</c> if the element has any attributes or child elements, <c>false</c> otherwise</returns>
|
private bool HasAttributesOrElements(XmlElement element)
|
{
|
foreach(XmlNode node in element.ChildNodes)
|
{
|
if (node.NodeType == XmlNodeType.Attribute || node.NodeType == XmlNodeType.Element)
|
{
|
return true;
|
}
|
}
|
return false;
|
}
|
|
/// <summary>
|
/// Test if a <see cref="Type"/> is constructible with <c>Activator.CreateInstance</c>.
|
/// </summary>
|
/// <param name="type">the type to inspect</param>
|
/// <returns><c>true</c> if the type is creatable using a default constructor, <c>false</c> otherwise</returns>
|
private static bool IsTypeConstructible(Type type)
|
{
|
if (type.IsClass && !type.IsAbstract)
|
{
|
ConstructorInfo defaultConstructor = type.GetConstructor(new Type[0]);
|
if (defaultConstructor != null && !defaultConstructor.IsAbstract && !defaultConstructor.IsPrivate)
|
{
|
return true;
|
}
|
}
|
return false;
|
}
|
|
/// <summary>
|
/// Look for a method on the <paramref name="targetType"/> that matches the <paramref name="name"/> supplied
|
/// </summary>
|
/// <param name="targetType">the type that has the method</param>
|
/// <param name="name">the name of the method</param>
|
/// <returns>the method info found</returns>
|
/// <remarks>
|
/// <para>
|
/// The method must be a public instance method on the <paramref name="targetType"/>.
|
/// The method must be named <paramref name="name"/> or "Add" followed by <paramref name="name"/>.
|
/// The method must take a single parameter.
|
/// </para>
|
/// </remarks>
|
private MethodInfo FindMethodInfo(Type targetType, string name)
|
{
|
string requiredMethodNameA = name;
|
string requiredMethodNameB = "Add" + name;
|
|
MethodInfo[] methods = targetType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
|
foreach(MethodInfo methInfo in methods)
|
{
|
if (!methInfo.IsStatic)
|
{
|
if (string.Compare(methInfo.Name, requiredMethodNameA, true, System.Globalization.CultureInfo.InvariantCulture) == 0 ||
|
string.Compare(methInfo.Name, requiredMethodNameB, true, System.Globalization.CultureInfo.InvariantCulture) == 0)
|
{
|
// Found matching method name
|
|
// Look for version with one arg only
|
System.Reflection.ParameterInfo[] methParams = methInfo.GetParameters();
|
if (methParams.Length == 1)
|
{
|
return methInfo;
|
}
|
}
|
}
|
}
|
return null;
|
}
|
|
/// <summary>
|
/// Converts a string value to a target type.
|
/// </summary>
|
/// <param name="type">The type of object to convert the string to.</param>
|
/// <param name="value">The string value to use as the value of the object.</param>
|
/// <returns>
|
/// <para>
|
/// An object of type <paramref name="type"/> with value <paramref name="value"/> or
|
/// <c>null</c> when the conversion could not be performed.
|
/// </para>
|
/// </returns>
|
protected object ConvertStringTo(Type type, string value)
|
{
|
// Hack to allow use of Level in property
|
if (typeof(Level) == type)
|
{
|
// Property wants a level
|
Level levelValue = m_hierarchy.LevelMap[value];
|
|
if (levelValue == null)
|
{
|
LogLog.Error(declaringType, "XmlHierarchyConfigurator: Unknown Level Specified ["+ value +"]");
|
}
|
|
return levelValue;
|
}
|
return OptionConverter.ConvertStringTo(type, value);
|
}
|
|
/// <summary>
|
/// Creates an object as specified in XML.
|
/// </summary>
|
/// <param name="element">The XML element that contains the definition of the object.</param>
|
/// <param name="defaultTargetType">The object type to use if not explicitly specified.</param>
|
/// <param name="typeConstraint">The type that the returned object must be or must inherit from.</param>
|
/// <returns>The object or <c>null</c></returns>
|
/// <remarks>
|
/// <para>
|
/// Parse an XML element and create an object instance based on the configuration
|
/// data.
|
/// </para>
|
/// <para>
|
/// The type of the instance may be specified in the XML. If not
|
/// specified then the <paramref name="defaultTargetType"/> is used
|
/// as the type. However the type is specified it must support the
|
/// <paramref name="typeConstraint"/> type.
|
/// </para>
|
/// </remarks>
|
protected object CreateObjectFromXml(XmlElement element, Type defaultTargetType, Type typeConstraint)
|
{
|
Type objectType = null;
|
|
// Get the object type
|
string objectTypeString = element.GetAttribute(TYPE_ATTR);
|
if (objectTypeString == null || objectTypeString.Length == 0)
|
{
|
if (defaultTargetType == null)
|
{
|
LogLog.Error(declaringType, "Object type not specified. Cannot create object of type ["+typeConstraint.FullName+"]. Missing Value or Type.");
|
return null;
|
}
|
else
|
{
|
// Use the default object type
|
objectType = defaultTargetType;
|
}
|
}
|
else
|
{
|
// Read the explicit object type
|
try
|
{
|
objectType = SystemInfo.GetTypeFromString(objectTypeString, true, true);
|
}
|
catch(Exception ex)
|
{
|
LogLog.Error(declaringType, "Failed to find type ["+objectTypeString+"]", ex);
|
return null;
|
}
|
}
|
|
bool requiresConversion = false;
|
|
// Got the object type. Check that it meets the typeConstraint
|
if (typeConstraint != null)
|
{
|
if (!typeConstraint.IsAssignableFrom(objectType))
|
{
|
// Check if there is an appropriate type converter
|
if (OptionConverter.CanConvertTypeTo(objectType, typeConstraint))
|
{
|
requiresConversion = true;
|
}
|
else
|
{
|
LogLog.Error(declaringType, "Object type ["+objectType.FullName+"] is not assignable to type ["+typeConstraint.FullName+"]. There are no acceptable type conversions.");
|
return null;
|
}
|
}
|
}
|
|
// Create using the default constructor
|
object createdObject = null;
|
try
|
{
|
createdObject = Activator.CreateInstance(objectType);
|
}
|
catch(Exception createInstanceEx)
|
{
|
LogLog.Error(declaringType, "XmlHierarchyConfigurator: Failed to construct object of type [" + objectType.FullName + "] Exception: "+createInstanceEx.ToString());
|
}
|
|
// Set any params on object
|
foreach (XmlNode currentNode in element.ChildNodes)
|
{
|
if (currentNode.NodeType == XmlNodeType.Element)
|
{
|
SetParameter((XmlElement)currentNode, createdObject);
|
}
|
}
|
|
// Check if we need to call ActivateOptions
|
IOptionHandler optionHandler = createdObject as IOptionHandler;
|
if (optionHandler != null)
|
{
|
optionHandler.ActivateOptions();
|
}
|
|
// Ok object should be initialized
|
|
if (requiresConversion)
|
{
|
// Convert the object type
|
return OptionConverter.ConvertTypeTo(createdObject, typeConstraint);
|
}
|
else
|
{
|
// The object is of the correct type
|
return createdObject;
|
}
|
}
|
|
#endregion Protected Instance Methods
|
|
#region Private Constants
|
|
// String constants used while parsing the XML data
|
private const string CONFIGURATION_TAG = "log4net";
|
private const string RENDERER_TAG = "renderer";
|
private const string APPENDER_TAG = "appender";
|
private const string APPENDER_REF_TAG = "appender-ref";
|
private const string PARAM_TAG = "param";
|
|
// TODO: Deprecate use of category tags
|
private const string CATEGORY_TAG = "category";
|
// TODO: Deprecate use of priority tag
|
private const string PRIORITY_TAG = "priority";
|
|
private const string LOGGER_TAG = "logger";
|
private const string NAME_ATTR = "name";
|
private const string TYPE_ATTR = "type";
|
private const string VALUE_ATTR = "value";
|
private const string ROOT_TAG = "root";
|
private const string LEVEL_TAG = "level";
|
private const string REF_ATTR = "ref";
|
private const string ADDITIVITY_ATTR = "additivity";
|
private const string THRESHOLD_ATTR = "threshold";
|
private const string CONFIG_DEBUG_ATTR = "configDebug";
|
private const string INTERNAL_DEBUG_ATTR = "debug";
|
private const string EMIT_INTERNAL_DEBUG_ATTR = "emitDebug";
|
private const string CONFIG_UPDATE_MODE_ATTR = "update";
|
private const string RENDERING_TYPE_ATTR = "renderingClass";
|
private const string RENDERED_TYPE_ATTR = "renderedClass";
|
|
// flag used on the level element
|
private const string INHERITED = "inherited";
|
|
#endregion Private Constants
|
|
#region Private Instance Fields
|
|
/// <summary>
|
/// key: appenderName, value: appender.
|
/// </summary>
|
private Hashtable m_appenderBag;
|
|
/// <summary>
|
/// The Hierarchy being configured.
|
/// </summary>
|
private readonly Hierarchy m_hierarchy;
|
|
#endregion Private Instance Fields
|
|
#region Private Static Fields
|
|
/// <summary>
|
/// The fully qualified type of the XmlHierarchyConfigurator class.
|
/// </summary>
|
/// <remarks>
|
/// Used by the internal logger to record the Type of the
|
/// log message.
|
/// </remarks>
|
private readonly static Type declaringType = typeof(XmlHierarchyConfigurator);
|
|
#endregion Private Static Fields
|
}
|
}
|