- Add support for an optional basic LRU cache for OGNL expressions and
  OGNL BeanInfo.
- Add support for cache limits applying to both normal and LRU caches.
  For a normal cache the entire cache will flush when the limit is reached.
- Add flags to allow switching between normal and LRU caches, and setting
  the maximum sizes.
This commit is contained in:
JCgH4164838Gh792C124B5
2022-01-30 20:57:59 -05:00
parent 60e1212bcc
commit 4d8108e766
4 changed files with 417 additions and 11 deletions
@@ -41,6 +41,7 @@ import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
@@ -57,8 +58,14 @@ public class OgnlUtil {
// Flag used to reduce flooding logs with WARNs about using DevMode excluded packages
private final AtomicBoolean warnReported = new AtomicBoolean(false);
private final ConcurrentMap<String, Object> expressions = new ConcurrentHashMap<>();
private final AtomicBoolean useLRUExpressionCache = new AtomicBoolean(false);
private final AtomicBoolean useLRUBeanInfoCache = new AtomicBoolean(false);
private final AtomicInteger expressionsCacheMaxSize = new AtomicInteger(25000);
private final AtomicInteger beanInfoCacheMaxSize = new AtomicInteger(25000);
private final ConcurrentMap<String, Object> expressionsCache = new ConcurrentHashMap<>();
private final LRUCache<String, Object> expressionsCacheLRU = new LRUCache<>(expressionsCacheMaxSize.get(), 16, 0.75f);
private final ConcurrentMap<Class<?>, BeanInfo> beanInfoCache = new ConcurrentHashMap<>();
private final LRUCache<Class<?>, BeanInfo> beanInfoCacheLRU = new LRUCache<>(beanInfoCacheMaxSize.get(), 16, 0.75f);
private TypeConverter defaultConverter;
private boolean devMode;
@@ -103,6 +110,28 @@ public class OgnlUtil {
enableExpressionCache = BooleanUtils.toBoolean(cache);
}
@Inject(value = StrutsConstants.STRUTS_OGNL_EXPRESSION_CACHE_MAXSIZE, required = false)
protected void setExpressionsCacheMaxSize(String maxSize) {
expressionsCacheMaxSize.set(Integer.parseInt(maxSize));
expressionsCacheLRU.setEvictionLimit(expressionsCacheMaxSize.get());
}
@Inject(value = StrutsConstants.STRUTS_OGNL_BEANINFO_CACHE_MAXSIZE, required = false)
protected void setBeanInfoCacheMaxSize(String maxSize) {
beanInfoCacheMaxSize.set(Integer.parseInt(maxSize));
beanInfoCacheLRU.setEvictionLimit(beanInfoCacheMaxSize.get());
}
@Inject(value = StrutsConstants.STRUTS_OGNL_EXPRESSION_CACHE_LRU_MODE, required = false)
protected void setUseLRUExpressionCache(String useLRUMode) {
useLRUExpressionCache.set(BooleanUtils.toBoolean(useLRUMode));
}
@Inject(value = StrutsConstants.STRUTS_OGNL_BEANINFO_CACHE_LRU_MODE, required = false)
protected void setUseLRUBeanInfoCache(String useLRUMode) {
useLRUBeanInfoCache.set(BooleanUtils.toBoolean(useLRUMode));
}
@Inject(value = StrutsConstants.STRUTS_OGNL_ENABLE_EVAL_EXPRESSION, required = false)
protected void setEnableEvalExpression(String evalExpression) {
this.enableEvalExpression = BooleanUtils.toBoolean(evalExpression);
@@ -273,7 +302,8 @@ public class OgnlUtil {
* @since 2.5.21
*/
public void clearExpressionCache() {
expressions.clear();
expressionsCache.clear();
expressionsCacheLRU.clear();
}
/**
@@ -284,7 +314,11 @@ public class OgnlUtil {
* @since 2.5.21
*/
public int expressionCacheSize() {
return expressions.size();
if (useLRUExpressionCache.get()) {
return expressionsCacheLRU.size();
} else {
return expressionsCache.size();
}
}
/**
@@ -302,6 +336,7 @@ public class OgnlUtil {
*/
public void clearBeanInfoCache() {
beanInfoCache.clear();
beanInfoCacheLRU.clear();
}
/**
@@ -312,7 +347,11 @@ public class OgnlUtil {
* @since 2.5.21
*/
public int beanInfoCacheSize() {
return beanInfoCache.size();
if (useLRUBeanInfoCache.get()) {
return beanInfoCacheLRU.size();
} else {
return beanInfoCache.size();
}
}
/**
@@ -532,11 +571,18 @@ public class OgnlUtil {
private <T> Object compileAndExecute(String expression, Map<String, Object> context, OgnlTask<T> task) throws OgnlException {
Object tree;
if (enableExpressionCache) {
tree = expressions.get(expression);
final Map<String, Object> expressionsCacheRef = (useLRUExpressionCache.get() ? expressionsCacheLRU.backingMapReference() : expressionsCache);
tree = expressionsCacheRef.get(expression);
if (tree == null) {
tree = Ognl.parseExpression(expression);
checkEnableEvalExpression(tree, context);
expressions.putIfAbsent(expression, tree);
expressionsCacheRef.putIfAbsent(expression, tree);
if (! useLRUExpressionCache.get()) {
// If using a standard cache, after attempting to add an expression check cache size against the limit.
if (expressionsCacheRef.size() > expressionsCacheMaxSize.get()) {
expressionsCacheRef.clear(); // Non-LRU cache has exceeded maximum configured size, so flush.
}
}
}
} else {
tree = Ognl.parseExpression(expression);
@@ -549,11 +595,18 @@ public class OgnlUtil {
private <T> Object compileAndExecuteMethod(String expression, Map<String, Object> context, OgnlTask<T> task) throws OgnlException {
Object tree;
if (enableExpressionCache) {
tree = expressions.get(expression);
final Map<String, Object> expressionsCacheRef = (useLRUExpressionCache.get() ? expressionsCacheLRU.backingMapReference() : expressionsCache);
tree = expressionsCacheRef.get(expression);
if (tree == null) {
tree = Ognl.parseExpression(expression);
checkSimpleMethod(tree, context);
expressions.putIfAbsent(expression, tree);
expressionsCacheRef.putIfAbsent(expression, tree);
if (! useLRUExpressionCache.get()) {
// If using a standard cache, after attempting to add an expression check cache size against the limit.
if (expressionsCacheRef.size() > expressionsCacheMaxSize.get()) {
expressionsCacheRef.clear(); // Non-LRU cache has exceeded maximum configured size, so flush.
}
}
}
} else {
tree = Ognl.parseExpression(expression);
@@ -759,11 +812,18 @@ public class OgnlUtil {
* @throws IntrospectionException is thrown if an exception occurs during introspection.
*/
public BeanInfo getBeanInfo(Class<?> clazz) throws IntrospectionException {
synchronized (beanInfoCache) {
BeanInfo beanInfo = beanInfoCache.get(clazz);
final Map<Class<?>, BeanInfo> beanInfoCacheRef = (useLRUBeanInfoCache.get() ? beanInfoCacheLRU.backingMapReference() : beanInfoCache);
synchronized (beanInfoCacheRef) {
BeanInfo beanInfo = beanInfoCacheRef.get(clazz);
if (beanInfo == null) {
beanInfo = Introspector.getBeanInfo(clazz, Object.class);
beanInfoCache.putIfAbsent(clazz, beanInfo);
beanInfoCacheRef.putIfAbsent(clazz, beanInfo);
if (! useLRUBeanInfoCache.get()) {
// If using a standard cache, after attempting to add beanInfo check cache size against the limit.
if (beanInfoCacheRef.size() > beanInfoCacheMaxSize.get()) {
beanInfoCacheRef.clear(); // Non-LRU cache has exceeded maximum configured size, so flush.
}
}
}
return beanInfo;
}
@@ -822,4 +882,66 @@ public class OgnlUtil {
T execute(Object tree) throws OgnlException;
}
/**
* A basic LRUCache implementation that utilizes a {@link Collections#synchronizedMap(java.util.Map)}
* backed by a {@link LinkedHashMap}. May be replaced by a more efficient implementation in the future.
*
* @param <Key> Key type for the LRUCache
* @param <Value> Value type for the LRUCache
*/
protected class LRUCache<Key, Value> {
private final Map<Key, Value> lruCache;
private final AtomicInteger cacheEvictionLimit = new AtomicInteger(25000);
public LRUCache(int evictionLimit, int initialCapacity, float loadFactor) {
this.cacheEvictionLimit.set(evictionLimit);
// Access-order mode selected (order mode true in LinkedHashMap constructor).
lruCache = Collections.synchronizedMap (new LinkedHashMap<Key, Value>(initialCapacity, loadFactor, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Key,Value> eldest) {
return (this.size() > cacheEvictionLimit.get());
}
});
}
public Value get(Key key) {
return lruCache.get(key);
}
public void put(Key key, Value value) {
lruCache.put(key, value);
}
public void putIfAbsent(Key key, Value value) {
lruCache.putIfAbsent(key, value);
}
public int size() {
return lruCache.size();
}
public void clear() {
lruCache.clear();
}
/**
* Since the {@link LRUCache} is only intended to be used internally, we can cheat a bit
* and allow access to the backing map directly via its reference. Allows it to be used
* like the other {@link Map} caches within the module.
*
* @return The Map that backs the LRU cache.
*/
public Map<Key, Value> backingMapReference() {
return lruCache;
}
public int getEvictionLimit() {
return this.cacheEvictionLimit.get();
}
public void setEvictionLimit(int cacheEvictionLimit) {
this.cacheEvictionLimit.set(cacheEvictionLimit);
}
}
}
@@ -249,6 +249,31 @@ public final class StrutsConstants {
/** Throw RuntimeException when a property is not found, or the evaluation of the expression fails */
public static final String STRUTS_EL_THROW_EXCEPTION = "struts.el.throwExceptionOnFailure";
/**
* Specifies a maximum number of cached BeanInfo used by OgnlUtility. Not specified/set by default. If
* a positive integer is specified, it will set a limit whose behaviour depends on whether the
* normal (default) cache or optional LRU cache is in place.
*
* For the normal (default) cache, exceeding the maximum will cause the entire cache to flush (clear).
* For the optional LRU cache, once the maximum is reached, the least-recently-used (LRU) entry will be
* removed when a new entry needs to be added (cache is fully-utilized).
*
* @since 2.6
*/
public static final String STRUTS_OGNL_BEANINFO_CACHE_MAXSIZE = "struts.ognl.beanInfoCacheMaxSize";
/**
* Set the cache mode of the BeanInfo cache used by OgnlUtility. A value of true means enable
* least-recently-used (LRU) mode, a value of false (or any non-true value) means to use the
* default cache.
*
* Note: When enabling LRU cache mode you must also set a maximum size (via {@link #STRUTS_OGNL_BEANINFO_CACHE_MAXSIZE})
* for it to be effective. Otherwise, there is no condition to evict a LRU entry (cache has no limit).
*
* @since 2.6
*/
public static final String STRUTS_OGNL_BEANINFO_CACHE_LRU_MODE = "struts.ognl.beanInfoCacheLRUMode";
/**
* Logs properties that are not found (very verbose)
* @since 2.6
@@ -274,6 +299,31 @@ public final class StrutsConstants {
*/
public static final String STRUTS_ENABLE_OGNL_EXPRESSION_CACHE = STRUTS_OGNL_ENABLE_EXPRESSION_CACHE;
/**
* Specifies a maximum number of cached parsed OGNL expressions. Not specified/set by default. If
* a positive integer is specified, it will set a limit whose behaviour depends on whether the
* normal (default) cache or optional LRU cache is in place.
*
* For the normal (default) cache, exceeding the maximum will cause the entire cache to flush (clear).
* For the optional LRU cache, once the maximum is reached, the least-recently-used (LRU) entry will be
* removed when a new entry needs to be added (cache is fully-utilized).
*
* @since 2.6
*/
public static final String STRUTS_OGNL_EXPRESSION_CACHE_MAXSIZE = "struts.ognl.expressionCacheMaxSize";
/**
* Set the cache mode of the parsed OGNL expression cache. A value of true means enable
* least-recently-used (LRU) mode, a value of false (or any non-true value) means to use the
* default cache.
*
* Note: When enabling LRU cache mode you must also set a maximum size (via {@link #STRUTS_OGNL_EXPRESSION_CACHE_MAXSIZE})
* for it to be effective. Otherwise, there is no condition to evict a LRU entry (cache has no limit).
*
* @since 2.6
*/
public static final String STRUTS_OGNL_EXPRESSION_CACHE_LRU_MODE = "struts.ognl.expressionCacheLRUMode";
/**
* Enables evaluation of OGNL expressions
* @since 2.6
@@ -224,6 +224,32 @@ struts.ognl.logMissingProperties=false
### if the application generates a lot of different expressions
struts.ognl.enableExpressionCache=true
### Specify a limit to the number of entries in the OGNL expressionCache.
### For the standard expressionCache mode, when the limit is exceeded the entire cache's
### content will be cleared (can help prevent memory leaks).
### For expressionCacheLRUMode true, the limit will ensure the cache does not exceed
### that size, dropping the oldest (least-recently-used) expressions to add new ones.
### NOTE: If not set, the default is 25000, which may be excessive.
#struts.ognl.expressionCacheMaxSize=1000
### Indicates if the OGNL expressionCache should use LRU mode.
### NOTE: When true, make sure to set the expressionCacheMaxSize to a reasonable value
### for your application. Otherwise the default limit will never (practically) be reached.
#struts.ognl.expressionCacheLRUMode=false
### Specify a limit to the number of entries in the OGNL beanInfoCache.
### For the standard beanInfoCache mode, when the limit is exceeded the entire cache's
### content will be cleared (can help prevent memory leaks).
### For beanInfoCacheLRUMode true, the limit will ensure the cache does not exceed
### that size, dropping the oldest (least-recently-used) expressions to add new ones.
### NOTE: If not set, the default is 25000, which may be excessive.
#struts.ognl.beanInfoCacheMaxSize=1000
### Indicates if the OGNL beanInfoCache should use LRU mode.
### NOTE: When true, make sure to set the beanInfoCacheMaxSize to a reasonable value
### for your application. Otherwise the default limit will never (practically) be reached.
#struts.ognl.beanInfoCacheLRUMode=false
### Indicates if Dispatcher should handle unexpected exceptions by calling sendError()
### or simply rethrow it as a ServletException to allow future processing by other frameworks like Spring Security
struts.handle.exception=true
@@ -129,6 +129,62 @@ public class OgnlUtilTest extends XWorkTestCase {
assertSame(expr0, expr2);
}
public void testCacheEnabledMaxSize() throws OgnlException {
ognlUtil.setEnableExpressionCache("true");
ognlUtil.setExpressionsCacheMaxSize("1");
Object expr0 = ognlUtil.compile("test");
Object expr2 = ognlUtil.compile("test");
assertSame(expr0, expr2);
assertEquals("Expression cache size should be at its limit", 1, ognlUtil.expressionCacheSize());
// Next epxression cached should cause the cache to clear (exceeding maximum sized).
Object expr3 = ognlUtil.compile("test1");
assertEquals("Expression cache should be empty", 0, ognlUtil.expressionCacheSize());
Object expr4 = ognlUtil.compile("test1");
Object expr5 = ognlUtil.compile("test1");
assertEquals("Expression cache size should still be at its limit", 1, ognlUtil.expressionCacheSize());
assertNotSame("2nd test expression cache attempt will exceed size and force clear, but somehow they match ?", expr3, expr4);
assertSame(expr4, expr5);
// Next epxression cached should cause the cache to clear (exceeding maximum sized).
Object expr6 = ognlUtil.compile("test");
assertEquals("Expression cache should be empty", 0, ognlUtil.expressionCacheSize());
Object expr7 = ognlUtil.compile("test");
Object expr8 = ognlUtil.compile("test");
assertNotSame("2nd test expression cache attempt will exceed size and force clear, but somehow they match ?", expr6, expr7);
assertSame(expr7, expr8);
assertEquals("Expression LRU cache size should still be at its limit", 1, ognlUtil.expressionCacheSize());
assertNotSame("1st test expression identical after ejection from LRU cache ?", expr5, expr0);
ognlUtil.setUseLRUExpressionCache("false");
}
public void testLRUCacheEnabled() throws OgnlException {
ognlUtil.setEnableExpressionCache("true");
ognlUtil.setUseLRUExpressionCache("true");
Object expr0 = ognlUtil.compile("test");
Object expr2 = ognlUtil.compile("test");
assertSame(expr0, expr2);
ognlUtil.setUseLRUExpressionCache("false");
}
public void testLRUCacheEnabledMaxSize() throws OgnlException {
ognlUtil.setEnableExpressionCache("true");
ognlUtil.setUseLRUExpressionCache("true");
ognlUtil.setExpressionsCacheMaxSize("1");
Object expr0 = ognlUtil.compile("test");
Object expr2 = ognlUtil.compile("test");
assertSame(expr0, expr2);
assertEquals("Expression LRU cache size should be at its limit", 1, ognlUtil.expressionCacheSize());
Object expr3 = ognlUtil.compile("test1");
Object expr4 = ognlUtil.compile("test1");
assertSame(expr3, expr4);
assertEquals("Expression LRU cache size should still be at its limit", 1, ognlUtil.expressionCacheSize());
Object expr5 = ognlUtil.compile("test");
Object expr6 = ognlUtil.compile("test");
assertSame(expr5, expr6);
assertEquals("Expression LRU cache size should still be at its limit", 1, ognlUtil.expressionCacheSize());
assertNotSame("1st test expression identical after ejection from LRU cache ?", expr5, expr0);
ognlUtil.setUseLRUExpressionCache("false");
}
public void testExpressionIsCachedIrrespectiveOfItsExecutionStatus() throws OgnlException {
Foo foo = new Foo();
OgnlContext context = (OgnlContext) ognlUtil.createDefaultContext(foo);
@@ -149,6 +205,29 @@ public class OgnlUtilTest extends XWorkTestCase {
}
}
public void testExpressionIsLRUCachedIrrespectiveOfItsExecutionStatus() throws OgnlException {
ognlUtil.setEnableExpressionCache("true");
ognlUtil.setUseLRUExpressionCache("true");
Foo foo = new Foo();
OgnlContext context = (OgnlContext) ognlUtil.createDefaultContext(foo);
// Expression which executes with success
try {
ognlUtil.getValue("@com.opensymphony.xwork2.ognl.OgnlUtilTest@STATIC_FINAL_PUBLIC_ATTRIBUTE", context, foo);
assertEquals("Successfully executed expression must have been cached", ognlUtil.expressionCacheSize(), 1);
} catch (Exception ex) {
fail("Expression execution should have succeeded here. Exception: " + ex);
}
// Expression which executes with failure
try {
ognlUtil.getValue("@com.opensymphony.xwork2.ognl.OgnlUtilTest@STATIC_PRIVATE_ATTRIBUTE", context, foo);
fail("Expression execution should have failed here");
} catch (Exception ex) {
assertEquals("Expression with failed execution must have been cached nevertheless", ognlUtil.expressionCacheSize(), 2);
}
ognlUtil.setUseLRUExpressionCache("false");
}
public void testMethodExpressionIsCachedIrrespectiveOfItsExecutionStatus() throws Exception {
Foo foo = new Foo();
OgnlContext context = (OgnlContext) ognlUtil.createDefaultContext(foo);
@@ -193,6 +272,32 @@ public class OgnlUtilTest extends XWorkTestCase {
assertTrue("Expression cache empty after usage ?", ognlUtil.expressionCacheSize() > 0);
}
public void testClearExpressionLRUCache() throws OgnlException {
ognlUtil.setEnableExpressionCache("true");
ognlUtil.setUseLRUExpressionCache("true");
// Test that the expression cache is functioning as expected.
Object expr0 = ognlUtil.compile("test");
Object expr1 = ognlUtil.compile("test");
Object expr2 = ognlUtil.compile("test");
// Cache in effect, so expr0, expr1, expr2 should be the same.
assertSame(expr0, expr1);
assertSame(expr0, expr2);
assertTrue("Expression cache empty before clear ?", ognlUtil.expressionCacheSize() > 0);
// Clear the Epxression cache and confirm subsequent requests are new.
ognlUtil.clearExpressionCache();
assertEquals("Expression cache not empty after clear ?", 0, ognlUtil.expressionCacheSize());
Object expr3 = ognlUtil.compile("test");
Object expr4 = ognlUtil.compile("test");
Object expr5 = ognlUtil.compile("test");
// Cache cleared, expr3 should be a new instance.
assertNotSame(expr0, expr3);
// Cache still in effect, so expr3, expr4, expr5 should be the same.
assertSame(expr3, expr4);
assertSame(expr3, expr5);
assertTrue("Expression cache empty after usage ?", ognlUtil.expressionCacheSize() > 0);
ognlUtil.setUseLRUExpressionCache("false");
}
public void testClearBeanInfoCache() throws IntrospectionException {
final TestBean1 testBean1 = new TestBean1();
final TestBean2 testBean2 = new TestBean2();
@@ -242,6 +347,79 @@ public class OgnlUtilTest extends XWorkTestCase {
assertTrue("BeanInfo cache empty after usage ?", ognlUtil.beanInfoCacheSize() > 0);
}
public void testBeanInfoCache() throws IntrospectionException {
final TestBean1 testBean1 = new TestBean1();
final TestBean2 testBean2 = new TestBean2();
// Test that the BeanInfo cache is functioning as expected.
Object beanInfo1_1 = ognlUtil.getBeanInfo(testBean1);
Object beanInfo1_2 = ognlUtil.getBeanInfo(testBean1);
Object beanInfo1_3 = ognlUtil.getBeanInfo(testBean1);
// Cache in effect, so beanInfo1_1, beanInfo1_2, beanInfo1_3 should be the same.
assertSame(beanInfo1_1, beanInfo1_2);
assertSame(beanInfo1_1, beanInfo1_3);
Object beanInfo2_1 = ognlUtil.getBeanInfo(testBean2);
Object beanInfo2_2 = ognlUtil.getBeanInfo(testBean2);
Object beanInfo2_3 = ognlUtil.getBeanInfo(testBean2);
// Cache in effect, so beanInfo2_1, beanInfo2_2, beanInfo2_3 should be the same.
assertSame(beanInfo2_1, beanInfo2_2);
assertSame(beanInfo2_1, beanInfo2_3);
// BeanInfo for TestBean1 and TestBean2 should always be different.
assertNotSame(beanInfo1_1, beanInfo2_1);
assertTrue("BeanInfo cache empty after usage ?", ognlUtil.beanInfoCacheSize() > 0);
}
public void testBeanInfoLRUCache() throws IntrospectionException {
ognlUtil.setUseLRUBeanInfoCache("true");
final TestBean1 testBean1 = new TestBean1();
final TestBean2 testBean2 = new TestBean2();
// Test that the BeanInfo cache is functioning as expected.
Object beanInfo1_1 = ognlUtil.getBeanInfo(testBean1);
Object beanInfo1_2 = ognlUtil.getBeanInfo(testBean1);
Object beanInfo1_3 = ognlUtil.getBeanInfo(testBean1);
// Cache in effect, so beanInfo1_1, beanInfo1_2, beanInfo1_3 should be the same.
assertSame(beanInfo1_1, beanInfo1_2);
assertSame(beanInfo1_1, beanInfo1_3);
Object beanInfo2_1 = ognlUtil.getBeanInfo(testBean2);
Object beanInfo2_2 = ognlUtil.getBeanInfo(testBean2);
Object beanInfo2_3 = ognlUtil.getBeanInfo(testBean2);
// Cache in effect, so beanInfo2_1, beanInfo2_2, beanInfo2_3 should be the same.
assertSame(beanInfo2_1, beanInfo2_2);
assertSame(beanInfo2_1, beanInfo2_3);
// BeanInfo for TestBean1 and TestBean2 should always be different.
assertNotSame(beanInfo1_1, beanInfo2_1);
assertTrue("BeanInfo cache empty after usage ?", ognlUtil.beanInfoCacheSize() > 0);
ognlUtil.setUseLRUBeanInfoCache("true");
}
public void testBeanInfoLRUCacheLimits() throws IntrospectionException {
ognlUtil.setUseLRUBeanInfoCache("true");
ognlUtil.setBeanInfoCacheMaxSize("1");
final TestBean1 testBean1 = new TestBean1();
final TestBean2 testBean2 = new TestBean2();
// Test that the BeanInfo cache is functioning as expected.
Object beanInfo1_1 = ognlUtil.getBeanInfo(testBean1);
Object beanInfo1_2 = ognlUtil.getBeanInfo(testBean1);
Object beanInfo1_3 = ognlUtil.getBeanInfo(testBean1);
// Cache in effect, so beanInfo1_1, beanInfo1_2, beanInfo1_3 should be the same.
assertSame(beanInfo1_1, beanInfo1_2);
assertSame(beanInfo1_1, beanInfo1_3);
Object beanInfo2_1 = ognlUtil.getBeanInfo(testBean2);
Object beanInfo2_2 = ognlUtil.getBeanInfo(testBean2);
Object beanInfo2_3 = ognlUtil.getBeanInfo(testBean2);
// Cache in effect, so beanInfo2_1, beanInfo2_2, beanInfo2_3 should be the same.
assertSame(beanInfo2_1, beanInfo2_2);
assertSame(beanInfo2_1, beanInfo2_3);
// BeanInfo for TestBean1 and TestBean2 should always be different.
assertNotSame(beanInfo1_1, beanInfo2_1);
assertTrue("BeanInfo cache empty after usage ?", ognlUtil.beanInfoCacheSize() > 0);
assertEquals("BeanInfo LRU cache size should be at its limit", 1, ognlUtil.beanInfoCacheSize());
// LRU cache should not contain TestBean1 beaninfo anymore. A new entry should exist in the cache.
Object beanInfo1_4 = ognlUtil.getBeanInfo(testBean1);
assertNotSame("BeanInfo dropped from LRU cache is the same as newly added ?", beanInfo1_1, beanInfo1_4);
ognlUtil.setUseLRUBeanInfoCache("false");
ognlUtil.setBeanInfoCacheMaxSize(String.valueOf(Integer.MAX_VALUE));
}
public void testClearRuntimeCache() {
// Confirm that no exceptions or failures arise when calling the convenience global clear method.
OgnlUtil.clearRuntimeCache();
@@ -1525,6 +1703,36 @@ public class OgnlUtilTest extends XWorkTestCase {
}
}
public void testOgnlUtilLRUCacheClass() throws OgnlException {
OgnlUtil.LRUCache<Integer, String> lruCache = ognlUtil.new LRUCache<>(2, 16, 0.75f);
Map<Integer, String> backingMap = lruCache.backingMapReference();
assertNotNull("Backing Map somehow null ?", backingMap);
assertEquals("Initial evictionLimit did not match initial value", 2, lruCache.getEvictionLimit());
lruCache.setEvictionLimit(3);
assertEquals("Updated evictionLimit did not match updated value", 3, lruCache.getEvictionLimit());
String lookupResult = lruCache.get(Integer.valueOf(0));
assertNull("Lookup of empty cache returned non-null value ?", lookupResult);
lruCache.put(Integer.valueOf(0), "Zero");
lookupResult = lruCache.get(Integer.valueOf(0));
assertEquals("Retrieved value does not match put value ?", "Zero", lookupResult);
lruCache.put(Integer.valueOf(1), "One");
lruCache.put(Integer.valueOf(2), "Two");
assertEquals("LRU cache not size evictionlimit after adding three values ?", lruCache.getEvictionLimit(), lruCache.size());
lookupResult = lruCache.get(Integer.valueOf(2));
assertEquals("Retrieved value does not match put value ?", "Two", lookupResult);
lruCache.put(Integer.valueOf(3), "Three");
assertEquals("LRU cache not size evictionlimit after adding values ?", lruCache.getEvictionLimit(), lruCache.size());
lookupResult = lruCache.get(Integer.valueOf(0));
assertNull("Lookup of value 0 (should have dropped off LRU cache) returned non-null value ?", lookupResult);
lruCache.putIfAbsent(Integer.valueOf(2), "Two");
lookupResult = lruCache.get(Integer.valueOf(2));
assertEquals("Retrieved value does not match put value ?", "Two", lookupResult);
assertEquals("LRUCache and backing map size different after puts ?", lruCache.size(), backingMap.size());
lruCache.clear();
assertEquals("LRU cache not empty after clear ?", 0, lruCache.size());
assertEquals("LRUCache and backing map size different after clear ?", lruCache.size(), backingMap.size());
}
private void reloadTestContainerConfiguration(boolean devMode, boolean allowStaticMethod) {
loadConfigurationProviders(new StubConfigurationProvider() {
@Override