Merge pull request #528 from JCgH4164838Gh792C124B5/localS2_26_OgnlUtilOptionalCache1

Potential expression cache enhancement for 2.6 series
This commit is contained in:
Lukasz Lenart
2022-05-16 07:38:59 +02:00
committed by GitHub
18 changed files with 1040 additions and 16 deletions
@@ -28,6 +28,10 @@ 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.BeanInfoCacheFactory;
import com.opensymphony.xwork2.ognl.DefaultOgnlBeanInfoCacheFactory;
import com.opensymphony.xwork2.ognl.DefaultOgnlExpressionCacheFactory;
import com.opensymphony.xwork2.ognl.ExpressionCacheFactory;
import com.opensymphony.xwork2.ognl.OgnlReflectionProvider;
import com.opensymphony.xwork2.ognl.OgnlUtil;
import com.opensymphony.xwork2.ognl.OgnlValueStackFactory;
@@ -79,30 +83,37 @@ public class DefaultConfiguration implements Configuration {
}
@Override
public PackageConfig getPackageConfig(String name) {
return packageContexts.get(name);
}
@Override
public List<UnknownHandlerConfig> getUnknownHandlerStack() {
return unknownHandlerStack;
}
@Override
public void setUnknownHandlerStack(List<UnknownHandlerConfig> unknownHandlerStack) {
this.unknownHandlerStack = unknownHandlerStack;
}
@Override
public Set<String> getPackageConfigNames() {
return packageContexts.keySet();
}
@Override
public Map<String, PackageConfig> getPackageConfigs() {
return packageContexts;
}
@Override
public Set<String> getLoadedFileNames() {
return loadedFileNames;
}
@Override
public RuntimeConfiguration getRuntimeConfiguration() {
return runtimeConfiguration;
}
@@ -110,10 +121,12 @@ public class DefaultConfiguration implements Configuration {
/**
* @return the container
*/
@Override
public Container getContainer() {
return container;
}
@Override
public void addPackageConfig(String name, PackageConfig packageContext) {
PackageConfig check = packageContexts.get(name);
if (check != null) {
@@ -131,6 +144,7 @@ public class DefaultConfiguration implements Configuration {
packageContexts.put(name, packageContext);
}
@Override
public PackageConfig removePackageConfig(String packageName) {
return packageContexts.remove(packageName);
}
@@ -138,11 +152,13 @@ public class DefaultConfiguration implements Configuration {
/**
* Allows the configuration to clean up any resources used
*/
@Override
public void destroy() {
packageContexts.clear();
loadedFileNames.clear();
}
@Override
public void rebuildRuntimeConfiguration() {
runtimeConfiguration = buildRuntimeConfiguration();
}
@@ -151,10 +167,12 @@ public class DefaultConfiguration implements Configuration {
* Calls the ConfigurationProviderFactory.getConfig() to tell it to reload the configuration and then calls
* buildRuntimeConfiguration().
*
* @param providers list of ContainerProvider
* @return list of package providers
*
* @throws ConfigurationException in case of any configuration errors
*/
@Override
public synchronized List<PackageProvider> reloadContainer(List<ContainerProvider> providers) throws ConfigurationException {
packageContexts.clear();
loadedFileNames.clear();
@@ -172,6 +190,7 @@ public class DefaultConfiguration implements Configuration {
props.setConstants(builder);
builder.factory(Configuration.class, new Factory<Configuration>() {
@Override
public Configuration create(Context context) throws Exception {
return DefaultConfiguration.this;
}
@@ -279,6 +298,8 @@ public class DefaultConfiguration implements Configuration {
builder.factory(ObjectTypeDeterminer.class, DefaultObjectTypeDeterminer.class, Scope.SINGLETON);
builder.factory(PropertyAccessor.class, CompoundRoot.class.getName(), CompoundRootAccessor.class, Scope.SINGLETON);
builder.factory(ExpressionCacheFactory.class, "defaultOgnlExpressionCacheFactory", DefaultOgnlExpressionCacheFactory.class, Scope.SINGLETON);
builder.factory(BeanInfoCacheFactory.class, "defaultOgnlBeanInfoCacheFactory", DefaultOgnlBeanInfoCacheFactory.class, Scope.SINGLETON);
builder.factory(OgnlUtil.class, Scope.SINGLETON);
builder.factory(ValueSubstitutor.class, EnvsValueSubstitutor.class, Scope.SINGLETON);
@@ -417,10 +438,10 @@ public class DefaultConfiguration implements Configuration {
private static class RuntimeConfigurationImpl implements RuntimeConfiguration {
private Map<String, Map<String, ActionConfig>> namespaceActionConfigs;
private Map<String, ActionConfigMatcher> namespaceActionConfigMatchers;
private NamespaceMatcher namespaceMatcher;
private Map<String, String> namespaceConfigs;
private final Map<String, Map<String, ActionConfig>> namespaceActionConfigs;
private final Map<String, ActionConfigMatcher> namespaceActionConfigMatchers;
private final NamespaceMatcher namespaceMatcher;
private final Map<String, String> namespaceConfigs;
public RuntimeConfigurationImpl(Map<String, Map<String, ActionConfig>> namespaceActionConfigs,
Map<String, String> namespaceConfigs,
@@ -448,6 +469,7 @@ public class DefaultConfiguration implements Configuration {
* @param namespace the namespace for the action or null for the empty namespace, ""
* @return the configuration information for action requested
*/
@Override
public ActionConfig getActionConfig(String namespace, String name) {
ActionConfig config = findActionConfigInNamespace(namespace, name);
@@ -503,6 +525,7 @@ public class DefaultConfiguration implements Configuration {
*
* @return a Map of namespace - > Map of ActionConfig objects, with the key being the action name
*/
@Override
public Map<String, Map<String, ActionConfig>> getActionConfigs() {
return namespaceActionConfigs;
}
@@ -26,6 +26,8 @@ import com.opensymphony.xwork2.StrutsTextProviderFactory;
import com.opensymphony.xwork2.TextProviderFactory;
import com.opensymphony.xwork2.factory.DefaultUnknownHandlerFactory;
import com.opensymphony.xwork2.factory.UnknownHandlerFactory;
import com.opensymphony.xwork2.ognl.BeanInfoCacheFactory;
import com.opensymphony.xwork2.ognl.ExpressionCacheFactory;
import com.opensymphony.xwork2.ognl.accessor.HttpParametersPropertyAccessor;
import com.opensymphony.xwork2.ognl.accessor.ParameterPropertyAccessor;
import com.opensymphony.xwork2.security.AcceptedPatternsChecker;
@@ -94,6 +96,8 @@ 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.util.StrutsLocalizedTextProvider;
import com.opensymphony.xwork2.util.OgnlTextParser;
import com.opensymphony.xwork2.util.PatternMatcher;
@@ -130,19 +134,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 {
@@ -208,6 +217,8 @@ public class StrutsDefaultConfigurationProvider implements ConfigurationProvider
.factory(TextProviderFactory.class, StrutsTextProviderFactory.class, Scope.SINGLETON)
.factory(LocaleProviderFactory.class, DefaultLocaleProviderFactory.class, Scope.SINGLETON)
.factory(ExpressionCacheFactory.class, "defaultOgnlExpressionCacheFactory", DefaultOgnlExpressionCacheFactory.class, Scope.SINGLETON)
.factory(BeanInfoCacheFactory.class, "defaultOgnlBeanInfoCacheFactory", DefaultOgnlBeanInfoCacheFactory.class, Scope.SINGLETON)
.factory(OgnlUtil.class, Scope.SINGLETON)
.factory(CollectionConverter.class, Scope.SINGLETON)
.factory(ArrayConverter.class, Scope.SINGLETON)
@@ -0,0 +1,23 @@
/*
* 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 proxy interface to be used with Struts DI mechanism
*/
public interface BeanInfoCacheFactory<Key, Value> extends OgnlCacheFactory <Key, Value> {
}
@@ -0,0 +1,44 @@
/*
* 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 <Key> The type for the cache key entries
* @param <Value> The type for the cache value entries
*/
public class DefaultOgnlBeanInfoCacheFactory<Key, Value> extends DefaultOgnlCacheFactory<Key, Value>
implements BeanInfoCacheFactory<Key, Value> {
@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);
}
}
@@ -0,0 +1,66 @@
/*
* 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 <Key> The type for the cache key entries
* @param <Value> The type for the cache value entries
*/
public class DefaultOgnlCacheFactory<Key, Value> implements OgnlCacheFactory<Key, Value> {
private final AtomicBoolean useLRUCache = new AtomicBoolean(false);
private final AtomicInteger cacheMaxSize = new AtomicInteger(25000);
@Override
public OgnlCache<Key, Value> buildOgnlCache() {
return buildOgnlCache(getCacheMaxSize(), 16, 0.75f, getUseLRUCache());
}
@Override
public OgnlCache<Key, Value> 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));
}
}
@@ -0,0 +1,44 @@
/*
* 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 <Key> The type for the cache key entries
* @param <Value> The type for the cache value entries
*/
public class DefaultOgnlExpressionCacheFactory<Key, Value> extends DefaultOgnlCacheFactory<Key, Value>
implements ExpressionCacheFactory<Key, Value> {
@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);
}
}
@@ -0,0 +1,23 @@
/*
* 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 proxy interface to be used with Struts DI mechanism
*/
public interface ExpressionCacheFactory<Key, Value> extends OgnlCacheFactory <Key, Value> {
}
@@ -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 <Key> The type for the cache key entries
* @param <Value> The type for the cache value entries
*/
public interface OgnlCache<Key, Value> {
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);
}
@@ -0,0 +1,30 @@
/*
* 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 <Key> The type for the cache key entries
* @param <Value> The type for the cache value entries
*/
interface OgnlCacheFactory<Key, Value> {
OgnlCache<Key, Value> buildOgnlCache();
OgnlCache<Key, Value> buildOgnlCache(int evictionLimit, int initialCapacity, float loadFactor, boolean lruCache);
int getCacheMaxSize();
boolean getUseLRUCache();
}
@@ -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 <Key> The type for the cache key entries
* @param <Value> The type for the cache value entries
*/
public class OgnlDefaultCache<Key, Value> implements OgnlCache<Key, Value> {
private final ConcurrentHashMap<Key, Value> 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();
}
}
}
@@ -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 <Key> The type for the cache key entries
* @param <Value> The type for the cache value entries
*/
public class OgnlLRUCache<Key, Value> implements OgnlCache<Key, Value> {
private final Map<Key, Value> 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<Key, Value>(initialCapacity, loadFactor, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Key,Value> 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);
}
}
@@ -38,8 +38,6 @@ 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.regex.Pattern;
@@ -57,8 +55,8 @@ 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 ConcurrentMap<Class<?>, BeanInfo> beanInfoCache = new ConcurrentHashMap<>();
private final OgnlCache<String, Object> expressionCache;
private final OgnlCache<Class<?>, BeanInfo> beanInfoCache;
private TypeConverter defaultConverter;
private boolean devMode;
@@ -77,7 +75,31 @@ public class OgnlUtil {
private boolean allowStaticFieldAccess = true;
private boolean disallowProxyMemberAccess;
/**
* Construct a new OgnlUtil instance for use with the framework
*
* @deprecated It is recommended to utilize the {@link OgnlUtil#OgnlUtil(com.opensymphony.xwork2.ognl.ExpressionCacheFactory, com.opensymphony.xwork2.ognl.BeanInfoCacheFactory) method instead.
*/
@Deprecated
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.
*
* NOTE: Although the extension points are defined for the optional cache factories, developer-defined overrides do
* do not appear to function at this time (it always appears to instantiate the default factories).
* Construction injectors do not allow the optional flag, so the definitions must be defined.
*
* @param ognlExpressionCacheFactory factory for Expression cache instance. If null, it uses a default
* @param ognlBeanInfoCacheFactory factory for BeanInfo cache instance. If null, it uses a default
*/
public OgnlUtil(
@Inject(value = StrutsConstants.STRUTS_OGNL_EXPRESSION_CACHE_FACTORY, required = false) ExpressionCacheFactory<String, Object> ognlExpressionCacheFactory,
@Inject(value = StrutsConstants.STRUTS_OGNL_BEANINFO_CACHE_FACTORY, required = false) BeanInfoCacheFactory<Class<?>, BeanInfo> ognlBeanInfoCacheFactory
) {
excludedClasses = Collections.unmodifiableSet(new HashSet<>());
excludedPackageNamePatterns = Collections.unmodifiableSet(new HashSet<>());
excludedPackageNames = Collections.unmodifiableSet(new HashSet<>());
@@ -85,6 +107,12 @@ public class OgnlUtil {
devModeExcludedClasses = Collections.unmodifiableSet(new HashSet<>());
devModeExcludedPackageNamePatterns = Collections.unmodifiableSet(new HashSet<>());
devModeExcludedPackageNames = Collections.unmodifiableSet(new HashSet<>());
OgnlCacheFactory<String, Object> ognlExpressionCacheFactory1 = (ognlExpressionCacheFactory != null ? ognlExpressionCacheFactory : new DefaultOgnlExpressionCacheFactory<>());
OgnlCacheFactory<Class<?>, BeanInfo> ognlBeanInfoCacheFactory1 = (ognlBeanInfoCacheFactory != null ? ognlBeanInfoCacheFactory : new DefaultOgnlBeanInfoCacheFactory<>());
this.expressionCache = ognlExpressionCacheFactory1.buildOgnlCache();
this.beanInfoCache = ognlBeanInfoCacheFactory1.buildOgnlCache();
}
@Inject
@@ -102,6 +130,16 @@ public class OgnlUtil {
enableExpressionCache = BooleanUtils.toBoolean(cache);
}
@Inject(value = StrutsConstants.STRUTS_OGNL_EXPRESSION_CACHE_MAXSIZE, required = false)
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) {
beanInfoCache.setEvictionLimit(Integer.parseInt(maxSize));
}
@Inject(value = StrutsConstants.STRUTS_OGNL_ENABLE_EVAL_EXPRESSION, required = false)
protected void setEnableEvalExpression(String evalExpression) {
this.enableEvalExpression = BooleanUtils.toBoolean(evalExpression);
@@ -267,7 +305,7 @@ public class OgnlUtil {
* @since 2.5.21
*/
public void clearExpressionCache() {
expressions.clear();
expressionCache.clear();
}
/**
@@ -278,7 +316,7 @@ public class OgnlUtil {
* @since 2.5.21
*/
public int expressionCacheSize() {
return expressions.size();
return expressionCache.size();
}
/**
@@ -526,11 +564,11 @@ 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);
tree = expressionCache.get(expression);
if (tree == null) {
tree = Ognl.parseExpression(expression);
checkEnableEvalExpression(tree, context);
expressions.putIfAbsent(expression, tree);
expressionCache.putIfAbsent(expression, tree);
}
} else {
tree = Ognl.parseExpression(expression);
@@ -543,11 +581,11 @@ 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);
tree = expressionCache.get(expression);
if (tree == null) {
tree = Ognl.parseExpression(expression);
checkSimpleMethod(tree, context);
expressions.putIfAbsent(expression, tree);
expressionCache.putIfAbsent(expression, tree);
}
} else {
tree = Ognl.parseExpression(expression);
@@ -250,6 +250,47 @@ 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.
*
* @since 2.6
*/
public static final String STRUTS_OGNL_EXPRESSION_CACHE_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.
*
* @since 2.6
*/
public static final String STRUTS_OGNL_BEANINFO_CACHE_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
* 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
@@ -275,6 +316,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
@@ -23,6 +23,8 @@ import com.opensymphony.xwork2.LocaleProviderFactory;
import com.opensymphony.xwork2.LocalizedTextProvider;
import com.opensymphony.xwork2.TextProviderFactory;
import com.opensymphony.xwork2.factory.UnknownHandlerFactory;
import com.opensymphony.xwork2.ognl.BeanInfoCacheFactory;
import com.opensymphony.xwork2.ognl.ExpressionCacheFactory;
import com.opensymphony.xwork2.security.AcceptedPatternsChecker;
import com.opensymphony.xwork2.security.ExcludedPatternsChecker;
import com.opensymphony.xwork2.FileManager;
@@ -366,6 +368,7 @@ import org.apache.struts2.views.util.UrlHelper;
*/
public class StrutsBeanSelectionProvider extends AbstractBeanSelectionProvider {
@Override
public void register(ContainerBuilder builder, LocatableProperties props) {
alias(ObjectFactory.class, StrutsConstants.STRUTS_OBJECTFACTORY, builder, props);
alias(ActionFactory.class, StrutsConstants.STRUTS_OBJECTFACTORY_ACTIONFACTORY, builder, props);
@@ -423,6 +426,9 @@ public class StrutsBeanSelectionProvider extends AbstractBeanSelectionProvider {
alias(DateFormatter.class, StrutsConstants.STRUTS_DATE_FORMATTER, builder, props, Scope.SINGLETON);
alias(ExpressionCacheFactory.class, StrutsConstants.STRUTS_OGNL_EXPRESSION_CACHE_FACTORY, builder, props, Scope.SINGLETON);
alias(BeanInfoCacheFactory.class, StrutsConstants.STRUTS_OGNL_BEANINFO_CACHE_FACTORY, builder, props, Scope.SINGLETON);
switchDevMode(props);
}
@@ -143,6 +143,8 @@ public class ConstantConfig {
private Boolean disallowProxyMemberAccess;
private Integer ognlAutoGrowthCollectionLimit;
private String staticContentPath;
private BeanConfig expressionCacheFactory;
private BeanConfig beaninfoCacheFactory;
protected String beanConfToString(BeanConfig beanConf) {
return beanConf == null ? null : beanConf.getName();
@@ -272,6 +274,8 @@ public class ConstantConfig {
map.put(StrutsConstants.STRUTS_DISALLOW_PROXY_MEMBER_ACCESS, Objects.toString(disallowProxyMemberAccess, null));
map.put(StrutsConstants.STRUTS_OGNL_AUTO_GROWTH_COLLECTION_LIMIT, Objects.toString(ognlAutoGrowthCollectionLimit, null));
map.put(StrutsConstants.STRUTS_UI_STATIC_CONTENT_PATH, Objects.toString(staticContentPath, StaticContentLoader.DEFAULT_STATIC_CONTENT_PATH));
map.put(StrutsConstants.STRUTS_OGNL_EXPRESSION_CACHE_FACTORY, beanConfToString(expressionCacheFactory));
map.put(StrutsConstants.STRUTS_OGNL_BEANINFO_CACHE_FACTORY, beanConfToString(beaninfoCacheFactory));
return map;
}
@@ -1331,4 +1335,28 @@ public class ConstantConfig {
public void setStaticContentPath(String staticContentPath) {
this.staticContentPath = StaticContentLoader.Validator.validateStaticContentPath(staticContentPath);
}
public BeanConfig getExpressionCacheFactory() {
return expressionCacheFactory;
}
public void setExpressionCacheFactory(BeanConfig expressionCacheFactory) {
this.expressionCacheFactory = expressionCacheFactory;
}
public void setExpressionCacheFactory(Class<?> clazz) {
this.expressionCacheFactory = new BeanConfig(clazz, clazz.getName());
}
public BeanConfig getBeaninfoCacheFactory() {
return beaninfoCacheFactory;
}
public void setBeaninfoCacheFactory(BeanConfig beaninfoCacheFactory) {
this.beaninfoCacheFactory = beaninfoCacheFactory;
}
public void setBeaninfoCacheFactory(Class<?> clazz) {
this.beaninfoCacheFactory = new BeanConfig(clazz, clazz.getName());
}
}
@@ -229,6 +229,37 @@ struts.ognl.logMissingProperties=false
### if the application generates a lot of different expressions
struts.ognl.enableExpressionCache=true
### Specify the OGNL expression cache factory and BeanInfo cache factory to use.
### Currently, the default implementations are used, but can be replaced with custom ones if desired.
struts.ognl.expressionCacheFactory=defaultOgnlExpressionCacheFactory
struts.ognl.beanInfoCacheFactory=defaultOgnlBeanInfoCacheFactory
### 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
@@ -231,6 +231,9 @@
<bean type="org.apache.struts2.components.date.DateFormatter" name="simpleDateFormatter" class="org.apache.struts2.components.date.SimpleDateFormatAdapter" scope="singleton"/>
<bean type="org.apache.struts2.components.date.DateFormatter" name="dateTimeFormatter" class="org.apache.struts2.components.date.DateTimeFormatterAdapter" scope="singleton"/>
<bean type="com.opensymphony.xwork2.ognl.OgnlCacheFactory" name="ognlExpressionCacheFactory" class="com.opensymphony.xwork2.ognl.DefaultOgnlExpressionCacheFactory" scope="singleton"/>
<bean type="com.opensymphony.xwork2.ognl.OgnlCacheFactory" name="ognlBeanInfoCacheFactory" class="com.opensymphony.xwork2.ognl.DefaultOgnlBeanInfoCacheFactory" scope="singleton"/>
<package name="struts-default" abstract="true">
<result-types>
<result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/>
@@ -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;
@@ -129,6 +130,61 @@ public class OgnlUtilTest extends XWorkTestCase {
assertSame(expr0, expr2);
}
public void testCacheEnabledMaxSize() throws OgnlException {
ognlUtil.setEnableExpressionCache("true");
ognlUtil.setExpressionCacheMaxSize("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);
}
public void testLRUCacheEnabled() throws OgnlException {
// Force usage of LRU cache factories for the OgnlUtil instance
this.ognlUtil = generateOgnlUtilInstanceWithDefaultLRUCacheFactories();
ognlUtil.setEnableExpressionCache("true");
Object expr0 = ognlUtil.compile("test");
Object expr2 = ognlUtil.compile("test");
assertSame(expr0, expr2);
}
public void testLRUCacheEnabledMaxSize() throws OgnlException {
// Force usage of LRU cache factories for the OgnlUtil instance
this.ognlUtil = generateOgnlUtilInstanceWithDefaultLRUCacheFactories();
ognlUtil.setEnableExpressionCache("true");
ognlUtil.setExpressionCacheMaxSize("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);
}
public void testExpressionIsCachedIrrespectiveOfItsExecutionStatus() throws OgnlException {
Foo foo = new Foo();
OgnlContext context = (OgnlContext) ognlUtil.createDefaultContext(foo);
@@ -149,6 +205,30 @@ 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");
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);
}
}
public void testMethodExpressionIsCachedIrrespectiveOfItsExecutionStatus() throws Exception {
Foo foo = new Foo();
OgnlContext context = (OgnlContext) ognlUtil.createDefaultContext(foo);
@@ -193,6 +273,32 @@ public class OgnlUtilTest extends XWorkTestCase {
assertTrue("Expression cache empty after usage ?", ognlUtil.expressionCacheSize() > 0);
}
public void testClearExpressionLRUCache() throws OgnlException {
// Force usage of LRU cache factories for the OgnlUtil instance
this.ognlUtil = generateOgnlUtilInstanceWithDefaultLRUCacheFactories();
ognlUtil.setEnableExpressionCache("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);
}
public void testClearBeanInfoCache() throws IntrospectionException {
final TestBean1 testBean1 = new TestBean1();
final TestBean2 testBean2 = new TestBean2();
@@ -242,6 +348,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 {
// 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.
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 testBeanInfoLRUCacheLimits() throws IntrospectionException {
// 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();
// 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.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();
@@ -1134,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<String, Object>(), new DefaultOgnlBeanInfoCacheFactory<Class<?>, BeanInfo>());
internalTestInitialEmptyOgnlUtilExclusions(basicOgnlUtil);
internalTestOgnlUtilExclusionsImmutable(basicOgnlUtil);
}
public void testOgnlUtilExcludedAdditivity() {
Set<Class<?>> excludedClasses;
Set<Pattern> excludedPackageNamePatterns;
@@ -1215,7 +1408,7 @@ public class OgnlUtilTest extends XWorkTestCase {
Object accessedValue;
try {
reloadTestContainerConfiguration(true, true); // Test with allowStaticFieldAccess true
reloadTestContainerConfiguration(true); // Test with allowStaticFieldAccess true
context = ognlUtil.createDefaultContext(null);
} catch (Exception ex) {
fail("unable to reload test configuration? Exception: " + ex);
@@ -1270,7 +1463,7 @@ public class OgnlUtilTest extends XWorkTestCase {
}
try {
reloadTestContainerConfiguration(true, false); // Re-test with allowStaticFieldAccess false
reloadTestContainerConfiguration(false); // Re-test with allowStaticFieldAccess false
context = ognlUtil.createDefaultContext(null);
} catch (Exception ex) {
fail("unable to reload test configuration? Exception: " + ex);
@@ -1497,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<String, Object>(), new DefaultOgnlBeanInfoCacheFactory<Class<?>, 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();
@@ -1511,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<String, Object>(), new DefaultOgnlBeanInfoCacheFactory<Class<?>, 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();
@@ -1525,6 +1774,132 @@ 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<String, Object>(), new DefaultOgnlBeanInfoCacheFactory<Class<?>, 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<Integer, String> 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 {
OgnlLRUCache<Integer, String> 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());
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);
lruCache.clear();
assertEquals("LRU cache not empty after clear ?", 0, lruCache.size());
}
/**
* Unit test primarily for code coverage
*/
public void testOgnlDefaultCacheFactoryCoverage() {
OgnlCache<String, Object> ognlCache;
DefaultOgnlCacheFactory defaultOgnlCacheFactory = new DefaultOgnlCacheFactory<String, Object>();
// Normal cache
defaultOgnlCacheFactory.setCacheMaxSize("12");
defaultOgnlCacheFactory.setUseLRUCache("false");
ognlCache = defaultOgnlCacheFactory.buildOgnlCache();
assertNotNull("No param build method result null ?", ognlCache);
assertEquals("Eviction limit for cache mismatches limit for factory ?", 12, ognlCache.getEvictionLimit() );
ognlCache = defaultOgnlCacheFactory.buildOgnlCache(6, 6, 0.75f, false);
assertNotNull("No param build method result null ?", ognlCache);
assertEquals("Eviction limit for cache mismatches limit for factory ?", 6, ognlCache.getEvictionLimit() );
// LRU cache
defaultOgnlCacheFactory.setCacheMaxSize("30");
defaultOgnlCacheFactory.setUseLRUCache("true");
ognlCache = defaultOgnlCacheFactory.buildOgnlCache();
assertNotNull("No param build method result null ?", ognlCache);
assertEquals("Eviction limit for cache mismatches limit for factory ?", 30, ognlCache.getEvictionLimit() );
ognlCache = defaultOgnlCacheFactory.buildOgnlCache(15, 15, 0.75f, false);
assertNotNull("No param build method result null ?", ognlCache);
assertEquals("Eviction limit for cache mismatches limit for factory ?", 15, ognlCache.getEvictionLimit() );
}
/**
* 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 DefaultOgnlExpressionCacheFactory<String, Object> expressionFactory = new DefaultOgnlExpressionCacheFactory<>();
final DefaultOgnlBeanInfoCacheFactory<Class<?>, BeanInfo> beanInfoFactory = new DefaultOgnlBeanInfoCacheFactory<>();
expressionFactory.setUseLRUCache("true");
expressionFactory.setCacheMaxSize("25");
beanInfoFactory.setUseLRUCache("true");
beanInfoFactory.setCacheMaxSize("25");
result = new OgnlUtil(expressionFactory, beanInfoFactory);
return result;
}
private void reloadTestContainerConfiguration(boolean devMode, boolean allowStaticFieldAccess) {
loadConfigurationProviders(new StubConfigurationProvider() {
@Override