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 @@ +