//
// System.Web.Caching
//
// Author:
// Patrik Torstensson (Patrik.Torstensson@labs2.com)
// Changes:
// Daniel Cazzulino [DHC] (dcazzulino@users.sf.net)
//
// (C) Copyright Patrik Torstensson, 2001
//
using System;
using System.Collections;
using System.Threading;
namespace System.Web.Caching
{
///
/// Implements a cache for Web applications and other. The difference
/// from the MS.NET implementation is that we / support to use the Cache
/// object as cache in our applications.
///
///
/// The Singleton cache is created per application domain, and it
/// remains valid as long as the application domain remains active.
///
///
/// Usage of the singleton cache:
///
/// Cache objManager = Cache.SingletonCache;
///
/// String obj = "tobecached";
/// objManager.Add("kalle", obj);
///
public sealed class Cache : IEnumerable
{
// Declarations
///
/// Used in the absoluteExpiration parameter in an Insert
/// method call to indicate the item should never expire. This
/// field is read-only.
///
public static readonly DateTime NoAbsoluteExpiration = DateTime.MaxValue;
///
/// Used as the slidingExpiration parameter in an Insert method
/// call to disable sliding expirations. This field is read-only.
///
public static readonly TimeSpan NoSlidingExpiration = TimeSpan.Zero;
// Helper objects
CacheExpires _objExpires;
// The data storage
// Todo: Make a specialized storage for the cache entries?
// todo: allow system to replace the storage?
Hashtable _arrEntries;
ReaderWriterLock _lockEntries;
static TimeSpan _datetimeOneYear = TimeSpan.FromDays (365);
int _nItems; // Number of items in the cache
public Cache ()
{
_nItems = 0;
_lockEntries = new ReaderWriterLock ();
_arrEntries = new Hashtable ();
_objExpires = new CacheExpires (this);
}
///
/// Internal method to create a enumerator and over all public
/// items in the cache and is used by GetEnumerator method.
///
///
/// Returns IDictionaryEnumerator that contains all public items in the cache
///
private IDictionaryEnumerator CreateEnumerator ()
{
Hashtable objTable;
//Locking with -1 provides a non-expiring lock.
_lockEntries.AcquireReaderLock (-1);
try {
// Create a new hashtable to return as collection of public items
objTable = new Hashtable (_arrEntries.Count);
foreach (DictionaryEntry objEntry in _arrEntries) {
if (objEntry.Key == null)
continue;
CacheEntry entry = (CacheEntry) objEntry.Value;
if (entry.TestFlag (CacheEntry.Flags.Public))
objTable.Add (objEntry.Key, entry.Item);
}
} finally {
_lockEntries.ReleaseReaderLock ();
}
return objTable.GetEnumerator ();
}
///
/// Implementation of IEnumerable interface and calls the GetEnumerator that returns
/// IDictionaryEnumerator.
///
IEnumerator IEnumerable.GetEnumerator ()
{
return GetEnumerator ();
}
///
/// Virtual override of the IEnumerable.GetEnumerator() method,
/// returns a specialized enumerator.
///
public IDictionaryEnumerator GetEnumerator ()
{
return CreateEnumerator ();
}
///
/// Touches a object in the cache. Used to update expire time and hit count.
///
/// The identifier for the cache item to retrieve.
internal void Touch(string strKey)
{
Get (strKey);
}
///
/// Adds the specified item to the Cache object with
/// dependencies, expiration and priority policies, and a
/// delegate you can use to notify your application when the
/// inserted item is removed from the Cache.
///
/// The cache key used to reference the item.
/// The item to be added to the cache.
///
/// The file or cache key dependencies for the item. When any
/// dependency changes, the object becomes invalid and is removed
/// from the cache. If there are no dependencies, this paramter
/// contains a null reference.
///
///
/// The time at which the added object expires and is removed from the cache.
///
///
/// The interval between the time the added object was last
/// accessed and when that object expires. If this value is the
/// equivalent of 20 minutes, the object expires and is removed
/// from the cache 20 minutes after it is last accessed.
///
///
/// The relative cost of the object, as expressed by the
/// CacheItemPriority enumeration. The cache uses this value when
/// it evicts objects; objects with a lower cost are removed from
/// the cache before objects with a higher cost.
///
///
/// A delegate that, if provided, is called when an object is
/// removed from the cache. You can use this to notify
/// applications when their objects are deleted from the
/// cache.
///
/// The Object item added to the Cache.
public object Add (string strKey,
object objItem,
CacheDependency objDependency,
DateTime absolutExpiration,
TimeSpan slidingExpiration,
CacheItemPriority enumPriority,
CacheItemRemovedCallback eventRemoveCallback)
{
return Add (strKey, objItem, objDependency, absolutExpiration,
slidingExpiration, enumPriority, eventRemoveCallback, true);
}
private object Add (string strKey,
object objItem,
CacheDependency objDependency,
DateTime absolutExpiration,
TimeSpan slidingExpiration,
CacheItemPriority enumPriority,
CacheItemRemovedCallback eventRemoveCallback,
bool pub)
{
if (strKey == null)
throw new ArgumentNullException ("strKey");
if (objItem == null)
throw new ArgumentNullException ("objItem");
if (slidingExpiration > _datetimeOneYear)
throw new ArgumentOutOfRangeException ("slidingExpiration");
CacheEntry objEntry;
CacheEntry objNewEntry;
long longHitRange = 10000;
// todo: check decay and make up the minHit range
objEntry = new CacheEntry (this,
strKey,
objItem,
objDependency,
eventRemoveCallback,
absolutExpiration,
slidingExpiration,
longHitRange,
pub,
enumPriority);
Interlocked.Increment (ref _nItems);
// If we have any kind of expiration add into the CacheExpires class
if (objEntry.HasSlidingExpiration || objEntry.HasAbsoluteExpiration)
_objExpires.Add (objEntry);
// Check and get the new item..
objNewEntry = UpdateCache (strKey, objEntry, true, CacheItemRemovedReason.Removed);
if (objNewEntry == null)
return null;
return objEntry.Item;
}
///
/// Inserts an item into the Cache object with a cache key to
/// reference its location and using default values provided by
/// the CacheItemPriority enumeration.
///
/// The cache key used to reference the item.
/// The item to be added to the cache.
public void Insert (string strKey, object objItem)
{
Add (strKey,
objItem,
null,
NoAbsoluteExpiration,
NoSlidingExpiration,
CacheItemPriority.Default,
null);
}
///
/// Inserts an object into the Cache that has file or key dependencies.
///
/// The cache key used to reference the item.
/// The item to be added to the cache.
///
/// The file or cache key dependencies for the item. When any
/// dependency changes, the object becomes invalid and is removed
/// from the cache. If there are no dependencies, this paramter
/// contains a null reference.
///
public void Insert (string strKey, object objItem, CacheDependency objDependency)
{
Add (strKey,
objItem,
objDependency,
NoAbsoluteExpiration,
NoSlidingExpiration,
CacheItemPriority.Default,
null);
}
///
/// Inserts an object into the Cache with dependencies and expiration policies.
///
/// The cache key used to reference the item.
/// The item to be added to the cache.
///
/// The file or cache key dependencies for the item. When any
/// dependency changes, the object becomes invalid and is removed
/// from the cache. If there are no dependencies, this paramter
/// contains a null reference.
///
///
/// The time at which the added object expires and is removed from the cache.
///
/// The interval between the
/// time the added object was last accessed and when that object
/// expires. If this value is the equivalent of 20 minutes, the
/// object expires and is removed from the cache 20 minutes after
/// it is last accessed.
///
public void Insert (string strKey,
object objItem,
CacheDependency objDependency,
DateTime absolutExpiration,
TimeSpan slidingExpiration)
{
Add (strKey,
objItem,
objDependency,
absolutExpiration,
slidingExpiration,
CacheItemPriority.Default,
null);
}
///
/// Inserts an object into the Cache object with dependencies,
/// expiration and priority policies, and a delegate you can use
/// to notify your application when the inserted item is removed
/// from the Cache.
///
/// The cache key used to reference the item.
/// The item to be added to the cache.
///
/// The file or cache key dependencies for the item. When any
/// dependency changes, the object becomes invalid and is removed
/// from the cache. If there are no dependencies, this paramter
/// contains a null reference.
///
///
/// The time at which the added object expires and is removed from the cache.
///
///
/// The interval between the time the added object was last
/// accessed and when that object expires. If this value is the
/// equivalent of 20 minutes, the object expires and is removed
/// from the cache 20 minutes after it is last accessed.
///
/// The relative cost of the object,
/// as expressed by the CacheItemPriority enumeration. The cache
/// uses this value when it evicts objects; objects with a lower
/// cost are removed from the cache before objects with a higher
/// cost.
///
/// A delegate that, if
/// provided, is called when an object is removed from the cache.
/// You can use this to notify applications when their objects
/// are deleted from the cache.
///
public void Insert (string strKey,
object objItem,
CacheDependency objDependency,
DateTime absolutExpiration,
TimeSpan slidingExpiration,
CacheItemPriority enumPriority,
CacheItemRemovedCallback eventRemoveCallback)
{
Add (strKey,
objItem,
objDependency,
absolutExpiration,
slidingExpiration,
enumPriority,
eventRemoveCallback);
}
internal void InsertPrivate (string strKey,
object objItem,
CacheDependency objDependency,
DateTime absolutExpiration,
TimeSpan slidingExpiration,
CacheItemPriority enumPriority,
CacheItemRemovedCallback eventRemoveCallback)
{
Add (strKey,
objItem,
objDependency,
absolutExpiration,
slidingExpiration,
enumPriority,
eventRemoveCallback, false);
}
///
/// Removes the specified item from the Cache object.
///
/// The cache key for the cache item to remove.
///
/// The item removed from the Cache. If the value in the key
/// parameter is not found, returns a null reference.
///
public object Remove (string strKey)
{
return Remove (strKey, CacheItemRemovedReason.Removed);
}
///
/// Internal method that updates the cache, decremenents the
/// number of existing items and call close on the cache entry.
/// This method is also used from the ExpiresBuckets class to
/// remove an item during GC flush.
///
/// The cache key for the cache item to remove.
/// Reason why the item is removed.
///
/// The item removed from the Cache. If the value in the key
/// parameter is not found, returns a null reference.
///
internal object Remove (string strKey, CacheItemRemovedReason enumReason)
{
CacheEntry objEntry = UpdateCache (strKey, null, true, enumReason);
if (objEntry == null)
return null;
Interlocked.Decrement (ref _nItems);
// Close the cache entry (calls the remove delegate)
objEntry.Close (enumReason);
return objEntry.Item;
}
///
/// Retrieves the specified item from the Cache object.
///
/// The identifier for the cache item to retrieve.
/// The retrieved cache item, or a null reference.
public object Get (string strKey)
{
CacheEntry objEntry = UpdateCache (strKey, null, false, CacheItemRemovedReason.Expired);
if (objEntry == null)
return null;
return objEntry.Item;
}
internal CacheEntry GetEntry (string strKey)
{
CacheEntry objEntry = UpdateCache (strKey, null, false, CacheItemRemovedReason.Expired);
if (objEntry == null)
return null;
return objEntry;
}
///
/// Internal method used for removing, updating and adding CacheEntries into the cache.
///
/// The identifier for the cache item to modify
///
/// CacheEntry to use for overwrite operation, if this
/// parameter is null and overwrite true the item is going to be
/// removed
///
///
/// If true the objEntry parameter is used to overwrite the
/// strKey entry
///
/// Reason why an item was removed
///
private CacheEntry UpdateCache (string strKey,
CacheEntry objEntry,
bool boolOverwrite,
CacheItemRemovedReason enumReason)
{
if (strKey == null)
throw new ArgumentNullException ("strKey");
long ticksNow = DateTime.Now.Ticks;
long ticksExpires = long.MaxValue;
bool boolGetItem = false;
bool boolExpiried = false;
bool boolWrite = false;
bool boolRemoved = false;
// Are we getting the item from the hashtable
if (boolOverwrite == false && strKey.Length > 0 && objEntry == null)
boolGetItem = true;
// TODO: Optimize this method, move out functionality outside the lock
_lockEntries.AcquireReaderLock (-1);
try {
if (boolGetItem) {
objEntry = (CacheEntry) _arrEntries [strKey];
if (objEntry == null)
return null;
}
if (objEntry != null) {
// Check if we have expired
if (objEntry.HasSlidingExpiration || objEntry.HasAbsoluteExpiration) {
if (objEntry.Expires < ticksNow) {
// We have expired, remove the item from the cache
boolWrite = true;
boolExpiried = true;
}
}
}
// Check if we going to modify the hashtable
if (boolWrite || (boolOverwrite && !boolExpiried)) {
// Upgrade our lock to write
Threading.LockCookie objCookie = _lockEntries.UpgradeToWriterLock (-1);
try {
// Check if we going to just modify an existing entry (or add)
if (boolOverwrite && objEntry != null) {
_arrEntries [strKey] = objEntry;
} else {
// We need to remove the item, fetch the item first
objEntry = (CacheEntry) _arrEntries [strKey];
if (objEntry != null)
_arrEntries.Remove (strKey);
boolRemoved = true;
}
} finally {
_lockEntries.DowngradeFromWriterLock (ref objCookie);
}
}
// If the entry haven't expired or been removed update the info
if (!boolExpiried && !boolRemoved) {
// Update that we got a hit
objEntry.Hits++;
if (objEntry.HasSlidingExpiration)
ticksExpires = ticksNow + objEntry.SlidingExpiration;
}
} finally {
_lockEntries.ReleaseLock ();
}
// If the item was removed we need to remove it from the CacheExpired class also
if (boolRemoved) {
if (objEntry != null) {
if (objEntry.HasAbsoluteExpiration || objEntry.HasSlidingExpiration)
_objExpires.Remove (objEntry);
objEntry.Close (enumReason);
}
return null;
}
// If we have sliding expiration and we have a correct hit, update the expiration manager
if (objEntry.HasSlidingExpiration) {
_objExpires.Update (objEntry, ticksExpires);
objEntry.Expires = ticksExpires;
}
// Return the cache entry
return objEntry;
}
///
/// Gets the number of items stored in the cache.
///
public int Count {
get { return _nItems; }
}
///
/// Gets or sets the cache item at the specified key.
///
public object this [string strKey] {
get {
return Get (strKey);
}
set {
Insert (strKey, value);
}
}
}
}