From e0a7be723786bdde4b5bebddf8c87a6be80b82b6 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Sun, 9 Dec 2012 09:01:00 +0500 Subject: [PATCH] Created RepositoryInstanceResolver/Factory to replace the configuration based instantiation of Repo's. Added unit tests to support resoling each instance, fixed CodeFirstTests to ensure that the base.TearDown() method is called. Changed the BaseMapper's to internal. --- src/Umbraco.Core/CoreBootManager.cs | 4 + .../Persistence/Mappers/BaseMapper.cs | 2 +- .../Mappers/DictionaryTranslationMapper.cs | 2 +- .../Persistence/RepositoryInstanceFactory.cs | 140 ++++++++++++++++++ .../Persistence/RepositoryInstanceResolver.cs | 46 ++++++ .../RepositoryInstanceTypeAttribute.cs | 23 +++ .../Persistence/RepositoryResolver.cs | 81 +++++----- src/Umbraco.Core/Umbraco.Core.csproj | 3 + src/Umbraco.Tests/CodeFirst/CodeFirstTests.cs | 3 + .../RepositoryInstanceResolverTests.cs | 52 +++++++ .../Persistence/RepositoryResolverTests.cs | 16 +- .../TestHelpers/BaseDatabaseFactoryTest.cs | 7 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + 13 files changed, 338 insertions(+), 42 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/RepositoryInstanceFactory.cs create mode 100644 src/Umbraco.Core/Persistence/RepositoryInstanceResolver.cs create mode 100644 src/Umbraco.Core/Persistence/RepositoryInstanceTypeAttribute.cs create mode 100644 src/Umbraco.Tests/Persistence/RepositoryInstanceResolverTests.cs diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs index b4122cbd8e..0ac6d18cd7 100644 --- a/src/Umbraco.Core/CoreBootManager.cs +++ b/src/Umbraco.Core/CoreBootManager.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using Umbraco.Core.Logging; using Umbraco.Core.ObjectResolution; +using Umbraco.Core.Persistence; using Umbraco.Core.PropertyEditors; namespace Umbraco.Core @@ -96,6 +97,9 @@ namespace Umbraco.Core /// protected virtual void InitializeResolvers() { + RepositoryInstanceResolver.Current = new RepositoryInstanceResolver( + new RepositoryInstanceFactory()); + CacheRefreshersResolver.Current = new CacheRefreshersResolver( PluginManager.Current.ResolveCacheRefreshers()); diff --git a/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs b/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs index fda1b4d7f1..86d5e026d8 100644 --- a/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Mappers { - public abstract class BaseMapper + internal abstract class BaseMapper { internal abstract void BuildMap(); diff --git a/src/Umbraco.Core/Persistence/Mappers/DictionaryTranslationMapper.cs b/src/Umbraco.Core/Persistence/Mappers/DictionaryTranslationMapper.cs index fe8e1a3c83..7b0409c3b3 100644 --- a/src/Umbraco.Core/Persistence/Mappers/DictionaryTranslationMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/DictionaryTranslationMapper.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Persistence.Mappers /// Represents a to DTO mapper used to translate the properties of the public api /// implementation to that of the database's DTO as sql: [tableName].[columnName]. /// - public class DictionaryTranslationMapper : BaseMapper + internal class DictionaryTranslationMapper : BaseMapper { private static readonly ConcurrentDictionary PropertyInfoCache = new ConcurrentDictionary(); diff --git a/src/Umbraco.Core/Persistence/RepositoryInstanceFactory.cs b/src/Umbraco.Core/Persistence/RepositoryInstanceFactory.cs new file mode 100644 index 0000000000..6f8212bc4b --- /dev/null +++ b/src/Umbraco.Core/Persistence/RepositoryInstanceFactory.cs @@ -0,0 +1,140 @@ +using Umbraco.Core.Persistence.Caching; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Persistence +{ + /// + /// Used to instantiate each repository type + /// + public class RepositoryInstanceFactory + { + + [RepositoryInstanceType(typeof(IUserTypeRepository))] + internal virtual IUserTypeRepository CreateUserTypeRepository(IUnitOfWork uow) + { + return new UserTypeRepository( + uow, + NullCacheProvider.Current); + + } + + [RepositoryInstanceType(typeof(IUserRepository))] + internal virtual IUserRepository CreateUserRepository(IUnitOfWork uow) + { + return new UserRepository( + uow, + NullCacheProvider.Current, + CreateUserTypeRepository(uow)); + + } + + [RepositoryInstanceType(typeof(IContentRepository))] + public virtual IContentRepository CreateContentRepository(IUnitOfWork uow) + { + return new ContentRepository( + uow, + RuntimeCacheProvider.Current, + CreateContentTypeRepository(uow), + CreateTemplateRepository(uow)); + } + + [RepositoryInstanceType(typeof(IContentTypeRepository))] + public virtual IContentTypeRepository CreateContentTypeRepository(IUnitOfWork uow) + { + return new ContentTypeRepository( + uow, + InMemoryCacheProvider.Current, + new TemplateRepository(uow, NullCacheProvider.Current)); + } + + [RepositoryInstanceType(typeof(IDataTypeDefinitionRepository))] + public virtual IDataTypeDefinitionRepository CreateDataTypeDefinitionRepository(IUnitOfWork uow) + { + return new DataTypeDefinitionRepository( + uow, + NullCacheProvider.Current); + } + + //TODO: Shouldn't IDictionaryRepository be public? + [RepositoryInstanceType(typeof(IDictionaryRepository))] + internal virtual IDictionaryRepository CreateDictionaryRepository(IUnitOfWork uow) + { + return new DictionaryRepository( + uow, + InMemoryCacheProvider.Current, + CreateLanguageRepository(uow)); + } + + [RepositoryInstanceType(typeof(ILanguageRepository))] + public virtual ILanguageRepository CreateLanguageRepository(IUnitOfWork uow) + { + return new LanguageRepository( + uow, + InMemoryCacheProvider.Current); + } + + //TODO: Shouldn't IMacroRepository be public? + [RepositoryInstanceType(typeof(IMacroRepository))] + internal virtual IMacroRepository CreateMacroRepository(IUnitOfWork uow) + { + return new MacroRepository( + uow, + InMemoryCacheProvider.Current); + } + + [RepositoryInstanceType(typeof(IMediaRepository))] + public virtual IMediaRepository CreateMediaRepository(IUnitOfWork uow) + { + return new MediaRepository( + uow, + RuntimeCacheProvider.Current, + CreateMediaTypeRepository(uow)); + } + + [RepositoryInstanceType(typeof(IMediaTypeRepository))] + public virtual IMediaTypeRepository CreateMediaTypeRepository(IUnitOfWork uow) + { + return new MediaTypeRepository( + uow, + InMemoryCacheProvider.Current); + } + + [RepositoryInstanceType(typeof(IRelationRepository))] + public virtual IRelationRepository CreateRelationRepository(IUnitOfWork uow) + { + return new RelationRepository( + uow, + NullCacheProvider.Current, + CreateRelationTypeRepository(uow)); + } + + [RepositoryInstanceType(typeof(IRelationTypeRepository))] + public virtual IRelationTypeRepository CreateRelationTypeRepository(IUnitOfWork uow) + { + return new RelationTypeRepository( + uow, + NullCacheProvider.Current); + } + + [RepositoryInstanceType(typeof(IScriptRepository))] + public virtual IScriptRepository CreateScriptRepository(IUnitOfWork uow) + { + return new ScriptRepository(uow); + } + + [RepositoryInstanceType(typeof(IStylesheetRepository))] + public virtual IStylesheetRepository CreateStylesheetRepository(IUnitOfWork uow) + { + return new StylesheetRepository(uow); + } + + [RepositoryInstanceType(typeof(ITemplateRepository))] + public virtual ITemplateRepository CreateTemplateRepository(IUnitOfWork uow) + { + return new TemplateRepository(uow, NullCacheProvider.Current); + } + + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/RepositoryInstanceResolver.cs b/src/Umbraco.Core/Persistence/RepositoryInstanceResolver.cs new file mode 100644 index 0000000000..e0c12ae299 --- /dev/null +++ b/src/Umbraco.Core/Persistence/RepositoryInstanceResolver.cs @@ -0,0 +1,46 @@ +using System; +using System.Linq; +using System.Reflection; +using Umbraco.Core.ObjectResolution; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Persistence +{ + /// + /// A resolver used to return the current implementation of the RepositoryInstanceFactory + /// + internal class RepositoryInstanceResolver : SingleObjectResolverBase + { + internal RepositoryInstanceResolver(RepositoryInstanceFactory registrar) + : base(registrar) + { + } + + /// + /// Return the repository based on the type + /// + /// + /// + /// + internal TRepository ResolveByType(IUnitOfWork unitOfWork) + { + //TODO: REMOVE all of these binding flags once the IDictionaryRepository, IMacroRepository are public! As this probably + // wont work in medium trust! + var createMethod = this.Value.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public) + .First(x => x.GetCustomAttributes(true).OfType() + .Any(instance => instance.InterfaceType.IsType())); + if (createMethod.GetParameters().Count() != 1 + || !createMethod.GetParameters().Single().ParameterType.IsType()) + { + throw new FormatException("The method " + createMethod.Name + " must only contain one parameter of type " + typeof(IUnitOfWork).FullName); + } + if (!createMethod.ReturnType.IsType()) + { + throw new FormatException("The method " + createMethod.Name + " must return the type " + typeof(TRepository).FullName); + } + + return (TRepository) createMethod.Invoke(this.Value, new object[] {unitOfWork}); + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/RepositoryInstanceTypeAttribute.cs b/src/Umbraco.Core/Persistence/RepositoryInstanceTypeAttribute.cs new file mode 100644 index 0000000000..4dc7fddcf1 --- /dev/null +++ b/src/Umbraco.Core/Persistence/RepositoryInstanceTypeAttribute.cs @@ -0,0 +1,23 @@ +using System; + +namespace Umbraco.Core.Persistence +{ + /// + /// Used to decorate each method of the class RepositoryInstanceFactory (or derived classes) to inform the system of what + /// type of Repository the method will be returning. + /// + /// + /// The reason for this attribute is because the RepositoryInstanceFactory (or derived classes) might contain methods multiple + /// methods that return the interface repository being asked for so we cannot simply rely on the return type of the methods. + /// + [AttributeUsage(AttributeTargets.Method)] + public class RepositoryInstanceTypeAttribute : Attribute + { + public Type InterfaceType { get; private set; } + + public RepositoryInstanceTypeAttribute(Type interfaceType) + { + InterfaceType = interfaceType; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/RepositoryResolver.cs b/src/Umbraco.Core/Persistence/RepositoryResolver.cs index 50bd73dfd3..ce1a6ae31e 100644 --- a/src/Umbraco.Core/Persistence/RepositoryResolver.cs +++ b/src/Umbraco.Core/Persistence/RepositoryResolver.cs @@ -10,7 +10,7 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence { - internal class RepositoryResolver + internal class RepositoryResolver { private static readonly ConcurrentDictionary Repositories = new ConcurrentDictionary(); @@ -32,47 +32,52 @@ namespace Umbraco.Core.Persistence string interfaceShortName = typeof(TRepository).Name; string entityTypeName = typeof(TEntity).Name; - //Check if the repository has already been created and is in the cache - if (Repositories.ContainsKey(interfaceShortName)) - { - repository = (TRepository)Repositories[interfaceShortName]; - if (unitOfWork != null && (typeof(IRepository).IsInstanceOfType(repository))) - { - repository.SetUnitOfWork(unitOfWork); - } - return repository; - } - - var settings = Infrastructure.Instance.Repositories; + //Check if the repository has already been created and is in the cache + //SD: Changed to TryGetValue as this will be a bit quicker since if we do a ContainsKey and then resolve, + // the underlying ConcurrentDictionary does this twice and thus does two locks. + object repositoryObject; + if (Repositories.TryGetValue(interfaceShortName, out repositoryObject)) + { + repository = (TRepository)repositoryObject; + if (unitOfWork != null && (typeof(IRepository).IsInstanceOfType(repository))) + { + repository.SetUnitOfWork(unitOfWork); + } + return repository; + } - Type repositoryType = null; + repository = RepositoryInstanceResolver.Current.ResolveByType(unitOfWork); - //Check if a valid interfaceShortName was passed in - if (settings.Repository.ContainsKey(interfaceShortName)) - { - repositoryType = Type.GetType(settings.Repository[interfaceShortName].RepositoryFullTypeName); - } - else - { - foreach (Repository element in settings.Repository) - { - if (element.InterfaceShortTypeName.Contains(entityTypeName)) - { - repositoryType = Type.GetType(settings.Repository[element.InterfaceShortTypeName].RepositoryFullTypeName); - break; - } - } - } + //var settings = Infrastructure.Instance.Repositories; - //If the repository type is null we should stop and throw an exception - if (repositoryType == null) - { - throw new Exception(string.Format("No repository matching the Repository interface '{0}' or Entity type '{1}' could be resolved", - interfaceShortName, entityTypeName)); - } + //Type repositoryType = null; - //Resolve the repository with its constructor dependencies - repository = Resolve(repositoryType, unitOfWork, interfaceShortName) as TRepository; + ////Check if a valid interfaceShortName was passed in + //if (settings.Repository.ContainsKey(interfaceShortName)) + //{ + // repositoryType = Type.GetType(settings.Repository[interfaceShortName].RepositoryFullTypeName); + //} + //else + //{ + // foreach (Repository element in settings.Repository) + // { + // if (element.InterfaceShortTypeName.Contains(entityTypeName)) + // { + // repositoryType = Type.GetType(settings.Repository[element.InterfaceShortTypeName].RepositoryFullTypeName); + // break; + // } + // } + //} + + ////If the repository type is null we should stop and throw an exception + //if (repositoryType == null) + //{ + // throw new Exception(string.Format("No repository matching the Repository interface '{0}' or Entity type '{1}' could be resolved", + // interfaceShortName, entityTypeName)); + //} + + ////Resolve the repository with its constructor dependencies + //repository = Resolve(repositoryType, unitOfWork, interfaceShortName) as TRepository; //Add the new repository instance to the cache Repositories.AddOrUpdate(interfaceShortName, repository, (x, y) => repository); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 3717354a71..710f94fbbf 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -383,6 +383,9 @@ + + + diff --git a/src/Umbraco.Tests/CodeFirst/CodeFirstTests.cs b/src/Umbraco.Tests/CodeFirst/CodeFirstTests.cs index 348b3ba254..659243a258 100644 --- a/src/Umbraco.Tests/CodeFirst/CodeFirstTests.cs +++ b/src/Umbraco.Tests/CodeFirst/CodeFirstTests.cs @@ -228,8 +228,11 @@ namespace Umbraco.Tests.CodeFirst [TearDown] public override void TearDown() { + DatabaseContext.Database.Dispose(); + base.TearDown(); + //reset the app context DataTypesResolver.Reset(); ApplicationContext.Current = null; diff --git a/src/Umbraco.Tests/Persistence/RepositoryInstanceResolverTests.cs b/src/Umbraco.Tests/Persistence/RepositoryInstanceResolverTests.cs new file mode 100644 index 0000000000..f48775e7d1 --- /dev/null +++ b/src/Umbraco.Tests/Persistence/RepositoryInstanceResolverTests.cs @@ -0,0 +1,52 @@ +using System; +using System.Reflection; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Tests.Persistence +{ + [TestFixture] + public class RepositoryInstanceResolverTests + { + [SetUp] + public void Setup() + { + RepositoryInstanceResolver.Current = new RepositoryInstanceResolver( + new RepositoryInstanceFactory()); + } + + [TearDown] + public void Teardown() + { + RepositoryInstanceResolver.Reset(); + } + + [TestCase(typeof(IUserTypeRepository))] + [TestCase(typeof(IUserRepository))] + [TestCase(typeof(IContentRepository))] + [TestCase(typeof(IMediaRepository))] + [TestCase(typeof(IMediaTypeRepository))] + [TestCase(typeof(IContentTypeRepository))] + [TestCase(typeof(IDataTypeDefinitionRepository))] + [TestCase(typeof(IDictionaryRepository))] + [TestCase(typeof(ILanguageRepository))] + [TestCase(typeof(IMacroRepository))] + [TestCase(typeof(IRelationRepository))] + [TestCase(typeof(IRelationTypeRepository))] + [TestCase(typeof(IScriptRepository))] + [TestCase(typeof(IStylesheetRepository))] + [TestCase(typeof(ITemplateRepository))] + public void ResolveRepository(Type repoType) + { + var method = typeof (RepositoryInstanceResolver).GetMethod("ResolveByType", BindingFlags.NonPublic | BindingFlags.Instance); + var gMethod = method.MakeGenericMethod(repoType); + var repo = gMethod.Invoke(RepositoryInstanceResolver.Current, new object[] {new PetaPocoUnitOfWork()}); + Assert.IsNotNull(repo); + Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(repoType, repo.GetType())); + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/RepositoryResolverTests.cs b/src/Umbraco.Tests/Persistence/RepositoryResolverTests.cs index 90fee938bd..777ca27a93 100644 --- a/src/Umbraco.Tests/Persistence/RepositoryResolverTests.cs +++ b/src/Umbraco.Tests/Persistence/RepositoryResolverTests.cs @@ -7,9 +7,23 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Tests.Persistence { - [TestFixture] + [TestFixture] public class RepositoryResolverTests { + + [SetUp] + public void Setup() + { + RepositoryInstanceResolver.Current = new RepositoryInstanceResolver( + new RepositoryInstanceFactory()); + } + + [TearDown] + public void Teardown() + { + RepositoryInstanceResolver.Reset(); + } + [Test] public void Can_Resolve_All_Repositories() { diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index 7198fe502f..a77cdb35aa 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -35,7 +35,10 @@ namespace Umbraco.Tests.TestHelpers AppDomain.CurrentDomain.SetData("DataDirectory", path); UmbracoSettings.UseLegacyXmlSchema = false; - + + RepositoryInstanceResolver.Current = new RepositoryInstanceResolver( + new RepositoryInstanceFactory()); + //Delete database file before continueing string filePath = string.Concat(path, "\\UmbracoPetaPocoTests.sdf"); if (File.Exists(filePath)) @@ -74,6 +77,8 @@ namespace Umbraco.Tests.TestHelpers ServiceContext = null; Resolution.IsFrozen = false; + RepositoryInstanceResolver.Reset(); + string path = TestHelper.CurrentAssemblyDirectory; AppDomain.CurrentDomain.SetData("DataDirectory", null); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 7ae3071121..160221e426 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -167,6 +167,7 @@ +