Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@
import com.opensymphony.xwork2.ognl.DefaultOgnlBeanInfoCacheFactory;
import com.opensymphony.xwork2.ognl.DefaultOgnlExpressionCacheFactory;
import com.opensymphony.xwork2.ognl.ExpressionCacheFactory;
import com.opensymphony.xwork2.ognl.ProxyCacheFactory;
import com.opensymphony.xwork2.ognl.StrutsProxyCacheFactory;
import com.opensymphony.xwork2.ognl.OgnlCacheFactory;
import com.opensymphony.xwork2.ognl.OgnlReflectionProvider;
import com.opensymphony.xwork2.ognl.OgnlUtil;
Expand All @@ -93,6 +95,7 @@
import com.opensymphony.xwork2.util.OgnlTextParser;
import com.opensymphony.xwork2.util.PatternMatcher;
import com.opensymphony.xwork2.util.StrutsLocalizedTextProvider;
import com.opensymphony.xwork2.util.StrutsProxyCacheFactoryBean;
import com.opensymphony.xwork2.util.TextParser;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.ValueStackFactory;
Expand Down Expand Up @@ -143,6 +146,8 @@ public class DefaultConfiguration implements Configuration {
constants.put(StrutsConstants.STRUTS_OGNL_EXPRESSION_CACHE_MAXSIZE, 10000);
constants.put(StrutsConstants.STRUTS_OGNL_BEANINFO_CACHE_TYPE, OgnlCacheFactory.CacheType.BASIC);
constants.put(StrutsConstants.STRUTS_OGNL_BEANINFO_CACHE_MAXSIZE, 10000);
constants.put(StrutsConstants.STRUTS_PROXY_CACHE_TYPE, OgnlCacheFactory.CacheType.BASIC);
constants.put(StrutsConstants.STRUTS_PROXY_CACHE_MAXSIZE, 10000);
constants.put(StrutsConstants.STRUTS_ENABLE_DYNAMIC_METHOD_INVOCATION, Boolean.FALSE);
BOOTSTRAP_CONSTANTS = Collections.unmodifiableMap(constants);
}
Expand Down Expand Up @@ -394,6 +399,8 @@ public static ContainerBuilder bootstrapFactories(ContainerBuilder builder) {

.factory(ExpressionCacheFactory.class, DefaultOgnlExpressionCacheFactory.class, Scope.SINGLETON)
.factory(BeanInfoCacheFactory.class, DefaultOgnlBeanInfoCacheFactory.class, Scope.SINGLETON)
.factory(ProxyCacheFactory.class, StrutsProxyCacheFactory.class, Scope.SINGLETON)
.factory(StrutsProxyCacheFactoryBean.class, Scope.SINGLETON)
.factory(OgnlUtil.class, Scope.SINGLETON)
.factory(SecurityMemberAccess.class, Scope.PROTOTYPE)
.factory(OgnlGuard.class, StrutsOgnlGuard.class, Scope.SINGLETON)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 factory interface for ProxyUtil cache to be used with Struts DI mechanism.
* This allows the proxy detection cache type to be configurable via Struts constants.
*
* @param <Key> The type for the cache key entries
* @param <Value> The type for the cache value entries
* @since 6.9.0
*/
public interface ProxyCacheFactory<Key, Value> extends OgnlCacheFactory<Key, Value> {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.commons.lang3.EnumUtils;
import org.apache.struts2.StrutsConstants;

/**
* Struts Proxy Cache factory implementation for ProxyUtil caches.
* <p>
* This factory is used to create caches for proxy detection in ProxyUtil.
* The cache type and size can be configured via Struts constants.
*
* @param <Key> The type for the cache key entries
* @param <Value> The type for the cache value entries
* @since 6.9.0
*/
public class StrutsProxyCacheFactory<Key, Value> extends DefaultOgnlCacheFactory<Key, Value>
implements ProxyCacheFactory<Key, Value> {

@Inject
public StrutsProxyCacheFactory(
@Inject(value = StrutsConstants.STRUTS_PROXY_CACHE_MAXSIZE) String cacheMaxSize,
@Inject(value = StrutsConstants.STRUTS_PROXY_CACHE_TYPE) String defaultCacheType) {
super(Integer.parseInt(cacheMaxSize), EnumUtils.getEnumIgnoreCase(CacheType.class, defaultCacheType));
}
}
71 changes: 71 additions & 0 deletions core/src/main/java/com/opensymphony/xwork2/util/LazyRef.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.util;

import java.util.function.Supplier;

/**
* A thread-safe lazy reference that computes its value on first access using
* double-checked locking. The cached value can be invalidated via {@link #reset()},
* causing the next {@link #get()} call to recompute the value.
*
* @param <T> the type of the lazily computed value
* @since 6.9.0
*/
public class LazyRef<T> implements Supplier<T> {

private final Supplier<T> factory;
private volatile T value;

/**
* Creates a new LazyRef with the given factory supplier.
*
* @param factory the supplier used to compute the value; must not be null
*/
public LazyRef(Supplier<T> factory) {
this.factory = factory;
}

/**
* Returns the cached value, computing it on first access or after a {@link #reset()}.
*
* @return the computed value
*/
@Override
public T get() {
T result = value;
if (result == null) {
synchronized (this) {
result = value;
if (result == null) {
result = factory.get();
value = result;
}
}
}
return result;
}

/**
* Invalidates the cached value so the next {@link #get()} call recomputes it.
*/
public void reset() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should technically be synchronized but anyway it's a super edge case so I wouldn't worry

value = null;
}
}
61 changes: 50 additions & 11 deletions core/src/main/java/com/opensymphony/xwork2/util/ProxyUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.opensymphony.xwork2.ognl.DefaultOgnlCacheFactory;
import com.opensymphony.xwork2.ognl.OgnlCache;
import com.opensymphony.xwork2.ognl.OgnlCacheFactory;
import com.opensymphony.xwork2.ognl.ProxyCacheFactory;
import org.apache.commons.lang3.reflect.ConstructorUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
Expand All @@ -41,7 +42,6 @@
* <p>
* Various utility methods dealing with proxies
* </p>
*
*/
public class ProxyUtil {
private static final String SPRING_ADVISED_CLASS_NAME = "org.springframework.aop.framework.Advised";
Expand All @@ -51,15 +51,45 @@ public class ProxyUtil {
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();

// Holder for the cache factory (set by container)
private static volatile ProxyCacheFactory<?, ?> cacheFactory;

// Lazy-initialized caches with reset support
private static final LazyRef<OgnlCache<Class<?>, Boolean>> isProxyCache =
new LazyRef<>(ProxyUtil::createCache);
private static final LazyRef<OgnlCache<Member, Boolean>> isProxyMemberCache =
new LazyRef<>(ProxyUtil::createCache);

/**
* Sets the cache factory. Called by the container during initialization.
* Resets existing caches so they are recreated with the new factory.
*
* @param factory the cache factory to use for creating proxy caches
* @since 6.9.0
*/
public static void setProxyCacheFactory(ProxyCacheFactory<?, ?> factory) {
cacheFactory = factory;
isProxyCache.reset();
isProxyMemberCache.reset();
}

@SuppressWarnings("unchecked")
private static <K, V> OgnlCache<K, V> createCache() {
if (cacheFactory != null) {
return ((ProxyCacheFactory<K, V>) cacheFactory).buildOgnlCache(
CACHE_MAX_SIZE, CACHE_INITIAL_CAPACITY, 0.75f, cacheFactory.getDefaultCacheType());
}
// Fallback to BASIC if container hasn't initialized yet
return new DefaultOgnlCacheFactory<K, V>(
CACHE_MAX_SIZE, OgnlCacheFactory.CacheType.BASIC, CACHE_INITIAL_CAPACITY).buildOgnlCache();
}

/**
* Determine the ultimate target class of the given instance, traversing
* not only a top-level proxy but any number of nested proxies as well &mdash;
* as long as possible without side effects.
*
* @param candidate the instance to check (might be a proxy)
* @return the ultimate target class (or the plain class of the given
* object as fallback; never {@code null})
Expand All @@ -78,24 +108,26 @@ public static Class<?> ultimateTargetClass(Object candidate) {

/**
* Check whether the given object is a proxy.
*
* @param object the object to check
*/
public static boolean isProxy(Object object) {
if (object == null) return false;
Class<?> clazz = object.getClass();
Boolean flag = isProxyCache.get(clazz);
Boolean flag = isProxyCache.get().get(clazz);
if (flag != null) {
return flag;
}

boolean isProxy = isSpringAopProxy(object) || isHibernateProxy(object);

isProxyCache.put(clazz, isProxy);
isProxyCache.get().put(clazz, isProxy);
return isProxy;
}

/**
* Check whether the given member is a proxy member of a proxy object or is a static proxy member.
*
* @param member the member to check
* @param object the object to check
*/
Expand All @@ -104,14 +136,14 @@ public static boolean isProxyMember(Member member, Object object) {
return false;
}

Boolean flag = isProxyMemberCache.get(member);
Boolean flag = isProxyMemberCache.get().get(member);
if (flag != null) {
return flag;
}

boolean isProxyMember = isSpringProxyMember(member) || isHibernateProxyMember(member);

isProxyMemberCache.put(member, isProxyMember);
isProxyMemberCache.get().put(member, isProxyMember);
return isProxyMember;
}

Expand Down Expand Up @@ -147,6 +179,7 @@ public static boolean isHibernateProxyMember(Member member) {
* Determine the ultimate target class of the given spring bean instance, traversing
* not only a top-level spring proxy but any number of nested spring proxies as well &mdash;
* as long as possible without side effects, that is, just for singleton targets.
*
* @param candidate the instance to check (might be a spring AOP proxy)
* @return the ultimate target class (or the plain class of the given
* object as fallback; never {@code null})
Expand All @@ -170,6 +203,7 @@ private static Class<?> springUltimateTargetClass(Object candidate) {

/**
* Check whether the given object is a Spring proxy.
*
* @param object the object to check
*/
private static boolean isSpringAopProxy(Object object) {
Expand All @@ -180,6 +214,7 @@ private static boolean isSpringAopProxy(Object object) {

/**
* Check whether the given member is a member of a spring proxy.
*
* @param member the member to check
*/
private static boolean isSpringProxyMember(Member member) {
Expand All @@ -201,6 +236,7 @@ private static boolean isSpringProxyMember(Member member) {

/**
* 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)
Expand All @@ -221,6 +257,7 @@ private static Object getSingletonTarget(Object candidate) {

/**
* Check whether the specified class is a CGLIB-generated class.
*
* @param clazz the class to check
*/
private static boolean isCglibProxyClass(Class<?> clazz) {
Expand All @@ -229,7 +266,8 @@ private static boolean isCglibProxyClass(Class<?> clazz) {

/**
* Check whether the given class implements an interface with a given class name.
* @param clazz the class to check
*
* @param clazz the class to check
* @param ifaceClassName the interface class name to check
*/
private static boolean implementsInterface(Class<?> clazz, String ifaceClassName) {
Expand All @@ -243,7 +281,8 @@ private static boolean implementsInterface(Class<?> clazz, String ifaceClassName

/**
* Check whether the given class has a given member.
* @param clazz the class to check
*
* @param clazz the class to check
* @param member the member to check
*/
private static boolean hasMember(Class<?> clazz, Member member) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.util;

import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.ognl.ProxyCacheFactory;

/**
* Bean that wires the ProxyCacheFactory to ProxyUtil during container initialization.
* <p>
* This bean is created by the container and receives the configured ProxyCacheFactory
* via dependency injection, then passes it to the static ProxyUtil class.
*
* @since 6.9.0
*/
public class StrutsProxyCacheFactoryBean {

@Inject
public StrutsProxyCacheFactoryBean(ProxyCacheFactory<?, ?> proxyCacheFactory) {
ProxyUtil.setProxyCacheFactory(proxyCacheFactory);
}
}
Loading