WW-4105 Adds support for Spring proxies when chaining actions

This commit is contained in:
Lukasz Lenart
2017-05-17 19:49:21 +02:00
7 changed files with 256 additions and 2 deletions
@@ -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<String, Object> 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);
}
}
}
@@ -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;
/**
* <code>ProxyUtil</code>
* <p>
* Various utility methods dealing with proxies
* </p>
*
*/
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 <em>target</em> object of the supplied {@code candidate}
* object, unwrapping not only a top-level proxy but also any number of
* nested proxies.
* <p>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 <em>as is</em>.
* @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> 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;
}
}
}
@@ -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;
}
}
@@ -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
}
}
@@ -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);
}
}
@@ -1,8 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="simple-action" class="com.opensymphony.xwork2.SimpleAction" scope="prototype"/>
@@ -40,4 +43,25 @@
<bean id="springResult" class="com.opensymphony.xwork2.spring.SpringResult" init-method="initialize">
<property name="stringParameter" value="my string"/>
</bean>
<bean id="pointcutted-test-bean" class="com.opensymphony.xwork2.TestBean">
<property name="name"><value>WW-4105</value></property>
<property name="count"><value>1</value></property>
</bean>
<bean id="pointcutted-test-sub-bean" class="com.opensymphony.xwork2.TestSubBean">
<property name="issueId"><value>WW-4105</value></property>
</bean>
<bean id="aspected-test-sub-bean" class="com.opensymphony.xwork2.TestSubBean" />
<aop:config>
<aop:aspect id="myAspect" ref="aspected-test-sub-bean">
<aop:pointcut id="testBeanGetName"
expression="execution(String com.opensymphony.xwork2.TestBean.getName()) and bean(pointcutted-test-bean)" />
<aop:after-returning pointcut-ref="testBeanGetName"
method="setIssueId" returning="issueId" />
<aop:pointcut id="testSubBeanGetIssueId"
expression="execution(String com.opensymphony.xwork2.TestSubBean.getIssueId()) and bean(pointcutted-test-sub-bean)" />
<aop:after-returning pointcut-ref="testSubBeanGetIssueId"
method="setName" returning="name" />
</aop:aspect>
</aop:config>
</beans>
@@ -6,8 +6,15 @@
<result-types>
<result-type name="null" class="com.opensymphony.xwork2.mock.MockResult" default="true"/>
<result-type name="springResult" class="springResult" />
<result-type name="chain"
class="com.opensymphony.xwork2.ActionChainResult" />
</result-types>
<interceptors>
<interceptor name="chain"
class="com.opensymphony.xwork2.interceptor.ChainingInterceptor"></interceptor>
</interceptors>
<action name="simpleAction" class="simple-action"/>
<action name="dependencyAction" class="dependency-action"/>
@@ -19,5 +26,17 @@
<action name="simpleActionSpringResult" class="simple-action">
<result name="error" type="springResult"/>
</action>
<action name="chainedAOPedTestBeanAction" class="pointcutted-test-bean"
method="getName">
<result name="WW-4105" type="chain">
<param name="actionName">chaintoAOPedTestSubBeanAction</param>
</result>
</action>
<action name="chaintoAOPedTestSubBeanAction" class="pointcutted-test-sub-bean"
method="getIssueId">
<interceptor-ref name="chain" />
<result name="WW-4105" type="null" />
</action>
</package>
</xwork>