#region License
/*
* Copyright 2002-2006 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.Collections;
using System.Reflection;
using AopAlliance.Intercept;
using Common.Logging;
using Spring.Caching;
using Spring.Expressions;
using Spring.Util;
using System;
#endregion
namespace Spring.Aspects.Cache{
/// <summary>
/// Implementation of a result caching advice.
/// </summary>
/// <remarks>
/// <p>
/// This advice can be used to cache the return value of the method.
/// </p>
/// <p>
/// Parameters that determine where, how and for how long the return value
/// will be cached are retrieved from the <see cref="CacheResultAttribute"/> and/or
/// <see cref="CacheResultItemsAttribute"/> that are defined on the pointcut.
/// </p>
/// </remarks>
/// <seealso cref="CacheResultAttribute"/>
/// <seealso cref="CacheResultItemsAttribute"/>
/// <author>Aleksandar Seovic</author>
public class CacheResultAdvice : BaseCacheAdvice, IMethodInterceptor
{
#region CacheResultAttribute & CacheResultItemsAttribute caching
private class CacheResultInfo
{
public readonly CacheResultAttribute ResultInfo;
public readonly CacheResultItemsAttribute[] ItemInfoArray;
public CacheResultInfo(CacheResultAttribute resultInfo, CacheResultItemsAttribute[] itemInfoArray)
{
ResultInfo = resultInfo;
ItemInfoArray = itemInfoArray;
}
}
private readonly Hashtable _cacheResultAttributeCache = new Hashtable();
private CacheResultInfo GetCacheResultInfo(MethodInfo method)
{
CacheResultInfo cacheResultInfo = (CacheResultInfo)_cacheResultAttributeCache[method];
// no need for locking here - last one wins
if (cacheResultInfo == null)
{
CacheResultAttribute resultInfo = (CacheResultAttribute)GetCustomAttribute(method, typeof(CacheResultAttribute));
CacheResultItemsAttribute[] itemInfoArray = (CacheResultItemsAttribute[])GetCustomAttributes(method, typeof(CacheResultItemsAttribute));
cacheResultInfo = new CacheResultInfo(resultInfo, itemInfoArray);
_cacheResultAttributeCache[method] = cacheResultInfo;
}
return cacheResultInfo;
}
#endregion
// NullValue
private static readonly object NullValue = new object();
/// <summary>
/// Applies caching around a method invocation.
/// </summary>
/// <remarks>
/// <p>
/// This method tries to retrieve an object from the cache, using the supplied
/// <paramref name="invocation"/> to generate a cache key. If an object is found
/// in the cache, the cached value is returned and the method call does not
/// proceed any further down the invocation chain.
/// </p>
/// <p>
/// If object does not exist in the cache, the advised method is called (using
/// <see cref="AopAlliance.Intercept.IJoinpoint.Proceed()"/>)
/// and any return value is cached for the next method invocation.
/// </p>
/// </remarks>
/// <param name="invocation">
/// The method invocation that is being intercepted.
/// </param>
/// <returns>
/// A cached object or the result of the
/// <see cref="AopAlliance.Intercept.IJoinpoint.Proceed()"/> call.
/// </returns>
/// <exception cref="System.Exception">
/// If any of the interceptors in the chain or the target object itself
/// throws an exception.
/// </exception>
public object Invoke(IMethodInvocation invocation)
{
CacheResultInfo cacheResultInfo = GetCacheResultInfo(invocation.Method);
// prepare variables for SpEL expressions
IDictionary vars = PrepareVariables(invocation.Method, invocation.Arguments);
bool cacheHit = false;
object returnValue = GetReturnValue(invocation, cacheResultInfo.ResultInfo, vars, out cacheHit);
if (!cacheHit && cacheResultInfo.ItemInfoArray.Length > 0 && returnValue is IEnumerable)
{
CacheResultItems((IEnumerable)returnValue, cacheResultInfo.ItemInfoArray, vars);
}
return returnValue;
}
/// <summary>
/// Obtains return value either from cache or by invoking target method
/// and caches it if necessary.
/// </summary>
/// <param name="invocation">
/// The method invocation that is being intercepted.
/// </param>
/// <param name="resultInfo">
/// Attribute specifying where and how to cache return value. Can be <c>null</c>,
/// in which case no caching of the result as a whole will be performed
/// (if the result is collection, individual items could still be cached separately).
/// </param>
/// <param name="vars">
/// Variables for expression evaluation.
/// </param>
/// <param name="cacheHit">
/// Returns <c>true</c> if the return value was found in cache, <c>false</c> otherwise.
/// </param>
/// <returns>
/// Return value for the specified <paramref name="invocation"/>.
/// </returns>
private object GetReturnValue(IMethodInvocation invocation, CacheResultAttribute resultInfo, IDictionary vars, out bool cacheHit)
{
if (resultInfo != null)
{
#region Instrumentation
bool isLogDebugEnabled = logger.IsDebugEnabled;
#endregion
AssertUtils.ArgumentNotNull(resultInfo.KeyExpression, "Key",
"The cache attribute is missing the key definition.");
object returnValue = null;
object resultKey = resultInfo.KeyExpression.GetValue(null, vars);
ICache cache = GetCache(resultInfo.CacheName);
returnValue = cache.Get(resultKey);
cacheHit = (returnValue != null);
if (!cacheHit)
{
#region Instrumentation
if (isLogDebugEnabled)
{
logger.Debug(String.Format("Object for key [{0}] was not found in cache [{1}]. Proceeding...", resultKey, resultInfo.CacheName));
}
#endregion
returnValue = invocation.Proceed();
if (EvalCondition(resultInfo.Condition, resultInfo.ConditionExpression, returnValue, vars))
{
#region Instrumentation
if (isLogDebugEnabled)
{
logger.Debug(String.Format("Caching object for key [{0}] into cache [{1}].", resultKey, resultInfo.CacheName));
}
#endregion
cache.Insert(resultKey, (returnValue == null) ? NullValue : returnValue, resultInfo.TimeToLiveTimeSpan);
}
}
else
{
#region Instrumentation
if (isLogDebugEnabled)
{
logger.Debug(String.Format("Object for key [{0}] found in cache [{1}]. Aborting invocation...", resultKey, resultInfo.CacheName));
}
#endregion
}
return (returnValue == NullValue) ? null : returnValue;
}
cacheHit = false;
return invocation.Proceed();
}
/// <summary>
/// Caches each item from the collection returned by target method.
/// </summary>
/// <param name="items">
/// A collection of items to cache.
/// </param>
/// <param name="itemInfoArray">
/// Attributes specifying where and how to cache each item from the collection.
/// </param>
/// <param name="vars">
/// Variables for expression evaluation.
/// </param>
private void CacheResultItems(IEnumerable items, CacheResultItemsAttribute[] itemInfoArray, IDictionary vars)
{
foreach (CacheResultItemsAttribute itemInfo in itemInfoArray)
{
AssertUtils.ArgumentNotNull(itemInfo.KeyExpression, "Key",
"The cache attribute is missing the key definition.");
ICache cache = GetCache(itemInfo.CacheName);
#region Instrumentation
bool isDebugEnabled = logger.IsDebugEnabled;
#endregion
foreach (object item in items)
{
if (EvalCondition(itemInfo.Condition, itemInfo.ConditionExpression, item, vars))
{
object itemKey = itemInfo.KeyExpression.GetValue(item, vars);
#region Instrumentation
if (isDebugEnabled)
{
logger.Debug("Caching collection item for key [" + itemKey + "].");
}
#endregion
cache.Insert(itemKey, (item == null ? NullValue : item), itemInfo.TimeToLiveTimeSpan);
}
}
}
}
}
}
|