#region License
/*
* Copyright 2002-2004 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#endregion
#region Imports
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Web;
using System.Web.Caching;
using System.Web.SessionState;
using Common.Logging;
using Spring.Collections;
using Spring.Context.Support;
using Spring.Objects.Factory.Config;
using Spring.Util;
#endregion
namespace Spring.Objects.Factory.Support{
/// <summary>
/// Concrete implementation of
/// <see cref="Spring.Objects.Factory.IListableObjectFactory"/> that knows
/// how to handle <see cref="IWebObjectDefinition"/>s.
/// </summary>
/// <remarks>
/// <p>
/// This class should only be used within the context of an ASP.NET web application.
/// </p>
/// </remarks>
/// <author>Aleksandar Seovic</author>
public class WebObjectFactory : DefaultListableObjectFactory
{
private readonly static ILog log = LogManager.GetLogger(typeof(WebObjectFactory));
private static bool s_eventHandlersRegistered = false;
private readonly static string OBJECTTABLEKEY = "spring.objects";
private delegate IDictionary ObjectDictionaryCreationHandler();
/// <summary>
/// Holds the virtual path this factory has been created from.
/// </summary>
private readonly string contextPath;
/// <summary>
/// Holds the handler reference for creating an object dictionary
/// matching this factory's case-sensitivity
/// </summary>
private readonly ObjectDictionaryCreationHandler createObjectDictionary;
#region Constructor (s) / Destructor
static WebObjectFactory()
{
//EnsureEventHandlersRegistered();
}
/// <summary>
/// Registers events handlers with <see cref="WebSupportModule"/> to ensure
/// proper disposal of 'request'- and 'session'-scoped objects
/// </summary>
private static void EnsureEventHandlersRegistered()
{
if (!s_eventHandlersRegistered)
{
lock(typeof(WebObjectFactory))
{
if (s_eventHandlersRegistered) return;
if (log.IsDebugEnabled) log.Debug("hooking up event handlers");
VirtualEnvironment.EndRequest += new VirtualEnvironment.RequestEventHandler(OnEndRequest);
VirtualEnvironment.EndSession += new VirtualEnvironment.SessionEventHandler(OnEndSession);
s_eventHandlersRegistered = true;
}
}
}
/// <summary>
/// Creates a new instance of the
/// <see cref="Spring.Objects.Factory.Support.WebObjectFactory"/> class.
/// </summary>
/// <param name="contextPath">The virtual path resources will be relative resolved to.</param>
/// <param name="caseSensitive">Flag specifying whether to make this object factory case sensitive or not.</param>
public WebObjectFactory(string contextPath, bool caseSensitive)
: this(contextPath, caseSensitive, null)
{
}
/// <summary>
/// Creates a new instance of the
/// <see cref="Spring.Objects.Factory.Support.WebObjectFactory"/> class.
/// </summary>
/// <param name="contextPath">The virtual path resources will be relative resolved to.</param>
/// <param name="caseSensitive">Flag specifying whether to make this object factory case sensitive or not.</param>
/// <param name="parentFactory">
/// The parent object factory.
/// </param>
public WebObjectFactory(string contextPath, bool caseSensitive, IObjectFactory parentFactory)
: base(caseSensitive, parentFactory)
{
this.contextPath = contextPath;
this.createObjectDictionary = (caseSensitive)
? new ObjectDictionaryCreationHandler(CreateCaseSensitiveDictionary)
: new ObjectDictionaryCreationHandler(CreateCaseInsensitiveDictionary);
InstantiationStrategy = new WebInstantiationStrategy();
}
#endregion
/// <summary>
/// Returns the virtual path this object factory is associated with.
/// </summary>
public string ContextPath
{
get { return contextPath; }
}
#region Convenience accessors for Http* objects
/// <summary>
/// Convinience accessor for HttpContext
/// </summary>
private HttpContext Context
{
get { return HttpContext.Current; }
}
/// <summary>
/// Get the table of 'request'-scoped objects.
/// </summary>
protected virtual IDictionary Request
{
get
{
IDictionary objecttable = null;
if (this.Context != null)
{
objecttable = this.Context.Items[OBJECTTABLEKEY] as IDictionary;
if (objecttable == null)
{
this.Context.Items[OBJECTTABLEKEY] = CreateObjectDictionary();
}
}
return objecttable;
}
}
/// <summary>
/// Get the table of 'session'-scoped objects. Returns null, if Session is disabled.
/// </summary>
protected virtual IDictionary Session
{
get
{
IDictionary objecttable = null;
if ((Context != null) && (Context.Session != null))
{
objecttable = Context.Session[OBJECTTABLEKEY] as IDictionary;
if (objecttable == null)
{
Context.Session[OBJECTTABLEKEY] = CreateObjectDictionary();
}
}
return objecttable;
}
}
#endregion
/// <summary>
/// Creates a dictionary matching this factory's case sensitivity behaviour.
/// </summary>
protected IDictionary CreateObjectDictionary()
{
return createObjectDictionary();
}
/// <summary>
/// Creates the root object definition.
/// </summary>
/// <param name="templateDefinition">The template definition.</param>
/// <returns>Root object definition.</returns>
protected override RootObjectDefinition CreateRootObjectDefinition(IObjectDefinition templateDefinition)
{
return new RootWebObjectDefinition(templateDefinition);
}
/// <summary>
/// Tries to find cached object for the specified name.
/// </summary>
/// <remarks>
/// This implementation tries to find object first in the Request scope,
/// then in the Session scope and finally in the Application scope.
/// </remarks>
/// <param name="objectName">Object name to look for.</param>
/// <returns>Cached object if found, null otherwise.</returns>
public override object GetSingleton(string objectName)
{
object instance = null;
instance = GetScopedSingleton(objectName, this.Request);
if (instance != null) return instance;
instance = GetScopedSingleton(objectName, this.Session);
if (instance != null) return instance;
instance = base.GetSingleton(objectName);
return instance;
}
/// <summary>
/// Looks up an <paramref name="objectName"/> in the specified cache dictionary.
/// </summary>
/// <param name="objectName">the name to lookup.</param>
/// <param name="scopedSingletonCache">the cache dictionary to search</param>
/// <returns>the found instance. null otherwise</returns>
protected object GetScopedSingleton(string objectName, IDictionary scopedSingletonCache)
{
if (scopedSingletonCache == null) return null;
lock (scopedSingletonCache.SyncRoot)
{
object instance = scopedSingletonCache[objectName];
if (instance == TemporarySingletonPlaceHolder)
{
throw new ObjectCurrentlyInCreationException(objectName);
}
return instance;
}
}
/// <summary>
/// Creates a singleton instance for the specified object name and definition.
/// </summary>
/// <param name="objectName">
/// The object name (will be used as the key in the singleton cache key).
/// </param>
/// <param name="objectDefinition">The object definition.</param>
/// <param name="arguments">
/// The arguments to use if creating a prototype using explicit arguments to
/// a static factory method. It is invalid to use a non-null arguments value
/// in any other case.
/// </param>
/// <returns>The created object instance.</returns>
protected override object CreateAndCacheSingletonInstance(
string objectName, RootObjectDefinition objectDefinition, object[] arguments)
{
if (IsWebScopedSingleton(objectDefinition)
)
{
ObjectScope scope = ((IWebObjectDefinition)objectDefinition).Scope;
if (scope == ObjectScope.Request)
{
IDictionary requestCache = this.Request;
if (requestCache == null)
{
throw new ObjectCreationException(string.Format("'request' scoped web singleton object '{0}' requires a valid Request.", objectName));
}
object instance = CreateAndCacheScopedSingletonInstance(objectName, objectDefinition, arguments, requestCache);
return instance;
}
else if (scope == ObjectScope.Session)
{
IDictionary sessionCache = this.Session;
if (sessionCache == null)
{
throw new ObjectCreationException(string.Format("'session' scoped web singleton object '{0}' requires a valid Session.", objectName));
}
object instance = CreateAndCacheScopedSingletonInstance(objectName, objectDefinition, arguments, sessionCache);
return instance;
}
throw new ObjectDefinitionException("Web singleton objects must be either request, session or application scoped.");
}
return base.CreateAndCacheSingletonInstance(objectName, objectDefinition, arguments);
}
/// <summary>
/// Creates a singleton instance for the specified object name and definition
/// and caches the instance in the specified dictionary
/// </summary>
/// <param name="objectName">
/// The object name (will be used as the key in the singleton cache key).
/// </param>
/// <param name="objectDefinition">The object definition.</param>
/// <param name="arguments">
/// The arguments to use if creating a prototype using explicit arguments to
/// a static factory method. It is invalid to use a non-null arguments value
/// in any other case.
/// </param>
/// <param name="scopedSingletonCache">the dictionary to be used for caching singleton instances</param>
/// <returns>The created object instance.</returns>
/// <remarks>
/// If the object is successfully created, <paramref name="scopedSingletonCache"/>
/// contains the cached instance with the key <paramref name="objectName"/>.
/// </remarks>
protected virtual object CreateAndCacheScopedSingletonInstance(string objectName, RootObjectDefinition objectDefinition, object[] arguments, IDictionary scopedSingletonCache)
{
object instance;
lock (scopedSingletonCache.SyncRoot)
{
EnsureEventHandlersRegistered();
instance = scopedSingletonCache[objectName];
if (instance == TemporarySingletonPlaceHolder)
{
throw new ObjectCurrentlyInCreationException(objectName);
}
else if (instance == null)
{
scopedSingletonCache.Add(objectName, TemporarySingletonPlaceHolder);
try
{
instance = InstantiateObject(objectName, objectDefinition, arguments, true, false);
AssertUtils.ArgumentNotNull(instance, "instance");
scopedSingletonCache[objectName] = instance;
}
catch
{
scopedSingletonCache.Remove(objectName);
throw;
}
}
}
return instance;
}
/// <summary>
/// Add the created, but yet unpopulated singleton to the singleton cache
/// to be able to resolve circular references
/// </summary>
/// <param name="objectName">the name of the object to add to the cache.</param>
/// <param name="objectDefinition">the definition used to create and populated the object.</param>
/// <param name="rawSingletonInstance">the raw object instance.</param>
/// <remarks>
/// Derived classes may override this method to select the right cache based on the object definition.
/// </remarks>
protected override void AddEagerlyCachedSingleton(string objectName, IObjectDefinition objectDefinition, object rawSingletonInstance)
{
if (IsWebScopedSingleton(objectDefinition))
{
ObjectScope scope = ((IWebObjectDefinition) objectDefinition).Scope;
if (scope == ObjectScope.Request)
{
this.Request[objectName] = rawSingletonInstance;
}
else if (scope == ObjectScope.Session)
{
this.Session[objectName] = rawSingletonInstance;
}
else
{
throw new ObjectDefinitionException("Web singleton objects must be either request, session or application scoped.");
}
}
else
{
base.AddEagerlyCachedSingleton(objectName, objectDefinition, rawSingletonInstance);
}
}
/// <summary>
/// Remove the specified singleton from the singleton cache that has
/// been added before by a call to <see cref="AddEagerlyCachedSingleton"/>
/// </summary>
/// <param name="objectName">the name of the object to remove from the cache.</param>
/// <param name="objectDefinition">the definition used to create and populated the object.</param>
/// <remarks>
/// Derived classes may override this method to select the right cache based on the object definition.
/// </remarks>
protected override void RemoveEagerlyCachedSingleton(string objectName, IObjectDefinition objectDefinition)
{
if (IsWebScopedSingleton(objectDefinition))
{
ObjectScope scope = ((IWebObjectDefinition) objectDefinition).Scope;
if (scope == ObjectScope.Request)
{
this.Request.Remove(objectName);
}
else if (scope == ObjectScope.Session)
{
this.Session.Remove(objectName);
}
else
{
throw new ObjectDefinitionException("Web singleton objects must be either 'request' or 'session' scoped.");
}
}
else
{
base.RemoveEagerlyCachedSingleton(objectName, objectDefinition);
}
}
private bool IsWebScopedSingleton(IObjectDefinition objectDefinition)
{
if (objectDefinition.IsSingleton
&& objectDefinition is IWebObjectDefinition)
{
ObjectScope scope = ((IWebObjectDefinition) objectDefinition).Scope;
return (scope == ObjectScope.Request) || (scope == ObjectScope.Session);
}
return false;
}
/// <summary>
/// Configures object instance by injecting dependencies, satisfying Spring lifecycle
/// interfaces and applying object post-processors.
/// </summary>
/// <param name="name">
/// The name of the object definition expressing the dependencies that are to
/// be injected into the supplied <parameref name="target"/> instance.
/// </param>
/// <param name="definition">
/// An object definition that should be used to configure object.
/// </param>
/// <param name="wrapper">
/// A wrapped object instance that is to be so configured.
/// </param>
/// <seealso cref="Spring.Objects.Factory.IObjectFactory.ConfigureObject(object, string)"/>
protected override object ConfigureObject(string name, RootObjectDefinition definition, IObjectWrapper wrapper)
{
// always configure object relative to contextPath
using (new HttpContextSwitch(contextPath))
{
return base.ConfigureObject(name, definition, wrapper);
}
}
/// <summary>
/// Disposes all 'request'-scoped objects at the end of each Request
/// </summary>
private static void OnEndRequest(HttpContext context)
{
IDictionary items = context.Items[OBJECTTABLEKEY] as IDictionary;
if (items != null)
{
log.Debug("disposing 'request'-scoped item cache");
ArrayList keys = new ArrayList(items.Keys);
for (int i = 0; i < keys.Count; i++)
{
IDisposable d = items[keys[i]] as IDisposable;
if (d != null)
{
d.Dispose();
}
}
}
}
/// <summary>
/// Disposes all 'session'-scoped objects at the end of a session
/// </summary>
private static void OnEndSession(HttpSessionState session, CacheItemRemovedReason reason)
{
IDictionary items = null;
try
{
items = session[OBJECTTABLEKEY] as IDictionary;
}
catch
{
// ignore exceptions while accessing session
}
if (items != null)
{
log.Debug("disposing 'session'-scoped item cache");
object key = null;
try
{
ArrayList keys = new ArrayList(items.Keys);
for (int i = 0; i < keys.Count; i++)
{
key = keys[i];
IDisposable d = items[key] as IDisposable;
if (d != null)
{
d.Dispose();
}
}
}
catch (Exception ex)
{
log.Fatal(string.Format("error during disposing session item with key '{0}'", key), ex);
}
}
}
/// <summary>
/// Creates a case insensitive hashtable instance
/// </summary>
private static IDictionary CreateCaseInsensitiveDictionary()
{
return new CaseInsensitiveHashtable(); //CollectionsUtil.CreateCaseInsensitiveHashtable();
}
/// <summary>
/// Creates a case sensitive hashtable instance
/// </summary>
private static IDictionary CreateCaseSensitiveDictionary()
{
return new Hashtable();
}
}
}
|