#region Copyright & License
//
// Copyright 2001-2005 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion
// .NET Compact Framework 1.0 has no support for reading assembly attributes
// and uses the CompactRepositorySelector instead
#if !NETCF
using System;
using System.Collections;
using System.Configuration;
using System.Reflection;
using log4net.Config;
using log4net.Util;
using log4net.Repository;
namespace log4net.Core
{
///
/// The default implementation of the interface.
///
///
///
/// Uses attributes defined on the calling assembly to determine how to
/// configure the hierarchy for the repository.
///
///
/// Nicko Cadell
/// Gert Driesen
public class DefaultRepositorySelector : IRepositorySelector
{
#region Public Events
///
/// Event to notify that a logger repository has been created.
///
///
/// Event to notify that a logger repository has been created.
///
///
///
/// Event raised when a new repository is created.
/// The event source will be this selector. The event args will
/// be a which
/// holds the newly created .
///
///
public event LoggerRepositoryCreationEventHandler LoggerRepositoryCreatedEvent
{
add { m_loggerRepositoryCreatedEvent += value; }
remove { m_loggerRepositoryCreatedEvent -= value; }
}
#endregion Public Events
#region Public Instance Constructors
///
/// Creates a new repository selector.
///
/// The type of the repositories to create, must implement
///
///
/// Create an new repository selector.
/// The default type for repositories must be specified,
/// an appropriate value would be .
///
///
/// is .
/// does not implement .
public DefaultRepositorySelector(Type defaultRepositoryType)
{
if (defaultRepositoryType == null)
{
throw new ArgumentNullException("defaultRepositoryType");
}
// Check that the type is a repository
if (! (typeof(ILoggerRepository).IsAssignableFrom(defaultRepositoryType)) )
{
throw log4net.Util.SystemInfo.CreateArgumentOutOfRangeException("defaultRepositoryType", defaultRepositoryType, "Parameter: defaultRepositoryType, Value: [" + defaultRepositoryType + "] out of range. Argument must implement the ILoggerRepository interface");
}
m_defaultRepositoryType = defaultRepositoryType;
LogLog.Debug("DefaultRepositorySelector: defaultRepositoryType [" + m_defaultRepositoryType + "]");
}
#endregion Public Instance Constructors
#region Implementation of IRepositorySelector
///
/// Gets the for the specified assembly.
///
/// The assembly use to lookup the .
///
///
/// The type of the created and the repository
/// to create can be overridden by specifying the
/// attribute on the .
///
///
/// The default values are to use the
/// implementation of the interface and to use the
/// as the name of the repository.
///
///
/// The created will be automatically configured using
/// any attributes defined on
/// the .
///
///
/// The for the assembly
/// is .
public ILoggerRepository GetRepository(Assembly repositoryAssembly)
{
if (repositoryAssembly == null)
{
throw new ArgumentNullException("repositoryAssembly");
}
return CreateRepository(repositoryAssembly, m_defaultRepositoryType);
}
///
/// Gets the for the specified repository.
///
/// The repository to use to lookup the .
/// The for the specified repository.
///
///
/// Returns the named repository. If is null
/// a is thrown. If the repository
/// does not exist a is thrown.
///
///
/// Use to create a repository.
///
///
/// is .
/// does not exist.
public ILoggerRepository GetRepository(string repositoryName)
{
if (repositoryName == null)
{
throw new ArgumentNullException("repositoryName");
}
lock(this)
{
// Lookup in map
ILoggerRepository rep = m_name2repositoryMap[repositoryName] as ILoggerRepository;
if (rep == null)
{
throw new LogException("Repository [" + repositoryName + "] is NOT defined.");
}
return rep;
}
}
///
/// Create a new repository for the assembly specified
///
/// the assembly to use to create the repository to associate with the .
/// The type of repository to create, must implement .
/// The repository created.
///
///
/// The created will be associated with the repository
/// specified such that a call to with the
/// same assembly specified will return the same repository instance.
///
///
/// The type of the created and
/// the repository to create can be overridden by specifying the
/// attribute on the
/// . The default values are to use the
/// implementation of the
/// interface and to use the
/// as the name of the repository.
///
///
/// The created will be automatically
/// configured using any
/// attributes defined on the .
///
///
/// If a repository for the already exists
/// that repository will be returned. An error will not be raised and that
/// repository may be of a different type to that specified in .
/// Also the attribute on the
/// assembly may be used to override the repository type specified in
/// .
///
///
/// is .
public ILoggerRepository CreateRepository(Assembly repositoryAssembly, Type repositoryType)
{
return CreateRepository(repositoryAssembly, repositoryType, DefaultRepositoryName, true);
}
///
/// Creates a new repository for the assembly specified.
///
/// the assembly to use to create the repository to associate with the .
/// The type of repository to create, must implement .
/// The name to assign to the created repository
/// Set to true to read and apply the assembly attributes
/// The repository created.
///
///
/// The created will be associated with the repository
/// specified such that a call to with the
/// same assembly specified will return the same repository instance.
///
///
/// The type of the created and
/// the repository to create can be overridden by specifying the
/// attribute on the
/// . The default values are to use the
/// implementation of the
/// interface and to use the
/// as the name of the repository.
///
///
/// The created will be automatically
/// configured using any
/// attributes defined on the .
///
///
/// If a repository for the already exists
/// that repository will be returned. An error will not be raised and that
/// repository may be of a different type to that specified in .
/// Also the attribute on the
/// assembly may be used to override the repository type specified in
/// .
///
///
/// is .
public ILoggerRepository CreateRepository(Assembly repositoryAssembly, Type repositoryType, string repositoryName, bool readAssemblyAttributes)
{
if (repositoryAssembly == null)
{
throw new ArgumentNullException("repositoryAssembly");
}
// If the type is not set then use the default type
if (repositoryType == null)
{
repositoryType = m_defaultRepositoryType;
}
lock(this)
{
// Lookup in map
ILoggerRepository rep = m_assembly2repositoryMap[repositoryAssembly] as ILoggerRepository;
if (rep == null)
{
// Not found, therefore create
LogLog.Debug("DefaultRepositorySelector: Creating repository for assembly [" + repositoryAssembly + "]");
// Must specify defaults
string actualRepositoryName = repositoryName;
Type actualRepositoryType = repositoryType;
if (readAssemblyAttributes)
{
// Get the repository and type from the assembly attributes
GetInfoForAssembly(repositoryAssembly, ref actualRepositoryName, ref actualRepositoryType);
}
LogLog.Debug("DefaultRepositorySelector: Assembly [" + repositoryAssembly + "] using repository [" + actualRepositoryName + "] and repository type [" + actualRepositoryType + "]");
// Lookup the repository in the map (as this may already be defined)
rep = m_name2repositoryMap[actualRepositoryName] as ILoggerRepository;
if (rep == null)
{
// Create the repository
rep = CreateRepository(actualRepositoryName, actualRepositoryType);
if (readAssemblyAttributes)
{
try
{
// Look for aliasing attributes
LoadAliases(repositoryAssembly, rep);
// Look for plugins defined on the assembly
LoadPlugins(repositoryAssembly, rep);
// Configure the repository using the assembly attributes
ConfigureRepository(repositoryAssembly, rep);
}
catch (Exception ex)
{
LogLog.Error("DefaultRepositorySelector: Failed to configure repository [" + actualRepositoryName + "] from assembly attributes.", ex);
}
}
}
else
{
LogLog.Debug("DefaultRepositorySelector: repository [" + actualRepositoryName + "] already exists, using repository type [" + rep.GetType().FullName + "]");
if (readAssemblyAttributes)
{
try
{
// Look for plugins defined on the assembly
LoadPlugins(repositoryAssembly, rep);
}
catch (Exception ex)
{
LogLog.Error("DefaultRepositorySelector: Failed to configure repository [" + actualRepositoryName + "] from assembly attributes.", ex);
}
}
}
m_assembly2repositoryMap[repositoryAssembly] = rep;
}
return rep;
}
}
///
/// Creates a new repository for the specified repository.
///
/// The repository to associate with the .
/// The type of repository to create, must implement .
/// If this param is then the default repository type is used.
/// The new repository.
///
///
/// The created will be associated with the repository
/// specified such that a call to with the
/// same repository specified will return the same repository instance.
///
///
/// is .
/// already exists.
public ILoggerRepository CreateRepository(string repositoryName, Type repositoryType)
{
if (repositoryName == null)
{
throw new ArgumentNullException("repositoryName");
}
// If the type is not set then use the default type
if (repositoryType == null)
{
repositoryType = m_defaultRepositoryType;
}
lock(this)
{
ILoggerRepository rep = null;
// First check that the repository does not exist
rep = m_name2repositoryMap[repositoryName] as ILoggerRepository;
if (rep != null)
{
throw new LogException("Repository [" + repositoryName + "] is already defined. Repositories cannot be redefined.");
}
else
{
// Lookup an alias before trying to create the new repository
ILoggerRepository aliasedRepository = m_alias2repositoryMap[repositoryName] as ILoggerRepository;
if (aliasedRepository != null)
{
// Found an alias
// Check repository type
if (aliasedRepository.GetType() == repositoryType)
{
// Repository type is compatible
LogLog.Debug("DefaultRepositorySelector: Aliasing repository [" + repositoryName + "] to existing repository [" + aliasedRepository.Name + "]");
rep = aliasedRepository;
// Store in map
m_name2repositoryMap[repositoryName] = rep;
}
else
{
// Invalid repository type for alias
LogLog.Error("DefaultRepositorySelector: Failed to alias repository [" + repositoryName + "] to existing repository ["+aliasedRepository.Name+"]. Requested repository type ["+repositoryType.FullName+"] is not compatible with existing type [" + aliasedRepository.GetType().FullName + "]");
// We now drop through to create the repository without aliasing
}
}
// If we could not find an alias
if (rep == null)
{
LogLog.Debug("DefaultRepositorySelector: Creating repository [" + repositoryName + "] using type [" + repositoryType + "]");
// Call the no arg constructor for the repositoryType
rep = (ILoggerRepository)Activator.CreateInstance(repositoryType);
// Set the name of the repository
rep.Name = repositoryName;
// Store in map
m_name2repositoryMap[repositoryName] = rep;
// Notify listeners that the repository has been created
OnLoggerRepositoryCreatedEvent(rep);
}
}
return rep;
}
}
///
/// Test if a named repository exists
///
/// the named repository to check
/// true if the repository exists
///
///
/// Test if a named repository exists. Use
/// to create a new repository and to retrieve
/// a repository.
///
///
public bool ExistsRepository(string repositoryName)
{
lock(this)
{
return m_name2repositoryMap.ContainsKey(repositoryName);
}
}
///
/// Gets a list of objects
///
/// an array of all known objects
///
///
/// Gets an array of all of the repositories created by this selector.
///
///
public ILoggerRepository[] GetAllRepositories()
{
lock(this)
{
ICollection reps = m_name2repositoryMap.Values;
ILoggerRepository[] all = new ILoggerRepository[reps.Count];
reps.CopyTo(all, 0);
return all;
}
}
#endregion Implementation of IRepositorySelector
#region Public Instance Methods
///
/// Aliases a repository to an existing repository.
///
/// The repository to alias.
/// The repository that the repository is aliased to.
///
///
/// The repository specified will be aliased to the repository when created.
/// The repository must not already exist.
///
///
/// When the repository is created it must utilize the same repository type as
/// the repository it is aliased to, otherwise the aliasing will fail.
///
///
///
/// is .
/// -or-
/// is .
///
public void AliasRepository(string repositoryAlias, ILoggerRepository repositoryTarget)
{
if (repositoryAlias == null)
{
throw new ArgumentNullException("repositoryAlias");
}
if (repositoryTarget == null)
{
throw new ArgumentNullException("repositoryTarget");
}
lock(this)
{
// Check if the alias is already set
if (m_alias2repositoryMap.Contains(repositoryAlias))
{
// Check if this is a duplicate of the current alias
if (repositoryTarget != ((ILoggerRepository)m_alias2repositoryMap[repositoryAlias]))
{
// Cannot redefine existing alias
throw new InvalidOperationException("Repository [" + repositoryAlias + "] is already aliased to repository [" + ((ILoggerRepository)m_alias2repositoryMap[repositoryAlias]).Name + "]. Aliases cannot be redefined.");
}
}
// Check if the alias is already mapped to a repository
else if (m_name2repositoryMap.Contains(repositoryAlias))
{
// Check if this is a duplicate of the current mapping
if ( repositoryTarget != ((ILoggerRepository)m_name2repositoryMap[repositoryAlias]) )
{
// Cannot define alias for already mapped repository
throw new InvalidOperationException("Repository [" + repositoryAlias + "] already exists and cannot be aliased to repository [" + repositoryTarget.Name + "].");
}
}
else
{
// Set the alias
m_alias2repositoryMap[repositoryAlias] = repositoryTarget;
}
}
}
#endregion Public Instance Methods
#region Protected Instance Methods
///
/// Notifies the registered listeners that the repository has been created.
///
/// The repository that has been created.
///
///
/// Raises the event.
///
///
protected virtual void OnLoggerRepositoryCreatedEvent(ILoggerRepository repository)
{
LoggerRepositoryCreationEventHandler handler = m_loggerRepositoryCreatedEvent;
if (handler != null)
{
handler(this, new LoggerRepositoryCreationEventArgs(repository));
}
}
#endregion Protected Instance Methods
#region Private Instance Methods
///
/// Gets the repository name and repository type for the specified assembly.
///
/// The assembly that has a .
/// in/out param to hold the repository name to use for the assembly, caller should set this to the default value before calling.
/// in/out param to hold the type of the repository to create for the assembly, caller should set this to the default value before calling.
/// is .
private void GetInfoForAssembly(Assembly assembly, ref string repositoryName, ref Type repositoryType)
{
if (assembly == null)
{
throw new ArgumentNullException("assembly");
}
try
{
LogLog.Debug("DefaultRepositorySelector: Assembly [" + assembly.FullName + "] Loaded From [" + SystemInfo.AssemblyLocationInfo(assembly) + "]");
}
catch
{
// Ignore exception from debug call
}
try
{
// Look for the RepositoryAttribute on the assembly
object[] repositoryAttributes = Attribute.GetCustomAttributes(assembly, typeof(log4net.Config.RepositoryAttribute), false);
if (repositoryAttributes == null || repositoryAttributes.Length == 0)
{
// This is not a problem, but its nice to know what is going on.
LogLog.Debug("DefaultRepositorySelector: Assembly [" + assembly + "] does not have a RepositoryAttribute specified.");
}
else
{
if (repositoryAttributes.Length > 1)
{
LogLog.Error("DefaultRepositorySelector: Assembly [" + assembly + "] has multiple log4net.Config.RepositoryAttribute assembly attributes. Only using first occurrence.");
}
log4net.Config.RepositoryAttribute domAttr = repositoryAttributes[0] as log4net.Config.RepositoryAttribute;
if (domAttr == null)
{
LogLog.Error("DefaultRepositorySelector: Assembly [" + assembly + "] has a RepositoryAttribute but it does not!.");
}
else
{
// If the Name property is set then override the default
if (domAttr.Name != null)
{
repositoryName = domAttr.Name;
}
// If the RepositoryType property is set then override the default
if (domAttr.RepositoryType != null)
{
// Check that the type is a repository
if (typeof(ILoggerRepository).IsAssignableFrom(domAttr.RepositoryType))
{
repositoryType = domAttr.RepositoryType;
}
else
{
LogLog.Error("DefaultRepositorySelector: Repository Type [" + domAttr.RepositoryType + "] must implement the ILoggerRepository interface.");
}
}
}
}
}
catch (Exception ex)
{
LogLog.Error("DefaultRepositorySelector: Unhandled exception in GetInfoForAssembly", ex);
}
}
///
/// Configures the repository using information from the assembly.
///
/// The assembly containing
/// attributes which define the configuration for the repository.
/// The repository to configure.
///
/// is .
/// -or-
/// is .
///
private void ConfigureRepository(Assembly assembly, ILoggerRepository repository)
{
if (assembly == null)
{
throw new ArgumentNullException("assembly");
}
if (repository == null)
{
throw new ArgumentNullException("repository");
}
// Look for the Configurator attributes (e.g. XmlConfiguratorAttribute) on the assembly
object[] configAttributes = Attribute.GetCustomAttributes(assembly, typeof(log4net.Config.ConfiguratorAttribute), false);
if (configAttributes != null && configAttributes.Length > 0)
{
// Sort the ConfiguratorAttributes in priority order
Array.Sort(configAttributes);
// Delegate to the attribute the job of configuring the repository
foreach(log4net.Config.ConfiguratorAttribute configAttr in configAttributes)
{
if (configAttr != null)
{
try
{
configAttr.Configure(assembly, repository);
}
catch (Exception ex)
{
LogLog.Error("DefaultRepositorySelector: Exception calling ["+configAttr.GetType().FullName+"] .Configure method.", ex);
}
}
}
}
if (repository.Name == DefaultRepositoryName)
{
// Try to configure the default repository using an AppSettings specified config file
// Do this even if the repository has been configured (or claims to be), this allows overriding
// of the default config files etc, if that is required.
string repositoryConfigFile = SystemInfo.GetAppSetting("log4net.Config");
if (repositoryConfigFile != null && repositoryConfigFile.Length > 0)
{
string applicationBaseDirectory = null;
try
{
applicationBaseDirectory = SystemInfo.ApplicationBaseDirectory;
}
catch(Exception ex)
{
LogLog.Warn("DefaultRepositorySelector: Exception getting ApplicationBaseDirectory. appSettings log4net.Config path ["+repositoryConfigFile+"] will be treated as an absolute URI", ex);
}
// As we are not going to watch the config file it is easiest to just resolve it as a
// URI and pass that to the Configurator
Uri repositoryConfigUri = null;
try
{
if (applicationBaseDirectory != null)
{
// Resolve the config path relative to the application base directory URI
repositoryConfigUri = new Uri(new Uri(applicationBaseDirectory), repositoryConfigFile);
}
else
{
repositoryConfigUri = new Uri(repositoryConfigFile);
}
}
catch(Exception ex)
{
LogLog.Error("DefaultRepositorySelector: Exception while parsing log4net.Config file path ["+repositoryConfigFile+"]", ex);
}
if (repositoryConfigUri != null)
{
LogLog.Debug("DefaultRepositorySelector: Loading configuration for default repository from AppSettings specified Config URI ["+repositoryConfigUri.ToString()+"]");
try
{
// TODO: Support other types of configurator
XmlConfigurator.Configure(repository, repositoryConfigUri);
}
catch (Exception ex)
{
LogLog.Error("DefaultRepositorySelector: Exception calling XmlConfigurator.Configure method with ConfigUri ["+repositoryConfigUri+"]", ex);
}
}
}
}
}
///
/// Loads the attribute defined plugins on the assembly.
///
/// The assembly that contains the attributes.
/// The repository to add the plugins to.
///
/// is .
/// -or-
/// is .
///
private void LoadPlugins(Assembly assembly, ILoggerRepository repository)
{
if (assembly == null)
{
throw new ArgumentNullException("assembly");
}
if (repository == null)
{
throw new ArgumentNullException("repository");
}
// Look for the PluginAttribute on the assembly
object[] configAttributes = Attribute.GetCustomAttributes(assembly, typeof(log4net.Config.PluginAttribute), false);
if (configAttributes != null && configAttributes.Length > 0)
{
foreach(log4net.Plugin.IPluginFactory configAttr in configAttributes)
{
try
{
// Create the plugin and add it to the repository
repository.PluginMap.Add(configAttr.CreatePlugin());
}
catch(Exception ex)
{
LogLog.Error("DefaultRepositorySelector: Failed to create plugin. Attribute [" + configAttr.ToString() + "]", ex);
}
}
}
}
///
/// Loads the attribute defined aliases on the assembly.
///
/// The assembly that contains the attributes.
/// The repository to alias to.
///
/// is .
/// -or-
/// is .
///
private void LoadAliases(Assembly assembly, ILoggerRepository repository)
{
if (assembly == null)
{
throw new ArgumentNullException("assembly");
}
if (repository == null)
{
throw new ArgumentNullException("repository");
}
// Look for the AliasRepositoryAttribute on the assembly
object[] configAttributes = Attribute.GetCustomAttributes(assembly, typeof(log4net.Config.AliasRepositoryAttribute), false);
if (configAttributes != null && configAttributes.Length > 0)
{
foreach(log4net.Config.AliasRepositoryAttribute configAttr in configAttributes)
{
try
{
AliasRepository(configAttr.Name, repository);
}
catch(Exception ex)
{
LogLog.Error("DefaultRepositorySelector: Failed to alias repository [" + configAttr.Name + "]", ex);
}
}
}
}
#endregion Private Instance Methods
#region Private Static Fields
private const string DefaultRepositoryName = "log4net-default-repository";
#endregion Private Static Fields
#region Private Instance Fields
private readonly Hashtable m_name2repositoryMap = new Hashtable();
private readonly Hashtable m_assembly2repositoryMap = new Hashtable();
private readonly Hashtable m_alias2repositoryMap = new Hashtable();
private readonly Type m_defaultRepositoryType;
private event LoggerRepositoryCreationEventHandler m_loggerRepositoryCreatedEvent;
#endregion Private Instance Fields
}
}
#endif // !NETCF