Merge branch 'master' of github.com:mono/mono
[mono.git] / mcs / class / System.Web / System.Web.Caching / OutputCacheModule.cs
1 //
2 // System.Web.Caching.OutputCacheModule
3 //
4 // Authors:
5 //  Jackson Harper (jackson@ximian.com)
6 //  Marek Habersack <mhabersack@novell.com>
7 //
8 // (C) 2003-2009 Novell, Inc (http://www.novell.com)
9 //
10
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 // 
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 // 
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31
32 using System;
33 using System.Collections;
34 using System.Collections.Generic;
35 using System.Configuration.Provider;
36 using System.IO;
37 using System.Text;
38 using System.Web;
39 using System.Web.Hosting;
40 using System.Web.UI;
41 using System.Web.Util;
42 using System.Web.Compilation;
43
44 namespace System.Web.Caching
45 {       
46         sealed class OutputCacheModule : IHttpModule
47         {
48                 OutputCacheProvider provider;
49                 CacheItemRemovedCallback response_removed;
50                 static object keysCacheLock = new object ();
51                 Dictionary <string, string> keysCache;
52                 Dictionary <string, string> entriesToInvalidate;
53 #if !NET_4_0
54                 internal OutputCacheProvider InternalProvider {
55                         get { return provider; }
56                 }
57 #endif
58                 public OutputCacheModule ()
59                 {
60                 }
61
62                 OutputCacheProvider FindCacheProvider (HttpApplication app)
63                 {                               
64 #if NET_4_0
65                         HttpContext ctx = HttpContext.Current;
66                         if (app == null) {
67                                 app = ctx != null ? ctx.ApplicationInstance : null;
68
69                                 if (app == null)
70                                         throw new InvalidOperationException ("Unable to find output cache provider.");
71                         }
72
73                         string providerName = app.GetOutputCacheProviderName (ctx);
74                         if (String.IsNullOrEmpty (providerName))
75                                 throw new ProviderException ("Invalid OutputCacheProvider name. Name must not be null or an empty string.");
76
77                         OutputCacheProvider ret = OutputCache.GetProvider (providerName);
78                         if (ret == null)
79                                 throw new ProviderException (String.Format ("OutputCacheProvider named '{0}' cannot be found.", providerName));
80
81                         return ret;
82 #else
83                         if (provider == null)
84                                 provider = new InMemoryOutputCacheProvider ();
85                         
86                         return provider;
87 #endif
88                 }
89                 
90                 public void Dispose ()
91                 {
92                 }
93
94                 public void Init (HttpApplication context)
95                 {
96                         context.ResolveRequestCache += new EventHandler(OnResolveRequestCache);
97                         context.UpdateRequestCache += new EventHandler(OnUpdateRequestCache);
98                         response_removed = new CacheItemRemovedCallback (OnRawResponseRemoved);
99                 }
100
101                 void OnBuildManagerRemoveEntry (BuildManagerRemoveEntryEventArgs args)
102                 {
103                         string entry = args.EntryName;
104                         HttpContext context = args.Context;
105                         string cacheValue;
106
107                         lock (keysCacheLock) {
108                                 if (!keysCache.TryGetValue (entry, out cacheValue))
109                                         return;
110
111                                 keysCache.Remove (entry);
112                                 if (context == null) {
113                                         if (entriesToInvalidate == null) {
114                                                 entriesToInvalidate = new Dictionary <string, string> (StringComparer.Ordinal);
115                                                 entriesToInvalidate.Add (entry, cacheValue);
116                                                 return;
117                                         } else if (!entriesToInvalidate.ContainsKey (entry)) {
118                                                 entriesToInvalidate.Add (entry, cacheValue);
119                                                 return;
120                                         }
121                                 }
122                         }
123
124                         OutputCacheProvider provider = FindCacheProvider (context != null ? context.ApplicationInstance : null);
125                         provider.Remove (entry);
126                         if (!String.IsNullOrEmpty (cacheValue))
127                                 provider.Remove (cacheValue);
128                 }
129
130                 void OnResolveRequestCache (object o, EventArgs args)
131                 {
132                         HttpApplication app = o as HttpApplication;
133                         HttpContext context = app != null ? app.Context : null;
134
135                         if (context == null)
136                                 return;
137
138                         OutputCacheProvider provider = FindCacheProvider (app);
139                         string vary_key = context.Request.FilePath;
140                         CachedVaryBy varyby = provider.Get (vary_key) as CachedVaryBy;
141                         string key;
142                         CachedRawResponse c;
143
144                         if (varyby == null)
145                                 return;
146
147                         key = varyby.CreateKey (vary_key, context);
148                         c = provider.Get (key) as CachedRawResponse;
149                         if (c == null)
150                                 return;
151
152                         lock (keysCacheLock) {
153                                 string invValue;
154                                 if (entriesToInvalidate != null && entriesToInvalidate.TryGetValue (vary_key, out invValue) && String.Compare (invValue, key, StringComparison.Ordinal) == 0) {
155                                         provider.Remove (vary_key);
156                                         provider.Remove (key);
157                                         entriesToInvalidate.Remove (vary_key);
158                                         return;
159                                 }
160                         }
161                         
162                         ArrayList callbacks = c.Policy.ValidationCallbacks;
163                         if (callbacks != null && callbacks.Count > 0) {
164                                 bool isValid = true;
165                                 bool isIgnored = false;
166
167                                 foreach (Pair p in callbacks) {
168                                         HttpCacheValidateHandler validate = (HttpCacheValidateHandler)p.First;
169                                         object data = p.Second;
170                                         HttpValidationStatus status = HttpValidationStatus.Valid;
171
172                                         try {
173                                                 validate (context, data, ref status);
174                                         } catch {
175                                                 // MS.NET hides the exception
176                                                 isValid = false;
177                                                 break;
178                                         }
179
180                                         if (status == HttpValidationStatus.Invalid) {
181                                                 isValid = false;
182                                                 break;
183                                         } else if (status == HttpValidationStatus.IgnoreThisRequest) {
184                                                 isIgnored = true;
185                                         }
186                                 }
187
188                                 if (!isValid) {
189                                         OnRawResponseRemoved (key, c, CacheItemRemovedReason.Removed);
190                                         return;
191                                 } else if (isIgnored)
192                                         return;
193                         }
194
195                         HttpResponse response = context.Response;                       
196                         response.ClearContent ();
197                         IList cachedData = c.GetData ();
198                         if (cachedData != null) {
199                                 Encoding outEnc = WebEncoding.ResponseEncoding;
200                                 
201                                 foreach (CachedRawResponse.DataItem d in cachedData) {
202                                         if (d.Length > 0) {
203                                                 response.BinaryWrite (d.Buffer, 0, (int)d.Length);
204                                                 continue;
205                                         }
206
207                                         if (d.Callback == null)
208                                                 continue;
209
210                                         string s = d.Callback (context);
211                                         if (s == null || s.Length == 0)
212                                                 continue;
213
214                                         byte[] bytes = outEnc.GetBytes (s);
215                                         response.BinaryWrite (bytes, 0, bytes.Length);
216                                 }
217                         }
218                         
219                         response.ClearHeaders ();
220                         response.SetCachedHeaders (c.Headers);
221                         response.StatusCode = c.StatusCode;
222                         response.StatusDescription = c.StatusDescription;
223                                 
224                         app.CompleteRequest ();
225                 }
226
227                 void OnUpdateRequestCache (object o, EventArgs args)
228                 {
229                         HttpApplication app = o as HttpApplication;
230                         HttpContext context = app != null ? app.Context : null;
231                         HttpResponse response = context != null ? context.Response : null;
232                         
233                         if (response != null && response.IsCached && response.StatusCode == 200 && !context.Trace.IsEnabled)
234                                 DoCacheInsert (context, app, response);
235                 }
236
237                 void DoCacheInsert (HttpContext context, HttpApplication app, HttpResponse response)
238                 {
239                         string vary_key = context.Request.FilePath;
240                         string key;
241                         OutputCacheProvider provider = FindCacheProvider (app);
242                         CachedVaryBy varyby = provider.Get (vary_key) as CachedVaryBy;
243                         CachedRawResponse prev = null;
244                         bool lookup = true;
245                         string cacheKey = null, cacheValue = null;
246                         HttpCachePolicy cachePolicy = response.Cache;
247                         
248                         if (varyby == null) {
249                                 varyby = new CachedVaryBy (cachePolicy, vary_key);
250                                 provider.Add (vary_key, varyby, Cache.NoAbsoluteExpiration);
251                                 lookup = false;
252                                 cacheKey = vary_key;
253                         } 
254
255                         key = varyby.CreateKey (vary_key, context);
256
257                         if (lookup)
258                                 prev = provider.Get (key) as CachedRawResponse;
259                         
260                         if (prev == null) {
261                                 CachedRawResponse c = response.GetCachedResponse ();
262                                 if (c != null) {
263                                         string [] keys = new string [] { vary_key };
264                                         DateTime utcExpiry, absoluteExpiration;
265                                         TimeSpan slidingExpiration;
266
267                                         c.VaryBy = varyby;
268                                         varyby.ItemList.Add (key);
269
270                                         if (cachePolicy.Sliding) {
271                                                 slidingExpiration = TimeSpan.FromSeconds (cachePolicy.Duration);
272                                                 absoluteExpiration = Cache.NoAbsoluteExpiration;
273                                                 utcExpiry = DateTime.UtcNow + slidingExpiration;
274                                         } else {
275                                                 slidingExpiration = Cache.NoSlidingExpiration;
276                                                 absoluteExpiration = cachePolicy.Expires;
277                                                 utcExpiry = absoluteExpiration.ToUniversalTime ();
278                                         }
279
280                                         provider.Set (key, c, utcExpiry);
281                                         HttpRuntime.InternalCache.Insert (key, c, new CacheDependency (null, keys), absoluteExpiration, slidingExpiration,
282                                                                           CacheItemPriority.Normal, response_removed);
283                                         cacheValue = key;
284                                 }
285                         }
286                         
287                         if (cacheKey != null) {
288                                 lock (keysCacheLock) {
289                                         if (keysCache == null) {
290                                                 BuildManager.RemoveEntry += new BuildManagerRemoveEntryEventHandler (OnBuildManagerRemoveEntry);
291                                                 keysCache = new Dictionary <string, string> (StringComparer.Ordinal);
292                                                 keysCache.Add (cacheKey, cacheValue);
293                                         } else if (!keysCache.ContainsKey (cacheKey))
294                                                 keysCache.Add (cacheKey, cacheValue);
295                                 }
296                         }
297                 }
298
299                 void OnRawResponseRemoved (string key, object value, CacheItemRemovedReason reason)
300                 {
301                         CachedRawResponse c = value as CachedRawResponse;
302                         CachedVaryBy varyby = c != null ? c.VaryBy : null;
303                         if (varyby == null)
304                                 return;
305
306                         List <string> itemList = varyby.ItemList;
307                         OutputCacheProvider provider = FindCacheProvider (null);
308                         
309                         itemList.Remove (key);
310                         provider.Remove (key);
311                         
312                         if (itemList.Count != 0)
313                                 return;                 
314
315                         provider.Remove (varyby.Key);
316                 }
317         }
318 }