Skip to content

Commit

Permalink
Issue patterns-group#96 - code complete
Browse files Browse the repository at this point in the history
* Added new DelegateInterceptor
* Changed LoggingInterceptor to inherit from DelegateInterceptor
* Updated test assertions to expect only one trace call for trapped error
  execution paths
* Updated ReSharper settings for "implicit scope closure"
  • Loading branch information
jbatte47 committed Aug 30, 2013
1 parent 9a0aaf9 commit 69d4e91
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 23 deletions.
119 changes: 119 additions & 0 deletions src/Patterns/Interception/DelegateInterceptor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#region FreeBSD

// Copyright (c) 2013, John Batte
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#endregion

using System;

using Castle.DynamicProxy;

using Patterns.ExceptionHandling;

namespace Patterns.Interception
{
/// <summary>
/// Provides an interceptor that allows its interception logic to be
/// injected; no-op fall-backs are used when no logic has been specified
/// for an interception step.
/// </summary>
public class DelegateInterceptor : IInterceptor
{
/// <summary>
/// Initializes a new instance of the <see cref="DelegateInterceptor" /> class.
/// </summary>
/// <param name="after">
/// The action to execute after proceeding. This action will not run
/// if the invocation throws an exception.
/// </param>
/// <param name="before">The action to execute before proceeding.</param>
/// <param name="condition">
/// The interception condition. If this condition returns <c>false</c>,
/// the invocation proceeds with no further interception.
/// </param>
/// <param name="finally">
/// The action to execute at the end of the interception, regardless of
/// whether or not an exception was thrown.
/// </param>
/// <param name="onError">The function to execute when an error occurs.</param>
public DelegateInterceptor(Action<IInvocation> after = null, Action<IInvocation> before = null,
Func<IInvocation, bool> condition = null, Action<IInvocation> @finally = null,
Func<IInvocation, Exception, ExceptionState> onError = null)
{
Initialize(after, before, condition, @finally, onError);
}

protected virtual Action<IInvocation> After { get; set; }

protected virtual Action<IInvocation> Before { get; set; }

protected virtual Func<IInvocation, bool> Condition { get; set; }

protected virtual Action<IInvocation> Finally { get; set; }

protected virtual Func<IInvocation, Exception, ExceptionState> OnError { get; set; }

/// <summary>
/// Intercepts the specified invocation.
/// </summary>
/// <param name="invocation">The invocation.</param>
public virtual void Intercept(IInvocation invocation)
{
if (Condition != null && !Condition(invocation))
{
invocation.Proceed();
return;
}

try
{
if (Before != null) Before(invocation);

invocation.Proceed();

if (After != null) After(invocation);
}
catch (Exception error)
{
Func<IInvocation, Exception, ExceptionState> errorHandler = OnError
?? ((thisCall, thisBug) => new ExceptionState(thisBug, false));
ExceptionState state = errorHandler(invocation, error);

if (state.IsHandled) return;

if (!ReferenceEquals(error, state.Exception) && state.Exception != null) throw state.Exception;

throw;
}
finally
{
if (Finally != null) Finally(invocation);
}
}

private void Initialize(Action<IInvocation> after, Action<IInvocation> before, Func<IInvocation, bool> condition,
Action<IInvocation> @finally, Func<IInvocation, Exception, ExceptionState> onError)
{
Condition = condition;
Before = before;
After = after;
Finally = @finally;
OnError = onError;
}
}
}
66 changes: 44 additions & 22 deletions src/Patterns/Logging/LoggingInterceptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
using Common.Logging;

using Patterns.Collections.Strategies;
using Patterns.ExceptionHandling;
using Patterns.Interception;

namespace Patterns.Logging
{
Expand All @@ -36,7 +38,7 @@ namespace Patterns.Logging
/// uses <see cref="ILog" /> to log Trace, Debug, Info, and Error
/// events throughout the execution of intercepted invocations.
/// </summary>
public class LoggingInterceptor : IInterceptor
public class LoggingInterceptor : DelegateInterceptor
{
private const string _nullArgument = "[NULL]";
private const string _argumentListFormat = "({0})";
Expand All @@ -53,54 +55,74 @@ public class LoggingInterceptor : IInterceptor

private static readonly JavaScriptSerializer _jsonSerializer = new JavaScriptSerializer();

/// <summary>
/// Initializes a new instance of the <see cref="LoggingInterceptor"/> class.
/// </summary>
/// <param name="config">The config.</param>
/// <param name="logFactory">The log factory.</param>
public LoggingInterceptor(ILoggingConfig config, Func<Type, ILog> logFactory) : this(config, logFactory, null) {}

/// <summary>
/// Initializes a new instance of the <see cref="LoggingInterceptor" /> class.
/// </summary>
/// <param name="config">The config.</param>
/// <param name="logFactory">The log factory.</param>
public LoggingInterceptor(ILoggingConfig config, Func<Type, ILog> logFactory)
/// <param name="condition">The intercept condition.</param>
public LoggingInterceptor(ILoggingConfig config, Func<Type, ILog> logFactory, Func<IInvocation, bool> condition)
{
_config = config;
_logFactory = logFactory;
_config = config;
Initialize(condition);
}

/// <summary>
/// Intercepts the specified invocation.
/// </summary>
/// <param name="invocation">The invocation.</param>
public void Intercept(IInvocation invocation)
private void Initialize(Func<IInvocation, bool> condition)
{
Condition = condition;
Before = LogBefore;
After = LogAfter;
OnError = LogError;
Finally = LogFinally;
}

protected virtual void LogBefore(IInvocation invocation)
{
ILog log = _logFactory(invocation.TargetType);
log.Trace(handler => handler(LoggingResources.MethodStartTraceFormat, invocation.TargetType, invocation.Method.Name));
log.Debug(handler => handler(LoggingResources.MethodArgsDebugFormat, invocation.Method.Name, GetMethodArguments(invocation)));
}

try
{
invocation.Proceed();
log.Info(handler => handler(LoggingResources.MethodInfoFormat, invocation.Method.Name, LoggingResources.MethodInfoPass));
}
catch (Exception error)
{
log.Info(handler => handler(LoggingResources.MethodInfoFormat, invocation.Method.Name, LoggingResources.MethodInfoFail));
log.Error(handler => handler(LoggingResources.ExceptionErrorFormat, invocation.Method.Name, error.ToFullString()));
if (!_config.TrapExceptions) throw;
}

protected virtual void LogAfter(IInvocation invocation)
{
ILog log = _logFactory(invocation.TargetType);
log.Info(handler => handler(LoggingResources.MethodInfoFormat, invocation.Method.Name, LoggingResources.MethodInfoPass));
log.Trace(handler => handler(LoggingResources.MethodStopTraceFormat, invocation.TargetType, invocation.Method.Name));
}

protected virtual ExceptionState LogError(IInvocation invocation, Exception error)
{
ILog log = _logFactory(invocation.TargetType);
log.Info(handler => handler(LoggingResources.MethodInfoFormat, invocation.Method.Name, LoggingResources.MethodInfoFail));
log.Error(handler => handler(LoggingResources.ExceptionErrorFormat, invocation.Method.Name, error.ToFullString()));
return new ExceptionState(error, _config.TrapExceptions);
}

protected virtual void LogFinally(IInvocation invocation)
{
if (invocation.ReturnValue == null) return;

ILog log = _logFactory(invocation.TargetType);

object value = ConvertValueForDisplay(invocation.ReturnValue);
log.Debug(handler => handler(LoggingResources.MethodReturnDebugFormat, invocation.Method.Name, value));
}

private static string GetMethodArguments(IInvocation invocation)
protected static string GetMethodArguments(IInvocation invocation)
{
object[] arguments = invocation.Arguments.Select(ConvertValueForDisplay).ToArray();
return string.Format(_argumentListFormat, string.Join(_argumentListSeparator, arguments));
}

private static object ConvertValueForDisplay(object value)
protected static object ConvertValueForDisplay(object value)
{
if (value == null) return _nullArgument;

Expand Down
1 change: 1 addition & 0 deletions src/Patterns/Patterns.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
<Compile Include="Configuration\IConfigurationSource.cs" />
<Compile Include="ExceptionHandling\ExceptionState.cs" />
<Compile Include="ExceptionHandling\Try.cs" />
<Compile Include="Interception\DelegateInterceptor.cs" />
<Compile Include="Logging\ILoggingConfig.cs" />
<Compile Include="Logging\LoggingConfig.cs" />
<Compile Include="Logging\LoggingInterceptor.cs" />
Expand Down
2 changes: 1 addition & 1 deletion src/_specs/Steps/Logging/MockVerificationSteps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public void VerifyLogBrokenExecution()
public void VerifyLogTrappedErrorExecution()
{
Mock<ILog> mockLog = _moq.Container.Mock<ILog>();
mockLog.Verify(log => log.Trace(It.IsAny<Action<FormatMessageHandler>>()), Times.Exactly(2));
mockLog.Verify(log => log.Trace(It.IsAny<Action<FormatMessageHandler>>()), Times.Exactly(1));
mockLog.Verify(log => log.Debug(It.IsAny<Action<FormatMessageHandler>>()), Times.Exactly(1));
mockLog.Verify(log => log.Info(It.IsAny<Action<FormatMessageHandler>>()), Times.Exactly(1));
mockLog.Verify(log => log.Error(It.IsAny<Action<FormatMessageHandler>>()), Times.Exactly(1));
Expand Down
1 change: 1 addition & 0 deletions src/code-patterns-master.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ImplicitlyCapturedClosure/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseIsOperator_002E2/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/FilterSettingsManager/CoverageFilterXml/@EntryValue">&lt;data&gt;&lt;IncludeFilters /&gt;&lt;ExcludeFilters /&gt;&lt;/data&gt;</s:String>
<s:String x:Key="/Default/FilterSettingsManager/AttributeFilterXml/@EntryValue">&lt;data /&gt;</s:String></wpf:ResourceDictionary>

0 comments on commit 69d4e91

Please sign in to comment.