diff --git a/src/Orchard.Azure/AzureFileSystem.cs b/src/Orchard.Azure/AzureFileSystem.cs
index b8170b117..2e293bef3 100644
--- a/src/Orchard.Azure/AzureFileSystem.cs
+++ b/src/Orchard.Azure/AzureFileSystem.cs
@@ -223,8 +223,8 @@ namespace Orchard.Azure {
EnsurePathIsRelative(path);
using ( new HttpContextWeaver() ) {
- Container.EnsureBlobExists(path);
- var blob = Container.GetBlockBlobReference(String.Concat(_root, path));
+ Container.EnsureBlobExists(Combine(_root, path));
+ var blob = Container.GetBlockBlobReference(Combine(_root, path));
blob.Delete();
}
}
diff --git a/src/Orchard.Web/Config/log4net.config b/src/Orchard.Web/Config/log4net.config
index 3dc7e72bd..999e11599 100644
--- a/src/Orchard.Web/Config/log4net.config
+++ b/src/Orchard.Web/Config/log4net.config
@@ -54,7 +54,7 @@
-
+
@@ -73,7 +73,7 @@
-
+
diff --git a/src/Orchard.Web/Core/Shapes/Views/MenuItem.cshtml b/src/Orchard.Web/Core/Shapes/Views/MenuItem.cshtml
index 7b96d871d..5b0d9b766 100644
--- a/src/Orchard.Web/Core/Shapes/Views/MenuItem.cshtml
+++ b/src/Orchard.Web/Core/Shapes/Views/MenuItem.cshtml
@@ -6,8 +6,10 @@
if (!HasText(Model.Text)) {
@DisplayChildren(Model)
} else {
- if (Model.Href.TrimEnd('/').ToUpperInvariant() == Request.Path.TrimEnd('/').ToUpperInvariant()) {
- Model.Classes.Add("current");
+ string requestUrl = Request.Path.Replace(Request.ApplicationPath, string.Empty).TrimEnd('/').ToUpperInvariant();
+ string modelUrl = Model.Href.Replace(Request.ApplicationPath, string.Empty).TrimEnd('/').ToUpperInvariant();
+ if ((!string.IsNullOrEmpty(modelUrl) && requestUrl.StartsWith(modelUrl)) || requestUrl == modelUrl) {
+ Model.Classes.Add("current");
}
var tag = Tag(Model, "li");
@tag.StartElement
diff --git a/src/Orchard.Web/Modules/Orchard.Users/Controllers/AccountController.cs b/src/Orchard.Web/Modules/Orchard.Users/Controllers/AccountController.cs
index 018ffdbbe..945c86768 100644
--- a/src/Orchard.Web/Modules/Orchard.Users/Controllers/AccountController.cs
+++ b/src/Orchard.Web/Modules/Orchard.Users/Controllers/AccountController.cs
@@ -14,6 +14,8 @@ using Orchard.Users.Services;
using Orchard.ContentManagement;
using Orchard.Users.Models;
using Orchard.UI.Notify;
+using Orchard.Users.Events;
+using System.Collections.Generic;
namespace Orchard.Users.Controllers {
[HandleError, Themed]
@@ -22,17 +24,19 @@ namespace Orchard.Users.Controllers {
private readonly IMembershipService _membershipService;
private readonly IUserService _userService;
private readonly IOrchardServices _orchardServices;
-
+ private readonly IEnumerable _userEventHandlers;
public AccountController(
IAuthenticationService authenticationService,
IMembershipService membershipService,
IUserService userService,
- IOrchardServices orchardServices) {
+ IOrchardServices orchardServices,
+ IEnumerable userEventHandlers) {
_authenticationService = authenticationService;
_membershipService = membershipService;
_userService = userService;
_orchardServices = orchardServices;
+ _userEventHandlers = userEventHandlers;
Logger = NullLogger.Instance;
T = NullLocalizer.Instance;
}
@@ -51,8 +55,13 @@ namespace Orchard.Users.Controllers {
}
//TODO: (erikpo) Add a setting for whether or not to log access denieds since these can fill up a database pretty fast from bots on a high traffic site
+ //Suggestion: Could instead use the new AccessDenined IUserEventHandler method and let modules decide if they want to log this event?
Logger.Information("Access denied to user #{0} '{1}' on {2}", currentUser.Id, currentUser.UserName, returnUrl);
+ foreach (var userEventHandler in _userEventHandlers) {
+ userEventHandler.AccessDenied(currentUser);
+ }
+
return View();
}
@@ -75,13 +84,20 @@ namespace Orchard.Users.Controllers {
}
_authenticationService.SignIn(user, false);
+ foreach (var userEventHandler in _userEventHandlers) {
+ userEventHandler.LoggedIn(user);
+ }
return this.RedirectLocal(returnUrl);
}
public ActionResult LogOff(string returnUrl) {
+ var wasLoggedInUser = _authenticationService.GetAuthenticatedUser();
_authenticationService.SignOut();
-
+ if (wasLoggedInUser != null)
+ foreach (var userEventHandler in _userEventHandlers) {
+ userEventHandler.LoggedOut(wasLoggedInUser);
+ }
return this.RedirectLocal(returnUrl);
}
@@ -116,12 +132,16 @@ namespace Orchard.Users.Controllers {
if (ValidateRegistration(userName, email, password, confirmPassword)) {
// Attempt to register the user
+ // No need to report this to IUserEventHandler because _membershipService does that for us
var user = _membershipService.CreateUser(new CreateUserParams(userName, password, email, null, null, false));
if (user != null) {
if ( user.As().EmailStatus == UserStatus.Pending ) {
_userService.SendChallengeEmail(user.As(), nonce => Url.AbsoluteAction(() => Url.Action("ChallengeEmail", "Account", new { Area = "Orchard.Users", nonce = nonce })));
+ foreach (var userEventHandler in _userEventHandlers) {
+ userEventHandler.SentChallengeEmail(user);
+ }
return RedirectToAction("ChallengeEmailSent");
}
@@ -194,6 +214,9 @@ namespace Orchard.Users.Controllers {
if ( validated != null ) {
_membershipService.SetPassword(validated, newPassword);
+ foreach (var userEventHandler in _userEventHandlers) {
+ userEventHandler.ChangedPassword(validated);
+ }
return RedirectToAction("ChangePasswordSuccess");
}
@@ -265,6 +288,10 @@ namespace Orchard.Users.Controllers {
var user = _userService.ValidateChallenge(nonce);
if ( user != null ) {
+ foreach (var userEventHandler in _userEventHandlers) {
+ userEventHandler.ConfirmedEmail(user);
+ }
+
return RedirectToAction("ChallengeEmailSuccess");
}
diff --git a/src/Orchard.Web/Modules/Orchard.Users/Events/IUserEventHandler.cs b/src/Orchard.Web/Modules/Orchard.Users/Events/IUserEventHandler.cs
index 79b0962ed..d6bbbb634 100644
--- a/src/Orchard.Web/Modules/Orchard.Users/Events/IUserEventHandler.cs
+++ b/src/Orchard.Web/Modules/Orchard.Users/Events/IUserEventHandler.cs
@@ -1,4 +1,5 @@
using Orchard.Events;
+using Orchard.Security;
namespace Orchard.Users.Events {
public interface IUserEventHandler : IEventHandler {
@@ -8,9 +9,39 @@ namespace Orchard.Users.Events {
void Creating(UserContext context);
///
- /// Called once a user has been created
+ /// Called after a user has been created
///
void Created(UserContext context);
+
+ ///
+ /// Called after a user has logged in
+ ///
+ void LoggedIn(IUser user);
+
+ ///
+ /// Called when a user explicitly logs out (as opposed to one whos session cookie simply expires)
+ ///
+ void LoggedOut(IUser user);
+
+ ///
+ /// Called when access is denied to a user
+ ///
+ void AccessDenied(IUser user);
+
+ ///
+ /// Called after a user has changed password
+ ///
+ void ChangedPassword(IUser user);
+
+ ///
+ /// Called after a user has confirmed their email address
+ ///
+ void SentChallengeEmail(IUser user);
+
+ ///
+ /// Called after a user has confirmed their email address
+ ///
+ void ConfirmedEmail(IUser user);
}
}
diff --git a/src/Orchard/Data/SessionConfigurationCache.cs b/src/Orchard/Data/SessionConfigurationCache.cs
index 2bc3a3a7c..00c529904 100644
--- a/src/Orchard/Data/SessionConfigurationCache.cs
+++ b/src/Orchard/Data/SessionConfigurationCache.cs
@@ -3,6 +3,7 @@ using System.Linq;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using NHibernate.Cfg;
+using Orchard.Environment;
using Orchard.Environment.Configuration;
using Orchard.Environment.ShellBuilders.Models;
using Orchard.FileSystems.AppData;
@@ -14,11 +15,13 @@ namespace Orchard.Data {
private readonly ShellSettings _shellSettings;
private readonly ShellBlueprint _shellBlueprint;
private readonly IAppDataFolder _appDataFolder;
+ private readonly IHostEnvironment _hostEnvironment;
- public SessionConfigurationCache(ShellSettings shellSettings, ShellBlueprint shellBlueprint, IAppDataFolder appDataFolder) {
+ public SessionConfigurationCache(ShellSettings shellSettings, ShellBlueprint shellBlueprint, IAppDataFolder appDataFolder, IHostEnvironment hostEnvironment) {
_shellSettings = shellSettings;
_shellBlueprint = shellBlueprint;
_appDataFolder = appDataFolder;
+ _hostEnvironment = hostEnvironment;
Logger = NullLogger.Instance;
}
@@ -51,6 +54,9 @@ namespace Orchard.Data {
}
private void StoreConfiguration(ConfigurationCache cache) {
+ if (!_hostEnvironment.IsFullTrust)
+ return;
+
var pathName = GetPathName(_shellSettings.Name);
try {
@@ -70,6 +76,9 @@ namespace Orchard.Data {
}
private ConfigurationCache ReadConfiguration(string hash) {
+ if (!_hostEnvironment.IsFullTrust)
+ return null;
+
var pathName = GetPathName(_shellSettings.Name);
if (!_appDataFolder.FileExists(pathName))
diff --git a/src/Orchard/Logging/LoggingModule.cs b/src/Orchard/Logging/LoggingModule.cs
index 559458f19..c1e3f2228 100644
--- a/src/Orchard/Logging/LoggingModule.cs
+++ b/src/Orchard/Logging/LoggingModule.cs
@@ -20,7 +20,7 @@ namespace Orchard.Logging {
protected override void Load(ContainerBuilder moduleBuilder) {
// by default, use Orchard's logger that delegates to Castle's logger factory
moduleBuilder.RegisterType().As().InstancePerLifetimeScope();
- moduleBuilder.RegisterType().As().InstancePerLifetimeScope();
+ moduleBuilder.RegisterType().As().InstancePerLifetimeScope();
// call CreateLogger in response to the request for an ILogger implementation
moduleBuilder.Register(CreateLogger).As().InstancePerDependency();
diff --git a/src/Orchard/Logging/OrchardLog4netFactory.cs b/src/Orchard/Logging/OrchardLog4netFactory.cs
new file mode 100644
index 000000000..4a5dd45d4
--- /dev/null
+++ b/src/Orchard/Logging/OrchardLog4netFactory.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Configuration;
+using System.IO;
+using System.Web;
+using System.Web.Hosting;
+
+using Castle.Core.Logging;
+
+using log4net;
+
+namespace Orchard.Logging {
+ public class OrchardLog4netFactory : AbstractLoggerFactory {
+ public OrchardLog4netFactory()
+ : this(ConfigurationManager.AppSettings["log4net.Config"]) {
+ }
+
+ public OrchardLog4netFactory(String configFilename) {
+ if (!String.IsNullOrWhiteSpace(configFilename)) {
+ var mappedConfigFilename = configFilename;
+
+ if (HostingEnvironment.IsHosted) {
+ if (!VirtualPathUtility.IsAppRelative(mappedConfigFilename)) {
+ if (!mappedConfigFilename.StartsWith("/")) {
+ mappedConfigFilename = "~/" + mappedConfigFilename;
+ }
+ else {
+ mappedConfigFilename = "~" + mappedConfigFilename;
+ }
+ }
+
+ mappedConfigFilename = HostingEnvironment.MapPath(mappedConfigFilename);
+ }
+
+ OrchardXmlConfigurator.Configure(mappedConfigFilename);
+ }
+ }
+
+ ///
+ /// Configures log4net with a stream containing XML.
+ ///
+ ///
+ public OrchardLog4netFactory(Stream config) {
+ OrchardXmlConfigurator.Configure(config);
+ }
+
+ public override Castle.Core.Logging.ILogger Create(string name, LoggerLevel level) {
+ throw new NotSupportedException("Logger levels cannot be set at runtime. Please review your configuration file.");
+ }
+
+ public override Castle.Core.Logging.ILogger Create(string name) {
+ return new OrchardLog4netLogger(LogManager.GetLogger(name), this);
+ }
+ }
+}
diff --git a/src/Orchard/Logging/OrchardLog4netLogger.cs b/src/Orchard/Logging/OrchardLog4netLogger.cs
new file mode 100644
index 000000000..d204495a8
--- /dev/null
+++ b/src/Orchard/Logging/OrchardLog4netLogger.cs
@@ -0,0 +1,333 @@
+using System;
+using System.Globalization;
+
+using log4net;
+using log4net.Core;
+using log4net.Util;
+
+using Logger = Castle.Core.Logging.ILogger;
+
+namespace Orchard.Logging {
+ [Serializable]
+ public class OrchardLog4netLogger : MarshalByRefObject, Logger {
+ private static readonly Type declaringType = typeof(OrchardLog4netLogger);
+
+ public OrchardLog4netLogger(log4net.Core.ILogger logger, OrchardLog4netFactory factory) {
+ Logger = logger;
+ Factory = factory;
+ }
+
+ internal OrchardLog4netLogger() {
+ }
+
+ internal OrchardLog4netLogger(ILog log, OrchardLog4netFactory factory)
+ : this(log.Logger, factory) {
+ }
+
+ public bool IsDebugEnabled {
+ get { return Logger.IsEnabledFor(Level.Debug); }
+ }
+
+ public bool IsErrorEnabled {
+ get { return Logger.IsEnabledFor(Level.Error); }
+ }
+
+ public bool IsFatalEnabled {
+ get { return Logger.IsEnabledFor(Level.Fatal); }
+ }
+
+ public bool IsInfoEnabled {
+ get { return Logger.IsEnabledFor(Level.Info); }
+ }
+
+ public bool IsWarnEnabled {
+ get { return Logger.IsEnabledFor(Level.Warn); }
+ }
+
+ protected internal OrchardLog4netFactory Factory { get; set; }
+
+ protected internal log4net.Core.ILogger Logger { get; set; }
+
+ public override string ToString() {
+ return Logger.ToString();
+ }
+
+ public virtual Logger CreateChildLogger(String name) {
+ return Factory.Create(Logger.Name + "." + name);
+ }
+
+ public void Debug(String message) {
+ if (IsDebugEnabled) {
+ Logger.Log(declaringType, Level.Debug, message, null);
+ }
+ }
+
+ public void Debug(Func messageFactory) {
+ if (IsDebugEnabled) {
+ Logger.Log(declaringType, Level.Debug, messageFactory.Invoke(), null);
+ }
+ }
+
+ public void Debug(String message, Exception exception) {
+ if (IsDebugEnabled) {
+ Logger.Log(declaringType, Level.Debug, message, exception);
+ }
+ }
+
+ public void DebugFormat(String format, params Object[] args) {
+ if (IsDebugEnabled) {
+ Logger.Log(declaringType, Level.Debug, new SystemStringFormat(CultureInfo.InvariantCulture, format, args), null);
+ }
+ }
+
+ public void DebugFormat(Exception exception, String format, params Object[] args) {
+ if (IsDebugEnabled) {
+ Logger.Log(declaringType, Level.Debug, new SystemStringFormat(CultureInfo.InvariantCulture, format, args), exception);
+ }
+ }
+
+ public void DebugFormat(IFormatProvider formatProvider, String format, params Object[] args) {
+ if (IsDebugEnabled) {
+ Logger.Log(declaringType, Level.Debug, new SystemStringFormat(formatProvider, format, args), null);
+ }
+ }
+
+ public void DebugFormat(Exception exception, IFormatProvider formatProvider, String format, params Object[] args) {
+ if (IsDebugEnabled) {
+ Logger.Log(declaringType, Level.Debug, new SystemStringFormat(formatProvider, format, args), exception);
+ }
+ }
+
+ public void Error(String message) {
+ if (IsErrorEnabled) {
+ Logger.Log(declaringType, Level.Error, message, null);
+ }
+ }
+
+ public void Error(Func messageFactory) {
+ if (IsErrorEnabled) {
+ Logger.Log(declaringType, Level.Error, messageFactory.Invoke(), null);
+ }
+ }
+
+ public void Error(String message, Exception exception) {
+ if (IsErrorEnabled) {
+ Logger.Log(declaringType, Level.Error, message, exception);
+ }
+ }
+
+ public void ErrorFormat(String format, params Object[] args) {
+ if (IsErrorEnabled) {
+ Logger.Log(declaringType, Level.Error, new SystemStringFormat(CultureInfo.InvariantCulture, format, args), null);
+ }
+ }
+
+ public void ErrorFormat(Exception exception, String format, params Object[] args) {
+ if (IsErrorEnabled) {
+ Logger.Log(declaringType, Level.Error, new SystemStringFormat(CultureInfo.InvariantCulture, format, args), exception);
+ }
+ }
+
+ public void ErrorFormat(IFormatProvider formatProvider, String format, params Object[] args) {
+ if (IsErrorEnabled) {
+ Logger.Log(declaringType, Level.Error, new SystemStringFormat(formatProvider, format, args), null);
+ }
+ }
+
+ public void ErrorFormat(Exception exception, IFormatProvider formatProvider, String format, params Object[] args) {
+ if (IsErrorEnabled) {
+ Logger.Log(declaringType, Level.Error, new SystemStringFormat(formatProvider, format, args), exception);
+ }
+ }
+
+ public void Fatal(String message) {
+ if (IsFatalEnabled) {
+ Logger.Log(declaringType, Level.Fatal, message, null);
+ }
+ }
+
+ public void Fatal(Func messageFactory) {
+ if (IsFatalEnabled) {
+ Logger.Log(declaringType, Level.Fatal, messageFactory.Invoke(), null);
+ }
+ }
+
+ public void Fatal(String message, Exception exception) {
+ if (IsFatalEnabled) {
+ Logger.Log(declaringType, Level.Fatal, message, exception);
+ }
+ }
+
+ public void FatalFormat(String format, params Object[] args) {
+ if (IsFatalEnabled) {
+ Logger.Log(declaringType, Level.Fatal, new SystemStringFormat(CultureInfo.InvariantCulture, format, args), null);
+ }
+ }
+
+ public void FatalFormat(Exception exception, String format, params Object[] args) {
+ if (IsFatalEnabled) {
+ Logger.Log(declaringType, Level.Fatal, new SystemStringFormat(CultureInfo.InvariantCulture, format, args), exception);
+ }
+ }
+
+ public void FatalFormat(IFormatProvider formatProvider, String format, params Object[] args) {
+ if (IsFatalEnabled) {
+ Logger.Log(declaringType, Level.Fatal, new SystemStringFormat(formatProvider, format, args), null);
+ }
+ }
+
+ public void FatalFormat(Exception exception, IFormatProvider formatProvider, String format, params Object[] args) {
+ if (IsFatalEnabled) {
+ Logger.Log(declaringType, Level.Fatal, new SystemStringFormat(formatProvider, format, args), exception);
+ }
+ }
+
+ public void Info(String message) {
+ if (IsInfoEnabled) {
+ Logger.Log(declaringType, Level.Info, message, null);
+ }
+ }
+
+ public void Info(Func messageFactory) {
+ if (IsInfoEnabled) {
+ Logger.Log(declaringType, Level.Info, messageFactory.Invoke(), null);
+ }
+ }
+
+ public void Info(String message, Exception exception) {
+ if (IsInfoEnabled) {
+ Logger.Log(declaringType, Level.Info, message, exception);
+ }
+ }
+
+ public void InfoFormat(String format, params Object[] args) {
+ if (IsInfoEnabled) {
+ Logger.Log(declaringType, Level.Info, new SystemStringFormat(CultureInfo.InvariantCulture, format, args), null);
+ }
+ }
+
+ public void InfoFormat(Exception exception, String format, params Object[] args) {
+ if (IsInfoEnabled) {
+ Logger.Log(declaringType, Level.Info, new SystemStringFormat(CultureInfo.InvariantCulture, format, args), exception);
+ }
+ }
+
+ public void InfoFormat(IFormatProvider formatProvider, String format, params Object[] args) {
+ if (IsInfoEnabled) {
+ Logger.Log(declaringType, Level.Info, new SystemStringFormat(formatProvider, format, args), null);
+ }
+ }
+
+ public void InfoFormat(Exception exception, IFormatProvider formatProvider, String format, params Object[] args) {
+ if (IsInfoEnabled) {
+ Logger.Log(declaringType, Level.Info, new SystemStringFormat(formatProvider, format, args), exception);
+ }
+ }
+
+ public void Warn(String message) {
+ if (IsWarnEnabled) {
+ Logger.Log(declaringType, Level.Warn, message, null);
+ }
+ }
+
+ public void Warn(Func messageFactory) {
+ if (IsWarnEnabled) {
+ Logger.Log(declaringType, Level.Warn, messageFactory.Invoke(), null);
+ }
+ }
+
+ public void Warn(String message, Exception exception) {
+ if (IsWarnEnabled) {
+ Logger.Log(declaringType, Level.Warn, message, exception);
+ }
+ }
+
+ public void WarnFormat(String format, params Object[] args) {
+ if (IsWarnEnabled) {
+ Logger.Log(declaringType, Level.Warn, new SystemStringFormat(CultureInfo.InvariantCulture, format, args), null);
+ }
+ }
+
+ public void WarnFormat(Exception exception, String format, params Object[] args) {
+ if (IsWarnEnabled) {
+ Logger.Log(declaringType, Level.Warn, new SystemStringFormat(CultureInfo.InvariantCulture, format, args), exception);
+ }
+ }
+
+ public void WarnFormat(IFormatProvider formatProvider, String format, params Object[] args) {
+ if (IsWarnEnabled) {
+ Logger.Log(declaringType, Level.Warn, new SystemStringFormat(formatProvider, format, args), null);
+ }
+ }
+
+ public void WarnFormat(Exception exception, IFormatProvider formatProvider, String format, params Object[] args) {
+ if (IsWarnEnabled) {
+ Logger.Log(declaringType, Level.Warn, new SystemStringFormat(formatProvider, format, args), exception);
+ }
+ }
+
+ [Obsolete("Use IsFatalEnabled instead")]
+ public bool IsFatalErrorEnabled {
+ get {
+ return Logger.IsEnabledFor(Level.Fatal);
+ }
+ }
+
+ [Obsolete("Use DebugFormat instead")]
+ public void Debug(string format, params object[] args) {
+ if (IsDebugEnabled) {
+ Logger.Log(declaringType, Level.Debug, string.Format(format, args), null);
+ }
+ }
+
+ [Obsolete("Use ErrorFormat instead")]
+ public void Error(string format, params object[] args) {
+ if (IsErrorEnabled) {
+ Logger.Log(declaringType, Level.Error, string.Format(format, args), null);
+ }
+ }
+
+ [Obsolete("Use FatalFormat instead")]
+ public void Fatal(string format, params object[] args) {
+ if (IsFatalEnabled) {
+ Logger.Log(declaringType, Level.Fatal, string.Format(format, args), null);
+ }
+ }
+
+ [Obsolete("Use Fatal instead")]
+ public void FatalError(string message) {
+ if (IsFatalErrorEnabled) {
+ Logger.Log(declaringType, Level.Fatal, message, null);
+ }
+ }
+
+ [Obsolete("Use FatalFormat instead")]
+ public void FatalError(string format, params object[] args) {
+ if (IsFatalErrorEnabled) {
+ Logger.Log(declaringType, Level.Fatal, string.Format(format, args), null);
+ }
+ }
+
+ [Obsolete("Use Fatal instead")]
+ public void FatalError(string message, Exception exception) {
+ if (IsFatalErrorEnabled) {
+ Logger.Log(declaringType, Level.Fatal, message, exception);
+ }
+ }
+
+ [Obsolete("Use InfoFormat instead")]
+ public void Info(string format, params object[] args) {
+ if (IsInfoEnabled) {
+ Logger.Log(declaringType, Level.Info, string.Format(format, args), null);
+ }
+ }
+
+ [Obsolete("Use WarnFormat instead")]
+ public void Warn(string format, params object[] args) {
+ if (IsWarnEnabled) {
+ Logger.Log(declaringType, Level.Warn, string.Format(format, args), null);
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard/Logging/OrchardXmlConfigurator.cs b/src/Orchard/Logging/OrchardXmlConfigurator.cs
new file mode 100644
index 000000000..965f389f9
--- /dev/null
+++ b/src/Orchard/Logging/OrchardXmlConfigurator.cs
@@ -0,0 +1,308 @@
+using System;
+using System.IO;
+using System.Reflection;
+using System.Xml;
+
+using log4net;
+using log4net.Repository;
+using log4net.Repository.Hierarchy;
+using log4net.Util;
+
+namespace Orchard.Logging {
+ public class OrchardXmlConfigurator {
+
+ ///
+ /// Private constructor
+ ///
+ private OrchardXmlConfigurator() {
+ }
+
+ ///
+ /// Configures log4net using the specified configuration file.
+ ///
+ /// The name of the XML file to load the configuration from.
+ ///
+ ///
+ /// The configuration file must be valid XML. It must contain
+ /// at least one element called log4net that holds
+ /// the log4net configuration data.
+ ///
+ ///
+ /// The log4net configuration file can possible be specified in the application's
+ /// configuration file (either MyAppName.exe.config for a
+ /// normal application on Web.config for an ASP.NET application).
+ ///
+ ///
+ /// The first element matching <configuration> will be read as the
+ /// configuration. If this file is also a .NET .config file then you must specify
+ /// a configuration section for the log4net element otherwise .NET will
+ /// complain. Set the type for the section handler to , for example:
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// The following example configures log4net using a configuration file, of which the
+ /// location is stored in the application's configuration file :
+ ///
+ ///
+ /// using log4net.Config;
+ /// using System.IO;
+ /// using System.Configuration;
+ ///
+ /// ...
+ ///
+ /// XmlConfigurator.Configure(ConfigurationSettings.AppSettings["log4net-config-file"]);
+ ///
+ ///
+ /// In the .config file, the path to the log4net can be specified like this :
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void Configure(string configFilename) {
+ Configure(LogManager.GetRepository(Assembly.GetCallingAssembly()), configFilename);
+ }
+
+ ///
+ /// Configures log4net using the specified configuration data stream.
+ ///
+ /// A stream to load the XML configuration from.
+ ///
+ ///
+ /// The configuration data must be valid XML. It must contain
+ /// at least one element called log4net that holds
+ /// the log4net configuration data.
+ ///
+ ///
+ /// Note that this method will NOT close the stream parameter.
+ ///
+ ///
+ public static void Configure(Stream configStream) {
+ Configure(LogManager.GetRepository(Assembly.GetCallingAssembly()), configStream);
+ }
+
+ ///
+ /// Configures the using the specified configuration
+ /// file.
+ ///
+ /// The repository to configure.
+ /// The name of the XML file to load the configuration from.
+ ///
+ ///
+ /// The configuration file must be valid XML. It must contain
+ /// at least one element called log4net that holds
+ /// the configuration data.
+ ///
+ ///
+ /// The log4net configuration file can possible be specified in the application's
+ /// configuration file (either MyAppName.exe.config for a
+ /// normal application on Web.config for an ASP.NET application).
+ ///
+ ///
+ /// The first element matching <configuration> will be read as the
+ /// configuration. If this file is also a .NET .config file then you must specify
+ /// a configuration section for the log4net element otherwise .NET will
+ /// complain. Set the type for the section handler to , for example:
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// The following example configures log4net using a configuration file, of which the
+ /// location is stored in the application's configuration file :
+ ///
+ ///
+ /// using log4net.Config;
+ /// using System.IO;
+ /// using System.Configuration;
+ ///
+ /// ...
+ ///
+ /// XmlConfigurator.Configure(ConfigurationSettings.AppSettings["log4net-config-file"]);
+ ///
+ ///
+ /// In the .config file, the path to the log4net can be specified like this :
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void Configure(ILoggerRepository repository, string configFilename) {
+ LogLog.Debug("XmlConfigurator: configuring repository [" + repository.Name + "] using file [" + configFilename + "]");
+
+ if (String.IsNullOrWhiteSpace(configFilename)) {
+ LogLog.Error("XmlConfigurator: Configure called with null 'configFilename' parameter");
+ }
+ else {
+ // Have to use File.Exists() rather than configFile.Exists()
+ // because configFile.Exists() caches the value, not what we want.
+ if (File.Exists(configFilename)) {
+ // Open the file for reading
+ FileStream fs = null;
+
+ // Try hard to open the file
+ for (var retry = 5; --retry >= 0; ) {
+ try {
+ fs = new FileStream(configFilename, FileMode.Open, FileAccess.Read, FileShare.Read);
+ break;
+ }
+ catch (IOException ex) {
+ if (retry == 0) {
+ LogLog.Error("XmlConfigurator: Failed to open XML config file [" + configFilename + "]", ex);
+
+ // The stream cannot be valid
+ fs = null;
+ }
+
+ System.Threading.Thread.Sleep(250);
+ }
+ }
+
+ if (fs != null) {
+ try {
+ // Load the configuration from the stream
+ Configure(repository, fs);
+ }
+ finally {
+ // Force the file closed whatever happens
+ fs.Close();
+ }
+ }
+ }
+ else {
+ LogLog.Debug("XmlConfigurator: config file [" + configFilename + "] not found. Configuration unchanged.");
+ }
+ }
+ }
+
+ ///
+ /// Configures the using the specified configuration
+ /// file.
+ ///
+ /// The repository to configure.
+ /// The stream to load the XML configuration from.
+ ///
+ ///
+ /// The configuration data must be valid XML. It must contain
+ /// at least one element called log4net that holds
+ /// the configuration data.
+ ///
+ ///
+ /// Note that this method will NOT close the stream parameter.
+ ///
+ ///
+ public static void Configure(ILoggerRepository repository, Stream configStream) {
+ LogLog.Debug("XmlConfigurator: configuring repository [" + repository.Name + "] using stream");
+
+ if (configStream == null) {
+ LogLog.Error("XmlConfigurator: Configure called with null 'configStream' parameter");
+ }
+ else {
+ // Load the config file into a document
+ var doc = new XmlDocument();
+ try {
+ // Create a text reader for the file stream
+ var xmlReader = new XmlTextReader(configStream) { DtdProcessing = DtdProcessing.Parse };
+
+ // Specify that the reader should not perform validation
+ var settings = new XmlReaderSettings { ValidationType = ValidationType.None };
+
+ // load the data into the document
+ doc.Load(xmlReader);
+ }
+ catch (Exception ex) {
+ LogLog.Error("XmlConfigurator: Error while loading XML configuration", ex);
+
+ // The document is invalid
+ doc = null;
+ }
+
+ if (doc != null) {
+ LogLog.Debug("XmlConfigurator: loading XML configuration");
+
+ // Configure using the 'log4net' element
+ var configNodeList = doc.GetElementsByTagName("log4net");
+ if (configNodeList.Count == 0) {
+ LogLog.Debug("XmlConfigurator: XML configuration does not contain a element. Configuration Aborted.");
+ }
+ else if (configNodeList.Count > 1) {
+ LogLog.Error("XmlConfigurator: XML configuration contains [" + configNodeList.Count + "] elements. Only one is allowed. Configuration Aborted.");
+ }
+ else {
+ ConfigureFromXml(repository, configNodeList[0] as XmlElement);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Configures the specified repository using a log4net element.
+ ///
+ /// The hierarchy to configure.
+ /// The element to parse.
+ ///
+ ///
+ /// Loads the log4net configuration from the XML element
+ /// supplied as .
+ ///
+ ///
+ /// This method is ultimately called by one of the Configure methods
+ /// to load the configuration from an .
+ ///
+ ///
+ private static void ConfigureFromXml(ILoggerRepository repository, XmlElement element) {
+ if (element == null) {
+ LogLog.Error("XmlConfigurator: ConfigureFromXml called with null 'element' parameter");
+ }
+ else if (repository == null) {
+ LogLog.Error("XmlConfigurator: ConfigureFromXml called with null 'repository' parameter");
+ }
+ else {
+ LogLog.Debug("XmlConfigurator: Configuring Repository [" + repository.Name + "]");
+
+ //
+ // Since we're not reinventing the whole Hierarchy class from log4net to add out optimizations to XmlRepositoryConfigurator
+ // we've to check the neccessary casts here to be able to complete the configuration.
+ //
+
+ // Needed to fire configuration changed event
+ var repositorySkeleton = repository as LoggerRepositorySkeleton;
+
+ // Needed to XmlHierarchyConfigurator
+ var hierarchy = repository as Hierarchy;
+
+ if (repositorySkeleton == null || hierarchy == null) {
+ LogLog.Warn("XmlConfigurator: Repository [" + repository + "] does not support the XmlConfigurator");
+ }
+ else {
+ var configurator = new OrchardXmlHierarchyConfigurator(hierarchy);
+
+ // Copy the xml data into the root of a new document
+ // this isolates the xml config data from the rest of
+ // the document
+ var newDoc = new XmlDocument();
+ var newElement = (XmlElement)newDoc.AppendChild(newDoc.ImportNode(element, true));
+
+ // Pass the configurator the config element
+ configurator.Configure(newElement);
+
+ repositorySkeleton.RaiseConfigurationChanged(EventArgs.Empty);
+ }
+ }
+ }
+ }
+}
diff --git a/src/Orchard/Logging/OrchardXmlHierarchyConfigurator.cs b/src/Orchard/Logging/OrchardXmlHierarchyConfigurator.cs
new file mode 100644
index 000000000..a04774c38
--- /dev/null
+++ b/src/Orchard/Logging/OrchardXmlHierarchyConfigurator.cs
@@ -0,0 +1,864 @@
+using System;
+using System.Collections;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+using System.Xml;
+
+using log4net.Appender;
+using log4net.Core;
+using log4net.ObjectRenderer;
+using log4net.Repository.Hierarchy;
+using log4net.Util;
+
+namespace Orchard.Logging {
+ ///
+ /// Initializes the log4net environment using an XML DOM.
+ ///
+ ///
+ ///
+ /// Configures a using an XML DOM.
+ ///
+ ///
+ /// Nicko Cadell
+ /// Gert Driesen
+ public class OrchardXmlHierarchyConfigurator {
+ private enum ConfigUpdateMode {
+ Merge,
+ Overwrite
+ }
+
+ // String constants used while parsing the XML data
+ private const string ConfigurationTag = "log4net";
+ private const string RendererTag = "renderer";
+ private const string AppenderTag = "appender";
+ private const string AppenderRefTag = "appender-ref";
+ private const string ParamTag = "param";
+
+ // TODO: Deprecate use of category tags
+ private const string CategoryTag = "category";
+ // TODO: Deprecate use of priority tag
+ private const string PriorityTag = "priority";
+
+ private const string LoggerTag = "logger";
+ private const string NameAttr = "name";
+ private const string TypeAttr = "type";
+ private const string ValueAttr = "value";
+ private const string RootTag = "root";
+ private const string LevelTag = "level";
+ private const string RefAttr = "ref";
+ private const string AdditivityAttr = "additivity";
+ private const string ThresholdAttr = "threshold";
+ private const string ConfigDebugAttr = "configDebug";
+ private const string InternalDebugAttr = "debug";
+ private const string ConfigUpdateModeAttr = "update";
+ private const string RenderingTypeAttr = "renderingClass";
+ private const string RenderedTypeAttr = "renderedClass";
+
+ // flag used on the level element
+ private const string Inherited = "inherited";
+
+ ///
+ /// key: appenderName, value: appender.
+ ///
+ private Hashtable _appenderBag;
+
+ ///
+ /// The Hierarchy being configured.
+ ///
+ private readonly Hierarchy _hierarchy;
+
+ ///
+ /// The snapshot of the environment variables at configuration time, or null if an error has occured during querying them.
+ ///
+ private IDictionary _environmentVariables;
+
+ ///
+ /// Construct the configurator for a hierarchy
+ ///
+ /// The hierarchy to build.
+ ///
+ ///
+ /// Initializes a new instance of the class
+ /// with the specified .
+ ///
+ ///
+ public OrchardXmlHierarchyConfigurator(Hierarchy hierarchy) {
+ _hierarchy = hierarchy;
+ _appenderBag = new Hashtable();
+ }
+
+ ///
+ /// Configure the hierarchy by parsing a DOM tree of XML elements.
+ ///
+ /// The root element to parse.
+ ///
+ ///
+ /// Configure the hierarchy by parsing a DOM tree of XML elements.
+ ///
+ ///
+ public void Configure(XmlElement element) {
+ if (element == null || _hierarchy == null) {
+ return;
+ }
+
+ var rootElementName = element.LocalName;
+
+ if (rootElementName != ConfigurationTag) {
+ LogLog.Error("XmlHierarchyConfigurator: Xml element is - not a <" + ConfigurationTag + "> element.");
+ return;
+ }
+
+ if (!LogLog.InternalDebugging) {
+ // Look for a debug attribute to enable internal debug
+ var debugAttribute = element.GetAttribute(InternalDebugAttr);
+ LogLog.Debug("XmlHierarchyConfigurator: " + InternalDebugAttr + " attribute [" + debugAttribute + "].");
+
+ if (debugAttribute.Length > 0 && debugAttribute != "null") {
+ LogLog.InternalDebugging = OptionConverter.ToBoolean(debugAttribute, true);
+ }
+ else {
+ LogLog.Debug("XmlHierarchyConfigurator: Ignoring " + InternalDebugAttr + " attribute.");
+ }
+
+ var confDebug = element.GetAttribute(ConfigDebugAttr);
+ if (confDebug.Length > 0 && confDebug != "null") {
+ LogLog.Warn("XmlHierarchyConfigurator: The \"" + ConfigDebugAttr + "\" attribute is deprecated.");
+ LogLog.Warn("XmlHierarchyConfigurator: Use the \"" + InternalDebugAttr + "\" attribute instead.");
+ LogLog.InternalDebugging = OptionConverter.ToBoolean(confDebug, true);
+ }
+ }
+
+ // Default mode is merge
+ var configUpdateMode = ConfigUpdateMode.Merge;
+
+ // Look for the config update attribute
+ var configUpdateModeAttribute = element.GetAttribute(ConfigUpdateModeAttr);
+ if (!String.IsNullOrEmpty(configUpdateModeAttribute)) {
+ // Parse the attribute
+ try {
+ configUpdateMode = (ConfigUpdateMode)OptionConverter.ConvertStringTo(typeof(ConfigUpdateMode), configUpdateModeAttribute);
+ }
+ catch {
+ LogLog.Error("XmlHierarchyConfigurator: Invalid " + ConfigUpdateModeAttr + " attribute value [" + configUpdateModeAttribute + "]");
+ }
+ }
+
+ // IMPL: The IFormatProvider argument to Enum.ToString() is deprecated in .NET 2.0
+ LogLog.Debug("XmlHierarchyConfigurator: Configuration update mode [" + configUpdateMode + "].");
+
+ // Only reset configuration if overwrite flag specified
+ if (configUpdateMode == ConfigUpdateMode.Overwrite) {
+ // Reset to original unset configuration
+ _hierarchy.ResetConfiguration();
+ LogLog.Debug("XmlHierarchyConfigurator: Configuration reset before reading config.");
+ }
+
+ // Try to retrieve the environment variables
+ try {
+ _environmentVariables = System.Environment.GetEnvironmentVariables();
+ }
+ catch (System.Security.SecurityException) {
+ _environmentVariables = null;
+
+ // 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("XmlHierarchyConfigurator: Security exception while trying to expand environment variables. Error Ignored. No Expansion.");
+ }
+
+ /* 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) {
+ var currentElement = (XmlElement)currentNode;
+
+ if (currentElement.LocalName == LoggerTag) {
+ ParseLogger(currentElement);
+ }
+ else if (currentElement.LocalName == CategoryTag) {
+ // TODO: deprecated use of category
+ ParseLogger(currentElement);
+ }
+ else if (currentElement.LocalName == RootTag) {
+ ParseRoot(currentElement);
+ }
+ else if (currentElement.LocalName == RendererTag) {
+ ParseRenderer(currentElement);
+ }
+ else if (currentElement.LocalName == AppenderTag) {
+ // 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, _hierarchy);
+ }
+ }
+ }
+
+ // Lastly set the hierarchy threshold
+ string thresholdStr = element.GetAttribute(ThresholdAttr);
+ LogLog.Debug("XmlHierarchyConfigurator: Hierarchy Threshold [" + thresholdStr + "]");
+ if (thresholdStr.Length > 0 && thresholdStr != "null") {
+ var thresholdLevel = (Level)ConvertStringTo(typeof(Level), thresholdStr);
+ if (thresholdLevel != null) {
+ _hierarchy.Threshold = thresholdLevel;
+ }
+ else {
+ LogLog.Warn("XmlHierarchyConfigurator: Unable to set hierarchy threshold using value [" + thresholdStr + "] (with acceptable conversion types)");
+ }
+ }
+
+ // Done reading config
+ }
+
+ ///
+ /// Parse appenders by IDREF.
+ ///
+ /// The appender ref element.
+ /// The instance of the appender that the ref refers to.
+ ///
+ ///
+ /// Parse an XML element that represents an appender and return
+ /// the appender.
+ ///
+ ///
+ protected IAppender FindAppenderByReference(XmlElement appenderRef) {
+ var appenderName = appenderRef.GetAttribute(RefAttr);
+
+ IAppender appender = (IAppender)_appenderBag[appenderName];
+ if (appender != null) {
+ return appender;
+ }
+ // Find the element with that id
+ XmlElement element = null;
+
+ if (!String.IsNullOrEmpty(appenderName)) {
+ foreach (XmlElement curAppenderElement in appenderRef.OwnerDocument.GetElementsByTagName(AppenderTag)) {
+ if (curAppenderElement.GetAttribute("name") != appenderName) {
+ continue;
+ }
+ element = curAppenderElement;
+ break;
+ }
+ }
+
+ if (element == null) {
+ LogLog.Error("XmlHierarchyConfigurator: No appender named [" + appenderName + "] could be found.");
+ return null;
+ }
+ appender = ParseAppender(element);
+ if (appender != null) {
+ _appenderBag[appenderName] = appender;
+ }
+ return appender;
+ }
+
+ ///
+ /// Parses an appender element.
+ ///
+ /// The appender element.
+ /// The appender instance or null when parsing failed.
+ ///
+ ///
+ /// Parse an XML element that represents an appender and return
+ /// the appender instance.
+ ///
+ ///
+ protected IAppender ParseAppender(XmlElement appenderElement) {
+ var appenderName = appenderElement.GetAttribute(NameAttr);
+ var typeName = appenderElement.GetAttribute(TypeAttr);
+
+ LogLog.Debug("XmlHierarchyConfigurator: Loading Appender [" + appenderName + "] type: [" + typeName + "]");
+ try {
+ var appender = (IAppender)Activator.CreateInstance(SystemInfo.GetTypeFromString(typeName, true, true));
+ appender.Name = appenderName;
+
+ foreach (XmlNode currentNode in appenderElement.ChildNodes) {
+ /* We're only interested in Elements */
+ if (currentNode.NodeType == XmlNodeType.Element) {
+ var currentElement = (XmlElement)currentNode;
+
+ // Look for the appender ref tag
+ if (currentElement.LocalName == AppenderRefTag) {
+ var refName = currentElement.GetAttribute(RefAttr);
+
+ var appenderContainer = appender as IAppenderAttachable;
+ if (appenderContainer != null) {
+ LogLog.Debug("XmlHierarchyConfigurator: Attaching appender named [" + refName + "] to appender named [" + appender.Name + "].");
+
+ var referencedAppender = FindAppenderByReference(currentElement);
+ if (referencedAppender != null) {
+ appenderContainer.AddAppender(referencedAppender);
+ }
+ }
+ else {
+ LogLog.Error("XmlHierarchyConfigurator: 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);
+ }
+ }
+ }
+
+ var optionHandler = appender as IOptionHandler;
+ if (optionHandler != null) {
+ optionHandler.ActivateOptions();
+ }
+
+ LogLog.Debug("XmlHierarchyConfigurator: 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("XmlHierarchyConfigurator: Could not create Appender [" + appenderName + "] of type [" + typeName + "]. Reported error follows.", ex);
+ return null;
+ }
+ }
+
+ ///
+ /// Parses a logger element.
+ ///
+ /// The logger element.
+ ///
+ ///
+ /// Parse an XML element that represents a logger.
+ ///
+ ///
+ protected void ParseLogger(XmlElement loggerElement) {
+ // Create a new log4net.Logger object from the element.
+ var loggerName = loggerElement.GetAttribute(NameAttr);
+
+ LogLog.Debug("XmlHierarchyConfigurator: Retrieving an instance of log4net.Repository.Logger for logger [" + loggerName + "].");
+ var log = _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.
+ if (log == null) {
+ return;
+ }
+ lock (log) {
+ var additivity = OptionConverter.ToBoolean(loggerElement.GetAttribute(AdditivityAttr), true);
+
+ LogLog.Debug("XmlHierarchyConfigurator: Setting [" + log.Name + "] additivity to [" + additivity + "].");
+ log.Additivity = additivity;
+ ParseChildrenOfLoggerElement(loggerElement, log, false);
+ }
+ }
+
+ ///
+ /// Parses the root logger element.
+ ///
+ /// The root element.
+ ///
+ ///
+ /// Parse an XML element that represents the root logger.
+ ///
+ ///
+ protected void ParseRoot(XmlElement rootElement) {
+ var root = _hierarchy.Root;
+ // logger configuration needs to be atomic
+ lock (root) {
+ ParseChildrenOfLoggerElement(rootElement, root, true);
+ }
+ }
+
+ ///
+ /// Parses the children of a logger element.
+ ///
+ /// The category element.
+ /// The logger instance.
+ /// Flag to indicate if the logger is the root logger.
+ ///
+ ///
+ /// Parse the child elements of a <logger> element.
+ ///
+ ///
+ 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) {
+ var currentElement = (XmlElement)currentNode;
+
+ if (currentElement.LocalName == AppenderRefTag) {
+ var appender = FindAppenderByReference(currentElement);
+ var refName = currentElement.GetAttribute(RefAttr);
+ if (appender != null) {
+ LogLog.Debug("XmlHierarchyConfigurator: Adding appender named [" + refName + "] to logger [" + log.Name + "].");
+ log.AddAppender(appender);
+ }
+ else {
+ LogLog.Error("XmlHierarchyConfigurator: Appender named [" + refName + "] not found.");
+ }
+ }
+ else if (currentElement.LocalName == LevelTag || currentElement.LocalName == PriorityTag) {
+ ParseLevel(currentElement, log, isRoot);
+ }
+ else {
+ SetParameter(currentElement, log);
+ }
+ }
+ }
+
+ var optionHandler = log as IOptionHandler;
+ if (optionHandler != null) {
+ optionHandler.ActivateOptions();
+ }
+ }
+
+ ///
+ /// Parses an object renderer.
+ ///
+ /// The renderer element.
+ ///
+ ///
+ /// Parse an XML element that represents a renderer.
+ ///
+ ///
+ protected void ParseRenderer(XmlElement element) {
+ var renderingClassName = element.GetAttribute(RenderingTypeAttr);
+ var renderedClassName = element.GetAttribute(RenderedTypeAttr);
+
+ LogLog.Debug("XmlHierarchyConfigurator: Rendering class [" + renderingClassName + "], Rendered class [" + renderedClassName + "].");
+ var renderer = (IObjectRenderer)OptionConverter.InstantiateByClassName(renderingClassName, typeof(IObjectRenderer), null);
+ if (renderer == null) {
+ LogLog.Error("XmlHierarchyConfigurator: Could not instantiate renderer [" + renderingClassName + "].");
+ return;
+ }
+ try {
+ _hierarchy.RendererMap.Put(SystemInfo.GetTypeFromString(renderedClassName, true, true), renderer);
+ }
+ catch (Exception e) {
+ LogLog.Error("XmlHierarchyConfigurator: Could not find class [" + renderedClassName + "].", e);
+ }
+ }
+
+ ///
+ /// Parses a level element.
+ ///
+ /// The level element.
+ /// The logger object to set the level on.
+ /// Flag to indicate if the logger is the root logger.
+ ///
+ ///
+ /// Parse an XML element that represents a level.
+ ///
+ ///
+ protected void ParseLevel(XmlElement element, Logger log, bool isRoot) {
+ var loggerName = log.Name;
+ if (isRoot) {
+ loggerName = "root";
+ }
+
+ var levelStr = element.GetAttribute(ValueAttr);
+ LogLog.Debug("XmlHierarchyConfigurator: Logger [" + loggerName + "] Level string is [" + levelStr + "].");
+
+ if (Inherited == levelStr) {
+ if (isRoot) {
+ LogLog.Error("XmlHierarchyConfigurator: Root level cannot be inherited. Ignoring directive.");
+ }
+ else {
+ LogLog.Debug("XmlHierarchyConfigurator: Logger [" + loggerName + "] level set to inherit from parent.");
+ log.Level = null;
+ }
+ }
+ else {
+ log.Level = log.Hierarchy.LevelMap[levelStr];
+ if (log.Level == null) {
+ LogLog.Error("XmlHierarchyConfigurator: Undefined level [" + levelStr + "] on Logger [" + loggerName + "].");
+ }
+ else {
+ LogLog.Debug("XmlHierarchyConfigurator: Logger [" + loggerName + "] level set to [name=\"" + log.Level.Name + "\",value=" + log.Level.Value + "].");
+ }
+ }
+ }
+
+ ///
+ /// Sets a parameter on an object.
+ ///
+ /// The parameter element.
+ /// The object to set the parameter on.
+ ///
+ /// 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 Parse 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.
+ ///
+ protected void SetParameter(XmlElement element, object target) {
+ // Get the property name
+ var name = element.GetAttribute(NameAttr);
+
+ // If the name attribute does not exist then use the name of the element
+ if (element.LocalName != ParamTag || String.IsNullOrEmpty(name)) {
+ name = element.LocalName;
+ }
+
+ // Look for the property on the target object
+ var targetType = target.GetType();
+ Type propertyType = null;
+
+ MethodInfo methInfo = null;
+
+ // Try to find a writable property
+ var 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(type)
+ methInfo = FindMethodInfo(targetType, name);
+
+ if (methInfo != null) {
+ propertyType = methInfo.GetParameters()[0].ParameterType;
+ }
+ }
+
+ if (propertyType == null) {
+ LogLog.Error("XmlHierarchyConfigurator: Cannot find Property [" + name + "] to set object on [" + target + "]");
+ }
+ else {
+ string propertyValue = null;
+
+ if (element.GetAttributeNode(ValueAttr) != null) {
+ propertyValue = element.GetAttribute(ValueAttr);
+ }
+ 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 (_environmentVariables != null) {
+ // Expand environment variables in the string.
+ propertyValue = OptionConverter.SubstituteVariables(propertyValue, _environmentVariables);
+ }
+
+ Type parsedObjectConversionTargetType = null;
+
+ // Check if a specific subtype is specified on the element using the 'type' attribute
+ var subTypeString = element.GetAttribute(TypeAttr);
+ if (!String.IsNullOrEmpty(subTypeString)) {
+ // Read the explicit subtype
+ try {
+ var subType = SystemInfo.GetTypeFromString(subTypeString, true, true);
+
+ LogLog.Debug("XmlHierarchyConfigurator: 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("XmlHierarchyConfigurator: 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("XmlHierarchyConfigurator: 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.
+
+ var convertedValue = ConvertStringTo(propertyType, propertyValue);
+
+ // Check if we need to do an additional conversion
+ if (convertedValue != null && parsedObjectConversionTargetType != null) {
+ LogLog.Debug("XmlHierarchyConfigurator: 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("XmlHierarchyConfigurator: Setting Property [" + propInfo.Name + "] to " + convertedValue.GetType().Name + " value [" + convertedValue + "]");
+
+ try {
+ // Pass to the property
+ propInfo.SetValue(target, convertedValue, BindingFlags.SetProperty, null, null, CultureInfo.InvariantCulture);
+ }
+ catch (TargetInvocationException targetInvocationEx) {
+ LogLog.Error("XmlHierarchyConfigurator: Failed to set parameter [" + propInfo.Name + "] on object [" + target + "] using value [" + convertedValue + "]", targetInvocationEx.InnerException);
+ }
+ }
+ else if (methInfo != null) {
+ // Got a converted result
+ LogLog.Debug("XmlHierarchyConfigurator: Setting Collection Property [" + methInfo.Name + "] to " + convertedValue.GetType().Name + " value [" + convertedValue + "]");
+
+ try {
+ // Pass to the property
+ methInfo.Invoke(target, BindingFlags.InvokeMethod, null, new[] { convertedValue }, CultureInfo.InvariantCulture);
+ }
+ catch (TargetInvocationException targetInvocationEx) {
+ LogLog.Error("XmlHierarchyConfigurator: Failed to set parameter [" + name + "] on object [" + target + "] using value [" + convertedValue + "]", targetInvocationEx.InnerException);
+ }
+ }
+ }
+ else {
+ LogLog.Warn("XmlHierarchyConfigurator: Unable to set property [" + name + "] on object [" + target + "] using value [" + propertyValue + "] (with acceptable conversion types)");
+ }
+ }
+ else {
+ object createdObject;
+
+ 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("XmlHierarchyConfigurator: Failed to create object to set param: " + name);
+ }
+ else {
+ if (propInfo != null) {
+ // Got a converted result
+ LogLog.Debug("XmlHierarchyConfigurator: Setting Property [" + propInfo.Name + "] to object [" + createdObject + "]");
+
+ try {
+ // Pass to the property
+ propInfo.SetValue(target, createdObject, BindingFlags.SetProperty, null, null, CultureInfo.InvariantCulture);
+ }
+ catch (TargetInvocationException targetInvocationEx) {
+ LogLog.Error("XmlHierarchyConfigurator: Failed to set parameter [" + propInfo.Name + "] on object [" + target + "] using value [" + createdObject + "]", targetInvocationEx.InnerException);
+ }
+ }
+ else if (methInfo != null) {
+ // Got a converted result
+ LogLog.Debug("XmlHierarchyConfigurator: Setting Collection Property [" + methInfo.Name + "] to object [" + createdObject + "]");
+
+ try {
+ // Pass to the property
+ methInfo.Invoke(target, BindingFlags.InvokeMethod, null, new[] { createdObject }, CultureInfo.InvariantCulture);
+ }
+ catch (TargetInvocationException targetInvocationEx) {
+ LogLog.Error("XmlHierarchyConfigurator: Failed to set parameter [" + methInfo.Name + "] on object [" + target + "] using value [" + createdObject + "]", targetInvocationEx.InnerException);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Test if an element has no attributes or child elements
+ ///
+ /// the element to inspect
+ /// true if the element has any attributes or child elements, false otherwise
+ private static bool HasAttributesOrElements(XmlElement element) {
+ return element.ChildNodes.Cast().Any(node => node.NodeType == XmlNodeType.Attribute || node.NodeType == XmlNodeType.Element);
+ }
+
+ ///
+ /// Test if a is constructible with Activator.CreateInstance.
+ ///
+ /// the type to inspect
+ /// true if the type is creatable using a default constructor, false otherwise
+ private static bool IsTypeConstructible(Type type) {
+ if (type.IsClass && !type.IsAbstract) {
+ var defaultConstructor = type.GetConstructor(new Type[0]);
+ if (defaultConstructor != null && !defaultConstructor.IsAbstract && !defaultConstructor.IsPrivate) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ ///
+ /// Look for a method on the that matches the supplied
+ ///
+ /// the type that has the method
+ /// the name of the method
+ /// the method info found
+ ///
+ ///
+ /// The method must be a public instance method on the .
+ /// The method must be named or "Add" followed by .
+ /// The method must take a single parameter.
+ ///
+ ///
+ private static MethodInfo FindMethodInfo(Type targetType, string name) {
+ var requiredMethodNameA = name;
+ var requiredMethodNameB = "Add" + name;
+
+ var methods = targetType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+
+ foreach (var methInfo in methods) {
+ if (!methInfo.IsStatic) {
+ if (string.Compare(methInfo.Name, requiredMethodNameA, true, CultureInfo.InvariantCulture) == 0 ||
+ string.Compare(methInfo.Name, requiredMethodNameB, true, CultureInfo.InvariantCulture) == 0) {
+ // Found matching method name
+
+ // Look for version with one arg only
+ var methParams = methInfo.GetParameters();
+ if (methParams.Length == 1) {
+ return methInfo;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ ///
+ /// Converts a string value to a target type.
+ ///
+ /// The type of object to convert the string to.
+ /// The string value to use as the value of the object.
+ ///
+ ///
+ /// An object of type with value or
+ /// null when the conversion could not be performed.
+ ///
+ ///
+ protected object ConvertStringTo(Type type, string value) {
+ // Hack to allow use of Level in property
+ if (typeof(Level) == type) {
+ // Property wants a level
+ var levelValue = _hierarchy.LevelMap[value];
+
+ if (levelValue == null) {
+ LogLog.Error("XmlHierarchyConfigurator: Unknown Level Specified [" + value + "]");
+ }
+
+ return levelValue;
+ }
+ return OptionConverter.ConvertStringTo(type, value);
+ }
+
+ ///
+ /// Creates an object as specified in XML.
+ ///
+ /// The XML element that contains the definition of the object.
+ /// The object type to use if not explicitly specified.
+ /// The type that the returned object must be or must inherit from.
+ /// The object or null
+ ///
+ ///
+ /// Parse an XML element and create an object instance based on the configuration
+ /// data.
+ ///
+ ///
+ /// The type of the instance may be specified in the XML. If not
+ /// specified then the is used
+ /// as the type. However the type is specified it must support the
+ /// type.
+ ///
+ ///
+ protected object CreateObjectFromXml(XmlElement element, Type defaultTargetType, Type typeConstraint) {
+ Type objectType;
+
+ // Get the object type
+ var objectTypeString = element.GetAttribute(TypeAttr);
+ if (String.IsNullOrEmpty(objectTypeString)) {
+ if (defaultTargetType == null) {
+ LogLog.Error("XmlHierarchyConfigurator: Object type not specified. Cannot create object of type [" + typeConstraint.FullName + "]. Missing Value or Type.");
+ return null;
+ }
+ // Use the default object type
+ objectType = defaultTargetType;
+ }
+ else {
+ // Read the explicit object type
+ try {
+ objectType = SystemInfo.GetTypeFromString(objectTypeString, true, true);
+ }
+ catch (Exception ex) {
+ LogLog.Error("XmlHierarchyConfigurator: Failed to find type [" + objectTypeString + "]", ex);
+ return null;
+ }
+ }
+
+ var 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("XmlHierarchyConfigurator: 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("XmlHierarchyConfigurator: Failed to construct object of type [" + objectType.FullName + "] Exception: " + createInstanceEx);
+ }
+
+ // Set any params on object
+ foreach (var currentNode in element.ChildNodes.Cast().Where(currentNode => currentNode.NodeType == XmlNodeType.Element)) {
+ SetParameter((XmlElement)currentNode, createdObject);
+ }
+
+ // Check if we need to call ActivateOptions
+ var optionHandler = createdObject as IOptionHandler;
+ if (optionHandler != null) {
+ optionHandler.ActivateOptions();
+ }
+
+ // Ok object should be initialized
+
+ return requiresConversion ? OptionConverter.ConvertTypeTo(createdObject, typeConstraint) : createdObject;
+ }
+ }
+}
diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj
index 0f6ec65c1..6e21aa3e1 100644
--- a/src/Orchard/Orchard.Framework.csproj
+++ b/src/Orchard/Orchard.Framework.csproj
@@ -200,6 +200,10 @@
+
+
+
+