diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ChainingInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ChainingInterceptor.java index 27a0f6861..59b1d8819 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ChainingInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ChainingInterceptor.java @@ -20,6 +20,7 @@ import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.Result; import com.opensymphony.xwork2.Unchainable; import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.ProxyUtil; import com.opensymphony.xwork2.util.CompoundRoot; import com.opensymphony.xwork2.util.TextParseUtil; import com.opensymphony.xwork2.util.ValueStack; @@ -161,7 +162,11 @@ public class ChainingInterceptor extends AbstractInterceptor { Map ctxMap = invocation.getInvocationContext().getContextMap(); for (Object object : list) { if (shouldCopy(object)) { - reflectionProvider.copy(object, invocation.getAction(), ctxMap, prepareExcludes(), includes); + Object action = invocation.getAction(); + if(ProxyUtil.isSpringAopProxy(action)) { + action = ProxyUtil.getSpringUltimateTargetObject(action); + } + reflectionProvider.copy(object, action, ctxMap, prepareExcludes(), includes); } } } diff --git a/core/src/main/java/com/opensymphony/xwork2/util/ProxyUtil.java b/core/src/main/java/com/opensymphony/xwork2/util/ProxyUtil.java new file mode 100644 index 000000000..6a8ac53e0 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/util/ProxyUtil.java @@ -0,0 +1,90 @@ +/* + * Copyright 2017 The 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.util; + +import org.apache.commons.lang3.reflect.MethodUtils; + +import java.lang.reflect.Proxy; + +/** + * ProxyUtil + *

+ * Various utility methods dealing with proxies + *

+ * + */ +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"; + + /** + * Get the ultimate target object of the supplied {@code candidate} + * object, unwrapping not only a top-level proxy but also any number of + * nested proxies. + *

If the supplied {@code candidate} is a Spring proxy, the ultimate target of all + * nested proxies will be returned; otherwise, the {@code candidate} + * will be returned as is. + * @param candidate the instance to check (potentially a Spring AOP proxy; + * never {@code null}) + * @return the target object or the {@code candidate} (never {@code null}) + * @throws IllegalStateException if an error occurs while unwrapping a proxy + */ + public static T getSpringUltimateTargetObject(Object candidate) { + try { + if (isSpringAopProxy(candidate) && implementsInterface(candidate.getClass(), SPRING_ADVISED_CLASS_NAME)) { + Object targetSource = MethodUtils.invokeMethod(candidate, "getTargetSource"); + Object target = MethodUtils.invokeMethod(targetSource, "getTarget"); + return getSpringUltimateTargetObject(target); + } + } + catch (Throwable ex) { + throw new IllegalStateException("Failed to unwrap proxied object", ex); + } + return (T) candidate; + } + + /** + * Check whether the given object is a Spring proxy. + * @param object the object to check + */ + public static boolean isSpringAopProxy(Object object) { + Class clazz = object.getClass(); + return (implementsInterface(clazz, SPRING_SPRINGPROXY_CLASS_NAME) && (Proxy.isProxyClass(clazz) + || isCglibProxyClass(clazz))); + } + + /** + * 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; + } + } +} diff --git a/core/src/test/java/com/opensymphony/xwork2/TestSubBean.java b/core/src/test/java/com/opensymphony/xwork2/TestSubBean.java new file mode 100644 index 000000000..5006876c3 --- /dev/null +++ b/core/src/test/java/com/opensymphony/xwork2/TestSubBean.java @@ -0,0 +1,14 @@ +package com.opensymphony.xwork2; + +public class TestSubBean extends TestBean { + + private String issueId; + + public String getIssueId() { + return issueId; + } + + public void setIssueId(String issueId) { + this.issueId = issueId; + } +} diff --git a/core/src/test/java/com/opensymphony/xwork2/spring/ActionsFromSpringTest.java b/core/src/test/java/com/opensymphony/xwork2/spring/ActionsFromSpringTest.java index fcb79a137..4bbe42926 100644 --- a/core/src/test/java/com/opensymphony/xwork2/spring/ActionsFromSpringTest.java +++ b/core/src/test/java/com/opensymphony/xwork2/spring/ActionsFromSpringTest.java @@ -77,4 +77,21 @@ public class ActionsFromSpringTest extends XWorkTestCase { assertTrue(springResult.isInitialize()); assertNotNull(springResult.getStringParameter()); } + + public void testChainingProxiedActions() throws Exception { + ActionProxy proxy = actionProxyFactory.createActionProxy(null, "chainedAOPedTestBeanAction", + null, null); + + proxy.execute(); + + TestSubBean chaintoAOPedAction = (TestSubBean) appContext.getBean("pointcutted-test-sub-bean"); + TestSubBean aspectState = (TestSubBean) appContext.getBean("aspected-test-sub-bean"); + + assertEquals(1, chaintoAOPedAction.getCount()); //check if chain + assertEquals("WW-4105", chaintoAOPedAction.getName()); + assertNotNull(aspectState.getIssueId()); //and AOP proxied actions + assertNotNull(aspectState.getName()); + assertEquals(aspectState.getName(), aspectState.getIssueId()); + assertEquals("WW-4105", aspectState.getIssueId()); //work together without any problem + } } diff --git a/core/src/test/java/com/opensymphony/xwork2/spring/SpringProxyUtilTest.java b/core/src/test/java/com/opensymphony/xwork2/spring/SpringProxyUtilTest.java new file mode 100644 index 000000000..9c047b842 --- /dev/null +++ b/core/src/test/java/com/opensymphony/xwork2/spring/SpringProxyUtilTest.java @@ -0,0 +1,85 @@ +/* + * Copyright 2017 The 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.spring; + +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; +import com.opensymphony.xwork2.util.ProxyUtil; +import org.springframework.context.ApplicationContext; + +/** + * Test various utility methods dealing with spring proxies. + * + */ +public class SpringProxyUtilTest extends XWorkTestCase { + private ApplicationContext appContext; + + @Override public void setUp() throws Exception { + super.setUp(); + + // Set up XWork + XmlConfigurationProvider provider = new XmlConfigurationProvider("com/opensymphony/xwork2/spring/actionContext-xwork.xml"); + container.inject(provider); + loadConfigurationProviders(provider); + appContext = ((SpringObjectFactory)container.getInstance(ObjectFactory.class)).appContext; + } + + public void testIsSpringAopProxy() throws Exception { + Object simpleAction = appContext.getBean("simple-action"); + assertFalse(ProxyUtil.isSpringAopProxy(simpleAction)); + + Object proxiedAction = appContext.getBean("proxied-action"); + assertTrue(ProxyUtil.isSpringAopProxy(proxiedAction)); + + Object autoProxiedAction = appContext.getBean("auto-proxied-action"); + assertTrue(ProxyUtil.isSpringAopProxy(autoProxiedAction)); + + Object pointcuttedTestBean = appContext.getBean("pointcutted-test-bean"); + assertTrue(ProxyUtil.isSpringAopProxy(pointcuttedTestBean)); + + Object pointcuttedTestSubBean = appContext.getBean("pointcutted-test-sub-bean"); + assertTrue(ProxyUtil.isSpringAopProxy(pointcuttedTestSubBean)); + + Object aspectedTestSubBean = appContext.getBean("aspected-test-sub-bean"); + assertFalse(ProxyUtil.isSpringAopProxy(aspectedTestSubBean)); + } + + public void testGetSpringUltimateTargetObject() throws Exception { + Object simpleAction = appContext.getBean("simple-action"); + Object simpleActionUltimateTargetObject = ProxyUtil.getSpringUltimateTargetObject(simpleAction); + assertEquals(simpleAction, simpleActionUltimateTargetObject); + + Object proxiedAction = appContext.getBean("proxied-action"); + Object proxiedActionUltimateTargetObject = ProxyUtil.getSpringUltimateTargetObject(proxiedAction); + assertEquals(SimpleAction.class, proxiedActionUltimateTargetObject.getClass()); + + Object autoProxiedAction = appContext.getBean("auto-proxied-action"); + Object autoProxiedActionUltimateTargetObject = ProxyUtil.getSpringUltimateTargetObject(autoProxiedAction); + assertEquals(SimpleAction.class, autoProxiedActionUltimateTargetObject.getClass()); + + Object pointcuttedTestBean = appContext.getBean("pointcutted-test-bean"); + Object pointcuttedTestBeanUltimateTargetObject = ProxyUtil.getSpringUltimateTargetObject(pointcuttedTestBean); + assertEquals(TestBean.class, pointcuttedTestBeanUltimateTargetObject.getClass()); + + Object pointcuttedTestSubBean = appContext.getBean("pointcutted-test-sub-bean"); + Object pointcuttedTestSubBeanUltimateTargetObject = ProxyUtil.getSpringUltimateTargetObject(pointcuttedTestSubBean); + assertEquals(TestSubBean.class, pointcuttedTestSubBeanUltimateTargetObject.getClass()); + + Object aspectedTestSubBean = appContext.getBean("aspected-test-sub-bean"); + Object aspectedTestSubBeanUltimateTargetObject = ProxyUtil.getSpringUltimateTargetObject(aspectedTestSubBean); + assertEquals(aspectedTestSubBean, aspectedTestSubBeanUltimateTargetObject); + } +} diff --git a/core/src/test/resources/com/opensymphony/xwork2/spring/actionContext-spring.xml b/core/src/test/resources/com/opensymphony/xwork2/spring/actionContext-spring.xml index 02a4f33e3..5c18f6eca 100644 --- a/core/src/test/resources/com/opensymphony/xwork2/spring/actionContext-spring.xml +++ b/core/src/test/resources/com/opensymphony/xwork2/spring/actionContext-spring.xml @@ -1,8 +1,11 @@ + http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/aop + http://www.springframework.org/schema/aop/spring-aop.xsd"> @@ -40,4 +43,25 @@ + + + WW-4105 + 1 + + + WW-4105 + + + + + + + + + + diff --git a/core/src/test/resources/com/opensymphony/xwork2/spring/actionContext-xwork.xml b/core/src/test/resources/com/opensymphony/xwork2/spring/actionContext-xwork.xml index 988956f2e..0cacc8d1e 100644 --- a/core/src/test/resources/com/opensymphony/xwork2/spring/actionContext-xwork.xml +++ b/core/src/test/resources/com/opensymphony/xwork2/spring/actionContext-xwork.xml @@ -6,8 +6,15 @@ + + + + + @@ -19,5 +26,17 @@ + + + + chaintoAOPedTestSubBeanAction + + + + + +