SPRNET-1368

Patch Applied.  Elegant (if straightforward!) fix.  Nice find re: the exposed error condition.
This commit is contained in:
sbohlen
2010-09-21 19:34:54 +00:00
parent 20196ec91f
commit 2b7f0f5f94
3 changed files with 138 additions and 19 deletions

View File

@@ -1,19 +1,19 @@
#region License
/*
* Copyright <20> 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.
/*
* Copyright <20> 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
@@ -84,8 +84,26 @@ namespace Spring.Aspects.Cache
#endregion
/// <summary>
/// Inner class to help cache null values.
/// </summary>
[Serializable] public sealed class NullValueMarkerType
{
/// <returns>true when other object is of same type.</returns>
public override bool Equals(object obj)
{
return obj is NullValueMarkerType;
}
/// <returns>13</returns>
public override int GetHashCode()
{
return 13;
}
}
// NullValue
private static readonly object NullValue = new object();
private static readonly object NullValue = new NullValueMarkerType();
/// <summary>
/// Applies caching around a method invocation.
@@ -170,6 +188,26 @@ namespace Spring.Aspects.Cache
ICache cache = GetCache(resultInfo.CacheName);
returnValue = cache.Get(resultKey);
cacheHit = (returnValue != null);
if (NullValue.Equals(returnValue))
{
returnValue = null;
}
Type returnType = invocation.Method.ReturnType;
if (returnValue != null && !returnType.IsInstanceOfType(returnValue))
{
#region Instrumentation
if (isLogDebugEnabled)
{
logger.Debug(String.Format("Object for key [{0}] was of type [{1}] which is not compatible with return type [{2}]. Proceeding...", resultKey, returnValue.GetType(), returnType));
}
#endregion
cacheHit = false;
returnValue = null;
}
if (!cacheHit)
{
#region Instrumentation
@@ -201,7 +239,7 @@ namespace Spring.Aspects.Cache
#endregion
}
return (returnValue == NullValue) ? null : returnValue;
return returnValue;
}
cacheHit = false;

View File

@@ -62,14 +62,14 @@ namespace Spring.Aspects.Cache
ProxyFactory pf = new ProxyFactory(new InventorStore());
pf.AddAdvisors(cacheAspect);
IInventorStore store = (IInventorStore) pf.GetProxy();
IInventorStore store = (IInventorStore)pf.GetProxy();
Assert.AreEqual(0, cache.Count);
IList inventors = store.GetAll();
Assert.AreEqual(2, cache.Count);
store.Delete((Inventor) inventors[0]);
store.Delete((Inventor)inventors[0]);
Assert.AreEqual(1, cache.Count);
Inventor tesla = store.Load("Nikola Tesla");
@@ -83,6 +83,24 @@ namespace Spring.Aspects.Cache
Assert.AreEqual(0, cache.Count);
}
[Test]
public void TestCacheResultDoesNotReturnInvalidType()
{
ICache cache = new NonExpiringCache();
context.ObjectFactory.RegisterSingleton("inventors", cache);
ProxyFactory pf = new ProxyFactory(new InventorStore());
pf.AddAdvisors(cacheAspect);
IInventorStore store = (IInventorStore)pf.GetProxy();
cache.Insert("Nikola Tesla", "WrongTypeForMethodSignature");
Inventor value = store.Load("Nikola Tesla");
Assert.That(value, Is.AssignableTo(typeof(Inventor)), "CacheAspect returned a cached type that is incompatible with the method signature return type");
}
/// <summary>
/// http://jira.springframework.org/browse/SPRNET-1226
/// </summary>

View File

@@ -21,7 +21,9 @@
#region Imports
using System.Collections;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using AopAlliance.Intercept;
using DotNetMock.Dynamic;
using NUnit.Framework;
@@ -46,6 +48,7 @@ namespace Spring.Aspects.Cache
private CacheResultAdvice advice;
private ICache resultCache;
private ICache itemCache;
private ICache binaryFormatterCache;
private CacheResultTarget cacheResultTarget = new CacheResultTarget();
[SetUp]
@@ -59,6 +62,7 @@ namespace Spring.Aspects.Cache
resultCache = new NonExpiringCache();
itemCache = new NonExpiringCache();
binaryFormatterCache = new BinaryFormatterCache();
}
/// <summary>
@@ -86,6 +90,36 @@ namespace Spring.Aspects.Cache
mockContext.Verify();
}
[Test]
public void CacheResultOfMethodThatReturnsNullWithSerializingCache()
{
MethodInfo method = new VoidMethod(cacheResultTarget.ReturnsNothing).Method;
object expectedReturnValue = null;
ExpectAttributeRetrieval(method);
ExpectCacheKeyGeneration(method, null);
ExpectCacheInstanceRetrieval("results", binaryFormatterCache);
ExpectCallToProceed(expectedReturnValue);
// check that the null retVal is cached as well - it might be
// the result of an expensive webservice/database call etc.
object returnValue = advice.Invoke((IMethodInvocation) mockInvocation.Object);
Assert.AreEqual(expectedReturnValue, returnValue);
Assert.AreEqual(1, binaryFormatterCache.Count);
// and again, but without Proceed()...
ExpectAttributeRetrieval(method);
ExpectCacheKeyGeneration(method, null);
ExpectCacheInstanceRetrieval("results", binaryFormatterCache);
// cached value should be returned
object cachedValue = advice.Invoke((IMethodInvocation)mockInvocation.Object);
Assert.IsNull(cachedValue, "Should recognize cached value as null-value marker.");
mockInvocation.Verify();
mockContext.Verify();
}
[Test]
public void CacheResultOfMethodThatReturnsObject()
{
@@ -571,4 +605,33 @@ namespace Spring.Aspects.Cache
}
#endregion
class BinaryFormatterCache : NonExpiringCache
{
protected override void DoInsert(object key, object value, System.TimeSpan timeToLive)
{
BinaryFormatter fmt = new BinaryFormatter();
using (MemoryStream stream = new MemoryStream())
{
fmt.Serialize(stream, value);
stream.Seek(0, SeekOrigin.Begin);
value = stream.ToArray();
}
base.DoInsert(key, value, timeToLive);
}
public override object Get(object key)
{
byte[] bytes = (byte[]) base.Get(key);
if (bytes == null)
{
return null;
}
BinaryFormatter fmt = new BinaryFormatter();
return fmt.Deserialize(new MemoryStream(bytes));
}
}
}