From c2f2de06ce3c5545878a3b421fdde3ca5495d3cc Mon Sep 17 00:00:00 2001 From: Yasser Zamani Date: Sun, 30 Apr 2017 09:20:57 +0430 Subject: [PATCH] WW-4105 Unwraps Spring proxy in actions chain --- .../interceptor/ChainingInterceptor.java | 7 +- .../xwork2/spring/SpringUtils.java | 89 +++++++++++++++++++ .../com/opensymphony/xwork2/TestSubBean.java | 14 +++ .../xwork2/spring/ActionsFromSpringTest.java | 17 ++++ .../xwork2/spring/SpringUtilsTest.java | 84 +++++++++++++++++ .../xwork2/spring/actionContext-spring.xml | 26 +++++- .../xwork2/spring/actionContext-xwork.xml | 19 ++++ 7 files changed, 254 insertions(+), 2 deletions(-) create mode 100644 core/src/main/java/com/opensymphony/xwork2/spring/SpringUtils.java create mode 100644 core/src/test/java/com/opensymphony/xwork2/TestSubBean.java create mode 100644 core/src/test/java/com/opensymphony/xwork2/spring/SpringUtilsTest.java 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..b90e2b662 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.spring.SpringUtils; 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(SpringUtils.isAopProxy(action)) { + action = SpringUtils.getUltimateTargetObject(action); + } + reflectionProvider.copy(object, action, ctxMap, prepareExcludes(), includes); } } } diff --git a/core/src/main/java/com/opensymphony/xwork2/spring/SpringUtils.java b/core/src/main/java/com/opensymphony/xwork2/spring/SpringUtils.java new file mode 100644 index 000000000..bb6584d24 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/spring/SpringUtils.java @@ -0,0 +1,89 @@ +/* + * 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.util.ClassLoaderUtil; +import org.apache.commons.lang3.reflect.MethodUtils; + +import java.lang.reflect.Proxy; + +/** + * SpringUtils + *

+ * Various utility methods dealing with spring framework + *

+ * + */ +public class SpringUtils { + /** + * 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 getUltimateTargetObject(Object candidate) { + try { + if (isAopProxy(candidate) && + implementsInterface(candidate.getClass(), "org.springframework.aop.framework.Advised")) { + Object targetSource = MethodUtils.invokeMethod(candidate, "getTargetSource"); + Object target = MethodUtils.invokeMethod(targetSource, "getTarget"); + return getUltimateTargetObject(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 isAopProxy(Object object) { + Class clazz = object.getClass(); + return (implementsInterface(clazz, "org.springframework.aop.SpringProxy") && + (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, SpringUtils.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/SpringUtilsTest.java b/core/src/test/java/com/opensymphony/xwork2/spring/SpringUtilsTest.java new file mode 100644 index 000000000..7a1e0d7b4 --- /dev/null +++ b/core/src/test/java/com/opensymphony/xwork2/spring/SpringUtilsTest.java @@ -0,0 +1,84 @@ +/* + * 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 org.springframework.context.ApplicationContext; + +/** + * Test various utility methods dealing with spring framework. + * + */ +public class SpringUtilsTest 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 testIsAopProxy() throws Exception { + Object simpleAction = appContext.getBean("simple-action"); + assertFalse(SpringUtils.isAopProxy(simpleAction)); + + Object proxiedAction = appContext.getBean("proxied-action"); + assertTrue(SpringUtils.isAopProxy(proxiedAction)); + + Object autoProxiedAction = appContext.getBean("auto-proxied-action"); + assertTrue(SpringUtils.isAopProxy(autoProxiedAction)); + + Object pointcuttedTestBean = appContext.getBean("pointcutted-test-bean"); + assertTrue(SpringUtils.isAopProxy(pointcuttedTestBean)); + + Object pointcuttedTestSubBean = appContext.getBean("pointcutted-test-sub-bean"); + assertTrue(SpringUtils.isAopProxy(pointcuttedTestSubBean)); + + Object aspectedTestSubBean = appContext.getBean("aspected-test-sub-bean"); + assertFalse(SpringUtils.isAopProxy(aspectedTestSubBean)); + } + + public void testGetUltimateTargetObject() throws Exception { + Object simpleAction = appContext.getBean("simple-action"); + Object simpleActionUltimateTargetObject = SpringUtils.getUltimateTargetObject(simpleAction); + assertEquals(simpleAction, simpleActionUltimateTargetObject); + + Object proxiedAction = appContext.getBean("proxied-action"); + Object proxiedActionUltimateTargetObject = SpringUtils.getUltimateTargetObject(proxiedAction); + assertEquals(SimpleAction.class, proxiedActionUltimateTargetObject.getClass()); + + Object autoProxiedAction = appContext.getBean("auto-proxied-action"); + Object autoProxiedActionUltimateTargetObject = SpringUtils.getUltimateTargetObject(autoProxiedAction); + assertEquals(SimpleAction.class, autoProxiedActionUltimateTargetObject.getClass()); + + Object pointcuttedTestBean = appContext.getBean("pointcutted-test-bean"); + Object pointcuttedTestBeanUltimateTargetObject = SpringUtils.getUltimateTargetObject(pointcuttedTestBean); + assertEquals(TestBean.class, pointcuttedTestBeanUltimateTargetObject.getClass()); + + Object pointcuttedTestSubBean = appContext.getBean("pointcutted-test-sub-bean"); + Object pointcuttedTestSubBeanUltimateTargetObject = SpringUtils.getUltimateTargetObject(pointcuttedTestSubBean); + assertEquals(TestSubBean.class, pointcuttedTestSubBeanUltimateTargetObject.getClass()); + + Object aspectedTestSubBean = appContext.getBean("aspected-test-sub-bean"); + Object aspectedTestSubBeanUltimateTargetObject = SpringUtils.getUltimateTargetObject(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 + + + + + +