blob: dbdfa4ffea13b019bd6a8610ead2e663340de872 [file] [log] [blame]
/*
* 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<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)
{
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<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);
}
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<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!");
}
// 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 == 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<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);
}
catch (Exception)
{
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 (Exception)
{
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 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 });
}
}
/// <summary>
/// Type Definition cache.
/// </summary>
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 });
}
}
}