Merge pull request #2735 from xmcclure/early-lookup-addr
[mono.git] / mcs / class / System.Web / System.Web.Caching / OutputCacheModule.cs
index e693c8de3bcabb75bc5f95d8394fb4607c79a303..fd576c97747f80cf7dad5443d5aad5110bb4fe73 100644 (file)
@@ -3,8 +3,9 @@
 //
 // Authors:
 //  Jackson Harper (jackson@ximian.com)
+//  Marek Habersack <mhabersack@novell.com>
 //
-// (C) 2003 Novell, Inc (http://www.novell.com)
+// (C) 2003-2009 Novell, Inc (http://www.novell.com)
 //
 
 //
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
 
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Configuration.Provider;
 using System.IO;
+using System.Text;
 using System.Web;
+using System.Web.Hosting;
 using System.Web.UI;
 using System.Web.Util;
-using System.Collections;
+using System.Web.Compilation;
 
-namespace System.Web.Caching {
-       
-       internal sealed class OutputCacheModule : IHttpModule {
-
-               private CacheItemRemovedCallback response_removed;
+namespace System.Web.Caching
+{      
+       sealed class OutputCacheModule : IHttpModule
+       {
+               CacheItemRemovedCallback response_removed;
+               static object keysCacheLock = new object ();
+               Dictionary <string, string> keysCache;
+               Dictionary <string, string> entriesToInvalidate;
                
                public OutputCacheModule ()
                {
                }
 
+               OutputCacheProvider FindCacheProvider (HttpApplication app)
+               {                               
+                       HttpContext ctx = HttpContext.Current;
+                       if (app == null) {
+                               app = ctx != null ? ctx.ApplicationInstance : null;
+
+                               if (app == null)
+                                       throw new InvalidOperationException ("Unable to find output cache provider.");
+                       }
+
+                       string providerName = app.GetOutputCacheProviderName (ctx);
+                       if (String.IsNullOrEmpty (providerName))
+                               throw new ProviderException ("Invalid OutputCacheProvider name. Name must not be null or an empty string.");
+
+                       OutputCacheProvider ret = OutputCache.GetProvider (providerName);
+                       if (ret == null)
+                               throw new ProviderException (String.Format ("OutputCacheProvider named '{0}' cannot be found.", providerName));
+
+                       return ret;
+               }
+               
                public void Dispose ()
                {
                }
 
-               public void Init (HttpApplication app)
+               public void Init (HttpApplication context)
                {
-                       app.ResolveRequestCache += new EventHandler(OnResolveRequestCache);
-                       app.UpdateRequestCache += new EventHandler(OnUpdateRequestCache);
+                       context.ResolveRequestCache += new EventHandler(OnResolveRequestCache);
+                       context.UpdateRequestCache += new EventHandler(OnUpdateRequestCache);
                        response_removed = new CacheItemRemovedCallback (OnRawResponseRemoved);
                }
 
+               void OnBuildManagerRemoveEntry (BuildManagerRemoveEntryEventArgs args)
+               {
+                       string entry = args.EntryName;
+                       HttpContext context = args.Context;
+                       string cacheValue;
+
+                       lock (keysCacheLock) {
+                               if (!keysCache.TryGetValue (entry, out cacheValue))
+                                       return;
+
+                               keysCache.Remove (entry);
+                               if (context == null) {
+                                       if (entriesToInvalidate == null) {
+                                               entriesToInvalidate = new Dictionary <string, string> (StringComparer.Ordinal);
+                                               entriesToInvalidate.Add (entry, cacheValue);
+                                               return;
+                                       } else if (!entriesToInvalidate.ContainsKey (entry)) {
+                                               entriesToInvalidate.Add (entry, cacheValue);
+                                               return;
+                                       }
+                               }
+                       }
+
+                       OutputCacheProvider provider = FindCacheProvider (context != null ? context.ApplicationInstance : null);
+                       provider.Remove (entry);
+                       if (!String.IsNullOrEmpty (cacheValue))
+                               provider.Remove (cacheValue);
+               }
+
                void OnResolveRequestCache (object o, EventArgs args)
                {
-                       HttpApplication app = (HttpApplication) o;
-                       HttpContext context = app.Context;
-                       
+                       HttpApplication app = o as HttpApplication;
+                       HttpContext context = app != null ? app.Context : null;
+
+                       if (context == null)
+                               return;
+
+                       OutputCacheProvider provider = FindCacheProvider (app);
                        string vary_key = context.Request.FilePath;
-                       CachedVaryBy varyby = context.Cache [vary_key] as CachedVaryBy;
+                       CachedVaryBy varyby = provider.Get (vary_key) as CachedVaryBy;
                        string key;
                        CachedRawResponse c;
 
@@ -70,11 +133,20 @@ namespace System.Web.Caching {
                                return;
 
                        key = varyby.CreateKey (vary_key, context);
-                       c = context.Cache [key] as CachedRawResponse;
-
+                       c = provider.Get (key) as CachedRawResponse;
                        if (c == null)
                                return;
 
+                       lock (keysCacheLock) {
+                               string invValue;
+                               if (entriesToInvalidate != null && entriesToInvalidate.TryGetValue (vary_key, out invValue) && String.Compare (invValue, key, StringComparison.Ordinal) == 0) {
+                                       provider.Remove (vary_key);
+                                       provider.Remove (key);
+                                       entriesToInvalidate.Remove (vary_key);
+                                       return;
+                               }
+                       }
+                       
                        ArrayList callbacks = c.Policy.ValidationCallbacks;
                        if (callbacks != null && callbacks.Count > 0) {
                                bool isValid = true;
@@ -104,89 +176,131 @@ namespace System.Web.Caching {
                                if (!isValid) {
                                        OnRawResponseRemoved (key, c, CacheItemRemovedReason.Removed);
                                        return;
-                               } else if (isIgnored) {
+                               } else if (isIgnored)
                                        return;
-                               }
                        }
-                       
-                       context.Response.ClearContent ();
-                       context.Response.BinaryWrite (c.GetData (), 0, c.ContentLength);
 
-                       context.Response.ClearHeaders ();
-                       context.Response.SetCachedHeaders (c.Headers);
+                       HttpResponse response = context.Response;                       
+                       response.ClearContent ();
+                       IList cachedData = c.GetData ();
+                       if (cachedData != null) {
+                               Encoding outEnc = WebEncoding.ResponseEncoding;
+                               
+                               foreach (CachedRawResponse.DataItem d in cachedData) {
+                                       if (d.Length > 0) {
+                                               response.BinaryWrite (d.Buffer, 0, (int)d.Length);
+                                               continue;
+                                       }
+
+                                       if (d.Callback == null)
+                                               continue;
 
-                       context.Response.StatusCode = c.StatusCode;
-                       context.Response.StatusDescription = c.StatusDescription;
+                                       string s = d.Callback (context);
+                                       if (s == null || s.Length == 0)
+                                               continue;
+
+                                       byte[] bytes = outEnc.GetBytes (s);
+                                       response.BinaryWrite (bytes, 0, bytes.Length);
+                               }
+                       }
+                       
+                       response.ClearHeaders ();
+                       response.SetCachedHeaders (c.Headers);
+                       response.StatusCode = c.StatusCode;
+                       response.StatusDescription = c.StatusDescription;
                                
                        app.CompleteRequest ();
                }
 
                void OnUpdateRequestCache (object o, EventArgs args)
                {
-                       HttpApplication app = (HttpApplication) o;
-                       HttpContext context = app.Context;
-
-                       if (context.Response.IsCached && context.Response.StatusCode == 200 && 
-                           !context.Trace.IsEnabled)
-                               DoCacheInsert (context);
-
+                       HttpApplication app = o as HttpApplication;
+                       HttpContext context = app != null ? app.Context : null;
+                       HttpResponse response = context != null ? context.Response : null;
+                       
+                       if (response != null && response.IsCached && response.StatusCode == 200 && !context.Trace.IsEnabled)
+                               DoCacheInsert (context, app, response);
                }
 
-               private void DoCacheInsert (HttpContext context)
+               void DoCacheInsert (HttpContext context, HttpApplication app, HttpResponse response)
                {
                        string vary_key = context.Request.FilePath;
                        string key;
-                       CachedVaryBy varyby = context.Cache [vary_key] as CachedVaryBy;
+                       OutputCacheProvider provider = FindCacheProvider (app);
+                       CachedVaryBy varyby = provider.Get (vary_key) as CachedVaryBy;
                        CachedRawResponse prev = null;
                        bool lookup = true;
+                       string cacheKey = null, cacheValue = null;
+                       HttpCachePolicy cachePolicy = response.Cache;
                        
                        if (varyby == null) {
-                               string path = context.Request.MapPath (vary_key);
-                               string [] files = new string [] { path };
-                               string [] keys = new string [0];
-                               varyby = new CachedVaryBy (context.Response.Cache, vary_key);
-                               context.InternalCache.Insert (vary_key, varyby,
-                                                             new CacheDependency (files, keys),
-                                                             Cache.NoAbsoluteExpiration,
-                                                             Cache.NoSlidingExpiration,
-                                                             CacheItemPriority.Normal, null);
+                               varyby = new CachedVaryBy (cachePolicy, vary_key);
+                               provider.Add (vary_key, varyby, Cache.NoAbsoluteExpiration);
                                lookup = false;
+                               cacheKey = vary_key;
                        } 
-                       
+
                        key = varyby.CreateKey (vary_key, context);
 
                        if (lookup)
-                               prev = context.Cache [key] as CachedRawResponse;
+                               prev = provider.Get (key) as CachedRawResponse;
                        
                        if (prev == null) {
-                               CachedRawResponse c = context.Response.GetCachedResponse ();
-                               string [] files = new string [] { };
-                               string [] keys = new string [] { vary_key };
-                               bool sliding = context.Response.Cache.Sliding;
-
-                               context.InternalCache.Insert (key, c, new CacheDependency (files, keys),
-                                                             (sliding ? Cache.NoAbsoluteExpiration :
-                                                              context.Response.Cache.Expires),
-                                                             (sliding ? TimeSpan.FromSeconds (
-                                                                     context.Response.Cache.Duration) :
-                                                              Cache.NoSlidingExpiration),
-                                                             CacheItemPriority.Normal, response_removed);
-                               c.VaryBy = varyby;
-                               varyby.ItemList.Add (key);
-                       } 
+                               CachedRawResponse c = response.GetCachedResponse ();
+                               if (c != null) {
+                                       string [] keys = new string [] { vary_key };
+                                       DateTime utcExpiry, absoluteExpiration;
+                                       TimeSpan slidingExpiration;
+
+                                       c.VaryBy = varyby;
+                                       varyby.ItemList.Add (key);
+
+                                       if (cachePolicy.Sliding) {
+                                               slidingExpiration = TimeSpan.FromSeconds (cachePolicy.Duration);
+                                               absoluteExpiration = Cache.NoAbsoluteExpiration;
+                                               utcExpiry = DateTime.UtcNow + slidingExpiration;
+                                       } else {
+                                               slidingExpiration = Cache.NoSlidingExpiration;
+                                               absoluteExpiration = cachePolicy.Expires;
+                                               utcExpiry = absoluteExpiration.ToUniversalTime ();
+                                       }
+
+                                       provider.Set (key, c, utcExpiry);
+                                       HttpRuntime.InternalCache.Insert (key, c, new CacheDependency (null, keys), absoluteExpiration, slidingExpiration,
+                                                                         CacheItemPriority.Normal, response_removed);
+                                       cacheValue = key;
+                               }
+                       }
+                       
+                       if (cacheKey != null) {
+                               lock (keysCacheLock) {
+                                       if (keysCache == null) {
+                                               BuildManager.RemoveEntry += new BuildManagerRemoveEntryEventHandler (OnBuildManagerRemoveEntry);
+                                               keysCache = new Dictionary <string, string> (StringComparer.Ordinal);
+                                               keysCache.Add (cacheKey, cacheValue);
+                                       } else if (!keysCache.ContainsKey (cacheKey))
+                                               keysCache.Add (cacheKey, cacheValue);
+                               }
+                       }
                }
 
-               private void OnRawResponseRemoved (string key, object value, CacheItemRemovedReason reason)
+               void OnRawResponseRemoved (string key, object value, CacheItemRemovedReason reason)
                {
-                       CachedRawResponse c = (CachedRawResponse) value;
-
-                       c.VaryBy.ItemList.Remove (key);                 
-                       if (c.VaryBy.ItemList.Count != 0)
+                       CachedRawResponse c = value as CachedRawResponse;
+                       CachedVaryBy varyby = c != null ? c.VaryBy : null;
+                       if (varyby == null)
                                return;
+
+                       List <string> itemList = varyby.ItemList;
+                       OutputCacheProvider provider = FindCacheProvider (null);
                        
-                       Cache cache = HttpRuntime.Cache;
-                       cache.Remove (c.VaryBy.Key);
+                       itemList.Remove (key);
+                       provider.Remove (key);
+                       
+                       if (itemList.Count != 0)
+                               return;                 
+
+                       provider.Remove (varyby.Key);
                }
        }
 }
-