| #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 <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 |
| { |
| #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 static readonly Type declaringType = typeof(XmlHierarchyConfigurator); |
| |
| #endregion Private Static Fields |
| } |
| } |