Merge pull request #1236 from apache/WW-5534-proxyutil

WW-5534 Simplify ProxyUtil, add OgnlCache#computeIfAbsent
This commit is contained in:
Kusal Kithul-Godage
2025-03-05 21:48:25 +11:00
committed by GitHub
10 changed files with 175 additions and 175 deletions
+7 -1
View File
@@ -187,13 +187,19 @@
<artifactId>commons-text</artifactId>
</dependency>
<!-- Optional used in org.apache.struts2.util.ProxyUtil to detect if object is HibernateProxy -->
<dependency>
<!-- Optional used in org.apache.struts2.util.ProxyUtil to detect if object is HibernateProxy -->
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.6.15.Final</version>
<optional>true</optional>
</dependency>
<dependency>
<!-- Optional used in org.apache.struts2.util.ProxyUtil to detect if object is Spring proxy -->
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
@@ -20,11 +20,11 @@ package org.apache.struts2.interceptor;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.result.ActionChainResult;
import org.apache.struts2.ActionInvocation;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.Unchainable;
import org.apache.struts2.inject.Inject;
import org.apache.struts2.result.ActionChainResult;
import org.apache.struts2.result.Result;
import org.apache.struts2.util.CompoundRoot;
import org.apache.struts2.util.ProxyUtil;
@@ -167,17 +167,18 @@ public class ChainingInterceptor extends AbstractInterceptor {
}
private void copyStack(ActionInvocation invocation, CompoundRoot root) {
List list = prepareList(root);
List<Object> list = prepareList(root);
Map<String, Object> ctxMap = invocation.getInvocationContext().getContextMap();
for (Object object : list) {
if (shouldCopy(object)) {
Object action = invocation.getAction();
Class<?> editable = null;
if(ProxyUtil.isProxy(action)) {
editable = ProxyUtil.ultimateTargetClass(action);
}
reflectionProvider.copy(object, action, ctxMap, prepareExcludes(), includes, editable);
if (!shouldCopy(object)) {
continue;
}
Object action = invocation.getAction();
Class<?> editable = null;
if (ProxyUtil.isProxy(action)) {
editable = ProxyUtil.ultimateTargetClass(action);
}
reflectionProvider.copy(object, action, ctxMap, prepareExcludes(), includes, editable);
}
}
@@ -204,9 +205,8 @@ public class ChainingInterceptor extends AbstractInterceptor {
return o != null && !(o instanceof Unchainable);
}
@SuppressWarnings("unchecked")
private List prepareList(CompoundRoot root) {
List list = new ArrayList(root);
private List<Object> prepareList(CompoundRoot root) {
var list = new ArrayList<>(root);
list.remove(0);
Collections.reverse(list);
return list;
@@ -15,21 +15,37 @@
*/
package org.apache.struts2.ognl;
import java.util.function.Function;
import static java.util.Objects.requireNonNull;
/**
* 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.
* A basic cache interface for use with OGNL processing (such as Expression, BeanInfo). Implementation must be
* thread-safe. 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
* @param <K> The type for the cache key entries
* @param <V> The type for the cache value entries
*/
public interface OgnlCache<Key, Value> {
public interface OgnlCache<K, V> {
Value get(Key key);
V get(K key);
void put(Key key, Value value);
/**
* @since 7.1
*/
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
requireNonNull(mappingFunction);
if (get(key) == null) {
putIfAbsent(key, mappingFunction.apply(key));
}
return get(key);
}
void putIfAbsent(Key key, Value value);
void put(K key, V value);
void putIfAbsent(K key, V value);
int size();
@@ -18,6 +18,8 @@ package org.apache.struts2.ognl;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.function.Function;
/**
* <p>This OGNL Cache implementation is backed by {@link Caffeine} which uses the Window TinyLfu algorithm.</p>
*
@@ -46,6 +48,11 @@ public class OgnlCaffeineCache<K, V> implements OgnlCache<K, V> {
return cache.getIfPresent(key);
}
@Override
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
return cache.asMap().computeIfAbsent(key, mappingFunction);
}
@Override
public void put(K key, V value) {
cache.put(key, value);
@@ -17,6 +17,7 @@ package org.apache.struts2.ognl;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
/**
* <p>Basic OGNL cache implementation.</p>
@@ -45,16 +46,21 @@ public class OgnlDefaultCache<K, V> implements OgnlCache<K, V> {
return ognlCache.get(key);
}
@Override
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
return ognlCache.computeIfAbsent(key, mappingFunction);
}
@Override
public void put(K key, V value) {
ognlCache.put(key, value);
this.clearIfEvictionLimitExceeded();
clearIfEvictionLimitExceeded();
}
@Override
public void putIfAbsent(K key, V value) {
ognlCache.putIfAbsent(key, value);
this.clearIfEvictionLimitExceeded();
clearIfEvictionLimitExceeded();
}
@Override
@@ -69,12 +75,12 @@ public class OgnlDefaultCache<K, V> implements OgnlCache<K, V> {
@Override
public int getEvictionLimit() {
return this.cacheEvictionLimit.get();
return cacheEvictionLimit.get();
}
@Override
public void setEvictionLimit(int cacheEvictionLimit) {
this.cacheEvictionLimit.set(cacheEvictionLimit);
public void setEvictionLimit(int newCacheEvictionLimit) {
cacheEvictionLimit.set(newCacheEvictionLimit);
}
/**
@@ -19,6 +19,7 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
/**
* <p>A basic OGNL LRU cache implementation.</p>
@@ -54,6 +55,11 @@ public class OgnlLRUCache<K, V> implements OgnlCache<K, V> {
return ognlLRUCache.get(key);
}
@Override
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
return ognlLRUCache.computeIfAbsent(key, mappingFunction);
}
@Override
public void put(K key, V value) {
ognlLRUCache.put(key, value);
@@ -18,12 +18,6 @@
*/
package org.apache.struts2.ognl;
import org.apache.struts2.conversion.impl.XWorkConverter;
import org.apache.struts2.inject.Container;
import org.apache.struts2.inject.Inject;
import org.apache.struts2.ognl.accessor.RootAccessor;
import org.apache.struts2.util.CompoundRoot;
import org.apache.struts2.util.reflection.ReflectionException;
import ognl.ClassResolver;
import ognl.Ognl;
import ognl.OgnlContext;
@@ -35,7 +29,12 @@ import org.apache.commons.lang3.BooleanUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.ognl.OgnlGuard;
import org.apache.struts2.conversion.impl.XWorkConverter;
import org.apache.struts2.inject.Container;
import org.apache.struts2.inject.Inject;
import org.apache.struts2.ognl.accessor.RootAccessor;
import org.apache.struts2.util.CompoundRoot;
import org.apache.struts2.util.reflection.ReflectionException;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
@@ -676,13 +675,19 @@ public class OgnlUtil {
* @throws IntrospectionException is thrown if an exception occurs during introspection.
*/
public BeanInfo getBeanInfo(Class<?> clazz) throws IntrospectionException {
synchronized (beanInfoCache) {
BeanInfo beanInfo = beanInfoCache.get(clazz);
if (beanInfo == null) {
beanInfo = Introspector.getBeanInfo(clazz, Object.class);
beanInfoCache.putIfAbsent(clazz, beanInfo);
try {
return beanInfoCache.computeIfAbsent(clazz, k -> {
try {
return Introspector.getBeanInfo(k, Object.class);
} catch (IntrospectionException e) {
throw new IllegalArgumentException(e);
}
});
} catch (IllegalArgumentException e) {
if (e.getCause() instanceof IntrospectionException innerEx) {
throw innerEx;
}
return beanInfo;
throw e;
}
}
@@ -26,15 +26,19 @@ import org.apache.struts2.ognl.OgnlCache;
import org.apache.struts2.ognl.OgnlCacheFactory;
import org.hibernate.Hibernate;
import org.hibernate.proxy.HibernateProxy;
import org.springframework.aop.SpringProxy;
import org.springframework.aop.TargetClassAware;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import static java.lang.reflect.Modifier.isPublic;
import static java.lang.reflect.Modifier.isStatic;
/**
* <code>ProxyUtil</code>
@@ -44,17 +48,14 @@ import static java.lang.reflect.Modifier.isPublic;
*
*/
public class ProxyUtil {
private static final String SPRING_ADVISED_CLASS_NAME = "org.springframework.aop.framework.Advised";
private static final String SPRING_SPRINGPROXY_CLASS_NAME = "org.springframework.aop.SpringProxy";
private static final String SPRING_SINGLETONTARGETSOURCE_CLASS_NAME = "org.springframework.aop.target.SingletonTargetSource";
private static final String SPRING_TARGETCLASSAWARE_CLASS_NAME = "org.springframework.aop.TargetClassAware";
private static final String HIBERNATE_HIBERNATEPROXY_CLASS_NAME = "org.hibernate.proxy.HibernateProxy";
private static final int CACHE_MAX_SIZE = 10000;
private static final int CACHE_INITIAL_CAPACITY = 256;
private static final OgnlCache<Class<?>, Boolean> isProxyCache = new DefaultOgnlCacheFactory<Class<?>, Boolean>(
CACHE_MAX_SIZE, OgnlCacheFactory.CacheType.WTLFU, CACHE_INITIAL_CAPACITY).buildOgnlCache();
private static final OgnlCache<Member, Boolean> isProxyMemberCache = new DefaultOgnlCacheFactory<Member, Boolean>(
CACHE_MAX_SIZE, OgnlCacheFactory.CacheType.WTLFU, CACHE_INITIAL_CAPACITY).buildOgnlCache();
private static final OgnlCache<Object, Class<?>> targetClassCache = new DefaultOgnlCacheFactory<Object, Class<?>>(
CACHE_MAX_SIZE, OgnlCacheFactory.CacheType.WTLFU, CACHE_INITIAL_CAPACITY).buildOgnlCache();
/**
* Determine the ultimate target class of the given instance, traversing
@@ -65,15 +66,18 @@ public class ProxyUtil {
* object as fallback; never {@code null})
*/
public static Class<?> ultimateTargetClass(Object candidate) {
Class<?> result = null;
if (isSpringAopProxy(candidate))
result = springUltimateTargetClass(candidate);
if (result == null) {
result = candidate.getClass();
}
return result;
return targetClassCache.computeIfAbsent(candidate, k -> {
Class<?> result = null;
if (isSpringAopProxy(k)) {
result = springUltimateTargetClass(k);
} else if (isHibernateProxy(k)) {
result = getHibernateProxyTarget(k).getClass();
}
if (result == null) {
result = k.getClass();
}
return result;
});
}
/**
@@ -82,16 +86,8 @@ public class ProxyUtil {
*/
public static boolean isProxy(Object object) {
if (object == null) return false;
Class<?> clazz = object.getClass();
Boolean flag = isProxyCache.get(clazz);
if (flag != null) {
return flag;
}
boolean isProxy = isSpringAopProxy(object) || isHibernateProxy(object);
isProxyCache.put(clazz, isProxy);
return isProxy;
return isProxyCache.computeIfAbsent(object.getClass(),
k -> isSpringAopProxy(object) || isHibernateProxy(object));
}
/**
@@ -100,19 +96,11 @@ public class ProxyUtil {
* @param object the object to check
*/
public static boolean isProxyMember(Member member, Object object) {
if (!Modifier.isStatic(member.getModifiers()) && !isProxy(object) && !isHibernateProxy(object)) {
if (!isStatic(member.getModifiers()) && !isProxy(object)) {
return false;
}
Boolean flag = isProxyMemberCache.get(member);
if (flag != null) {
return flag;
}
boolean isProxyMember = isSpringProxyMember(member) || isHibernateProxyMember(member);
isProxyMemberCache.put(member, isProxyMember);
return isProxyMember;
return isProxyMemberCache.computeIfAbsent(member,
k -> isSpringProxyMember(member) || isHibernateProxyMember(member));
}
/**
@@ -123,7 +111,7 @@ public class ProxyUtil {
public static boolean isHibernateProxy(Object object) {
try {
return object != null && HibernateProxy.class.isAssignableFrom(object.getClass());
} catch (NoClassDefFoundError ignored) {
} catch (LinkageError ignored) {
return false;
}
}
@@ -135,12 +123,10 @@ public class ProxyUtil {
*/
public static boolean isHibernateProxyMember(Member member) {
try {
Class<?> clazz = ClassLoaderUtil.loadClass(HIBERNATE_HIBERNATEPROXY_CLASS_NAME, ProxyUtil.class);
return hasMember(clazz, member);
} catch (ClassNotFoundException ignored) {
return hasMember(HibernateProxy.class, member);
} catch (LinkageError ignored) {
return false;
}
return false;
}
/**
@@ -152,20 +138,11 @@ public class ProxyUtil {
* object as fallback; never {@code null})
*/
private static Class<?> springUltimateTargetClass(Object candidate) {
Object current = candidate;
Class<?> result = null;
while (null != current && implementsInterface(current.getClass(), SPRING_TARGETCLASSAWARE_CLASS_NAME)) {
try {
result = (Class<?>) MethodUtils.invokeMethod(current, "getTargetClass");
} catch (Throwable ignored) {
}
current = getSingletonTarget(current);
try {
return AopProxyUtils.ultimateTargetClass(candidate);
} catch (LinkageError ignored) {
return candidate.getClass();
}
if (result == null) {
Class<?> clazz = candidate.getClass();
result = (isCglibProxyClass(clazz) ? clazz.getSuperclass() : candidate.getClass());
}
return result;
}
/**
@@ -173,9 +150,11 @@ public class ProxyUtil {
* @param object the object to check
*/
private static boolean isSpringAopProxy(Object object) {
Class<?> clazz = object.getClass();
return (implementsInterface(clazz, SPRING_SPRINGPROXY_CLASS_NAME) && (Proxy.isProxyClass(clazz)
|| isCglibProxyClass(clazz)));
try {
return AopUtils.isAopProxy(object);
} catch (LinkageError ignored) {
return false;
}
}
/**
@@ -184,79 +163,32 @@ public class ProxyUtil {
*/
private static boolean isSpringProxyMember(Member member) {
try {
Class<?> clazz = ClassLoaderUtil.loadClass(SPRING_ADVISED_CLASS_NAME, ProxyUtil.class);
if (hasMember(clazz, member))
if (hasMember(Advised.class, member))
return true;
clazz = ClassLoaderUtil.loadClass(SPRING_TARGETCLASSAWARE_CLASS_NAME, ProxyUtil.class);
if (hasMember(clazz, member))
if (hasMember(TargetClassAware.class, member))
return true;
clazz = ClassLoaderUtil.loadClass(SPRING_SPRINGPROXY_CLASS_NAME, ProxyUtil.class);
if (hasMember(clazz, member))
if (hasMember(SpringProxy.class, member))
return true;
} catch (ClassNotFoundException ignored) {
} catch (LinkageError ignored) {
}
return false;
}
/**
* Obtain the singleton target object behind the given spring proxy, if any.
* @param candidate the (potential) spring proxy to check
* @return the singleton target object, or {@code null} in any other case
* (not a spring proxy, not an existing singleton target)
*/
private static Object getSingletonTarget(Object candidate) {
try {
if (implementsInterface(candidate.getClass(), SPRING_ADVISED_CLASS_NAME)) {
Object targetSource = MethodUtils.invokeMethod(candidate, "getTargetSource");
if (implementsInterface(targetSource.getClass(), SPRING_SINGLETONTARGETSOURCE_CLASS_NAME)) {
return MethodUtils.invokeMethod(targetSource, "getTarget");
}
}
} catch (Throwable ignored) {
}
return null;
}
/**
* Check whether the specified class is a CGLIB-generated class.
* @param clazz the class to check
*/
private static boolean isCglibProxyClass(Class<?> clazz) {
return (clazz != null && clazz.getName().contains("$$"));
}
/**
* Check whether the given class implements an interface with a given class name.
* @param clazz the class to check
* @param ifaceClassName the interface class name to check
*/
private static boolean implementsInterface(Class<?> clazz, String ifaceClassName) {
try {
Class<?> ifaceClass = ClassLoaderUtil.loadClass(ifaceClassName, ProxyUtil.class);
return ifaceClass.isAssignableFrom(clazz);
} catch (ClassNotFoundException e) {
return false;
}
}
/**
* Check whether the given class has a given member.
* @param clazz the class to check
* @param member the member to check
*/
private static boolean hasMember(Class<?> clazz, Member member) {
if (member instanceof Method) {
return null != MethodUtils.getMatchingMethod(clazz, member.getName(), ((Method) member).getParameterTypes());
if (member instanceof Method method) {
return null != MethodUtils.getMatchingMethod(clazz, member.getName(), method.getParameterTypes());
}
if (member instanceof Field) {
return null != FieldUtils.getField(clazz, member.getName(), true);
}
if (member instanceof Constructor) {
return null != ConstructorUtils.getMatchingAccessibleConstructor(clazz, ((Constructor) member).getParameterTypes());
if (member instanceof Constructor<?> constructor) {
return null != ConstructorUtils.getMatchingAccessibleConstructor(clazz, constructor.getParameterTypes());
}
return false;
}
@@ -266,26 +198,34 @@ public class ProxyUtil {
public static Object getHibernateProxyTarget(Object object) {
try {
return Hibernate.unproxy(object);
} catch (NoClassDefFoundError ignored) {
} catch (LinkageError ignored) {
return object;
}
}
/**
* @deprecated since 7.1, use {@link #resolveTargetMember(Member, Class)} instead.
*/
@Deprecated
public static Member resolveTargetMember(Member proxyMember, Object target) {
return resolveTargetMember(proxyMember, target.getClass());
}
/**
* @return matching member on target object if one exists, otherwise the same member
*/
public static Member resolveTargetMember(Member proxyMember, Object target) {
public static Member resolveTargetMember(Member proxyMember, Class<?> targetClass) {
int mod = proxyMember.getModifiers();
if (proxyMember instanceof Method) {
if (isPublic(mod)) {
return MethodUtils.getMatchingAccessibleMethod(target.getClass(), proxyMember.getName(), ((Method) proxyMember).getParameterTypes());
return MethodUtils.getMatchingAccessibleMethod(targetClass, proxyMember.getName(), ((Method) proxyMember).getParameterTypes());
} else {
return MethodUtils.getMatchingMethod(target.getClass(), proxyMember.getName(), ((Method) proxyMember).getParameterTypes());
return MethodUtils.getMatchingMethod(targetClass, proxyMember.getName(), ((Method) proxyMember).getParameterTypes());
}
} else if (proxyMember instanceof Field) {
return FieldUtils.getField(target.getClass(), proxyMember.getName(), isPublic(mod));
return FieldUtils.getField(targetClass, proxyMember.getName(), isPublic(mod));
} else if (proxyMember instanceof Constructor && isPublic(mod)) {
return ConstructorUtils.getMatchingAccessibleConstructor(target.getClass(), ((Constructor<?>) proxyMember).getParameterTypes());
return ConstructorUtils.getMatchingAccessibleConstructor(targetClass, ((Constructor<?>) proxyMember).getParameterTypes());
}
return proxyMember;
}
@@ -18,8 +18,18 @@
*/
package org.apache.struts2.ognl;
import ognl.InappropriateExpressionException;
import ognl.MethodFailedException;
import ognl.NoSuchPropertyException;
import ognl.NullHandler;
import ognl.Ognl;
import ognl.OgnlContext;
import ognl.OgnlException;
import ognl.OgnlRuntime;
import ognl.SimpleNode;
import org.apache.struts2.ActionContext;
import org.apache.struts2.text.StubTextProvider;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.StrutsException;
import org.apache.struts2.StubValueStack;
import org.apache.struts2.XWorkTestCase;
import org.apache.struts2.config.ConfigurationException;
@@ -30,6 +40,7 @@ import org.apache.struts2.ognl.accessor.CompoundRootAccessor;
import org.apache.struts2.ognl.accessor.RootAccessor;
import org.apache.struts2.test.StubConfigurationProvider;
import org.apache.struts2.test.User;
import org.apache.struts2.text.StubTextProvider;
import org.apache.struts2.util.Bar;
import org.apache.struts2.util.CompoundRoot;
import org.apache.struts2.util.Foo;
@@ -37,20 +48,11 @@ import org.apache.struts2.util.Owner;
import org.apache.struts2.util.ValueStack;
import org.apache.struts2.util.location.LocatableProperties;
import org.apache.struts2.util.reflection.ReflectionContextState;
import ognl.InappropriateExpressionException;
import ognl.MethodFailedException;
import ognl.NoSuchPropertyException;
import ognl.NullHandler;
import ognl.Ognl;
import ognl.OgnlContext;
import ognl.OgnlException;
import ognl.OgnlRuntime;
import ognl.SimpleNode;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.StrutsException;
import org.mockito.MockedStatic;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.util.ArrayList;
@@ -66,6 +68,7 @@ import static org.apache.struts2.ognl.OgnlCacheFactory.CacheType.BASIC;
import static org.apache.struts2.ognl.OgnlCacheFactory.CacheType.LRU;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.mockStatic;
public class OgnlUtilTest extends XWorkTestCase {
@@ -432,6 +435,17 @@ public class OgnlUtilTest extends XWorkTestCase {
assertNotSame("BeanInfo dropped from LRU cache is the same as newly added ?", beanInfo1_1, beanInfo1_4);
}
/**
* Ensure any {@link IntrospectionException} thrown by {@link Introspector} are propagated as is.
*/
public void testBeanInfoCacheExceptionHandling() {
try (MockedStatic<Introspector> introspector = mockStatic(Introspector.class)) {
var exception = new IntrospectionException("Test Exception");
introspector.when(() -> Introspector.getBeanInfo(TestBean1.class, Object.class)).thenThrow(exception);
assertSame(exception, assertThrows(IntrospectionException.class, () -> ognlUtil.getBeanInfo(new TestBean1())));
}
}
public void testClearRuntimeCache() {
// Confirm that no exceptions or failures arise when calling the convenience global clear method.
OgnlUtil.clearRuntimeCache();
@@ -18,15 +18,15 @@
*/
package org.apache.struts2.json;
import org.apache.struts2.inject.Inject;
import org.apache.struts2.util.ProxyUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.inject.Inject;
import org.apache.struts2.json.annotations.JSON;
import org.apache.struts2.json.annotations.JSONFieldBridge;
import org.apache.struts2.json.annotations.JSONParameter;
import org.apache.struts2.json.bridge.FieldBridge;
import org.apache.struts2.json.bridge.ParameterizedBridge;
import org.apache.struts2.util.ProxyUtil;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
@@ -221,7 +221,7 @@ public class DefaultJSONWriter implements JSONWriter {
BeanInfo info;
try {
Class clazz = excludeProxyProperties ? ProxyUtil.ultimateTargetClass(object) : object.getClass();
Class<?> clazz = excludeProxyProperties ? ProxyUtil.ultimateTargetClass(object) : object.getClass();
info = ((object == this.root) && this.ignoreHierarchy)
? getBeanInfoIgnoreHierarchy(clazz)