Merge pull request #5714 from alexischr/update_bockbuild
[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                 CacheItemRemovedCallback response_removed;
49                 static object keysCacheLock = new object ();
50                 Dictionary <string, string> keysCache;
51                 Dictionary <string, string> entriesToInvalidate;
52                 
53                 public OutputCacheModule ()
54                 {
55                 }
56
57                 OutputCacheProvider FindCacheProvider (HttpApplication app)
58                 {                               
59                         HttpContext ctx = HttpContext.Current;
60                         if (app == null) {
61                                 app = ctx != null ? ctx.ApplicationInstance : null;
62
63                                 if (app == null)
64                                         throw new InvalidOperationException ("Unable to find output cache provider.");
65                         }
66
67                         string providerName = app.GetOutputCacheProviderName (ctx);
68                         if (String.IsNullOrEmpty (providerName))
69                                 throw new ProviderException ("Invalid OutputCacheProvider name. Name must not be null or an empty string.");
70
71                         OutputCacheProvider ret = OutputCache.GetProvider (providerName);
72                         if (ret == null)
73                                 throw new ProviderException (String.Format ("OutputCacheProvider named '{0}' cannot be found.", providerName));
74
75                         return ret;
76                 }
77                 
78                 public void Dispose ()
79                 {
80                 }
81
82                 public void Init (HttpApplication context)
83                 {
84                         context.ResolveRequestCache += new EventHandler(OnResolveRequestCache);
85                         context.UpdateRequestCache += new EventHandler(OnUpdateRequestCache);
86                         response_removed = new CacheItemRemovedCallback (OnRawResponseRemoved);
87                 }
88
89                 void OnBuildManagerRemoveEntry (BuildManagerRemoveEntryEventArgs args)
90                 {
91                         string entry = args.EntryName;
92                         HttpContext context = args.Context;
93                         string cacheValue;
94
95                         lock (keysCacheLock) {
96                                 if (!keysCache.TryGetValue (entry, out cacheValue))
97                                         return;
98
99                                 keysCache.Remove (entry);
100                                 if (context == null) {
101                                         if (entriesToInvalidate == null) {
102                                                 entriesToInvalidate = new Dictionary <string, string> (StringComparer.Ordinal);
103                                                 entriesToInvalidate.Add (entry, cacheValue);
104                                                 return;
105                                         } else if (!entriesToInvalidate.ContainsKey (entry)) {
106                                                 entriesToInvalidate.Add (entry, cacheValue);
107                                                 return;
108                                         }
109                                 }
110                         }
111
112                         OutputCacheProvider provider = FindCacheProvider (context != null ? context.ApplicationInstance : null);
113                         provider.Remove (entry);
114                         if (!String.IsNullOrEmpty (cacheValue))
115                                 provider.Remove (cacheValue);
116                 }
117
118                 void OnResolveRequestCache (object o, EventArgs args)
119                 {
120                         HttpApplication app = o as HttpApplication;
121                         HttpContext context = app != null ? app.Context : null;
122
123                         if (context == null)
124                                 return;
125
126                         OutputCacheProvider provider = FindCacheProvider (app);
127                         string vary_key = context.Request.FilePath;
128                         CachedVaryBy varyby = provider.Get (vary_key) as CachedVaryBy;
129                         string key;
130                         CachedRawResponse c;
131
132                         if (varyby == null)
133                                 return;
134
135                         key = varyby.CreateKey (vary_key, context);
136                         c = provider.Get (key) as CachedRawResponse;
137                         if (c == null)
138                                 return;
139
140                         lock (keysCacheLock) {
141                                 string invValue;
142                                 if (entriesToInvalidate != null && entriesToInvalidate.TryGetValue (vary_key, out invValue) && String.Compare (invValue, key, StringComparison.Ordinal) == 0) {
143                                         provider.Remove (vary_key);
144                                         provider.Remove (key);
145                                         entriesToInvalidate.Remove (vary_key);
146                                         return;
147                                 }
148                         }
149                         
150                         ArrayList callbacks = c.Policy.ValidationCallbacks;
151                         if (callbacks != null && callbacks.Count > 0) {
152                                 bool isValid = true;
153                                 bool isIgnored = false;
154
155                                 foreach (Pair p in callbacks) {
156                                         HttpCacheValidateHandler validate = (HttpCacheValidateHandler)p.First;
157                                         object data = p.Second;
158                                         HttpValidationStatus status = HttpValidationStatus.Valid;
159
160                                         try {
161                                                 validate (context, data, ref status);
162                                         } catch {
163                                                 // MS.NET hides the exception
164                                                 isValid = false;
165                                                 break;
166                                         }
167
168                                         if (status == HttpValidationStatus.Invalid) {
169                                                 isValid = false;
170                                                 break;
171                                         } else if (status == HttpValidationStatus.IgnoreThisRequest) {
172                                                 isIgnored = true;
173                                         }
174                                 }
175
176                                 if (!isValid) {
177                                         OnRawResponseRemoved (key, c, CacheItemRemovedReason.Removed);
178                                         return;
179                                 } else if (isIgnored)
180                                         return;
181                         }
182
183                         HttpResponse response = context.Response;                       
184                         response.ClearContent ();
185                         IList cachedData = c.GetData ();
186                         if (cachedData != null) {
187                                 Encoding outEnc = WebEncoding.ResponseEncoding;
188                                 
189                                 foreach (CachedRawResponse.DataItem d in cachedData) {
190                                         if (d.Length > 0) {
191                                                 response.BinaryWrite (d.Buffer, 0, (int)d.Length);
192                                                 continue;
193                                         }
194
195                                         if (d.Callback == null)
196                                                 continue;
197
198                                         string s = d.Callback (context);
199                                         if (s == null || s.Length == 0)
200                                                 continue;
201
202                                         byte[] bytes = outEnc.GetBytes (s);
203                                         response.BinaryWrite (bytes, 0, bytes.Length);
204                                 }
205                         }
206                         
207                         response.ClearHeaders ();
208                         response.SetCachedHeaders (c.Headers);
209                         response.StatusCode = c.StatusCode;
210                         response.StatusDescription = c.StatusDescription;
211                                 
212                         app.CompleteRequest ();
213                 }
214
215                 void OnUpdateRequestCache (object o, EventArgs args)
216                 {
217                         HttpApplication app = o as HttpApplication;
218                         HttpContext context = app != null ? app.Context : null;
219                         HttpResponse response = context != null ? context.Response : null;
220                         
221                         if (response != null && response.IsCached && response.StatusCode == 200 && !context.Trace.IsEnabled)
222                                 DoCacheInsert (context, app, response);
223                 }
224
225                 void DoCacheInsert (HttpContext context, HttpApplication app, HttpResponse response)
226                 {
227                         string vary_key = context.Request.FilePath;
228                         string key;
229                         OutputCacheProvider provider = FindCacheProvider (app);
230                         CachedVaryBy varyby = provider.Get (vary_key) as CachedVaryBy;
231                         CachedRawResponse prev = null;
232                         bool lookup = true;
233                         string cacheKey = null, cacheValue = null;
234                         HttpCachePolicy cachePolicy = response.Cache;
235                         
236                         if (varyby == null) {
237                                 varyby = new CachedVaryBy (cachePolicy, vary_key);
238                                 provider.Add (vary_key, varyby, Cache.NoAbsoluteExpiration);
239                                 lookup = false;
240                                 cacheKey = vary_key;
241                         } 
242
243                         key = varyby.CreateKey (vary_key, context);
244
245                         if (lookup)
246                                 prev = provider.Get (key) as CachedRawResponse;
247                         
248                         if (prev == null) {
249                                 CachedRawResponse c = response.GetCachedResponse ();
250                                 if (c != null) {
251                                         string [] keys = new string [] { vary_key };
252                                         DateTime utcExpiry, absoluteExpiration;
253                                         TimeSpan slidingExpiration;
254
255                                         c.VaryBy = varyby;
256                                         varyby.ItemList.Add (key);
257
258                                         if (cachePolicy.Sliding) {
259                                                 slidingExpiration = TimeSpan.FromSeconds (cachePolicy.Duration);
260                                                 absoluteExpiration = Cache.NoAbsoluteExpiration;
261                                                 utcExpiry = DateTime.UtcNow + slidingExpiration;
262                                         } else {
263                                                 slidingExpiration = Cache.NoSlidingExpiration;
264                                                 absoluteExpiration = cachePolicy.Expires;
265                                                 utcExpiry = absoluteExpiration.ToUniversalTime ();
266                                         }
267
268                                         provider.Set (key, c, utcExpiry);
269                                         HttpRuntime.InternalCache.Insert (key, c, new CacheDependency (null, keys), absoluteExpiration, slidingExpiration,
270                                                                           CacheItemPriority.Normal, response_removed);
271                                         cacheValue = key;
272                                 }
273                         }
274                         
275                         if (cacheKey != null) {
276                                 lock (keysCacheLock) {
277                                         if (keysCache == null) {
278                                                 BuildManager.RemoveEntry += new BuildManagerRemoveEntryEventHandler (OnBuildManagerRemoveEntry);
279                                                 keysCache = new Dictionary <string, string> (StringComparer.Ordinal);
280                                                 keysCache.Add (cacheKey, cacheValue);
281                                         } else if (!keysCache.ContainsKey (cacheKey))
282                                                 keysCache.Add (cacheKey, cacheValue);
283                                 }
284                         }
285                 }
286
287                 void OnRawResponseRemoved (string key, object value, CacheItemRemovedReason reason)
288                 {
289                         CachedRawResponse c = value as CachedRawResponse;
290                         CachedVaryBy varyby = c != null ? c.VaryBy : null;
291                         if (varyby == null)
292                                 return;
293
294                         List <string> itemList = varyby.ItemList;
295                         OutputCacheProvider provider = FindCacheProvider (null);
296                         
297                         itemList.Remove (key);
298                         provider.Remove (key);
299                         
300                         if (itemList.Count != 0)
301                                 return;                 
302
303                         provider.Remove (varyby.Key);
304                 }
305         }
306 }