using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;
using Pfz.Extensions.AttributeExtensions;
using Pfz.Databasing.Filtering;
namespace Pfz.Databasing.Tiers
{
/// <summary>
/// Tier that allows for you to execute queries over a base inheritance level
/// and load all the "persisted childs" for such inheritance.
/// For example:
/// IIdRecord -> base interface for any object that uses only Id as it's key.
/// INamedRecord -> base interface for any object that has a Name, and is child
/// of IIdRecord.
/// IProduct -> persisted interface of products.
/// IPerson -> persisted interface of persons.
///
/// Products and Persons are named records. So, executing a query over
/// a INamedRecord must load all matching records from Products and Persons.
/// </summary>
public sealed class InheritanceTier:
TierBase
{
/// <summary>
/// Gets a value indicating if this tier was created in this application.
/// </summary>
public static bool IsInheritanceTierEnabled { get; private set; }
private static bool fCanRunInheritanceValidators = true;
/// <summary>
/// Gets or sets a value to allow/forbid the execution of Inheritance
/// validators in insert, update and delete.
/// This value is true by default, but only works if
/// the InheritanceTier and the BusinessRulesTier are created.
/// </summary>
public static bool CanRunInheritanceValidators
{
get
{
return fCanRunInheritanceValidators;
}
set
{
fCanRunInheritanceValidators = value;
}
}
private static readonly MethodInfo fFastLoadByPartialSql = typeof(IDatabaseManager).GetMethod("FastLoadByPartialSql");
/// <summary>
/// Gets the BaseManager.
/// While setting it, also sets the IsInheritanceTierEnabled static property.
/// </summary>
public override IDatabaseManager BaseManager
{
get
{
return base.BaseManager;
}
protected internal set
{
base.BaseManager = value;
IsInheritanceTierEnabled = true;
}
}
private static readonly MethodInfo fTryLoadByPrimaryKey = typeof(IDatabaseManager).GetMethod("TryLoadByPrimaryKey", new Type[]{typeof(IDatabaseConnection), typeof(object[])});
/// <summary>
/// Implements inheritance support for this loading method.
/// </summary>
public override T TryLoadByPrimaryKey<T>(IDatabaseConnection connection, params object[] primaryKeyValues)
{
var persistedTypes = p_GetPersistedTypes(typeof(T));
object[] parameters = new object[]{connection, primaryKeyValues};
T result = null;
foreach(var type in persistedTypes)
{
MethodInfo method = fTryLoadByPrimaryKey.MakeGenericMethod(type);
object possibleResult = method.Invoke(BaseManager, parameters);
if (possibleResult != null)
{
if (result != null)
throw new DatabaseException("More than one record found by the primary key.");
result = (T)possibleResult;
}
}
return result;
}
/// <summary>
/// Implements inheritance support for this loading method.
/// </summary>
public override IFastEnumerator<T> FastLoadByPartialSql<T>(IDatabaseConnection connection, string sql, params object[] parameterValues)
{
var persistedTypes = p_GetPersistedTypes(typeof(T));
return new p_LoadByPartialSqlFastEnumerator<T>(BaseManager, persistedTypes, connection, sql, parameterValues);
}
/// <summary>
/// Implements inheritance support for this loading method.
/// </summary>
public override IFastEnumerator<object[]> AdvancedLoad(IDatabaseConnection connection, Type initialRecordType, ReadOnlyCollection<PropertyInfoPath> selectPaths, FilterGroup filter)
{
var persistedTypes = p_GetPersistedTypes(initialRecordType);
return new p_AdvancedLoad(BaseManager, persistedTypes, connection, initialRecordType, selectPaths, filter);
}
private SearchMode fSearchMode;
/// <summary>
/// Gets or sets the search type for inheritors.
/// The default is SameAssembly.
/// </summary>
public SearchMode SearchMode
{
get
{
return fSearchMode;
}
set
{
if (value == fSearchMode)
return;
if (value == SearchMode.GivenAssemblies)
{
fGivenAssemblies = new HashSet<Assembly>();
AppDomain.CurrentDomain.AssemblyLoad += p_AssemblyLoad;
}
else
fGivenAssemblies = null;
if (value == SearchMode.AllLoadedAssemblies)
AppDomain.CurrentDomain.AssemblyLoad += p_AssemblyLoad;
else
AppDomain.CurrentDomain.AssemblyLoad -= p_AssemblyLoad;
fSearchMode = value;
}
}
private void p_AssemblyLoad(object sender, EventArgs e)
{
lock(fDictionaryLock)
fDictionary = null;
}
private HashSet<Assembly> fGivenAssemblies;
/// <summary>
/// Adds an assembly to search for inheritors, and sets the SearchType
/// to GivenAssemblies.
/// </summary>
public void AddSearchAssembly(Assembly assembly)
{
SearchMode = SearchMode.GivenAssemblies;
fGivenAssemblies.Add(assembly);
lock(fDictionaryLock)
fDictionary = null;
}
private object fDictionaryLock = new object();
private volatile Dictionary<Type, Type[]> fDictionary;
/// <summary>
/// Gets a read-only array of all persisted types based on the given base-type.
/// </summary>
public ReadOnlyCollection<Type> GetPersistedTypes(Type baseType)
{
if (baseType == null)
throw new ArgumentNullException("baseType");
Type[] result = p_GetPersistedTypes(baseType);
return new ReadOnlyCollection<Type>(result);
}
private Type[] p_GetPersistedTypes(Type baseType)
{
Type[] result;
lock (fDictionaryLock)
{
var dictionary = fDictionary;
if (dictionary != null)
if (dictionary.TryGetValue(baseType, out result))
return result;
}
if (!baseType.IsInterface)
throw new ArgumentException("baseType must be an interface type.", "baseType");
if (!typeof(IRecord).IsAssignableFrom(baseType))
throw new ArgumentException("baseType must be a IRecord.", "baseType");
List<Type> list = new List<Type>();
switch (fSearchMode)
{
case SearchMode.SameAssembly:
foreach (var type in baseType.Assembly.GetTypes())
p_AddAssembly(list, baseType, type);
break;
case SearchMode.AllLoadedAssemblies:
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
foreach (var type in assembly.GetTypes())
p_AddAssembly(list, baseType, type);
break;
case SearchMode.GivenAssemblies:
foreach (var assembly in fGivenAssemblies)
foreach (var type in assembly.GetTypes())
p_AddAssembly(list, baseType, type);
break;
}
result = list.ToArray();
lock (fDictionaryLock)
{
var dictionary = fDictionary;
if (dictionary == null)
{
dictionary = new Dictionary<Type, Type[]>();
fDictionary = dictionary;
}
dictionary[baseType] = result;
}
return result;
}
private static void p_AddAssembly(List<Type> list, Type baseType, Type type)
{
if (!type.IsInterface)
return;
if (!baseType.IsAssignableFrom(type))
return;
if (type.ContainsCustomAttribute<DatabasePersistedAttribute>())
list.Add(type);
}
private sealed class p_LoadByPartialSqlFastEnumerator<T>:
IFastEnumerator<T>
where
T: class, IRecord
{
private IDatabaseManager fManager;
private IEnumerator fPersistedTypesEnumerator;
private IDatabaseConnection fConnectionAndTransaction;
private string fSql;
private object[] fParameterValues;
IFastEnumerator fFastEnumerator;
private object[] fMethodParameters;
internal p_LoadByPartialSqlFastEnumerator(IDatabaseManager manager, Type[] persistedTypes, IDatabaseConnection connection, string sql, object[] parameterValues)
{
fManager = manager;
fConnectionAndTransaction = connection;
fSql = sql;
fParameterValues = parameterValues;
fPersistedTypesEnumerator = persistedTypes.GetEnumerator();
fMethodParameters = new object[]{connection, sql, parameterValues};
if (fPersistedTypesEnumerator.MoveNext())
{
Type type = (Type)fPersistedTypesEnumerator.Current;
object fastEnumerator = fFastLoadByPartialSql.MakeGenericMethod(type).Invoke(fManager, fMethodParameters);
fFastEnumerator = (IFastEnumerator)fastEnumerator;
}
}
public void Dispose()
{
var fastEnumerator = fFastEnumerator;
if (fFastEnumerator != null)
{
fFastEnumerator = null;
fastEnumerator.Dispose();
}
fPersistedTypesEnumerator = null;
}
public T GetNext()
{
if (fFastEnumerator == null)
return null;
var fastEnumerator = fFastEnumerator;
object result = fastEnumerator.GetNext();
while(result == null)
{
fFastEnumerator = null;
fastEnumerator.Dispose();
if (!fPersistedTypesEnumerator.MoveNext())
return null;
Type type = (Type)fPersistedTypesEnumerator.Current;
fastEnumerator = (IFastEnumerator)fFastLoadByPartialSql.MakeGenericMethod(type).Invoke(fManager, fMethodParameters);
fFastEnumerator = fastEnumerator;
result = fFastEnumerator.GetNext();
}
return (T)result;
}
object IFastEnumerator.GetNext()
{
return GetNext();
}
}
private sealed class p_AdvancedLoad:
IFastEnumerator<object[]>
{
private static readonly MethodInfo fAdvancedLoad = typeof(IDatabaseManager).GetMethod("AdvancedLoad");
private Type[] fPersistedTypes;
private int fPersistedTypesIndex;
private IDatabaseManager fBaseManager;
private IDatabaseConnection fConnection;
private Type fInitialRecordType;
private ReadOnlyCollection<PropertyInfoPath> fSelectPaths;
private FilterGroup fFilter;
private IFastEnumerator<object[]> fFastEnumerator;
private object[] fParameters;
internal p_AdvancedLoad(IDatabaseManager baseManager, Type[] persistedTypes, IDatabaseConnection connection, Type initialRecordType, ReadOnlyCollection<PropertyInfoPath> selectPaths, FilterGroup filter)
{
fBaseManager = baseManager;
fPersistedTypes = persistedTypes;
fConnection = connection;
fInitialRecordType = initialRecordType;
fSelectPaths = selectPaths;
fFilter = filter;
if (fPersistedTypes.Length == 0)
return;
Type initialPersistedType = fPersistedTypes[fPersistedTypesIndex];
fParameters = new object[]{connection, initialPersistedType, selectPaths, filter};
fFastEnumerator = (IFastEnumerator<object[]>)fAdvancedLoad.Invoke(fBaseManager, fParameters);
}
public void Dispose()
{
var fastEnumerator = fFastEnumerator;
if (fastEnumerator != null)
{
fFastEnumerator = null;
fastEnumerator.Dispose();
}
}
public object[] GetNext()
{
if (fFastEnumerator == null)
return null;
object[] result = fFastEnumerator.GetNext();
if (result != null)
return result;
while(true)
{
fFastEnumerator.Dispose();
fFastEnumerator = null;
fPersistedTypesIndex++;
if (fPersistedTypesIndex >= fPersistedTypes.Length)
return null;
Type type = fPersistedTypes[fPersistedTypesIndex];
fParameters[1] = type;
fFastEnumerator = (IFastEnumerator<object[]>)fAdvancedLoad.Invoke(fBaseManager, fParameters);
result = fFastEnumerator.GetNext();
if (result != null)
return result;
}
}
object IFastEnumerator.GetNext()
{
return GetNext();
}
}
}
}