blob: f5021c10789efca2fa410fc5f57ac410d1eb8305 [file] [log] [blame]
#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
{
#if NETSTANDARD1_3
IAppender appender = (IAppender)Activator.CreateInstance(SystemInfo.GetTypeFromString(this.GetType().GetTypeInfo().Assembly, typeName, true, true));
#else
IAppender appender = (IAppender)Activator.CreateInstance(SystemInfo.GetTypeFromString(typeName, true, true));
#endif
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, "Created 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 &lt;logger&gt; 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
{
#if NETSTANDARD1_3
m_hierarchy.RendererMap.Put(SystemInfo.GetTypeFromString(this.GetType().GetTypeInfo().Assembly, renderedClassName, true, true), renderer);
#else
m_hierarchy.RendererMap.Put(SystemInfo.GetTypeFromString(renderedClassName, true, true), renderer);
#endif
}
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 || NETSTANDARD1_3) // NETSTANDARD1_3: System.Runtime.InteropServices.RuntimeInformation not available on desktop 4.6
try
{
// Expand environment variables in the string.
IDictionary environmentVariables = Environment.GetEnvironmentVariables();
if (HasCaseInsensitiveEnvironment) {
environmentVariables = CreateCaseInsensitiveWrapper(environmentVariables);
}
propertyValue = OptionConverter.SubstituteVariables(propertyValue, environmentVariables);
}
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
{
#if NETSTANDARD1_3
Type subType = SystemInfo.GetTypeFromString(this.GetType().GetTypeInfo().Assembly, subTypeString, true, true);
#else
Type subType = SystemInfo.GetTypeFromString(subTypeString, true, true);
#endif
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
#if NETSTANDARD1_3 // TODO BindingFlags is available for netstandard1.5
propInfo.SetValue(target, convertedValue, null);
#else
propInfo.SetValue(target, convertedValue, BindingFlags.SetProperty, null, null, CultureInfo.InvariantCulture);
#endif
}
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
#if NETSTANDARD1_3 // TODO BindingFlags is available for netstandard1.5
methInfo.Invoke(target, new[] { convertedValue });
#else
methInfo.Invoke(target, BindingFlags.InvokeMethod, null, new object[] {convertedValue}, CultureInfo.InvariantCulture);
#endif
}
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
#if NETSTANDARD1_3 // TODO BindingFlags is available for netstandard1.5
propInfo.SetValue(target, createdObject, null);
#else
propInfo.SetValue(target, createdObject, BindingFlags.SetProperty, null, null, CultureInfo.InvariantCulture);
#endif
}
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
#if NETSTANDARD1_3 // TODO BindingFlags is available for netstandard1.5
methInfo.Invoke(target, new[] { createdObject });
#else
methInfo.Invoke(target, BindingFlags.InvokeMethod, null, new object[] {createdObject}, CultureInfo.InvariantCulture);
#endif
}
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 NETSTANDARD1_3
TypeInfo typeInfo = type.GetTypeInfo();
if (typeInfo.IsClass && !typeInfo.IsAbstract)
#else
if (type.IsClass && !type.IsAbstract)
#endif
{
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)
{
string methodInfoName = methInfo.Name;
if (SystemInfo.EqualsIgnoringCase(methodInfoName, requiredMethodNameA) ||
SystemInfo.EqualsIgnoringCase(methodInfoName, requiredMethodNameB))
{
// 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
{
#if NETSTANDARD1_3
objectType = SystemInfo.GetTypeFromString(this.GetType().GetTypeInfo().Assembly, objectTypeString, true, true);
#else
objectType = SystemInfo.GetTypeFromString(objectTypeString, true, true);
#endif
}
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
#if !(NETCF || NETSTANDARD1_3) // NETSTANDARD1_3: System.Runtime.InteropServices.RuntimeInformation not available on desktop 4.6
private bool HasCaseInsensitiveEnvironment
{
get
{
#if NET_1_0 || NET_1_1 || CLI_1_0
// actually there is no guarantee, but we don't know better
return true;
#elif MONO_1_0
// see above
return false;
#else
PlatformID platform = Environment.OSVersion.Platform;
return platform != PlatformID.Unix && platform != PlatformID.MacOSX;
#endif
}
}
private IDictionary CreateCaseInsensitiveWrapper(IDictionary dict)
{
if (dict == null)
{
return dict;
}
Hashtable hash = SystemInfo.CreateCaseInsensitiveHashtable();
foreach (DictionaryEntry entry in dict) {
hash[entry.Key] = entry.Value;
}
return hash;
}
#endif
#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
}
}