/* | |
* 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 PortCMIS.Client; | |
using PortCMIS.Data; | |
using PortCMIS.Utils; | |
using System; | |
using System.Collections.Generic; | |
using System.Globalization; | |
using System.Linq; | |
using System.Text; | |
using System.Threading; | |
namespace PortCMIS.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<string, string> cacheLevelConfig); | |
object this[string key] { get; set; } | |
void Remove(string key); | |
} | |
internal class Cache : IBindingCache | |
{ | |
private IList<Type> cacheLevels; | |
private IList<IDictionary<string, string>> 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) | |
{ | |
lock (cacheLock) | |
{ | |
if (cacheLevels != null) | |
{ | |
throw new InvalidOperationException("Cache already initialize!"); | |
} | |
if (cacheLevelConfig == null || cacheLevelConfig.Length == 0) | |
{ | |
throw new ArgumentException("Cache config must not be empty!", nameof(cacheLevelConfig)); | |
} | |
cacheLevels = new List<Type>(); | |
cacheLevelParameters = new List<IDictionary<string, string>>(); | |
// 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); | |
} | |
} | |
public void Put(string[] keys, object value) | |
{ | |
if (keys == null) { return; } | |
if (keys.Length != cacheLevels.Count) | |
{ | |
throw new ArgumentException("Wrong number of keys!", nameof(keys)); | |
} | |
lock (cacheLock) | |
{ | |
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 (Logger.IsDebugEnabled) | |
{ | |
Logger.Debug("Binding Cache: " + name + ": put [" + GetFormattedKeys(keys) + "] = " + value); | |
} | |
} | |
} | |
public object Get(string[] keys) | |
{ | |
if (keys == null) { return null; } | |
if (keys.Length != cacheLevels.Count) | |
{ | |
throw new ArgumentException("Wrong number of keys!", nameof(keys)); | |
} | |
object result = null; | |
lock (cacheLock) | |
{ | |
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]]; | |
} | |
return result; | |
} | |
public void Remove(string[] keys) | |
{ | |
if (keys == null) { return; } | |
lock (cacheLock) | |
{ | |
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 (Logger.IsDebugEnabled) | |
{ | |
Logger.Debug("Binding Cache: " + name + ": removed [" + GetFormattedKeys(keys) + "]"); | |
} | |
} | |
} | |
public int Check(string[] keys) | |
{ | |
if (keys == null) { return -1; } | |
lock (cacheLock) | |
{ | |
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; | |
} | |
} | |
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!", nameof(typeName), e); | |
} | |
cacheLevels.Add(levelType); | |
// process parameters | |
if (parameters == null) | |
{ | |
cacheLevelParameters.Add(null); | |
} | |
else | |
{ | |
Dictionary<string, string> parameterDict = new Dictionary<string, string>(); | |
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!", nameof(level)); | |
} | |
// 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<string, string> cacheLevelConfig); | |
public virtual object this[string key] | |
{ | |
get | |
{ | |
object value = GetValue(key ?? NullKey); | |
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 ?? NullKey, 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<string, string> 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, CultureInfo.InvariantCulture); | |
} | |
catch | |
{ | |
return defValue; | |
} | |
} | |
protected bool GetBooleanParameter(IDictionary<string, string> 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 | |
{ | |
return defValue; | |
} | |
} | |
} | |
internal class DictionaryCacheLevel : AbstractCacheLevel | |
{ | |
public const string Capacity = "capacity"; | |
public const string SingleValue = "singleValue"; | |
private IDictionary<string, object> dict; | |
public override void Initialize(IDictionary<string, string> parameters) | |
{ | |
int initialCapacity = GetIntParameter(parameters, Capacity, 32); | |
bool singleValue = GetBooleanParameter(parameters, SingleValue, false); | |
dict = new Dictionary<string, object>(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<string, object> cache; | |
public override void Initialize(IDictionary<string, string> parameters) | |
{ | |
int maxEntries = GetIntParameter(parameters, MaxEntries, 100); | |
cache = new LRUCache<string, object>(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 ---- | |
/// <summary> | |
/// Repository Info cache. | |
/// </summary> | |
internal class RepositoryInfoCache | |
{ | |
private const int CacheSizeRepositories = 10; | |
private readonly IBindingCache cache; | |
public RepositoryInfoCache(IBindingSession 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(CultureInfo.InvariantCulture) }); | |
} | |
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 }); | |
} | |
} | |
/// <summary> | |
/// Type Definition cache. | |
/// </summary> | |
internal class TypeDefinitionCache | |
{ | |
private const int CacheSizeRepositories = 10; | |
private const int CacheSizeTypes = 100; | |
private readonly IBindingCache cache; | |
public TypeDefinitionCache(IBindingSession 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(CultureInfo.InvariantCulture), // repository | |
typeof(LruCacheLevel).FullName + " " + LruCacheLevel.MaxEntries + "=" + typeCount.ToString(CultureInfo.InvariantCulture) // 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 }); | |
} | |
} | |
} |