- added object cache implementation
- bug fixes
git-svn-id: https://svn.apache.org/repos/asf/incubator/chemistry/dotcmis/trunk@1069900 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/DotCMIS.suo b/DotCMIS.suo
deleted file mode 100644
index 6b2db92..0000000
--- a/DotCMIS.suo
+++ /dev/null
Binary files differ
diff --git a/DotCMIS/DotCMIS.csproj b/DotCMIS/DotCMIS.csproj
index 7009548..f4b2d58 100644
--- a/DotCMIS/DotCMIS.csproj
+++ b/DotCMIS/DotCMIS.csproj
@@ -76,6 +76,7 @@
<DesignTime>True</DesignTime>
<DependentUpon>Reference.svcmap</DependentUpon>
</Compile>
+ <Compile Include="util.cs" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
diff --git a/DotCMIS/binding/binding-caches.cs b/DotCMIS/binding/binding-caches.cs
index 42ef619..91de73d 100644
--- a/DotCMIS/binding/binding-caches.cs
+++ b/DotCMIS/binding/binding-caches.cs
@@ -24,6 +24,7 @@
using System.Threading;
using DotCMIS.Binding.Impl;
using DotCMIS.Data;
+using DotCMIS.Util;
namespace DotCMIS.Binding
{
@@ -350,11 +351,10 @@
}
}
- internal abstract class AbstractDictionaryCacheLevel : IBindingCacheLevel
+ internal abstract class AbstractCacheLevel : IBindingCacheLevel
{
protected static string NullKey = "";
- private IDictionary<string, object> dict;
private bool fallbackEnabled = false;
private string fallbackKey = null;
private bool singleValueEnabled = false;
@@ -365,42 +365,48 @@
{
get
{
- object value = null;
- if (dict.TryGetValue(key == null ? NullKey : key, out value))
+ object value = GetValue(key == null ? NullKey : key);
+ if (value != null)
{
return value;
}
- if (fallbackEnabled && dict.TryGetValue(fallbackKey, out value))
+ if (fallbackEnabled)
{
- return value;
+ value = GetValue(fallbackKey);
+ if (value != null)
+ {
+ return value;
+ }
}
- if (singleValueEnabled && dict.Count == 1)
+ if (singleValueEnabled)
{
- value = dict.Values.First();
+ value = GetSingleValue();
+ if (value != null)
+ {
+ return value;
+ }
}
- return value;
+ return null;
}
set
{
if (value != null)
{
- dict[key == null ? NullKey : key] = value;
+ AddValue(key == null ? NullKey : key, value);
}
}
}
- public virtual void Remove(string key)
- {
- dict.Remove(key);
- }
+ public abstract void Remove(string key);
- public void SetDictionary(IDictionary<string, object> dict)
- {
- this.dict = dict;
- }
+ protected abstract object GetValue(string key);
+
+ protected abstract object GetSingleValue();
+
+ protected abstract void AddValue(string key, object value);
protected void EnableKeyFallback(string key)
{
@@ -470,91 +476,93 @@
}
}
- internal class DictionaryCacheLevel : AbstractDictionaryCacheLevel
+ 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);
- SetDictionary(new Dictionary<string, object>(initialCapacity));
+ 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 : AbstractDictionaryCacheLevel
+ internal class LruCacheLevel : AbstractCacheLevel
{
public const string MaxEntries = "maxEntries";
- private LinkedList<string> keyList;
- private int maxEntries;
+ private LRUCache<string, object> cache;
public override void Initialize(IDictionary<string, string> parameters)
{
- maxEntries = GetIntParameter(parameters, MaxEntries, 100);
- keyList = new LinkedList<string>();
- SetDictionary(new Dictionary<string, object>(maxEntries + 1));
- }
+ int maxEntries = GetIntParameter(parameters, MaxEntries, 100);
- public override object this[string key]
- {
- get
- {
- object value = base[key];
- if (value != null)
- {
- LinkedListNode<string> node = keyList.Find(key);
- if (node == null)
- {
- throw new ApplicationException("Cache error!");
- }
- else
- {
- keyList.Remove(node);
- keyList.AddFirst(node);
- }
- }
-
- return value;
- }
- set
- {
- if (value == null)
- {
- return;
- }
-
- LinkedListNode<string> node = keyList.Find(key);
- if (node == null)
- {
- keyList.AddFirst(key);
- while (keyList.Count > maxEntries)
- {
- LinkedListNode<string> lastNode = keyList.Last;
- base.Remove(lastNode.Value);
- keyList.RemoveLast();
- }
- }
- else
- {
- keyList.Remove(node);
- keyList.AddFirst(node);
- }
-
- base[key] = value;
- }
+ cache = new LRUCache<string, object>(maxEntries, TimeSpan.FromDays(1));
}
public override void Remove(string key)
{
- keyList.Remove(key);
- base.Remove(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);
}
}
diff --git a/DotCMIS/client/client-caches.cs b/DotCMIS/client/client-caches.cs
index 7c103ba..b5121a7 100644
--- a/DotCMIS/client/client-caches.cs
+++ b/DotCMIS/client/client-caches.cs
@@ -20,6 +20,8 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
+using System.Threading;
+using DotCMIS.Util;
namespace DotCMIS.Client.Impl.Cache
{
@@ -54,4 +56,253 @@
public void Clear() { }
public int CacheSize { get { return 0; } }
}
+
+ public class CmisObjectCache : ICache
+ {
+ private int cacheSize;
+ private int cacheTtl;
+ private int pathToIdSize;
+ private int pathToIdTtl;
+
+ private LRUCache<string, IDictionary<string, ICmisObject>> objectCache;
+ private LRUCache<string, string> pathToIdCache;
+
+ private object cacheLock = new object();
+
+ public CmisObjectCache() { }
+
+ public void Initialize(ISession session, IDictionary<string, string> parameters)
+ {
+ Lock();
+ try
+ {
+ // cache size
+ cacheSize = 1000;
+ try
+ {
+ string cacheSizeStr;
+ if (parameters.TryGetValue(SessionParameter.CacheSizeObjects, out cacheSizeStr))
+ {
+ cacheSize = Int32.Parse(cacheSizeStr);
+ if (cacheSize < 0)
+ {
+ cacheSize = 0;
+ }
+ }
+ }
+ catch (Exception) { }
+
+ // cache time-to-live
+ cacheTtl = 2 * 60 * 60 * 1000;
+ try
+ {
+ string cacheTtlStr;
+ if (parameters.TryGetValue(SessionParameter.CacheTTLObjects, out cacheTtlStr))
+ {
+ cacheTtl = Int32.Parse(cacheTtlStr);
+ if (cacheTtl < 0)
+ {
+ cacheTtl = 2 * 60 * 60 * 1000;
+ }
+ }
+ }
+ catch (Exception) { }
+
+ // path-to-id size
+ pathToIdSize = 1000;
+ try
+ {
+ string pathToIdSizeStr;
+ if (parameters.TryGetValue(SessionParameter.CacheSizePathToId, out pathToIdSizeStr))
+ {
+ pathToIdSize = Int32.Parse(pathToIdSizeStr);
+ if (pathToIdSize < 0)
+ {
+ pathToIdSize = 0;
+ }
+ }
+ }
+ catch (Exception) { }
+
+ // path-to-id time-to-live
+ pathToIdTtl = 30 * 60 * 1000;
+ try
+ {
+ string pathToIdTtlStr;
+ if (parameters.TryGetValue(SessionParameter.CacheTTLPathToId, out pathToIdTtlStr))
+ {
+ pathToIdTtl = Int32.Parse(pathToIdTtlStr);
+ if (pathToIdTtl < 0)
+ {
+ pathToIdTtl = 30 * 60 * 1000;
+ }
+ }
+ }
+ catch (Exception) { }
+
+ InitializeInternals();
+ }
+ finally
+ {
+ Unlock();
+ }
+ }
+
+ private void InitializeInternals()
+ {
+ Lock();
+ try
+ {
+ objectCache = new LRUCache<string, IDictionary<string, ICmisObject>>(cacheSize, TimeSpan.FromMilliseconds(cacheTtl));
+ pathToIdCache = new LRUCache<string, string>(pathToIdSize, TimeSpan.FromMilliseconds(pathToIdTtl));
+ }
+ finally
+ {
+ Unlock();
+ }
+ }
+
+ public void Clear()
+ {
+ InitializeInternals();
+ }
+
+ public bool ContainsId(string objectId, string cacheKey)
+ {
+ Lock();
+ try
+ {
+ return objectCache.Get(objectId) != null;
+ }
+ finally
+ {
+ Unlock();
+ }
+ }
+
+ public bool ContainsPath(string path, string cacheKey)
+ {
+ Lock();
+ try
+ {
+ return pathToIdCache.Get(path) != null;
+ }
+ finally
+ {
+ Unlock();
+ }
+ }
+
+ public ICmisObject GetById(string objectId, string cacheKey)
+ {
+ Lock();
+ try
+ {
+ IDictionary<string, ICmisObject> cacheKeyDict = objectCache.Get(objectId);
+ if (cacheKeyDict == null)
+ {
+ return null;
+ }
+
+ ICmisObject cmisObject;
+ if (cacheKeyDict.TryGetValue(cacheKey, out cmisObject))
+ {
+ return cmisObject;
+ }
+
+ return null;
+ }
+ finally
+ {
+ Unlock();
+ }
+ }
+
+ public ICmisObject GetByPath(string path, string cacheKey)
+ {
+ Lock();
+ try
+ {
+ string id = pathToIdCache.Get(path);
+ if (id == null)
+ {
+ return null;
+ }
+
+ return GetById(id, cacheKey);
+ }
+ finally
+ {
+ Unlock();
+ }
+ }
+
+ public void Put(ICmisObject cmisObject, string cacheKey)
+ {
+ // no object, no id, no cache key - no cache
+ if (cmisObject == null || cmisObject.Id == null || cacheKey == null)
+ {
+ return;
+ }
+
+ Lock();
+ try
+ {
+ IDictionary<string, ICmisObject> cacheKeyDict = objectCache.Get(cmisObject.Id);
+ if (cacheKeyDict == null)
+ {
+ cacheKeyDict = new Dictionary<string, ICmisObject>();
+ objectCache.Add(cmisObject.Id, cacheKeyDict);
+ }
+
+ cacheKeyDict[cacheKey] = cmisObject;
+
+ // folders may have a path, use it!
+ string path = cmisObject.GetPropertyValue(PropertyIds.Path) as string;
+ if (path != null)
+ {
+ pathToIdCache.Add(path, cmisObject.Id);
+ }
+ }
+ finally
+ {
+ Unlock();
+ }
+ }
+
+ public void PutPath(string path, ICmisObject cmisObject, string cacheKey)
+ {
+ // no path, no object, no id, no cache key - no cache
+ if (path ==null || cmisObject == null || cmisObject.Id == null || cacheKey == null)
+ {
+ return;
+ }
+
+ Lock();
+ try
+ {
+ Put(cmisObject, cacheKey);
+ pathToIdCache.Add(path, cmisObject.Id);
+ }
+ finally
+ {
+ Unlock();
+ }
+ }
+
+ public int CacheSize
+ {
+ get { return cacheSize; }
+ }
+
+ protected void Lock()
+ {
+ Monitor.Enter(cacheLock);
+ }
+
+ protected void Unlock()
+ {
+ Monitor.Exit(cacheLock);
+ }
+ }
}
diff --git a/DotCMIS/client/client-impl.cs b/DotCMIS/client/client-impl.cs
index 4bd58dd..7461469 100644
--- a/DotCMIS/client/client-impl.cs
+++ b/DotCMIS/client/client-impl.cs
@@ -257,7 +257,7 @@
}
else
{
- cacheType = typeof(NoCache);
+ cacheType = typeof(CmisObjectCache);
}
ICache cacheObject = Activator.CreateInstance(cacheType) as ICache;
diff --git a/DotCMIS/client/client-intf.cs b/DotCMIS/client/client-intf.cs
index 329d702..930fb96 100644
--- a/DotCMIS/client/client-intf.cs
+++ b/DotCMIS/client/client-intf.cs
@@ -99,7 +99,21 @@
}
/// <summary>
- /// Session interface.
+ /// A session is a connection to a CMIS repository with a specific user.
+ /// <para>
+ /// Not all operations might be supported by the connected repository. Either DotCMIS or the repository will
+ /// throw an exception if an unsupported operation is called.
+ /// The capabilities of the repository can be discovered by evaluating the repository info
+ /// (see <see cref="RepositoryInfo"/>).
+ /// </para>
+ /// <para>
+ /// Almost all methods might throw exceptions derived from <see cref="DotCMIS.Exceptions.CmisBaseException"/>!
+ /// </para>
+ /// <para>
+ /// (Please refer to the <a href="http://docs.oasis-open.org/cmis/CMIS/v1.0/os/">CMIS specification</a>
+ /// for details about the domain model, terms, concepts, base types, properties, ids and query names,
+ /// query language, etc.)
+ /// </para>
/// </summary>
public interface ISession
{
@@ -336,17 +350,71 @@
/// </summary>
public interface IProperty
{
+ /// <summary>
+ /// Gets the property id.
+ /// </summary>
string Id { get; }
+
+ /// <summary>
+ /// Gets the property local name.
+ /// </summary>
string LocalName { get; }
+
+ /// <summary>
+ /// Gets the property display name.
+ /// </summary>
string DisplayName { get; }
+
+ /// <summary>
+ /// Gets the property query name.
+ /// </summary>
string QueryName { get; }
+
+ /// <summary>
+ /// Gets if the property is a multi-value proprty.
+ /// </summary>
bool IsMultiValued { get; }
+
+ /// <summary>
+ /// Gets the proprty type.
+ /// </summary>
PropertyType? PropertyType { get; }
+
+ /// <summary>
+ /// Gets the property defintion.
+ /// </summary>
IPropertyDefinition PropertyDefinition { get; }
+
+ /// <summary>
+ /// Gets the value of the property.
+ /// </summary>
+ /// <remarks>
+ /// If the property is a single-value property the single value is returned.
+ /// If the property is a multi-value property a IList<object> is returned.
+ /// </remarks>
object Value { get; }
+
+ /// <summary>
+ /// Gets the value list of the property.
+ /// </summary>
+ /// <remarks>
+ /// If the property is a single-value property a list with one or no items is returend.
+ /// </remarks>
IList<object> Values { get; }
+
+ /// <summary>
+ /// Gets the first value of the value list or <c>null</c> if the list has no values.
+ /// </summary>
object FirstValue { get; }
+
+ /// <summary>
+ /// Gets a string representation of the first value of the value list.
+ /// </summary>
string ValueAsString { get; }
+
+ /// <summary>
+ /// Gets a string representation of the value list.
+ /// </summary>
string ValuesAsString { get; }
}
@@ -430,17 +498,45 @@
/// </summary>
public interface ICmisObject : IObjectId, ICmisObjectProperties
{
- // object
+ /// <summary>
+ /// Gets the allowable actions if they have been fetched for this object.
+ /// </summary>
IAllowableActions AllowableActions { get; }
+
+ /// <summary>
+ /// Gets the relationships if they have been fetched for this object.
+ /// </summary>
IList<IRelationship> Relationships { get; }
+
+ /// <summary>
+ /// Gets the ACL if it has been fetched for this object.
+ /// </summary>
IAcl Acl { get; }
- // object service
+ /// <summary>
+ /// Deletes this object.
+ /// </summary>
+ /// <param name="allVersions">if this object is a document this parameter defines if just this version or all versions should be deleted</param>
void Delete(bool allVersions);
+
+ /// <summary>
+ /// Updates the properties that are provided.
+ /// </summary>
+ /// <param name="properties">the properties to update</param>
+ /// <returns>the updated object (a repository might have created a new object)</returns>
ICmisObject UpdateProperties(IDictionary<string, object> properties);
+
+ /// <summary>
+ /// Updates the properties that are provided.
+ /// </summary>
+ /// <param name="properties">the properties to update</param>
+ /// <param name="refresh">indicates if the object should be refresh after the update</param>
+ /// <returns>the object id of the updated object (a repository might have created a new object)</returns>
IObjectId UpdateProperties(IDictionary<string, object> properties, bool refresh);
- // renditions
+ /// <summary>
+ /// Gets the renditions if they have been fetched for this object.
+ /// </summary>
IList<IRendition> Renditions { get; }
// policy service
@@ -456,8 +552,19 @@
// extensions
IList<ICmisExtensionElement> GetExtensions(ExtensionLevel level);
+ /// <summary>
+ /// Gets the timestamp of the last refresh.
+ /// </summary>
DateTime RefreshTimestamp { get; }
+
+ /// <summary>
+ /// Reloads the data from the repository.
+ /// </summary>
void Refresh();
+
+ /// <summary>
+ /// Reloads the data from the repository if the last refresh did not occur within <c>durationInMillis</c>.
+ /// </summary>
void RefreshIfOld(long durationInMillis);
}
diff --git a/DotCMIS/client/client-objectfactory.cs b/DotCMIS/client/client-objectfactory.cs
index 50863c8..85216f2 100644
--- a/DotCMIS/client/client-objectfactory.cs
+++ b/DotCMIS/client/client-objectfactory.cs
@@ -210,7 +210,13 @@
// get the type
if (type == null)
{
- string typeId = properties[PropertyIds.ObjectTypeId] as string;
+ object typeIdObject;
+ if (!properties.TryGetValue(PropertyIds.ObjectTypeId, out typeIdObject))
+ {
+ throw new ArgumentException("Type or type property must be set!");
+ }
+
+ string typeId = typeIdObject as string;
if (typeId == null)
{
throw new ArgumentException("Type or type property must be set!");
diff --git a/DotCMIS/const.cs b/DotCMIS/const.cs
index cade7fe..1f09da3 100644
--- a/DotCMIS/const.cs
+++ b/DotCMIS/const.cs
@@ -66,6 +66,10 @@
public const string CacheClass = "org.apache.chemistry.dotcmis.cache.classname";
public const string RepositoryId = "org.apache.chemistry.dotcmis.session.repository.id";
+ public const string CacheSizeObjects = "org.apache.chemistry.dotcmis.cache.objects.size";
+ public const string CacheTTLObjects = "org.apache.chemistry.dotcmis.cache.objects.ttl";
+ public const string CacheSizePathToId = "org.apache.chemistry.dotcmis.cache.pathtoid.size";
+ public const string CacheTTLPathToId = "org.apache.chemistry.dotcmis.cache.pathtoid.ttl";
public const string CachePathOmit = "org.apache.chemistry.dotcmis.cache.path.omit";
}
diff --git a/DotCMIS/util.cs b/DotCMIS/util.cs
new file mode 100644
index 0000000..6264482
--- /dev/null
+++ b/DotCMIS/util.cs
@@ -0,0 +1,132 @@
+/*
+ * 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.Linq;
+using System.Text;
+
+namespace DotCMIS.Util
+{
+ /// <summary>
+ /// LRU cache implementation. Not thread safe!
+ /// </summary>
+ internal class LRUCache<K, V>
+ {
+ private int capacity;
+ private TimeSpan ttl;
+ private Dictionary<K, LinkedListNode<LRUCacheItem<K, V>>> cacheDict = new Dictionary<K, LinkedListNode<LRUCacheItem<K, V>>>();
+ private LinkedList<LRUCacheItem<K, V>> lruList = new LinkedList<LRUCacheItem<K, V>>();
+
+ public LRUCache(int capacity, TimeSpan ttl)
+ {
+ this.capacity = capacity;
+ this.ttl = ttl;
+ }
+
+ public V Get(K key)
+ {
+ LinkedListNode<LRUCacheItem<K, V>> node;
+ if (cacheDict.TryGetValue(key, out node))
+ {
+ lruList.Remove(node);
+
+ if (node.Value.IsExpired)
+ {
+ cacheDict.Remove(node.Value.Key);
+ return default(V);
+ }
+
+ lruList.AddLast(node);
+
+ return node.Value.Value;
+ }
+
+ return default(V);
+ }
+
+ public V GetLatest()
+ {
+ if (lruList.Count == 0)
+ {
+ return default(V);
+ }
+
+ return lruList.First().Value;
+ }
+
+ public void Add(K key, V val)
+ {
+ Remove(key);
+
+ if (cacheDict.Count >= capacity)
+ {
+ RemoveFirst();
+ }
+
+ LRUCacheItem<K, V> cacheItem = new LRUCacheItem<K, V>(key, val, DateTime.UtcNow + ttl);
+ LinkedListNode<LRUCacheItem<K, V>> node = new LinkedListNode<LRUCacheItem<K, V>>(cacheItem);
+
+ lruList.AddLast(node);
+ cacheDict.Add(key, node);
+ }
+
+ protected void RemoveFirst()
+ {
+ LinkedListNode<LRUCacheItem<K, V>> node = lruList.First;
+ lruList.RemoveFirst();
+ cacheDict.Remove(node.Value.Key);
+ }
+
+ public void Remove(K key)
+ {
+ LinkedListNode<LRUCacheItem<K, V>> node;
+ if (cacheDict.TryGetValue(key, out node))
+ {
+ lruList.Remove(node);
+ cacheDict.Remove(node.Value.Key);
+ }
+ }
+
+ public int Count
+ {
+ get { return lruList.Count; }
+ }
+ }
+
+ internal class LRUCacheItem<K, V>
+ {
+ public LRUCacheItem(K key, V value, DateTime expiration)
+ {
+ Key = key;
+ Value = value;
+ Expiration = expiration;
+ }
+
+ public K Key { get; private set; }
+
+ public V Value { get; private set; }
+
+ public DateTime Expiration { get; private set; }
+
+ public bool IsExpired
+ {
+ get { return Value == null || DateTime.UtcNow > Expiration; }
+ }
+ }
+}
diff --git a/DotCMISUnitTest/CacheTest.cs b/DotCMISUnitTest/CacheTest.cs
new file mode 100644
index 0000000..ac63bda
--- /dev/null
+++ b/DotCMISUnitTest/CacheTest.cs
@@ -0,0 +1,208 @@
+/*
+ * 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 DotCMIS.Client;
+using DotCMIS.Client.Impl.Cache;
+using DotCMIS.Data;
+using DotCMIS.Data.Extensions;
+using DotCMIS.Enums;
+using NUnit.Framework;
+using DotCMIS;
+using System.Threading;
+
+namespace DotCMISUnitTest
+{
+ [TestFixture]
+ class CacheTest
+ {
+ [Test]
+ public void TestCache()
+ {
+ IDictionary<string, string> parameters = new Dictionary<string, string>();
+
+ CmisObjectCache cache = new CmisObjectCache();
+ cache.Initialize(null, parameters);
+
+ string cacheKey1 = "ck1";
+ string cacheKey2 = "ck2";
+
+ // add first object
+ MockObject mock1 = new MockObject("1");
+ cache.Put(mock1, cacheKey1);
+
+ ICmisObject o1 = cache.GetById(mock1.Id, cacheKey1);
+ Assert.NotNull(o1);
+ Assert.AreEqual(mock1.Id, o1.Id);
+ Assert.Null(cache.GetById(mock1.Id, cacheKey2));
+ Assert.Null(cache.GetById("- some id -", cacheKey1));
+
+ // add second object - same id
+ MockObject mock2 = new MockObject("1");
+ cache.Put(mock2, cacheKey2);
+
+ o1 = cache.GetById(mock1.Id, cacheKey1);
+ Assert.NotNull(o1);
+ Assert.AreEqual(mock1.Id, o1.Id);
+
+ ICmisObject o2 = cache.GetById(mock2.Id, cacheKey2);
+ Assert.NotNull(o2);
+ Assert.AreEqual(mock2.Id, o2.Id);
+
+ // add third object - other id
+
+ MockObject mock3 = new MockObject("3");
+ cache.Put(mock3, cacheKey1);
+
+ o1 = cache.GetById(mock1.Id, cacheKey1);
+ Assert.NotNull(o1);
+ Assert.AreEqual(mock1.Id, o1.Id);
+
+ o2 = cache.GetById(mock2.Id, cacheKey2);
+ Assert.NotNull(o2);
+ Assert.AreEqual(mock2.Id, o2.Id);
+
+ ICmisObject o3 = cache.GetById(mock3.Id, cacheKey1);
+ Assert.NotNull(o3);
+ Assert.AreEqual(mock3.Id, o3.Id);
+ }
+
+ [Test]
+ public void TestLRU()
+ {
+ IDictionary<string, string> parameters = new Dictionary<string, string>();
+ parameters[SessionParameter.CacheSizeObjects] = "10";
+
+ CmisObjectCache cache = new CmisObjectCache();
+ cache.Initialize(null, parameters);
+
+ string cacheKey1 = "ck1";
+
+ MockObject[] mocks = new MockObject[10];
+ for (int i = 0; i < 10; i++)
+ {
+ mocks[i] = new MockObject("m" + i);
+ cache.Put(mocks[i], cacheKey1);
+ }
+
+ for (int i = 0; i < 10; i++)
+ {
+ mocks[i] = new MockObject("m" + i);
+ Assert.NotNull(cache.GetById("m" + i, cacheKey1));
+ }
+
+ MockObject newMock = new MockObject("new");
+ cache.Put(newMock, cacheKey1);
+ Assert.NotNull(cache.GetById(newMock.Id, cacheKey1));
+
+ for (int i = 1; i < 10; i++)
+ {
+ mocks[i] = new MockObject("m" + i);
+ Assert.NotNull(cache.GetById("m" + i, cacheKey1));
+ }
+
+ Assert.Null(cache.GetById("m0", cacheKey1));
+ }
+
+ [Test]
+ public void TestExpiration()
+ {
+ IDictionary<string, string> parameters = new Dictionary<string, string>();
+ parameters[SessionParameter.CacheTTLObjects] = "500";
+
+ CmisObjectCache cache = new CmisObjectCache();
+ cache.Initialize(null, parameters);
+
+ string cacheKey1 = "ck1";
+
+ MockObject mock1 = new MockObject("1");
+ cache.Put(mock1, cacheKey1);
+
+ Assert.NotNull(cache.GetById(mock1.Id, cacheKey1));
+
+ Thread.Sleep(TimeSpan.FromSeconds(1));
+
+ Assert.Null(cache.GetById(mock1.Id, cacheKey1));
+ }
+
+ [Test]
+ public void TestPath()
+ {
+ IDictionary<string, string> parameters = new Dictionary<string, string>();
+
+ CmisObjectCache cache = new CmisObjectCache();
+ cache.Initialize(null, parameters);
+
+ string cacheKey1 = "ck1";
+ string path = "/test/path";
+
+ MockObject mock1 = new MockObject("1");
+ cache.PutPath(path, mock1, cacheKey1);
+
+ ICmisObject o1 = cache.GetById(mock1.Id, cacheKey1);
+ Assert.NotNull(o1);
+ Assert.AreEqual(mock1.Id, o1.Id);
+
+ ICmisObject o2 = cache.GetByPath(path, cacheKey1);
+ Assert.NotNull(o2);
+ Assert.AreEqual(mock1.Id, o2.Id);
+
+ Assert.Null(cache.GetByPath("/some/other/path/", cacheKey1));
+ }
+ }
+
+ class MockObject : ICmisObject
+ {
+ public MockObject(string id)
+ {
+ Id = id;
+ }
+
+ public string Id { get; private set; }
+ public IList<IProperty> Properties { get { return null; } }
+ public IProperty this[string propertyId] { get { return null; } }
+ public object GetPropertyValue(string propertyId) { return null; }
+ public string Name { get { return null; } }
+ public string CreatedBy { get { return null; } }
+ public DateTime? CreationDate { get { return null; } }
+ public string LastModifiedBy { get { return null; } }
+ public DateTime? LastModificationDate { get { return null; } }
+ public BaseTypeId BaseTypeId { get { return BaseTypeId.CmisDocument; } }
+ public IObjectType BaseType { get { return null; } }
+ public IObjectType ObjectType { get { return null; } }
+ public string ChangeToken { get { return null; } }
+ public IAllowableActions AllowableActions { get { return null; } }
+ public IList<IRelationship> Relationships { get { return null; } }
+ public IAcl Acl { get { return null; } }
+ public void Delete(bool allVersions) { }
+ public ICmisObject UpdateProperties(IDictionary<string, object> properties) { return null; }
+ public IObjectId UpdateProperties(IDictionary<string, object> properties, bool refresh) { return null; }
+ public IList<IRendition> Renditions { get { return null; } }
+ public void ApplyPolicy(params IObjectId[] policyId) { }
+ public void RemovePolicy(params IObjectId[] policyId) { }
+ public IList<IPolicy> Policies { get { return null; } }
+ public IAcl ApplyAcl(IList<IAce> AddAces, IList<IAce> removeAces, AclPropagation? aclPropagation) { return null; }
+ public IAcl AddAcl(IList<IAce> AddAces, AclPropagation? aclPropagation) { return null; }
+ public IAcl RemoveAcl(IList<IAce> RemoveAces, AclPropagation? aclPropagation) { return null; }
+ public IList<ICmisExtensionElement> GetExtensions(ExtensionLevel level) { return null; }
+ public DateTime RefreshTimestamp { get { return DateTime.Now; } }
+ public void Refresh() { }
+ public void RefreshIfOld(long durationInMillis) { }
+ }
+}
diff --git a/DotCMISUnitTest/DotCMISUnitTest.csproj b/DotCMISUnitTest/DotCMISUnitTest.csproj
index 5e12df9..e7d976c 100644
--- a/DotCMISUnitTest/DotCMISUnitTest.csproj
+++ b/DotCMISUnitTest/DotCMISUnitTest.csproj
@@ -43,6 +43,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
+ <Compile Include="CacheTest.cs" />
<Compile Include="CRUDTest.cs" />
<Compile Include="EnumeratorTest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
diff --git a/DotCMISUnitTest/SmokeTest.cs b/DotCMISUnitTest/SmokeTest.cs
index 49822f4..ebc4624 100644
--- a/DotCMISUnitTest/SmokeTest.cs
+++ b/DotCMISUnitTest/SmokeTest.cs
@@ -23,6 +23,11 @@
using DotCMIS.Enums;
using NUnit.Framework;
using System;
+using DotCMIS.Data;
+using DotCMIS.Data.Impl;
+using System.Text;
+using System.IO;
+using DotCMIS.Exceptions;
namespace DotCMISUnitTest
{
@@ -30,7 +35,7 @@
class SmokeTest : TestFramework
{
[Test]
- public void TestSession()
+ public void SmokeTestSession()
{
Assert.NotNull(Session);
Assert.NotNull(Session.Binding);
@@ -168,5 +173,80 @@
Console.WriteLine(hit.GetPropertyValueById(PropertyIds.Name) + " (" + hit.GetPropertyValueById(PropertyIds.ObjectId) + ")");
}
}
+
+ [Test]
+ public void SmokeTestCreateDocument()
+ {
+ IFolder rootFolder = Session.GetRootFolder();
+
+ IDictionary<string, object> properties = new Dictionary<string, object>();
+ properties[PropertyIds.Name] = "test-smoke.txt";
+ properties[PropertyIds.ObjectTypeId] = "cmis:document";
+
+ byte[] content = UTF8Encoding.UTF8.GetBytes("Hello World!");
+
+ ContentStream contentStream = new ContentStream();
+ contentStream.FileName = properties[PropertyIds.Name] as string;
+ contentStream.MimeType = "text/plain";
+ contentStream.Length = content.Length;
+ contentStream.Stream = new MemoryStream(content);
+
+ IDocument doc = rootFolder.CreateDocument(properties, contentStream, null);
+
+ // check doc
+ Assert.NotNull(doc);
+ Assert.NotNull(doc.Id);
+
+ // check versions
+ IList<IDocument> versions = doc.GetAllVersions();
+ Assert.NotNull(versions);
+ Assert.AreEqual(1, versions.Count);
+ Assert.AreEqual(doc.Id, versions[0].Id);
+
+ // check content
+ IContentStream retrievedContentStream = doc.GetContentStream();
+ Assert.NotNull(retrievedContentStream);
+ Assert.NotNull(retrievedContentStream.Stream);
+
+ doc.Delete(true);
+
+ try
+ {
+ doc.Refresh();
+ Assert.Fail("Document shouldn't exist anymore!");
+ }
+ catch (CmisObjectNotFoundException) { }
+ }
+
+ [Test]
+ public void SmokeTestCreateFolder()
+ {
+ IFolder rootFolder = Session.GetRootFolder();
+
+ IDictionary<string, object> properties = new Dictionary<string, object>();
+ properties[PropertyIds.Name] = "test-smoke";
+ properties[PropertyIds.ObjectTypeId] = "cmis:folder";
+
+ IFolder folder = rootFolder.CreateFolder(properties);
+
+ // check folder
+ Assert.NotNull(folder);
+ Assert.NotNull(folder.Id);
+
+ // check children
+ foreach (ICmisObject cmisObject in folder.GetChildren())
+ {
+ Assert.Fail("Folder shouldn't have children!");
+ }
+
+ folder.Delete(true);
+
+ try
+ {
+ folder.Refresh();
+ Assert.Fail("Folder shouldn't exist anymore!");
+ }
+ catch (CmisObjectNotFoundException) { }
+ }
}
}