/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; using DotCMIS.Binding.Impl; using DotCMIS.Data; using DotCMIS.Util; namespace DotCMIS.Binding { // --- base cache implementation --- internal interface IBindingCache { void Initialize(string[] cacheLevelConfig); void Put(string[] keys, object value); object Get(string[] keys); void Remove(string[] keys); int Check(string[] keys); void Lock(); void Unlock(); } internal interface IBindingCacheLevel { void Initialize(IDictionary cacheLevelConfig); object this[string key] { get; set; } void Remove(string key); } internal class Cache : IBindingCache { private IList cacheLevels; private IList> cacheLevelParameters; private IBindingCacheLevel root; private string name; private object cacheLock = new object(); public Cache() : this("Cache") { } public Cache(string name) { this.name = name; } public void Initialize(string[] cacheLevelConfig) { if (cacheLevels != null) { throw new ApplicationException("Cache already initialize!"); } if ((cacheLevelConfig == null) || (cacheLevelConfig.Length == 0)) { throw new ArgumentException("Cache config must not be empty!"); } Lock(); try { cacheLevels = new List(); cacheLevelParameters = new List>(); // build level lists foreach (string config in cacheLevelConfig) { int x = config.IndexOf(' '); if (x == -1) { AddLevel(config, null); } else { AddLevel(config.Substring(0, x), config.Substring(x + 1)); } } root = CreateCacheLevel(0); } finally { Unlock(); } } public void Put(string[] keys, object value) { if (keys == null) { return; } if (keys.Length != cacheLevels.Count) { throw new ArgumentException("Wrong number of keys!"); } Lock(); try { IBindingCacheLevel cacheLevel = root; // follow the branch for (int i = 0; i < keys.Length - 1; i++) { object level = cacheLevel[keys[i]]; // does the branch exist? if (level == null) { level = CreateCacheLevel(i + 1); cacheLevel[keys[i]] = level; } // next level cacheLevel = (IBindingCacheLevel)level; } cacheLevel[keys[keys.Length - 1]] = value; if (DotCMISDebug.DotCMISSwitch.TraceVerbose) { Trace.WriteLine(name + ": put [" + GetFormattedKeys(keys) + "] = " + value); } } finally { Unlock(); } } public object Get(string[] keys) { if (keys == null) { return null; } if (keys.Length != cacheLevels.Count) { throw new ArgumentException("Wrong number of keys!"); } object result = null; Lock(); try { IBindingCacheLevel cacheLevel = root; // follow the branch for (int i = 0; i < keys.Length - 1; i++) { object level = cacheLevel[keys[i]]; // does the branch exist? if (level == null) { return null; } // next level cacheLevel = (IBindingCacheLevel)level; } // get the value result = cacheLevel[keys[keys.Length - 1]]; } finally { Unlock(); } return result; } public void Remove(string[] keys) { if (keys == null) { return; } Lock(); try { IBindingCacheLevel cacheLevel = root; // follow the branch for (int i = 0; i < keys.Length - 1; i++) { object level = cacheLevel[keys[i]]; // does the branch exist? if (level == null) { return; } // next level cacheLevel = (IBindingCacheLevel)level; } cacheLevel.Remove(keys[keys.Length - 1]); if (DotCMISDebug.DotCMISSwitch.TraceVerbose) { Trace.WriteLine(name + ": removed [" + GetFormattedKeys(keys) + "]"); } } finally { Unlock(); } } public int Check(string[] keys) { if (keys == null) { return -1; } Lock(); try { IBindingCacheLevel cacheLevel = root; // follow the branch for (int i = 0; i < keys.Length - 1; i++) { object level = cacheLevel[keys[i]]; // does the branch exist? if (level == null) { return i; } // next level cacheLevel = (IBindingCacheLevel)level; } return keys.Length; } finally { Unlock(); } } public void Lock() { Monitor.Enter(cacheLock); } public void Unlock() { Monitor.Exit(cacheLock); } // --- internal --- private void AddLevel(string typeName, string parameters) { Type levelType; try { levelType = Type.GetType(typeName); } catch (Exception e) { throw new ArgumentException("Class '" + typeName + "' not found!", e); } if (!typeof(IBindingCacheLevel).IsAssignableFrom(levelType)) { throw new ArgumentException("Class '" + typeName + "' does not implement the ICacheLevel interface!"); } cacheLevels.Add(levelType); // process parameters if (parameters == null) { cacheLevelParameters.Add(null); } else { Dictionary parameterDict = new Dictionary(); cacheLevelParameters.Add(parameterDict); foreach (string pair in parameters.Split(',')) { string[] keyValue = pair.Split('='); if (keyValue.Length == 1) { parameterDict[keyValue[0]] = ""; } else { parameterDict[keyValue[0]] = keyValue[1]; } } } } private IBindingCacheLevel CreateCacheLevel(int level) { if ((level < 0) || (level >= cacheLevels.Count)) { throw new ArgumentException("Cache level doesn't fit the configuration!"); } // get the class and create an instance Type levelType = cacheLevels[level]; IBindingCacheLevel cacheLevel = null; try { cacheLevel = (IBindingCacheLevel)Activator.CreateInstance(levelType); } catch (Exception e) { throw new ArgumentException("Cache level problem?!", e); } // initialize it cacheLevel.Initialize(cacheLevelParameters[level]); return cacheLevel; } private string GetFormattedKeys(string[] keys) { StringBuilder sb = new StringBuilder(); foreach (string k in keys) { if (sb.Length > 0) { sb.Append(", "); } sb.Append(k); } return sb.ToString(); } } internal abstract class AbstractCacheLevel : IBindingCacheLevel { protected static string NullKey = ""; private bool fallbackEnabled = false; private string fallbackKey = null; private bool singleValueEnabled = false; public abstract void Initialize(IDictionary cacheLevelConfig); public virtual object this[string key] { get { object value = GetValue(key == null ? NullKey : key); if (value != null) { return value; } if (fallbackEnabled) { value = GetValue(fallbackKey); if (value != null) { return value; } } if (singleValueEnabled) { value = GetSingleValue(); if (value != null) { return value; } } return null; } set { if (value != null) { AddValue(key == null ? NullKey : key, value); } } } public abstract void Remove(string key); protected abstract object GetValue(string key); protected abstract object GetSingleValue(); protected abstract void AddValue(string key, object value); protected void EnableKeyFallback(string key) { fallbackKey = key; fallbackEnabled = true; } protected void DisableKeyFallback() { fallbackEnabled = false; } protected void EnableSingeValueFallback() { singleValueEnabled = true; } protected void DisableSingeValueFallback() { singleValueEnabled = false; } protected int GetIntParameter(IDictionary parameters, string name, int defValue) { if (parameters == null) { return defValue; } string value; if (!parameters.TryGetValue(name, out value)) { return defValue; } try { return Int32.Parse(value); } catch (Exception) { return defValue; } } protected bool GetBooleanParameter(IDictionary parameters, string name, bool defValue) { if (parameters == null) { return defValue; } string value; if (!parameters.TryGetValue(name, out value)) { return defValue; } try { return Boolean.Parse(value); } catch (Exception) { return defValue; } } } internal class DictionaryCacheLevel : AbstractCacheLevel { public const string Capacity = "capacity"; public const string SingleValue = "singleValue"; private IDictionary dict; public override void Initialize(IDictionary parameters) { int initialCapacity = GetIntParameter(parameters, Capacity, 32); bool singleValue = GetBooleanParameter(parameters, SingleValue, false); dict = new Dictionary(initialCapacity); if (singleValue) { EnableSingeValueFallback(); } } public override void Remove(string key) { dict.Remove(key); } protected override object GetValue(string key) { object value; if (dict.TryGetValue(key, out value)) { return value; } return null; } protected override object GetSingleValue() { if (dict.Count == 1) { return dict.Values.First(); } return null; } protected override void AddValue(string key, object value) { dict[key] = value; } } internal class LruCacheLevel : AbstractCacheLevel { public const string MaxEntries = "maxEntries"; private LRUCache cache; public override void Initialize(IDictionary parameters) { int maxEntries = GetIntParameter(parameters, MaxEntries, 100); cache = new LRUCache(maxEntries, TimeSpan.FromDays(1)); } public override void Remove(string key) { cache.Remove(key); } protected override object GetValue(string key) { return cache.Get(key); } protected override object GetSingleValue() { if (cache.Count == 1) { return cache.GetLatest(); } return null; } protected override void AddValue(string key, object value) { cache.Add(key, value); } } // ---- Caches ---- /// /// Repository Info cache. /// internal class RepositoryInfoCache { private const int CacheSizeRepositories = 10; private IBindingCache cache; public RepositoryInfoCache(BindingSession session) { int repCount = session.GetValue(SessionParameter.CacheSizeRepositories, CacheSizeRepositories); if (repCount < 1) { repCount = CacheSizeRepositories; } cache = new Cache("Repository Info Cache"); cache.Initialize(new string[] { typeof(DictionaryCacheLevel).FullName + " " + DictionaryCacheLevel.Capacity + "=" + repCount.ToString() }); } public void Put(IRepositoryInfo repositoryInfo) { if ((repositoryInfo == null) || (repositoryInfo.Id == null)) { return; } cache.Put(new string[] { repositoryInfo.Id }, repositoryInfo); } public IRepositoryInfo Get(string repositoryId) { return (IRepositoryInfo)cache.Get(new string[] { repositoryId }); } public void Remove(string repositoryId) { cache.Remove(new string[] { repositoryId }); } } /// /// Type Definition cache. /// internal class TypeDefinitionCache { private const int CacheSizeRepositories = 10; private const int CacheSizeTypes = 100; private IBindingCache cache; public TypeDefinitionCache(BindingSession session) { int repCount = session.GetValue(SessionParameter.CacheSizeRepositories, CacheSizeRepositories); if (repCount < 1) { repCount = CacheSizeRepositories; } int typeCount = session.GetValue(SessionParameter.CacheSizeTypes, CacheSizeTypes); if (typeCount < 1) { typeCount = CacheSizeTypes; } cache = new Cache("Type Definition Cache"); cache.Initialize(new string[] { typeof(DictionaryCacheLevel).FullName + " " + DictionaryCacheLevel.Capacity + "=" + repCount.ToString(), // repository typeof(LruCacheLevel).FullName + " " + LruCacheLevel.MaxEntries + "=" + typeCount.ToString() // type }); } public void Put(string repositoryId, ITypeDefinition typeDefinition) { if ((typeDefinition == null) || (typeDefinition.Id == null)) { return; } cache.Put(new string[] { repositoryId, typeDefinition.Id }, typeDefinition); } public ITypeDefinition Get(string repositoryId, string typeId) { return (ITypeDefinition)cache.Get(new string[] { repositoryId, typeId }); } public void Remove(string repositoryId, string typeId) { cache.Remove(new string[] { repositoryId, typeId }); } public void Remove(string repositoryId) { cache.Remove(new string[] { repositoryId }); } } }