guibranco/CrispyWaffle

View on GitHub
Src/CrispyWaffle/Cache/CacheManager.cs

Summary

Maintainability
C
1 day
Test Coverage
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using CrispyWaffle.Composition;
using CrispyWaffle.Log;

namespace CrispyWaffle.Cache
{
    /// <summary>
    /// The cache manager class.
    /// </summary>
    public static class CacheManager
    {
        /// <summary>
        /// The repositories
        /// </summary>
        private static readonly SortedList<int, ICacheRepository> _repositories =
            new SortedList<int, ICacheRepository>();

        /// <summary>
        /// The current priority
        /// </summary>
        private static int _currentPriority;

        /// <summary>
        /// The memory type
        /// </summary>
        private static readonly Type _memoryType = typeof(MemoryCacheRepository);

        /// <summary>
        /// The is memory repository in list
        /// </summary>
        private static bool _isMemoryRepositoryInList;

        /// <summary>
        /// Adds the repository.
        /// </summary>
        /// <typeparam name="TCacheRepository">The type of the i cache repository.</typeparam>
        /// <returns></returns>
        public static ICacheRepository AddRepository<TCacheRepository>()
            where TCacheRepository : ICacheRepository
        {
            var repository = ServiceLocator.Resolve<TCacheRepository>();
            AddRepository(repository, _currentPriority++);
            return repository;
        }

        /// <summary>
        /// Adds the repository to the repositories providers list.
        /// </summary>
        /// <param name="repository">The repository.</param>
        /// <returns></returns>
        public static ICacheRepository AddRepository(ICacheRepository repository)
        {
            AddRepository(repository, _currentPriority++);
            return repository;
        }

        /// <summary>
        /// Adds the repository.
        /// </summary>
        /// <typeparam name="TCacheRepository">The type of the i cache repository.</typeparam>
        /// <param name="priority">The priority.</param>
        /// <returns></returns>
        public static ICacheRepository AddRepository<TCacheRepository>(int priority)
            where TCacheRepository : ICacheRepository
        {
            var repository = ServiceLocator.Resolve<TCacheRepository>();
            AddRepository(repository, priority);
            return repository;
        }

        /// <summary>
        /// Adds the repository with the defined priority.
        /// </summary>
        /// <param name="repository">The repository.</param>
        /// <param name="priority">The priority.</param>
        /// <returns>Returns the priority with the repository was added</returns>
        public static int AddRepository(ICacheRepository repository, int priority)
        {
            while (true)
            {
                if (!_repositories.ContainsKey(priority))
                {
                    _repositories.Add(priority, repository);
                    LogConsumer.Trace(
                        "Adding cache repository of type {0} with priority {1}",
                        repository.GetType().FullName,
                        priority
                    );
                    if (repository.GetType() == _memoryType)
                    {
                        _isMemoryRepositoryInList = true;
                    }

                    return priority;
                }

                priority++;
            }
        }

        /// <summary>
        /// Stores the specified key.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value">The value.</param>
        /// <param name="key">The key.</param>
        public static void Set<T>(T value, [Localizable(false)] string key)
        {
            LogConsumer.Trace("Adding {0} to {1} cache repositories", key, _repositories.Count);
            foreach (var repository in _repositories.Values)
            {
                repository.Set(value, key);
            }
        }

        /// <summary>
        /// Sets the specified value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value">The value.</param>
        /// <param name="key">The key.</param>
        /// <param name="subKey">The sub key.</param>
        public static void Set<T>(
            T value,
            [Localizable(false)] string key,
            [Localizable(false)] string subKey
        )
        {
            LogConsumer.Trace(
                "Adding {0}/{2} to {1} cache repositories",
                key,
                _repositories.Count,
                subKey
            );
            foreach (var repository in _repositories.Values)
            {
                repository.Set(value, key, subKey);
            }
        }

        /// <summary>
        /// Sets the specified value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value">The value.</param>
        /// <param name="key">The key.</param>
        /// <param name="ttl">The TTL.</param>
        public static void Set<T>(T value, [Localizable(false)] string key, TimeSpan ttl)
        {
            LogConsumer.Trace(
                "Adding {0} to {1} cache repositories with TTL of {2:g}",
                key,
                _repositories.Count,
                ttl
            );
            foreach (var repository in _repositories.Values)
            {
                repository.Set(value, key, ttl);
            }
        }

        /// <summary>
        /// Sets to.
        /// </summary>
        /// <typeparam name="TCacheRepository">The type of the cache repository.</typeparam>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="value">The value.</param>
        /// <param name="key">The key.</param>
        /// <exception cref="InvalidOperationException"></exception>
        public static void SetTo<TCacheRepository, TValue>(
            TValue value,
            [Localizable(false)] string key
        )
        {
            var type = typeof(TCacheRepository);
            LogConsumer.Trace("Adding {0} to repository of type {1}", key, type.FullName);
            var repository = _repositories.SingleOrDefault(r => type == r.Value.GetType()).Value;
            if (repository == null)
            {
                throw new InvalidOperationException(
                    $"The repository of type {type.FullName} isn't available in the repositories providers list"
                );
            }

            repository.Set(value, key);
        }

        /// <summary>
        /// Sets to.
        /// </summary>
        /// <typeparam name="TCacheRepository">The type of the cache repository.</typeparam>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="value">The value.</param>
        /// <param name="key">The key.</param>
        /// <param name="subKey">The sub key.</param>
        /// <exception cref="InvalidOperationException"></exception>
        public static void SetTo<TCacheRepository, TValue>(
            TValue value,
            [Localizable(false)] string key,
            [Localizable(false)] string subKey
        )
        {
            var type = typeof(TCacheRepository);
            LogConsumer.Trace(
                "Adding {0}/{2} to repository of type {1}",
                key,
                type.FullName,
                subKey
            );
            var repository = _repositories.SingleOrDefault(r => type == r.Value.GetType()).Value;
            if (repository == null)
            {
                throw new InvalidOperationException(
                    $"The repository of type {type.FullName} isn't available in the repositories providers list"
                );
            }

            repository.Set(value, key, subKey);
        }

        /// <summary>
        /// Sets to.
        /// </summary>
        /// <typeparam name="TCacheRepository">The type of the cache repository.</typeparam>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="value">The value.</param>
        /// <param name="key">The key.</param>
        /// <param name="ttl">The TTL.</param>
        /// <exception cref="InvalidOperationException"></exception>
        public static void SetTo<TCacheRepository, TValue>(
            TValue value,
            [Localizable(false)] string key,
            TimeSpan ttl
        )
        {
            var type = typeof(TCacheRepository);
            LogConsumer.Trace(
                "Adding {0} to repository of type {1} with TTL of {2:g}",
                key,
                type.FullName,
                ttl
            );
            var repository = _repositories.SingleOrDefault(r => type == r.Value.GetType()).Value;
            if (repository == null)
            {
                throw new InvalidOperationException(
                    $"The repository of type {type.FullName} isn't available in the repositories providers list"
                );
            }

            repository.Set(value, key, ttl);
        }

        /// <summary>
        /// Sets to.
        /// </summary>
        /// <typeparam name="TCacheRepository">The type of the cache repository.</typeparam>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="value">The value.</param>
        /// <param name="key">The key.</param>
        /// <param name="subKey">The sub key.</param>
        /// <param name="ttl">The TTL.</param>
        /// <exception cref="InvalidOperationException"></exception>
        public static void SetTo<TCacheRepository, TValue>(
            TValue value,
            [Localizable(false)] string key,
            [Localizable(false)] string subKey,
            TimeSpan ttl
        )
        {
            var type = typeof(TCacheRepository);
            LogConsumer.Trace(
                "Adding {0}/{2} to repository of type {1} with TTL of {2:g}",
                key,
                type.FullName,
                ttl,
                subKey
            );
            var repository = _repositories.SingleOrDefault(r => type == r.Value.GetType()).Value;
            if (repository == null)
            {
                throw new InvalidOperationException(
                    $"The repository of type {type.FullName} isn't available in the repositories providers list"
                );
            }

            repository.Set(value, key, subKey);
        }

        /// <summary>
        /// Gets the object with the specified key.
        /// </summary>
        /// <typeparam name="T">The type of object (the object will be cast to this type)</typeparam>
        /// <param name="key">The key.</param>
        /// <returns>The object as <typeparamref name="T"/></returns>
        /// <exception cref="InvalidOperationException">Throws when the object with the specified key doesn't exists</exception>
        public static T Get<T>([Localizable(false)] string key)
        {
            LogConsumer.Trace(
                "Getting {0} from any of {1} cache repositories",
                key,
                _repositories.Count
            );
            foreach (var repository in _repositories.Values)
            {
                if (!repository.TryGet(key, out T value))
                {
                    continue;
                }

                if (
                    _isMemoryRepositoryInList
                    && repository.GetType() != typeof(MemoryCacheRepository)
                )
                {
                    SetTo<MemoryCacheRepository, T>(value, key);
                }

                return value;
            }

            throw new InvalidOperationException($"Unable to get the item with key {key}");
        }

        /// <summary>
        /// Gets the specified key.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">The key.</param>
        /// <param name="subKey">The sub key.</param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public static T Get<T>([Localizable(false)] string key, [Localizable(false)] string subKey)
        {
            LogConsumer.Trace(
                "Getting {0}/{2} from any of {1} cache repositories",
                key,
                _repositories.Count,
                subKey
            );
            foreach (var repository in _repositories.Values)
            {
                if (!repository.TryGet(key, subKey, out T value))
                {
                    continue;
                }

                if (
                    _isMemoryRepositoryInList
                    && repository.GetType() != typeof(MemoryCacheRepository)
                )
                {
                    SetTo<MemoryCacheRepository, T>(value, key, subKey);
                }

                return value;
            }

            throw new InvalidOperationException(
                $"Unable to get the item with key {key} and sub key {subKey}"
            );
        }

        /// <summary>
        ///  Gets the object with the specified key from the specified repository type, if the repository is in the repositories providers list.
        /// </summary>
        /// <typeparam name="TCacheRepository">The type of the cache repository.</typeparam>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="key">The key.</param>
        /// <returns>The <typeparamref name="TValue"/></returns>
        /// <exception cref="InvalidOperationException">Throws when the repository is not in the repositories providers list or the key isn't available at that repository</exception>
        public static TValue GetFrom<TCacheRepository, TValue>([Localizable(false)] string key)
        {
            var type = typeof(TCacheRepository);
            LogConsumer.Trace("Getting {0} from repository {1}", key, type.FullName);
            var repository = _repositories.SingleOrDefault(r => type == r.Value.GetType()).Value;
            if (repository == null)
            {
                throw new InvalidOperationException(
                    $"The repository of type {type.FullName} isn't available in the repositories providers list"
                );
            }

            return repository.Get<TValue>(key);
        }

        /// <summary>
        /// Gets from.
        /// </summary>
        /// <typeparam name="TCacheRepository">The type of the cache repository.</typeparam>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="key">The key.</param>
        /// <param name="subKey">The sub key.</param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public static TValue GetFrom<TCacheRepository, TValue>(
            [Localizable(false)] string key,
            [Localizable(false)] string subKey
        )
        {
            var type = typeof(TCacheRepository);
            LogConsumer.Trace("Getting {0}/{2} from repository {1}", key, type.FullName, subKey);
            var repository = _repositories.SingleOrDefault(r => type == r.Value.GetType()).Value;
            if (repository == null)
            {
                throw new InvalidOperationException(
                    $"The repository of type {type.FullName} isn't available in the repositories providers list"
                );
            }

            return repository.Get<TValue>(key, subKey);
        }

        /// <summary>
        /// Tries to get a value based on its key, if exists in any repository return true, else false.
        /// The out parameter value is the object requested.
        /// </summary>
        /// <typeparam name="T">The type of object (the object will be cast to this type)</typeparam>
        /// <param name="key">The key.</param>
        /// <param name="value">The value.</param>
        /// <returns>Returns <b>True</b> if the object with the key exists, false otherwise</returns>
        public static bool TryGet<T>([Localizable(false)] string key, out T value)
        {
            LogConsumer.Trace(
                "Trying to get {0} from any of {1} repositories",
                key,
                _repositories.Count
            );
            value = default;
            foreach (var repository in _repositories.Values)
            {
                if (!repository.TryGet(key, out value))
                {
                    continue;
                }

                if (
                    _isMemoryRepositoryInList
                    && repository.GetType() != typeof(MemoryCacheRepository)
                )
                {
                    SetTo<MemoryCacheRepository, T>(value, key);
                }

                return true;
            }

            return false;
        }

        /// <summary>
        /// Tries the get.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">The key.</param>
        /// <param name="subKey">The sub key.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static bool TryGet<T>(
            [Localizable(false)] string key,
            [Localizable(false)] string subKey,
            out T value
        )
        {
            LogConsumer.Trace(
                "Trying to get {0}/{2} from any of {1} repositories",
                key,
                _repositories.Count,
                subKey
            );
            value = default;
            foreach (var repository in _repositories.Values)
            {
                if (!repository.TryGet(key, subKey, out value))
                {
                    continue;
                }

                if (
                    _isMemoryRepositoryInList
                    && repository.GetType() != typeof(MemoryCacheRepository)
                )
                {
                    SetTo<MemoryCacheRepository, T>(value, key, subKey);
                }

                return true;
            }

            return false;
        }

        /// <summary>
        /// TTLs the specified key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        public static TimeSpan TTL([Localizable(false)] string key)
        {
            LogConsumer.Trace(
                "Trying to get TTL of key {0} from {1} repositories",
                key,
                _repositories.Count
            );
            var result = new TimeSpan(0);
            foreach (var repository in _repositories.Values)
            {
                var currentResult = repository.TTL(key);
                if (currentResult == result)
                {
                    continue;
                }

                return currentResult;
            }

            return new TimeSpan(0);
        }

        /// <summary>
        /// Removes the specified key.
        /// </summary>
        /// <param name="key">The key.</param>
        public static void Remove([Localizable(false)] string key)
        {
            LogConsumer.Trace("Removing key {0} from {1} repositories", key, _repositories.Count);
            foreach (var repository in _repositories.Values)
            {
                repository.Remove(key);
            }
        }

        /// <summary>
        /// Removes the specified key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="subKey">The sub key.</param>
        public static void Remove(
            [Localizable(false)] string key,
            [Localizable(false)] string subKey
        )
        {
            LogConsumer.Trace(
                "Removing key {0} and sub key {2} from {1} repositories",
                key,
                _repositories.Count,
                subKey
            );
            foreach (var repository in _repositories.Values)
            {
                repository.Remove(key, subKey);
            }
        }

        /// <summary>
        /// Removes from.
        /// </summary>
        /// <typeparam name="TCacheRepository">The type of the cache repository.</typeparam>
        /// <param name="key">The key.</param>
        /// <exception cref="InvalidOperationException"></exception>
        public static void RemoveFrom<TCacheRepository>([Localizable(false)] string key)
        {
            var type = typeof(TCacheRepository);
            LogConsumer.Trace("Removing key {0} from {1} repository", key, _repositories.Count);
            var repository = _repositories.SingleOrDefault(r => type == r.Value.GetType()).Value;
            if (repository == null)
            {
                throw new InvalidOperationException(
                    $"The repository of type {type.FullName} isn't available in the repositories providers list"
                );
            }

            repository.Remove(key);
        }

        /// <summary>
        /// Removes from.
        /// </summary>
        /// <typeparam name="TCacheRepository">The type of the cache repository.</typeparam>
        /// <param name="key">The key.</param>
        /// <param name="subKey">The sub key.</param>
        /// <exception cref="InvalidOperationException"></exception>
        public static void RemoveFrom<TCacheRepository>(
            [Localizable(false)] string key,
            [Localizable(false)] string subKey
        )
        {
            var type = typeof(TCacheRepository);
            LogConsumer.Trace(
                "Removing key {0} and sub key {2} from {1} repository",
                key,
                _repositories.Count,
                subKey
            );
            var repository = _repositories.SingleOrDefault(r => type == r.Value.GetType()).Value;
            if (repository == null)
            {
                throw new InvalidOperationException(
                    $"The repository of type {type.FullName} isn't available in the repositories providers list"
                );
            }

            repository.Remove(key, subKey);
        }
    }
}