mirror of
https://github.com/apache/struts.git
synced 2026-08-06 07:06:58 +00:00
Merge pull request #1236 from apache/WW-5534-proxyutil
WW-5534 Simplify ProxyUtil, add OgnlCache#computeIfAbsent
This commit is contained in:
+7
-1
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user