// // System.Web.Caching.Cache // // Author(s): // Lluis Sanchez (lluis@ximian.com) // // (C) 2005 Novell, Inc (http://www.novell.com) // // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System.Threading; using System.Collections; using System.Security.Permissions; #if NET_2_0 using System.Collections.Generic; #endif namespace System.Web.Caching { // CAS - no InheritanceDemand here as the class is sealed [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)] public sealed class Cache: IEnumerable { #if NET_2_0 Dictionary cache; #else Hashtable cache; #endif Cache dependencyCache; public static readonly DateTime NoAbsoluteExpiration = DateTime.MaxValue; public static readonly TimeSpan NoSlidingExpiration = TimeSpan.Zero; public Cache () { #if NET_2_0 cache = new Dictionary (); #else cache = new Hashtable (); #endif } public int Count { get { return cache.Count; } } public object this [string key] { get { return Get (key); } set { Insert (key, value); } } public object Add (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback) { if (key == null) throw new ArgumentNullException ("key"); lock (cache) { CacheItem it; #if NET_2_0 cache.TryGetValue (key, out it); #else it = (CacheItem) cache [key]; #endif if (it != null) return it.Value; Insert (key, value, dependencies, absoluteExpiration, slidingExpiration, priority, onRemoveCallback, false); } return null; } public object Get (string key) { lock (cache) { CacheItem it; #if NET_2_0 if (!cache.TryGetValue (key, out it)) return null; #else it = (CacheItem) cache [key]; if (it == null) return null; #endif if (it.Dependency != null && it.Dependency.HasChanged) { Remove (it.Key, CacheItemRemovedReason.DependencyChanged, false); return null; } if (it.SlidingExpiration != NoSlidingExpiration) { it.AbsoluteExpiration = DateTime.Now + it.SlidingExpiration; it.Timer.Change ((int)it.SlidingExpiration.TotalMilliseconds, Timeout.Infinite); } else if (DateTime.Now >= it.AbsoluteExpiration) { Remove (key, CacheItemRemovedReason.Expired, false); return null; } return it.Value; } } public void Insert (string key, object value) { Insert (key, value, null, NoAbsoluteExpiration, NoSlidingExpiration, CacheItemPriority.Normal, null, true); } public void Insert (string key, object value, CacheDependency dependencies) { Insert (key, value, dependencies, NoAbsoluteExpiration, NoSlidingExpiration, CacheItemPriority.Normal, null, true); } public void Insert (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration) { Insert (key, value, dependencies, absoluteExpiration, slidingExpiration, CacheItemPriority.Normal, null, true); } public void Insert (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback) { Insert (key, value, dependencies, absoluteExpiration, slidingExpiration, CacheItemPriority.Normal, onRemoveCallback, true); } void Insert (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback, bool doLock) { if (key == null) throw new ArgumentNullException ("key"); if (value == null) throw new ArgumentNullException ("value"); if (slidingExpiration < TimeSpan.Zero || slidingExpiration > TimeSpan.FromDays (365)) throw new ArgumentNullException ("slidingExpiration"); if (absoluteExpiration != NoAbsoluteExpiration && slidingExpiration != NoSlidingExpiration) throw new ArgumentException ("Both absoluteExpiration and slidingExpiration are specified"); CacheItem ci = new CacheItem (); ci.Value = value; ci.Key = key; if (dependencies != null) { ci.Dependency = dependencies; dependencies.DependencyChanged += new EventHandler (OnDependencyChanged); dependencies.SetCache (DependencyCache); } ci.Priority = priority; SetItemTimeout (ci, absoluteExpiration, slidingExpiration, onRemoveCallback, key, doLock); } internal void SetItemTimeout (string key, DateTime absoluteExpiration, TimeSpan slidingExpiration, bool doLock) { CacheItem ci = null; try { if (doLock) Monitor.Enter (cache); #if NET_2_0 cache.TryGetValue (key, out ci); #else ci = (CacheItem) cache [key]; #endif if (ci != null) SetItemTimeout (ci, absoluteExpiration, slidingExpiration, ci.OnRemoveCallback, null, false); } finally { if (doLock) Monitor.Exit (cache); } } void SetItemTimeout (CacheItem ci, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemRemovedCallback onRemoveCallback, string key, bool doLock) { ci.SlidingExpiration = slidingExpiration; if (slidingExpiration != NoSlidingExpiration) ci.AbsoluteExpiration = DateTime.Now + slidingExpiration; else ci.AbsoluteExpiration = absoluteExpiration; ci.OnRemoveCallback = onRemoveCallback; try { if (doLock) Monitor.Enter (cache); if (ci.Timer != null) { ci.Timer.Dispose (); ci.Timer = null; } if (key != null) cache [key] = ci; ci.LastChange = DateTime.Now; if (ci.AbsoluteExpiration != NoAbsoluteExpiration) { int remaining = Math.Max (0, (int)(ci.AbsoluteExpiration - DateTime.Now).TotalMilliseconds); ci.Timer = new Timer (new TimerCallback (ItemExpired), ci, remaining, Timeout.Infinite); } } finally { if (doLock) Monitor.Exit (cache); } } public object Remove (string key) { return Remove (key, CacheItemRemovedReason.Removed, true); } object Remove (string key, CacheItemRemovedReason reason, bool doLock) { CacheItem it = null; try { if (doLock) Monitor.Enter (cache); #if NET_2_0 cache.TryGetValue (key, out it); #else it = (CacheItem) cache [key]; #endif cache.Remove (key); } finally { if (doLock) Monitor.Exit (cache); } if (it != null) { Timer t = it.Timer; if (t != null) t.Dispose (); if (it.Dependency != null) { #if NET_2_0 it.Dependency.SetCache (null); #endif it.Dependency.DependencyChanged -= new EventHandler (OnDependencyChanged); it.Dependency.Dispose (); } if (it.OnRemoveCallback != null) { try { it.OnRemoveCallback (key, it.Value, reason); } catch { //TODO: anything to be done here? } } return it.Value; } else return null; } // Used when shutting down the application so that // session_end events are sent for all sessions. internal void InvokePrivateCallbacks () { CacheItemRemovedReason reason = CacheItemRemovedReason.Removed; lock (cache) { foreach (string key in cache.Keys) { CacheItem item; #if NET_2_0 cache.TryGetValue (key, out item); #else item = (CacheItem) cache [key]; #endif if (item != null && item.OnRemoveCallback != null) { try { item.OnRemoveCallback (key, item.Value, reason); } catch { //TODO: anything to be done here? } } } } } public IDictionaryEnumerator GetEnumerator () { ArrayList list = new ArrayList (); lock (cache) { #if NET_2_0 foreach (CacheItem it in cache.Values) list.Add (it); #else list.AddRange (cache.Values); #endif } return new CacheItemEnumerator (list); } IEnumerator IEnumerable.GetEnumerator () { return GetEnumerator (); } void OnDependencyChanged (object o, EventArgs a) { CheckDependencies (); } void ItemExpired(object cacheItem) { CacheItem ci = (CacheItem)cacheItem; ci.Timer.Dispose(); ci.Timer = null; Remove (ci.Key, CacheItemRemovedReason.Expired, true); } internal void CheckDependencies () { ArrayList list; lock (cache) { list = new ArrayList (); #if NET_2_0 foreach (CacheItem it in cache.Values) list.Add (it); #else list.AddRange (cache.Values); #endif foreach (CacheItem it in list) { if (it.Dependency != null && it.Dependency.HasChanged) Remove (it.Key, CacheItemRemovedReason.DependencyChanged, false); } } } internal DateTime GetKeyLastChange (string key) { lock (cache) { CacheItem it; #if NET_2_0 if (!cache.TryGetValue (key, out it)) return DateTime.MaxValue; #else it = (CacheItem) cache [key]; #endif if (it == null) return DateTime.MaxValue; return it.LastChange; } } internal Cache DependencyCache { get { if (dependencyCache == null) return this; return dependencyCache; } set { dependencyCache = value; } } } sealed class CacheItem { public object Value; public string Key; public CacheDependency Dependency; public DateTime AbsoluteExpiration; public TimeSpan SlidingExpiration; public CacheItemPriority Priority; public CacheItemRemovedCallback OnRemoveCallback; public DateTime LastChange; public Timer Timer; } sealed class CacheItemEnumerator: IDictionaryEnumerator { ArrayList list; int pos = -1; public CacheItemEnumerator (ArrayList list) { this.list = list; } CacheItem Item { get { if (pos < 0 || pos >= list.Count) throw new InvalidOperationException (); return (CacheItem) list [pos]; } } public DictionaryEntry Entry { get { return new DictionaryEntry (Item.Key, Item.Value); } } public object Key { get { return Item.Key; } } public object Value { get { return Item.Value; } } public object Current { get { return Entry; } } public bool MoveNext () { return (++pos < list.Count); } public void Reset () { pos = -1; } } }