1317e3b89603f23703b4803f1181d7f7d6fb0221
[mono.git] / mcs / class / System.Web / System.Web.Caching / Cache.cs
1 //
2 // System.Web.Caching.Cache
3 //
4 // Author(s):
5 //  Lluis Sanchez (lluis@ximian.com)
6 //
7 // (C) 2005 Novell, Inc (http://www.novell.com)
8 //
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
17 //
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 //
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 //
29
30 using System.Threading;
31 using System.Collections;
32 using System.Security.Permissions;
33 #if NET_2_0
34 using System.Collections.Generic;
35 #endif
36
37 namespace System.Web.Caching
38 {
39         // CAS - no InheritanceDemand here as the class is sealed
40         [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
41         public sealed class Cache: IEnumerable
42         {
43 #if NET_2_0
44                 Dictionary <string, CacheItem> cache;
45 #else
46                 Hashtable cache;
47 #endif
48                 Cache dependencyCache;
49                 public static readonly DateTime NoAbsoluteExpiration = DateTime.MaxValue;
50                 public static readonly TimeSpan NoSlidingExpiration = TimeSpan.Zero;
51                 
52                 public Cache ()
53                 {
54 #if NET_2_0
55                         cache = new Dictionary <string, CacheItem> ();
56 #else
57                         cache = new Hashtable ();
58 #endif
59                 }
60                 
61                 public int Count {
62                         get { return cache.Count; }
63                 }
64                 
65                 public object this [string key] {
66                         get { return Get (key); }
67                         set { Insert (key, value); }
68                 }
69                 
70                 public object Add (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback)
71                 {
72                         if (key == null)
73                                 throw new ArgumentNullException ("key");
74
75                         lock (cache) {
76                                 CacheItem it;
77
78 #if NET_2_0
79                                 cache.TryGetValue (key, out it);
80 #else
81                                 it = (CacheItem) cache [key];
82 #endif
83                                 if (it != null)
84                                         return it.Value;
85                                 Insert (key, value, dependencies, absoluteExpiration, slidingExpiration, priority, onRemoveCallback, false);
86                         }
87                         return null;
88                 }
89                 
90                 public object Get (string key)
91                 {
92                         lock (cache) {
93                                 CacheItem it;
94 #if NET_2_0
95                                 if (!cache.TryGetValue (key, out it))
96                                         return null;
97 #else
98                                 it = (CacheItem) cache [key];
99                                 if (it == null)
100                                         return null;
101 #endif
102                                 
103                                 if (it.Dependency != null && it.Dependency.HasChanged) {
104                                         Remove (it.Key, CacheItemRemovedReason.DependencyChanged, false);
105                                         return null;
106                                 }
107
108                                 if (it.SlidingExpiration != NoSlidingExpiration) {
109                                         it.AbsoluteExpiration = DateTime.Now + it.SlidingExpiration;
110                                         it.Timer.Change ((int)it.SlidingExpiration.TotalMilliseconds, Timeout.Infinite);
111                                 } else if (DateTime.Now >= it.AbsoluteExpiration) {
112                                         Remove (key, CacheItemRemovedReason.Expired, false);
113                                         return null;
114                                 }
115
116                                 return it.Value;
117                         }
118                 }
119                 
120                 public void Insert (string key, object value)
121                 {
122                         Insert (key, value, null, NoAbsoluteExpiration, NoSlidingExpiration, CacheItemPriority.Normal, null, true);
123                 }
124                 
125                 public void Insert (string key, object value, CacheDependency dependencies)
126                 {
127                         Insert (key, value, dependencies, NoAbsoluteExpiration, NoSlidingExpiration, CacheItemPriority.Normal, null, true);
128                 }
129                 
130                 public void Insert (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration)
131                 {
132                         Insert (key, value, dependencies, absoluteExpiration, slidingExpiration, CacheItemPriority.Normal, null, true);
133                 }
134                 
135                 public void Insert (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration,
136                                     CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback)
137                 {
138                         Insert (key, value, dependencies, absoluteExpiration, slidingExpiration, CacheItemPriority.Normal, onRemoveCallback, true);
139                 }
140
141                 void Insert (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration,
142                              CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback, bool doLock)
143                 {
144                         if (key == null)
145                                 throw new ArgumentNullException ("key");
146                         if (value == null)
147                                 throw new ArgumentNullException ("value");
148                         if (slidingExpiration < TimeSpan.Zero || slidingExpiration > TimeSpan.FromDays (365))
149                                 throw new ArgumentNullException ("slidingExpiration");
150                         if (absoluteExpiration != NoAbsoluteExpiration && slidingExpiration != NoSlidingExpiration)
151                                 throw new ArgumentException ("Both absoluteExpiration and slidingExpiration are specified");
152                                 
153                         CacheItem ci = new CacheItem ();
154                         ci.Value = value;
155                         ci.Key = key;
156                         
157                         if (dependencies != null) {
158                                 ci.Dependency = dependencies;
159                                 dependencies.DependencyChanged += new EventHandler (OnDependencyChanged);
160                                 dependencies.SetCache (DependencyCache);
161                         }
162
163                         ci.Priority = priority;
164                         SetItemTimeout (ci, absoluteExpiration, slidingExpiration, onRemoveCallback, key, doLock);
165                 }
166                 
167                 internal void SetItemTimeout (string key, DateTime absoluteExpiration, TimeSpan slidingExpiration, bool doLock)
168                 {
169                         CacheItem ci = null;
170
171                         try {
172                                 if (doLock)
173                                         Monitor.Enter (cache);
174 #if NET_2_0
175                                 cache.TryGetValue (key, out ci);
176 #else
177                                 ci = (CacheItem) cache [key];
178 #endif
179
180                                 if (ci != null)
181                                         SetItemTimeout (ci, absoluteExpiration, slidingExpiration, ci.OnRemoveCallback, null, false);
182                         } finally {
183                                 if (doLock)
184                                         Monitor.Exit (cache);
185                         }
186                 }
187
188                 void SetItemTimeout (CacheItem ci, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemRemovedCallback onRemoveCallback,
189                                      string key, bool doLock)
190                 {
191                         ci.SlidingExpiration = slidingExpiration;
192                         if (slidingExpiration != NoSlidingExpiration)
193                                 ci.AbsoluteExpiration = DateTime.Now + slidingExpiration;
194                         else
195                                 ci.AbsoluteExpiration = absoluteExpiration;                     
196
197                         ci.OnRemoveCallback = onRemoveCallback;
198                         
199                         try {
200                                 if (doLock)
201                                         Monitor.Enter (cache);
202                                 
203                                 if (ci.Timer != null) {
204                                         ci.Timer.Dispose ();
205                                         ci.Timer = null;
206                                 }
207
208                                 if (key != null)
209                                         cache [key] = ci;
210                                 
211                                 ci.LastChange = DateTime.Now;
212                                 if (ci.AbsoluteExpiration != NoAbsoluteExpiration) {
213                                         int remaining = Math.Max (0, (int)(ci.AbsoluteExpiration - DateTime.Now).TotalMilliseconds);
214                                         ci.Timer = new Timer (new TimerCallback (ItemExpired), ci, remaining, Timeout.Infinite);
215                                 }
216                         } finally {
217                                 if (doLock)
218                                         Monitor.Exit (cache);
219                         }
220                 }
221                 
222                 public object Remove (string key)
223                 {
224                         return Remove (key, CacheItemRemovedReason.Removed, true);
225                 }
226                 
227                 object Remove (string key, CacheItemRemovedReason reason, bool doLock)
228                 {
229                         CacheItem it = null;
230
231                         try {
232                                 if (doLock)
233                                         Monitor.Enter (cache);
234 #if NET_2_0
235                                 cache.TryGetValue (key, out it);
236 #else
237                                 it = (CacheItem) cache [key];
238 #endif
239                                 cache.Remove (key);
240                         } finally {
241                                 if (doLock)
242                                         Monitor.Exit (cache);
243                         }
244
245                         if (it != null) {
246                                 Timer t = it.Timer;
247                                 if (t != null)
248                                         t.Dispose ();
249                                 
250                                 if (it.Dependency != null) {
251 #if NET_2_0
252                                         it.Dependency.SetCache (null);
253 #endif
254                                         it.Dependency.DependencyChanged -= new EventHandler (OnDependencyChanged);
255                                         it.Dependency.Dispose ();
256                                 }
257                                 if (it.OnRemoveCallback != null) {
258                                         try {
259                                                 it.OnRemoveCallback (key, it.Value, reason);
260                                         } catch {
261                                                 //TODO: anything to be done here?
262                                         }
263                                 }
264                                 return it.Value;
265                         } else
266                                 return null;
267                 }
268
269                 // Used when shutting down the application so that
270                 // session_end events are sent for all sessions.
271                 internal void InvokePrivateCallbacks ()
272                 {
273                         CacheItemRemovedReason reason = CacheItemRemovedReason.Removed;
274                         lock (cache) {
275                                 foreach (string key in cache.Keys) {
276                                         CacheItem item;
277 #if NET_2_0
278                                         cache.TryGetValue (key, out item);
279 #else
280                                         item = (CacheItem) cache [key];
281 #endif
282
283                                         if (item != null && item.OnRemoveCallback != null) {
284                                                 try {
285                                                         item.OnRemoveCallback (key, item.Value, reason);
286                                                 } catch {
287                                                         //TODO: anything to be done here?
288                                                 }
289                                         }
290                                 }
291                         }
292                 }
293
294                 public IDictionaryEnumerator GetEnumerator ()
295                 {
296                         ArrayList list = new ArrayList ();
297                         lock (cache) {
298 #if NET_2_0
299                                 foreach (CacheItem it in cache.Values)
300                                         list.Add (it);
301 #else
302                                 list.AddRange (cache.Values);
303 #endif
304                         }
305                         return new CacheItemEnumerator (list);
306                 }
307                 
308                 IEnumerator IEnumerable.GetEnumerator ()
309                 {
310                         return GetEnumerator ();
311                 }
312                 
313                 void OnDependencyChanged (object o, EventArgs a)
314                 {
315                         CheckDependencies ();
316                 }
317                 
318                 void ItemExpired(object cacheItem) {
319                         CacheItem ci = (CacheItem)cacheItem;
320                         ci.Timer.Dispose();
321                         ci.Timer = null;
322
323                         Remove (ci.Key, CacheItemRemovedReason.Expired, true);
324                 }
325                 
326                 internal void CheckDependencies ()
327                 {
328                         ArrayList list;
329                         lock (cache) {
330                                 list = new ArrayList ();
331 #if NET_2_0
332                                 foreach (CacheItem it in cache.Values)
333                                         list.Add (it);
334 #else
335                                 list.AddRange (cache.Values);
336 #endif
337                         
338                                 foreach (CacheItem it in list) {
339                                         if (it.Dependency != null && it.Dependency.HasChanged)
340                                                 Remove (it.Key, CacheItemRemovedReason.DependencyChanged, false);
341                                 }
342                         }
343                 }
344                 
345                 internal DateTime GetKeyLastChange (string key)
346                 {
347                         lock (cache) {
348                                 CacheItem it;
349 #if NET_2_0
350                                 if (!cache.TryGetValue (key, out it))
351                                         return DateTime.MaxValue;
352 #else
353                                 it = (CacheItem) cache [key];
354 #endif
355                                 if (it == null)
356                                         return DateTime.MaxValue;
357                                 
358                                 return it.LastChange;
359                         }
360                 }
361
362                 internal Cache DependencyCache {
363                         get {
364                                 if (dependencyCache == null)
365                                         return this;
366
367                                 return dependencyCache;
368                         }
369                         set { dependencyCache = value; }
370                 }
371         }
372
373         class CacheItem
374         {
375                 public object Value;
376                 public string Key;
377                 public CacheDependency Dependency;
378                 public DateTime AbsoluteExpiration;
379                 public TimeSpan SlidingExpiration;
380                 public CacheItemPriority Priority;
381                 public CacheItemRemovedCallback OnRemoveCallback;
382                 public DateTime LastChange;
383                 public Timer Timer;
384         }
385                 
386         class CacheItemEnumerator: IDictionaryEnumerator
387         {
388                 ArrayList list;
389                 int pos = -1;
390                 
391                 public CacheItemEnumerator (ArrayList list)
392                 {
393                         this.list = list;
394                 }
395                 
396                 CacheItem Item {
397                         get {
398                                 if (pos < 0 || pos >= list.Count)
399                                         throw new InvalidOperationException ();
400                                 return (CacheItem) list [pos];
401                         }
402                 }
403                 
404                 public DictionaryEntry Entry {
405                         get { return new DictionaryEntry (Item.Key, Item.Value); }
406                 }
407                 
408                 public object Key {
409                         get { return Item.Key; }
410                 }
411                 
412                 public object Value {
413                         get { return Item.Value; }
414                 }
415                 
416                 public object Current {
417                         get { return Entry; }
418                 }
419                 
420                 public bool MoveNext ()
421                 {
422                         return (++pos < list.Count);
423                 }
424                 
425                 public void Reset ()
426                 {
427                         pos = -1;
428                 }
429         }
430 }