using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Tango.Core;
using Tango.Core.DI;
using Tango.FSE.BL.Connectivity;
using Tango.Logging;
namespace Tango.FSE.BL
{
///
/// Represents a cascading/fallback data retrieval mechanism.
///
///
///
public class DataResolver : ExtendedObject where T : class
{
private static object diskLock = new object();
private static IConnectivityProvider _connectivity;
private Func _onlineAction;
private Func _webAction;
private Func _diskCacheAction;
private Func _memoryCacheAction;
private Action _onError;
private Action _onComplete;
private List _nodes;
private bool _throwOnError;
private String _callingName;
private bool _enableLogs = true;
///
/// Prevents a default instance of the class from being created.
///
private DataResolver()
{
if (_connectivity == null)
{
_connectivity = TangoIOC.Default.GetInstance();
}
_nodes = new List() { DataResolverNode.Online };
_throwOnError = true;
}
///
/// Executes this data resolver.
///
///
/// Could not execute data resolver with zero nodes.
public T Execute()
{
if (_enableLogs) LogManager.Log($"Executing data resolver for {_callingName}...");
T result = null;
var nodes = _nodes.Distinct().ToList();
if (nodes.Count == 0)
{
throw new InvalidOperationException("Could not execute data resolver with zero nodes.");
}
DataResolverContext context = new DataResolverContext();
foreach (var node in nodes)
{
String fallingBackText = String.Empty;
if (nodes.IndexOf(node) < nodes.Count - 1)
{
fallingBackText = $" Falling back to {nodes[nodes.IndexOf(node) + 1]}...";
}
if (node == DataResolverNode.Web)
{
try
{
if (_enableLogs) LogManager.Log($"Trying {_callingName} via web...");
result = ExecuteWebAction(context);
break;
}
catch (Exception ex)
{
if (_enableLogs) LogManager.Log($"{_callingName} via web failed with exception: {ex.FlattenMessage()}{fallingBackText}", LogCategory.Warning);
context.LastError = ex;
}
}
else if (node == DataResolverNode.Online)
{
try
{
if (_enableLogs) LogManager.Log($"Trying {_callingName} via online...");
result = ExecuteOnlineAction(context);
break;
}
catch (Exception ex)
{
if (_enableLogs) LogManager.Log($"{_callingName} via online failed with exception: {ex.FlattenMessage()}{fallingBackText}", LogCategory.Warning);
context.LastError = ex;
}
}
else if (node == DataResolverNode.DiskCache)
{
try
{
if (_enableLogs) LogManager.Log($"Trying {_callingName} via disk cache...");
result = _diskCacheAction.Invoke(context);
break;
}
catch (Exception ex)
{
if (_enableLogs) LogManager.Log($"{_callingName} via disk cache failed with exception: {ex.FlattenMessage()}{fallingBackText}");
context.LastError = ex;
}
}
else if (node == DataResolverNode.InMemoryCache)
{
try
{
if (_enableLogs) LogManager.Log($"Trying {_callingName} via in-memory cache...");
result = _memoryCacheAction.Invoke(context);
break;
}
catch (Exception ex)
{
if (_enableLogs) LogManager.Log($"{_callingName} via in-memory cache failed with exception: {ex.FlattenMessage()}{fallingBackText}");
context.LastError = ex;
}
}
if (context.Aborted)
{
if (_enableLogs) LogManager.Log($"{_callingName} aborted.");
break;
}
}
if (result != null)
{
if (_enableLogs) LogManager.Log($"{_callingName} completed successfully.");
_onComplete?.Invoke(result);
return result;
}
if (context.LastError == null)
{
context.LastError = new InvalidOperationException("No nodes were run successfully but could not retrieve the last error.");
}
if (_enableLogs) LogManager.Log(context.LastError, $"{_callingName} failed.");
_onError?.Invoke(context.LastError);
if (_throwOnError)
{
throw context.LastError;
}
else
{
return null;
}
}
private T ExecuteOnlineAction(DataResolverContext context)
{
if (!_connectivity.CheckOnline())
{
throw new InternetConnectionException();
}
return _onlineAction.Invoke(context);
}
private T ExecuteWebAction(DataResolverContext context)
{
if (!_connectivity.CheckOnline())
{
throw new InternetConnectionException();
}
return _webAction.Invoke(context);
}
#region Context
///
/// Represents a data resolver node context.
///
///
public class DataResolverContext
{
internal bool Aborted { get; set; }
///
/// Gets the last error, or maybe the reason that the current node is running.
///
public Exception LastError { get; internal set; }
internal DataResolverContext()
{
}
///
/// Aborts the entire data resolver.
///
public void AbortCascade()
{
Aborted = true;
}
}
#endregion
#region Builder
///
/// Build a instance.
///
///
public class Builder
{
private DataResolver _resolver;
///
/// Prevents a default instance of the class from being created.
///
private Builder()
{
_resolver = new DataResolver();
}
///
/// Creates a new builder instance.
///
///
public static Builder New()
{
var method = new StackTrace().GetFrame(1).GetMethod();
var cls = method.ReflectedType.Name;
var builder = new Builder();
builder._resolver._callingName = $"'{cls}.{method.Name}'";
return builder;
}
///
/// Specify the method for the online node.
///
/// The method.
///
public Builder Online(Func func)
{
_resolver._onlineAction = func;
return this;
}
///
/// Specify the method for the web client node.
///
/// The method.
///
public Builder Web(Func func)
{
_resolver._webAction = func;
return this;
}
///
/// Specify the method for the disk cache node.
///
/// The method.
///
public Builder DiskCache(Func func)
{
_resolver._diskCacheAction = func;
return this;
}
///
/// Specify the method for the in-memory node.
///
/// The method.
///
public Builder InMemoryCache(Func func)
{
_resolver._memoryCacheAction = func;
return this;
}
///
/// Configure the node cascade priority.
///
/// The nodes.
///
public Builder ConfigureCascade(params DataResolverNode[] nodes)
{
_resolver._nodes = nodes.ToList();
return this;
}
///
/// Called when all nodes have failed.
///
/// The on error.
///
public Builder OnError(Action onError)
{
_resolver._onError = onError;
return this;
}
///
/// Called when one of the nodes completed successful.
///
/// The on complete.
///
public Builder OnComplete(Action onComplete)
{
_resolver._onComplete = onComplete;
return this;
}
///
/// Specify whether to throw the last exception when all the nodes have failed.
///
/// if set to true [throw on error].
///
public Builder ThrowOnError(bool throwOnError)
{
_resolver._throwOnError = throwOnError;
return this;
}
///
/// Specify whether to enable data resolver logging.
///
/// if set to enables logging.
///
public Builder EnableLogs(bool enableLogs)
{
_resolver._enableLogs = enableLogs;
return this;
}
///
/// Specify a custom name for the calling service when logs are enabled.
///
/// Name of the calling service.
///
public Builder WithLogsCallingName(String callingName)
{
var method = new StackTrace().GetFrame(1).GetMethod();
_resolver._callingName = $"'{callingName}.{method.Name}'";
return this;
}
///
/// Builds this instance.
///
///
public DataResolver Build()
{
return _resolver;
}
///
/// Builds the executes the instance.
///
///
public T BuildExecute()
{
return Build().Execute();
}
///
/// Builds the executes the instance asynchronous.
///
///
public Task BuildExecuteAsync()
{
return Task.Factory.StartNew(() => Build().Execute());
}
}
#endregion
}
}