diff --git a/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java b/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java index 6db3139da..01c9eee20 100644 --- a/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java +++ b/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java @@ -28,6 +28,9 @@ import com.opensymphony.xwork2.conversion.*; import com.opensymphony.xwork2.conversion.impl.*; import com.opensymphony.xwork2.factory.*; import com.opensymphony.xwork2.inject.*; +import com.opensymphony.xwork2.ognl.DefaultOgnlBeanInfoCacheFactory; +import com.opensymphony.xwork2.ognl.DefaultOgnlExpressionCacheFactory; +import com.opensymphony.xwork2.ognl.OgnlCacheFactory; import com.opensymphony.xwork2.ognl.OgnlReflectionProvider; import com.opensymphony.xwork2.ognl.OgnlUtil; import com.opensymphony.xwork2.ognl.OgnlValueStackFactory; @@ -283,6 +286,9 @@ public class DefaultConfiguration implements Configuration { builder.factory(ValueSubstitutor.class, EnvsValueSubstitutor.class, Scope.SINGLETON); + builder.factory(OgnlCacheFactory.class, StrutsConstants.STRUTS_OGNL_EXPRESSIONCACHE_FACTORY, DefaultOgnlExpressionCacheFactory.class, Scope.SINGLETON); + builder.factory(OgnlCacheFactory.class, StrutsConstants.STRUTS_OGNL_BEANINFOCACHE_FACTORY, DefaultOgnlBeanInfoCacheFactory.class, Scope.SINGLETON); + builder.constant(StrutsConstants.STRUTS_DEVMODE, "false"); builder.constant(StrutsConstants.STRUTS_OGNL_LOG_MISSING_PROPERTIES, "false"); builder.constant(StrutsConstants.STRUTS_OGNL_ENABLE_EVAL_EXPRESSION, "false"); diff --git a/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java b/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java index 400674d2d..2043970c4 100644 --- a/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java +++ b/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java @@ -91,6 +91,9 @@ import com.opensymphony.xwork2.ognl.accessor.XWorkMapPropertyAccessor; import com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor; import com.opensymphony.xwork2.util.CompoundRoot; import com.opensymphony.xwork2.LocalizedTextProvider; +import com.opensymphony.xwork2.ognl.DefaultOgnlBeanInfoCacheFactory; +import com.opensymphony.xwork2.ognl.DefaultOgnlExpressionCacheFactory; +import com.opensymphony.xwork2.ognl.OgnlCacheFactory; import com.opensymphony.xwork2.util.StrutsLocalizedTextProvider; import com.opensymphony.xwork2.util.OgnlTextParser; import com.opensymphony.xwork2.util.PatternMatcher; @@ -127,19 +130,24 @@ import java.util.Set; public class StrutsDefaultConfigurationProvider implements ConfigurationProvider { + @Override public void destroy() { } + @Override public void init(Configuration configuration) throws ConfigurationException { } + @Override public void loadPackages() throws ConfigurationException { } + @Override public boolean needsReload() { return false; } + @Override public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException { @@ -218,6 +226,9 @@ public class StrutsDefaultConfigurationProvider implements ConfigurationProvider , Scope.SINGLETON) .factory(ValueSubstitutor.class, EnvsValueSubstitutor.class, Scope.SINGLETON) + + .factory(OgnlCacheFactory.class, StrutsConstants.STRUTS_OGNL_EXPRESSIONCACHE_FACTORY, DefaultOgnlExpressionCacheFactory.class, Scope.SINGLETON) + .factory(OgnlCacheFactory.class, StrutsConstants.STRUTS_OGNL_BEANINFOCACHE_FACTORY, DefaultOgnlBeanInfoCacheFactory.class, Scope.SINGLETON) ; props.setProperty(StrutsConstants.STRUTS_ENABLE_DYNAMIC_METHOD_INVOCATION, Boolean.FALSE.toString()); diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/DefaultOgnlBeanInfoCacheFactory.java b/core/src/main/java/com/opensymphony/xwork2/ognl/DefaultOgnlBeanInfoCacheFactory.java new file mode 100644 index 000000000..dd3eb1a44 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/DefaultOgnlBeanInfoCacheFactory.java @@ -0,0 +1,43 @@ +/* + * Copyright 2022 Apache Software Foundation. + * + * 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. + */ +package com.opensymphony.xwork2.ognl; + +import com.opensymphony.xwork2.inject.Inject; +import org.apache.struts2.StrutsConstants; + +/** + * Default OGNL Cache factory implementation. + * + * Currently used for BeanInfo cache creation. + * + * @param The type for the cache key entries + * @param The type for the cache value entries + */ +public class DefaultOgnlBeanInfoCacheFactory extends DefaultOgnlCacheFactory { + + @Override + @Inject(value = StrutsConstants.STRUTS_OGNL_BEANINFO_CACHE_MAXSIZE, required = false) + protected void setCacheMaxSize(String maxSize) { + super.setCacheMaxSize(maxSize); + } + + @Override + @Inject(value = StrutsConstants.STRUTS_OGNL_BEANINFO_CACHE_LRU_MODE, required = false) + protected void setUseLRUCache(String useLRUMode) { + super.setUseLRUCache(useLRUMode); + } + +} diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/DefaultOgnlCacheFactory.java b/core/src/main/java/com/opensymphony/xwork2/ognl/DefaultOgnlCacheFactory.java new file mode 100644 index 000000000..8bd9a2099 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/DefaultOgnlCacheFactory.java @@ -0,0 +1,61 @@ +/* + * Copyright 2022 Apache Software Foundation. + * + * 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. + */ +package com.opensymphony.xwork2.ognl; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.commons.lang3.BooleanUtils; + +/** + * Default OGNL Cache factory implementation. + * + * Currently used for Expression cache and BeanInfo cache creation. + * + * @param The type for the cache key entries + * @param The type for the cache value entries + */ +public class DefaultOgnlCacheFactory implements OgnlCacheFactory { + + private final AtomicBoolean useLRUCache = new AtomicBoolean(false); + private final AtomicInteger cacheMaxSize = new AtomicInteger(25000); + + @Override + public OgnlCache buildOgnlCache(int evictionLimit, int initialCapacity, float loadFactor, boolean lruCache) { + if (lruCache) { + return new OgnlLRUCache<>(evictionLimit, initialCapacity, loadFactor); + } else { + return new OgnlDefaultCache<>(evictionLimit, initialCapacity, loadFactor); + } + } + + @Override + public int getCacheMaxSize() { + return cacheMaxSize.get(); + } + + protected void setCacheMaxSize(String maxSize) { + cacheMaxSize.set(Integer.parseInt(maxSize)); + } + + @Override + public boolean getUseLRUCache() { + return useLRUCache.get(); + } + + protected void setUseLRUCache(String useLRUMode) { + useLRUCache.set(BooleanUtils.toBoolean(useLRUMode)); + } +} diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/DefaultOgnlExpressionCacheFactory.java b/core/src/main/java/com/opensymphony/xwork2/ognl/DefaultOgnlExpressionCacheFactory.java new file mode 100644 index 000000000..ff623b333 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/DefaultOgnlExpressionCacheFactory.java @@ -0,0 +1,43 @@ +/* + * Copyright 2022 Apache Software Foundation. + * + * 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. + */ +package com.opensymphony.xwork2.ognl; + +import com.opensymphony.xwork2.inject.Inject; +import org.apache.struts2.StrutsConstants; + +/** + * Default OGNL Expression Cache factory implementation. + * + * Currently used for Expression cache creation. + * + * @param The type for the cache key entries + * @param The type for the cache value entries + */ +public class DefaultOgnlExpressionCacheFactory extends DefaultOgnlCacheFactory { + + @Override + @Inject(value = StrutsConstants.STRUTS_OGNL_EXPRESSION_CACHE_MAXSIZE, required = false) + protected void setCacheMaxSize(String maxSize) { + super.setCacheMaxSize(maxSize); + } + + @Override + @Inject(value = StrutsConstants.STRUTS_OGNL_EXPRESSION_CACHE_LRU_MODE, required = false) + protected void setUseLRUCache(String useLRUMode) { + super.setUseLRUCache(useLRUMode); + } + +} diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlCache.java b/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlCache.java new file mode 100644 index 000000000..83893c153 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlCache.java @@ -0,0 +1,41 @@ +/* + * Copyright 2022 Apache Software Foundation. + * + * 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. + */ +package com.opensymphony.xwork2.ognl; + +/** + * A basic cache interface for use with OGNL processing (such as Expression, BeanInfo). + * All OGNL caches will have an eviction limit, but setting an extremely high value can + * simulate an "effectively unlimited" cache. + * + * @param The type for the cache key entries + * @param The type for the cache value entries + */ +public interface OgnlCache { + + public Value get(Key key); + + public void put(Key key, Value value); + + public void putIfAbsent(Key key, Value value); + + public int size(); + + public void clear(); + + public int getEvictionLimit(); + + public void setEvictionLimit(int cacheEvictionLimit); +} diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlCacheFactory.java b/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlCacheFactory.java new file mode 100644 index 000000000..eabb1a955 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlCacheFactory.java @@ -0,0 +1,29 @@ +/* + * Copyright 2022 Apache Software Foundation. + * + * 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. + */ +package com.opensymphony.xwork2.ognl; + +/** + * Used by {@link com.opensymphony.xwork2.ognl.OgnlUtil} to create appropriate OGNL + * caches based on configuration. + * + * @param The type for the cache key entries + * @param The type for the cache value entries + */ +public interface OgnlCacheFactory { + OgnlCache buildOgnlCache(int evictionLimit, int initialCapacity, float loadFactor, boolean lruCache); + int getCacheMaxSize(); + boolean getUseLRUCache(); +} diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlDefaultCache.java b/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlDefaultCache.java new file mode 100644 index 000000000..20431e133 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlDefaultCache.java @@ -0,0 +1,85 @@ +/* + * Copyright 2022 Apache Software Foundation. + * + * 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. + */ +package com.opensymphony.xwork2.ognl; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Default OGNL cache implementation. + * + * Setting a very high eviction limit simulates an unlimited cache. + * Setting too low an eviction limit will make the cache ineffective. + * + * @param The type for the cache key entries + * @param The type for the cache value entries + */ +public class OgnlDefaultCache implements OgnlCache { + + private final ConcurrentHashMap ognlCache; + private final AtomicInteger cacheEvictionLimit = new AtomicInteger(25000); + + public OgnlDefaultCache(int evictionLimit, int initialCapacity, float loadFactor) { + this.cacheEvictionLimit.set(evictionLimit); + ognlCache = new ConcurrentHashMap<>(initialCapacity, loadFactor); + } + + @Override + public Value get(Key key) { + return ognlCache.get(key); + } + + @Override + public void put(Key key, Value value) { + ognlCache.put(key, value); + this.clearIfEvictionLimitExceeded(); + } + + @Override + public void putIfAbsent(Key key, Value value) { + ognlCache.putIfAbsent(key, value); + this.clearIfEvictionLimitExceeded(); + } + + @Override + public int size() { + return ognlCache.size(); + } + + @Override + public void clear() { + ognlCache.clear(); + } + + @Override + public int getEvictionLimit() { + return this.cacheEvictionLimit.get(); + } + + @Override + public void setEvictionLimit(int cacheEvictionLimit) { + this.cacheEvictionLimit.set(cacheEvictionLimit); + } + + /** + * Clear the cache if the eviction limit has been exceeded. + */ + private void clearIfEvictionLimitExceeded() { + if (ognlCache.size() > cacheEvictionLimit.get()) { + ognlCache.clear(); + } + } +} diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlLRUCache.java b/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlLRUCache.java new file mode 100644 index 000000000..a99adca2a --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlLRUCache.java @@ -0,0 +1,87 @@ +/* + * Copyright 2022 Apache Software Foundation. + * + * 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. + */ +package com.opensymphony.xwork2.ognl; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * A basic OGNL LRU cache implementation. + * + * The implementation utilizes a {@link Collections#synchronizedMap(java.util.Map)} + * backed by a {@link LinkedHashMap}. May be replaced by a more efficient implementation in the future. + * + * Setting too low an eviction limit will produce more overhead than value. + * Setting too high an eviction limit may also produce more overhead than value. + * An appropriate eviction limit will need to be determined on an individual application basis. + * + * @param The type for the cache key entries + * @param The type for the cache value entries + */ +public class OgnlLRUCache implements OgnlCache { + + private final Map ognlLRUCache; + private final AtomicInteger cacheEvictionLimit = new AtomicInteger(2500); + + public OgnlLRUCache(int evictionLimit, int initialCapacity, float loadFactor) { + this.cacheEvictionLimit.set(evictionLimit); + // Access-order mode selected (order mode true in LinkedHashMap constructor). + ognlLRUCache = Collections.synchronizedMap (new LinkedHashMap(initialCapacity, loadFactor, true) { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return (this.size() > cacheEvictionLimit.get()); + } + }); + } + + @Override + public Value get(Key key) { + return ognlLRUCache.get(key); + } + + @Override + public void put(Key key, Value value) { + ognlLRUCache.put(key, value); + } + + @Override + public void putIfAbsent(Key key, Value value) { + ognlLRUCache.putIfAbsent(key, value); + } + + @Override + public int size() { + return ognlLRUCache.size(); + } + + @Override + public void clear() { + ognlLRUCache.clear(); + } + + @Override + public int getEvictionLimit() { + return this.cacheEvictionLimit.get(); + } + + @Override + public void setEvictionLimit(int cacheEvictionLimit) { + this.cacheEvictionLimit.set(cacheEvictionLimit); + } + +} diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlUtil.java b/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlUtil.java index c3eb840da..6854926f5 100644 --- a/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlUtil.java +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlUtil.java @@ -38,10 +38,7 @@ import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; 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; @@ -58,14 +55,10 @@ 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 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 expressionsCache = new ConcurrentHashMap<>(); - private final LRUCache expressionsCacheLRU = new LRUCache<>(expressionsCacheMaxSize.get(), 16, 0.75f); - private final ConcurrentMap, BeanInfo> beanInfoCache = new ConcurrentHashMap<>(); - private final LRUCache, BeanInfo> beanInfoCacheLRU = new LRUCache<>(beanInfoCacheMaxSize.get(), 16, 0.75f); + private final OgnlCacheFactory ognlExpressionCacheFactory; + private final OgnlCacheFactory, BeanInfo> ognlBeanInfoCacheFactory; + private final OgnlCache expressionCache; + private final OgnlCache, BeanInfo> beanInfoCache; private TypeConverter defaultConverter; private boolean devMode; @@ -85,7 +78,25 @@ public class OgnlUtil { private boolean allowStaticMethodAccess; private boolean disallowProxyMemberAccess; + /** + * Construct a new OgnlUtil instance for use with the framework + */ public OgnlUtil() { + this(null, null); // Instantiate default Expression and BeanInfo caches (null factories) + } + + /** + * Construct a new OgnlUtil instance for use with the framework, with optional + * cache factories for Ognl Expression and BeanInfo caches. + * + * @param ognlExpressionCacheFactory factory for Expression cache instance. If null, use default + * @param ognlBeanInfoCacheFactory for BeanInfo cache instance. If null, use default + */ + @Inject + public OgnlUtil( + @Inject(value = StrutsConstants.STRUTS_OGNL_EXPRESSIONCACHE_FACTORY, required = false) OgnlCacheFactory ognlExpressionCacheFactory, + @Inject(value = StrutsConstants.STRUTS_OGNL_BEANINFOCACHE_FACTORY, required = false) OgnlCacheFactory, BeanInfo> ognlBeanInfoCacheFactory + ) { excludedClasses = Collections.unmodifiableSet(new HashSet<>()); excludedPackageNamePatterns = Collections.unmodifiableSet(new HashSet<>()); excludedPackageNames = Collections.unmodifiableSet(new HashSet<>()); @@ -93,6 +104,19 @@ public class OgnlUtil { devModeExcludedClasses = Collections.unmodifiableSet(new HashSet<>()); devModeExcludedPackageNamePatterns = Collections.unmodifiableSet(new HashSet<>()); devModeExcludedPackageNames = Collections.unmodifiableSet(new HashSet<>()); + this.ognlExpressionCacheFactory = ognlExpressionCacheFactory; + this.ognlBeanInfoCacheFactory = ognlBeanInfoCacheFactory; + + if (ognlExpressionCacheFactory != null) { + this.expressionCache = ognlExpressionCacheFactory.buildOgnlCache(ognlExpressionCacheFactory.getCacheMaxSize(), 16, 0.75f, ognlExpressionCacheFactory.getUseLRUCache()); + } else { + this.expressionCache = new OgnlDefaultCache<>(25000, 16, 0.75f); + } + if (ognlBeanInfoCacheFactory != null) { + this.beanInfoCache = ognlBeanInfoCacheFactory.buildOgnlCache(ognlBeanInfoCacheFactory.getCacheMaxSize(), 16, 0.75f, ognlBeanInfoCacheFactory.getUseLRUCache()); + } else { + this.beanInfoCache = new OgnlDefaultCache<>(25000, 16, 0.75f); + } } @Inject @@ -111,25 +135,13 @@ public class OgnlUtil { } @Inject(value = StrutsConstants.STRUTS_OGNL_EXPRESSION_CACHE_MAXSIZE, required = false) - protected void setExpressionsCacheMaxSize(String maxSize) { - expressionsCacheMaxSize.set(Integer.parseInt(maxSize)); - expressionsCacheLRU.setEvictionLimit(expressionsCacheMaxSize.get()); + protected void setExpressionCacheMaxSize(String maxSize) { + expressionCache.setEvictionLimit(Integer.parseInt(maxSize)); } @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)); + beanInfoCache.setEvictionLimit(Integer.parseInt(maxSize)); } @Inject(value = StrutsConstants.STRUTS_OGNL_ENABLE_EVAL_EXPRESSION, required = false) @@ -302,8 +314,7 @@ public class OgnlUtil { * @since 2.5.21 */ public void clearExpressionCache() { - expressionsCache.clear(); - expressionsCacheLRU.clear(); + expressionCache.clear(); } /** @@ -314,11 +325,7 @@ public class OgnlUtil { * @since 2.5.21 */ public int expressionCacheSize() { - if (useLRUExpressionCache.get()) { - return expressionsCacheLRU.size(); - } else { - return expressionsCache.size(); - } + return expressionCache.size(); } /** @@ -336,7 +343,6 @@ public class OgnlUtil { */ public void clearBeanInfoCache() { beanInfoCache.clear(); - beanInfoCacheLRU.clear(); } /** @@ -347,11 +353,7 @@ public class OgnlUtil { * @since 2.5.21 */ public int beanInfoCacheSize() { - if (useLRUBeanInfoCache.get()) { - return beanInfoCacheLRU.size(); - } else { - return beanInfoCache.size(); - } + return beanInfoCache.size(); } /** @@ -571,18 +573,11 @@ public class OgnlUtil { private Object compileAndExecute(String expression, Map context, OgnlTask task) throws OgnlException { Object tree; if (enableExpressionCache) { - final Map expressionsCacheRef = (useLRUExpressionCache.get() ? expressionsCacheLRU.backingMapReference() : expressionsCache); - tree = expressionsCacheRef.get(expression); + tree = expressionCache.get(expression); if (tree == null) { tree = Ognl.parseExpression(expression); checkEnableEvalExpression(tree, context); - 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. - } - } + expressionCache.putIfAbsent(expression, tree); } } else { tree = Ognl.parseExpression(expression); @@ -595,18 +590,11 @@ public class OgnlUtil { private Object compileAndExecuteMethod(String expression, Map context, OgnlTask task) throws OgnlException { Object tree; if (enableExpressionCache) { - final Map expressionsCacheRef = (useLRUExpressionCache.get() ? expressionsCacheLRU.backingMapReference() : expressionsCache); - tree = expressionsCacheRef.get(expression); + tree = expressionCache.get(expression); if (tree == null) { tree = Ognl.parseExpression(expression); checkSimpleMethod(tree, context); - 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. - } - } + expressionCache.putIfAbsent(expression, tree); } } else { tree = Ognl.parseExpression(expression); @@ -812,18 +800,11 @@ public class OgnlUtil { * @throws IntrospectionException is thrown if an exception occurs during introspection. */ public BeanInfo getBeanInfo(Class clazz) throws IntrospectionException { - final Map, BeanInfo> beanInfoCacheRef = (useLRUBeanInfoCache.get() ? beanInfoCacheLRU.backingMapReference() : beanInfoCache); - synchronized (beanInfoCacheRef) { - BeanInfo beanInfo = beanInfoCacheRef.get(clazz); + synchronized (beanInfoCache) { + BeanInfo beanInfo = beanInfoCache.get(clazz); if (beanInfo == null) { beanInfo = Introspector.getBeanInfo(clazz, Object.class); - 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. - } - } + beanInfoCache.putIfAbsent(clazz, beanInfo); } return beanInfo; } @@ -882,66 +863,4 @@ 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 type for the LRUCache - * @param Value type for the LRUCache - */ - protected class LRUCache { - private final Map 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(initialCapacity, loadFactor, true) { - @Override - protected boolean removeEldestEntry(Map.Entry 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 backingMapReference() { - return lruCache; - } - - public int getEvictionLimit() { - return this.cacheEvictionLimit.get(); - } - - public void setEvictionLimit(int cacheEvictionLimit) { - this.cacheEvictionLimit.set(cacheEvictionLimit); - } - - } } diff --git a/core/src/main/java/org/apache/struts2/StrutsConstants.java b/core/src/main/java/org/apache/struts2/StrutsConstants.java index ba6ff87ff..84b608077 100644 --- a/core/src/main/java/org/apache/struts2/StrutsConstants.java +++ b/core/src/main/java/org/apache/struts2/StrutsConstants.java @@ -249,6 +249,18 @@ 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 an OGNL expression cache factory implementation. A default implementation is provided, but + * could be replaced by a custom one if desired. + */ + public static final String STRUTS_OGNL_EXPRESSIONCACHE_FACTORY = "struts.ognl.expressionCacheFactory"; + + /** + * Specifies an OGNL BeanInfo cache factory implementation. A default implementation is provided, but + * could be replaced by a custom one if desired. + */ + public static final String STRUTS_OGNL_BEANINFOCACHE_FACTORY = "struts.ognl.beanInfoCacheFactory"; + /** * 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 diff --git a/core/src/main/resources/org/apache/struts2/default.properties b/core/src/main/resources/org/apache/struts2/default.properties index b88b6030d..eef441ebc 100644 --- a/core/src/main/resources/org/apache/struts2/default.properties +++ b/core/src/main/resources/org/apache/struts2/default.properties @@ -230,12 +230,12 @@ struts.ognl.enableExpressionCache=true ### 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 +# 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 +# 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 @@ -243,12 +243,12 @@ struts.ognl.enableExpressionCache=true ### 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 +# 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 +### 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 diff --git a/core/src/main/resources/struts-default.xml b/core/src/main/resources/struts-default.xml index 9dd8fbfa3..65b89f169 100644 --- a/core/src/main/resources/struts-default.xml +++ b/core/src/main/resources/struts-default.xml @@ -228,6 +228,9 @@ + + + diff --git a/core/src/test/java/com/opensymphony/xwork2/ognl/OgnlUtilTest.java b/core/src/test/java/com/opensymphony/xwork2/ognl/OgnlUtilTest.java index 8cebb1670..9c250ba51 100644 --- a/core/src/test/java/com/opensymphony/xwork2/ognl/OgnlUtilTest.java +++ b/core/src/test/java/com/opensymphony/xwork2/ognl/OgnlUtilTest.java @@ -35,6 +35,7 @@ import com.opensymphony.xwork2.util.Owner; import com.opensymphony.xwork2.util.ValueStack; import com.opensymphony.xwork2.util.location.LocatableProperties; import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import java.beans.BeanInfo; import ognl.InappropriateExpressionException; import ognl.MethodFailedException; import ognl.NoSuchPropertyException; @@ -131,7 +132,7 @@ public class OgnlUtilTest extends XWorkTestCase { public void testCacheEnabledMaxSize() throws OgnlException { ognlUtil.setEnableExpressionCache("true"); - ognlUtil.setExpressionsCacheMaxSize("1"); + ognlUtil.setExpressionCacheMaxSize("1"); Object expr0 = ognlUtil.compile("test"); Object expr2 = ognlUtil.compile("test"); assertSame(expr0, expr2); @@ -153,22 +154,22 @@ public class OgnlUtilTest extends XWorkTestCase { 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 { + // Force usage of LRU cache factories for the OgnlUtil instance + this.ognlUtil = generateOgnlUtilInstanceWithDefaultLRUCacheFactories(); 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 { + // Force usage of LRU cache factories for the OgnlUtil instance + this.ognlUtil = generateOgnlUtilInstanceWithDefaultLRUCacheFactories(); ognlUtil.setEnableExpressionCache("true"); - ognlUtil.setUseLRUExpressionCache("true"); - ognlUtil.setExpressionsCacheMaxSize("1"); + ognlUtil.setExpressionCacheMaxSize("1"); Object expr0 = ognlUtil.compile("test"); Object expr2 = ognlUtil.compile("test"); assertSame(expr0, expr2); @@ -182,7 +183,6 @@ public class OgnlUtilTest extends XWorkTestCase { 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 { @@ -206,8 +206,10 @@ public class OgnlUtilTest extends XWorkTestCase { } public void testExpressionIsLRUCachedIrrespectiveOfItsExecutionStatus() throws OgnlException { + // Force usage of LRU cache factories for the OgnlUtil instance + this.ognlUtil = generateOgnlUtilInstanceWithDefaultLRUCacheFactories(); + ognlUtil.setContainer(container); // Must be explicitly set as the generated OgnlUtil instance has no container ognlUtil.setEnableExpressionCache("true"); - ognlUtil.setUseLRUExpressionCache("true"); Foo foo = new Foo(); OgnlContext context = (OgnlContext) ognlUtil.createDefaultContext(foo); @@ -225,7 +227,6 @@ public class OgnlUtilTest extends XWorkTestCase { } catch (Exception ex) { assertEquals("Expression with failed execution must have been cached nevertheless", ognlUtil.expressionCacheSize(), 2); } - ognlUtil.setUseLRUExpressionCache("false"); } public void testMethodExpressionIsCachedIrrespectiveOfItsExecutionStatus() throws Exception { @@ -273,8 +274,9 @@ public class OgnlUtilTest extends XWorkTestCase { } public void testClearExpressionLRUCache() throws OgnlException { + // Force usage of LRU cache factories for the OgnlUtil instance + this.ognlUtil = generateOgnlUtilInstanceWithDefaultLRUCacheFactories(); 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"); @@ -295,7 +297,6 @@ public class OgnlUtilTest extends XWorkTestCase { assertSame(expr3, expr4); assertSame(expr3, expr5); assertTrue("Expression cache empty after usage ?", ognlUtil.expressionCacheSize() > 0); - ognlUtil.setUseLRUExpressionCache("false"); } public void testClearBeanInfoCache() throws IntrospectionException { @@ -369,7 +370,8 @@ public class OgnlUtilTest extends XWorkTestCase { } public void testBeanInfoLRUCache() throws IntrospectionException { - ognlUtil.setUseLRUBeanInfoCache("true"); + // Force usage of LRU cache factories for the OgnlUtil instance + this.ognlUtil = generateOgnlUtilInstanceWithDefaultLRUCacheFactories(); final TestBean1 testBean1 = new TestBean1(); final TestBean2 testBean2 = new TestBean2(); // Test that the BeanInfo cache is functioning as expected. @@ -388,11 +390,11 @@ public class OgnlUtilTest extends XWorkTestCase { // 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"); + // Force usage of LRU cache factories for the OgnlUtil instance + this.ognlUtil = generateOgnlUtilInstanceWithDefaultLRUCacheFactories(); ognlUtil.setBeanInfoCacheMaxSize("1"); final TestBean1 testBean1 = new TestBean1(); final TestBean2 testBean2 = new TestBean2(); @@ -416,7 +418,6 @@ public class OgnlUtilTest extends XWorkTestCase { // 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)); } @@ -1312,6 +1313,20 @@ public class OgnlUtilTest extends XWorkTestCase { internalTestOgnlUtilExclusionsImmutable(basicOgnlUtil); } + public void testDefaultOgnlUtilExclusionsAlternateConstructor() { + OgnlUtil basicOgnlUtil = new OgnlUtil(null, null); + + internalTestInitialEmptyOgnlUtilExclusions(basicOgnlUtil); + internalTestOgnlUtilExclusionsImmutable(basicOgnlUtil); + } + + public void testDefaultOgnlUtilExclusionsAlternateConstructorPopulated() { + OgnlUtil basicOgnlUtil = new OgnlUtil(new DefaultOgnlExpressionCacheFactory(), new DefaultOgnlBeanInfoCacheFactory, BeanInfo>()); + + internalTestInitialEmptyOgnlUtilExclusions(basicOgnlUtil); + internalTestOgnlUtilExclusionsImmutable(basicOgnlUtil); + } + public void testOgnlUtilExcludedAdditivity() { Set> excludedClasses; Set excludedPackageNamePatterns; @@ -1675,6 +1690,34 @@ public class OgnlUtilTest extends XWorkTestCase { } } + public void testGetExcludedPackageNamesAlternateConstructor() { + // Getter should return an immutable collection + OgnlUtil util = new OgnlUtil(null, null); + util.setExcludedPackageNames("java.lang,java.awt"); + assertEquals(util.getExcludedPackageNames().size(), 2); + try { + util.getExcludedPackageNames().clear(); + } catch (Exception ex) { + assertTrue(ex instanceof UnsupportedOperationException); + } finally { + assertEquals(util.getExcludedPackageNames().size(), 2); + } + } + + public void testGetExcludedPackageNamesAlternateConstructorPopulated() { + // Getter should return an immutable collection + OgnlUtil util = new OgnlUtil(new DefaultOgnlExpressionCacheFactory(), new DefaultOgnlBeanInfoCacheFactory, BeanInfo>()); + util.setExcludedPackageNames("java.lang,java.awt"); + assertEquals(util.getExcludedPackageNames().size(), 2); + try { + util.getExcludedPackageNames().clear(); + } catch (Exception ex) { + assertTrue(ex instanceof UnsupportedOperationException); + } finally { + assertEquals(util.getExcludedPackageNames().size(), 2); + } + } + public void testGetExcludedClasses() { // Getter should return an immutable collection OgnlUtil util = new OgnlUtil(); @@ -1689,6 +1732,34 @@ public class OgnlUtilTest extends XWorkTestCase { } } + public void testGetExcludedClassesAlternateConstructor() { + // Getter should return an immutable collection + OgnlUtil util = new OgnlUtil(null, null); + util.setExcludedClasses("java.lang.Runtime,java.lang.ProcessBuilder,java.net.URL"); + assertEquals(util.getExcludedClasses().size(), 3); + try { + util.getExcludedClasses().clear(); + } catch (Exception ex) { + assertTrue(ex instanceof UnsupportedOperationException); + } finally { + assertEquals(util.getExcludedClasses().size(), 3); + } + } + + public void testGetExcludedClassesAlternateConstructorPopulated() { + // Getter should return an immutable collection + OgnlUtil util = new OgnlUtil(new DefaultOgnlExpressionCacheFactory(), new DefaultOgnlBeanInfoCacheFactory, BeanInfo>()); + util.setExcludedClasses("java.lang.Runtime,java.lang.ProcessBuilder,java.net.URL"); + assertEquals(util.getExcludedClasses().size(), 3); + try { + util.getExcludedClasses().clear(); + } catch (Exception ex) { + assertTrue(ex instanceof UnsupportedOperationException); + } finally { + assertEquals(util.getExcludedClasses().size(), 3); + } + } + public void testGetExcludedPackageNamePatterns() { // Getter should return an immutable collection OgnlUtil util = new OgnlUtil(); @@ -1703,10 +1774,64 @@ public class OgnlUtilTest extends XWorkTestCase { } } + public void testGetExcludedPackageNamePatternsAlternateConstructor() { + // Getter should return an immutable collection + OgnlUtil util = new OgnlUtil(null, null); + util.setExcludedPackageNamePatterns("java.lang."); + assertEquals(util.getExcludedPackageNamePatterns().size(), 1); + try { + util.getExcludedPackageNamePatterns().clear(); + } catch (Exception ex) { + assertTrue(ex instanceof UnsupportedOperationException); + } finally { + assertEquals(util.getExcludedPackageNamePatterns().size(), 1); + } + } + + public void testGetExcludedPackageNamePatternsAlternateConstructorPopulated() { + // Getter should return an immutable collection + OgnlUtil util = new OgnlUtil(new DefaultOgnlExpressionCacheFactory(), new DefaultOgnlBeanInfoCacheFactory, BeanInfo>()); + util.setExcludedPackageNamePatterns("java.lang."); + assertEquals(util.getExcludedPackageNamePatterns().size(), 1); + try { + util.getExcludedPackageNamePatterns().clear(); + } catch (Exception ex) { + assertTrue(ex instanceof UnsupportedOperationException); + } finally { + assertEquals(util.getExcludedPackageNamePatterns().size(), 1); + } + } + + public void testOgnlUtilDefaultCacheClass() throws OgnlException { + OgnlDefaultCache defaultCache = new OgnlDefaultCache<>(2, 16, 0.75f); + assertEquals("Initial evictionLimit did not match initial value", 2, defaultCache.getEvictionLimit()); + defaultCache.setEvictionLimit(3); + assertEquals("Updated evictionLimit did not match updated value", 3, defaultCache.getEvictionLimit()); + String lookupResult = defaultCache.get(Integer.valueOf(0)); + assertNull("Lookup of empty cache returned non-null value ?", lookupResult); + defaultCache.put(Integer.valueOf(0), "Zero"); + lookupResult = defaultCache.get(Integer.valueOf(0)); + assertEquals("Retrieved value does not match put value ?", "Zero", lookupResult); + defaultCache.put(Integer.valueOf(1), "One"); + defaultCache.put(Integer.valueOf(2), "Two"); + assertEquals("Default cache not size evictionlimit after adding three values ?", defaultCache.getEvictionLimit(), defaultCache.size()); + lookupResult = defaultCache.get(Integer.valueOf(2)); + assertEquals("Retrieved value does not match put value ?", "Two", lookupResult); + defaultCache.put(Integer.valueOf(3), "Three"); + assertEquals("Default cache not size zero after an add that exceeded the evection limit ?", 0, defaultCache.size()); + lookupResult = defaultCache.get(Integer.valueOf(0)); + assertNull("Lookup of value 0 (should have been evicted with everything) returned non-null value ?", lookupResult); + lookupResult = defaultCache.get(Integer.valueOf(3)); + assertNull("Lookup of value 3 (should have been evicted with everything) returned non-null value ?", lookupResult); + defaultCache.putIfAbsent(Integer.valueOf(2), "Two"); + lookupResult = defaultCache.get(Integer.valueOf(2)); + assertEquals("Retrieved value does not match put value ?", "Two", lookupResult); + defaultCache.clear(); + assertEquals("Default cache not empty after clear ?", 0, defaultCache.size()); + } + public void testOgnlUtilLRUCacheClass() throws OgnlException { - OgnlUtil.LRUCache lruCache = ognlUtil.new LRUCache<>(2, 16, 0.75f); - Map backingMap = lruCache.backingMapReference(); - assertNotNull("Backing Map somehow null ?", backingMap); + OgnlLRUCache lruCache = new OgnlLRUCache<>(2, 16, 0.75f); 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()); @@ -1727,10 +1852,24 @@ public class OgnlUtilTest extends XWorkTestCase { 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()); + } + + /** + * Generate a new OgnlUtil instance (not configured by the {@link ContainerBuilder}) that can be used for + * basic tests, with its Expression and BeanInfo factories set to LRU mode. + * + * @return OgnlUtil instance with LRU enabled Expression and BeanInfo factories + */ + private OgnlUtil generateOgnlUtilInstanceWithDefaultLRUCacheFactories() { + final OgnlUtil result; + final DefaultOgnlCacheFactory expressionFactory = new DefaultOgnlExpressionCacheFactory(); + final DefaultOgnlCacheFactory beanInfoFactory = new DefaultOgnlBeanInfoCacheFactory, BeanInfo>(); + expressionFactory.setUseLRUCache("true"); + beanInfoFactory.setUseLRUCache("true"); + result = new OgnlUtil(expressionFactory, beanInfoFactory); + return result; } private void reloadTestContainerConfiguration(boolean devMode, boolean allowStaticMethod) {