From c2f2de06ce3c5545878a3b421fdde3ca5495d3cc Mon Sep 17 00:00:00 2001 From: Yasser Zamani Date: Sun, 30 Apr 2017 09:20:57 +0430 Subject: [PATCH 1/2] 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 + + + + + + From 9a8b4f4ebcb3787bef76198a7374bb5dcb69908e Mon Sep 17 00:00:00 2001 From: Yasser Zamani Date: Thu, 11 May 2017 12:49:46 +0430 Subject: [PATCH 2/2] WW-4105 Removes SpringUtils to ProxyUtil --- .../interceptor/ChainingInterceptor.java | 6 ++-- .../SpringUtils.java => util/ProxyUtil.java} | 27 +++++++-------- ...tilsTest.java => SpringProxyUtilTest.java} | 33 ++++++++++--------- 3 files changed, 34 insertions(+), 32 deletions(-) rename core/src/main/java/com/opensymphony/xwork2/{spring/SpringUtils.java => util/ProxyUtil.java} (76%) rename core/src/test/java/com/opensymphony/xwork2/spring/{SpringUtilsTest.java => SpringProxyUtilTest.java} (66%) 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 b90e2b662..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,7 +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.ProxyUtil; import com.opensymphony.xwork2.util.CompoundRoot; import com.opensymphony.xwork2.util.TextParseUtil; import com.opensymphony.xwork2.util.ValueStack; @@ -163,8 +163,8 @@ public class ChainingInterceptor extends AbstractInterceptor { for (Object object : list) { if (shouldCopy(object)) { Object action = invocation.getAction(); - if(SpringUtils.isAopProxy(action)) { - action = SpringUtils.getUltimateTargetObject(action); + 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/spring/SpringUtils.java b/core/src/main/java/com/opensymphony/xwork2/util/ProxyUtil.java similarity index 76% rename from core/src/main/java/com/opensymphony/xwork2/spring/SpringUtils.java rename to core/src/main/java/com/opensymphony/xwork2/util/ProxyUtil.java index bb6584d24..6a8ac53e0 100644 --- a/core/src/main/java/com/opensymphony/xwork2/spring/SpringUtils.java +++ b/core/src/main/java/com/opensymphony/xwork2/util/ProxyUtil.java @@ -13,21 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.opensymphony.xwork2.spring; +package com.opensymphony.xwork2.util; -import com.opensymphony.xwork2.util.ClassLoaderUtil; import org.apache.commons.lang3.reflect.MethodUtils; import java.lang.reflect.Proxy; /** - * SpringUtils + * ProxyUtil *

- * Various utility methods dealing with spring framework + * Various utility methods dealing with proxies *

* */ -public class SpringUtils { +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 @@ -40,13 +42,12 @@ public class SpringUtils { * @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) { + public static T getSpringUltimateTargetObject(Object candidate) { try { - if (isAopProxy(candidate) && - implementsInterface(candidate.getClass(), "org.springframework.aop.framework.Advised")) { + if (isSpringAopProxy(candidate) && implementsInterface(candidate.getClass(), SPRING_ADVISED_CLASS_NAME)) { Object targetSource = MethodUtils.invokeMethod(candidate, "getTargetSource"); Object target = MethodUtils.invokeMethod(targetSource, "getTarget"); - return getUltimateTargetObject(target); + return getSpringUltimateTargetObject(target); } } catch (Throwable ex) { @@ -59,10 +60,10 @@ public class SpringUtils { * Check whether the given object is a Spring proxy. * @param object the object to check */ - public static boolean isAopProxy(Object object) { + public static boolean isSpringAopProxy(Object object) { Class clazz = object.getClass(); - return (implementsInterface(clazz, "org.springframework.aop.SpringProxy") && - (Proxy.isProxyClass(clazz) || isCglibProxyClass(clazz))); + return (implementsInterface(clazz, SPRING_SPRINGPROXY_CLASS_NAME) && (Proxy.isProxyClass(clazz) + || isCglibProxyClass(clazz))); } /** @@ -80,7 +81,7 @@ public class SpringUtils { */ private static boolean implementsInterface(Class clazz, String ifaceClassName) { try { - Class ifaceClass = ClassLoaderUtil.loadClass(ifaceClassName, SpringUtils.class); + 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/spring/SpringUtilsTest.java b/core/src/test/java/com/opensymphony/xwork2/spring/SpringProxyUtilTest.java similarity index 66% rename from core/src/test/java/com/opensymphony/xwork2/spring/SpringUtilsTest.java rename to core/src/test/java/com/opensymphony/xwork2/spring/SpringProxyUtilTest.java index 7a1e0d7b4..9c047b842 100644 --- a/core/src/test/java/com/opensymphony/xwork2/spring/SpringUtilsTest.java +++ b/core/src/test/java/com/opensymphony/xwork2/spring/SpringProxyUtilTest.java @@ -17,13 +17,14 @@ 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 framework. + * Test various utility methods dealing with spring proxies. * */ -public class SpringUtilsTest extends XWorkTestCase { +public class SpringProxyUtilTest extends XWorkTestCase { private ApplicationContext appContext; @Override public void setUp() throws Exception { @@ -36,49 +37,49 @@ public class SpringUtilsTest extends XWorkTestCase { appContext = ((SpringObjectFactory)container.getInstance(ObjectFactory.class)).appContext; } - public void testIsAopProxy() throws Exception { + public void testIsSpringAopProxy() throws Exception { Object simpleAction = appContext.getBean("simple-action"); - assertFalse(SpringUtils.isAopProxy(simpleAction)); + assertFalse(ProxyUtil.isSpringAopProxy(simpleAction)); Object proxiedAction = appContext.getBean("proxied-action"); - assertTrue(SpringUtils.isAopProxy(proxiedAction)); + assertTrue(ProxyUtil.isSpringAopProxy(proxiedAction)); Object autoProxiedAction = appContext.getBean("auto-proxied-action"); - assertTrue(SpringUtils.isAopProxy(autoProxiedAction)); + assertTrue(ProxyUtil.isSpringAopProxy(autoProxiedAction)); Object pointcuttedTestBean = appContext.getBean("pointcutted-test-bean"); - assertTrue(SpringUtils.isAopProxy(pointcuttedTestBean)); + assertTrue(ProxyUtil.isSpringAopProxy(pointcuttedTestBean)); Object pointcuttedTestSubBean = appContext.getBean("pointcutted-test-sub-bean"); - assertTrue(SpringUtils.isAopProxy(pointcuttedTestSubBean)); + assertTrue(ProxyUtil.isSpringAopProxy(pointcuttedTestSubBean)); Object aspectedTestSubBean = appContext.getBean("aspected-test-sub-bean"); - assertFalse(SpringUtils.isAopProxy(aspectedTestSubBean)); + assertFalse(ProxyUtil.isSpringAopProxy(aspectedTestSubBean)); } - public void testGetUltimateTargetObject() throws Exception { + public void testGetSpringUltimateTargetObject() throws Exception { Object simpleAction = appContext.getBean("simple-action"); - Object simpleActionUltimateTargetObject = SpringUtils.getUltimateTargetObject(simpleAction); + Object simpleActionUltimateTargetObject = ProxyUtil.getSpringUltimateTargetObject(simpleAction); assertEquals(simpleAction, simpleActionUltimateTargetObject); Object proxiedAction = appContext.getBean("proxied-action"); - Object proxiedActionUltimateTargetObject = SpringUtils.getUltimateTargetObject(proxiedAction); + Object proxiedActionUltimateTargetObject = ProxyUtil.getSpringUltimateTargetObject(proxiedAction); assertEquals(SimpleAction.class, proxiedActionUltimateTargetObject.getClass()); Object autoProxiedAction = appContext.getBean("auto-proxied-action"); - Object autoProxiedActionUltimateTargetObject = SpringUtils.getUltimateTargetObject(autoProxiedAction); + Object autoProxiedActionUltimateTargetObject = ProxyUtil.getSpringUltimateTargetObject(autoProxiedAction); assertEquals(SimpleAction.class, autoProxiedActionUltimateTargetObject.getClass()); Object pointcuttedTestBean = appContext.getBean("pointcutted-test-bean"); - Object pointcuttedTestBeanUltimateTargetObject = SpringUtils.getUltimateTargetObject(pointcuttedTestBean); + Object pointcuttedTestBeanUltimateTargetObject = ProxyUtil.getSpringUltimateTargetObject(pointcuttedTestBean); assertEquals(TestBean.class, pointcuttedTestBeanUltimateTargetObject.getClass()); Object pointcuttedTestSubBean = appContext.getBean("pointcutted-test-sub-bean"); - Object pointcuttedTestSubBeanUltimateTargetObject = SpringUtils.getUltimateTargetObject(pointcuttedTestSubBean); + Object pointcuttedTestSubBeanUltimateTargetObject = ProxyUtil.getSpringUltimateTargetObject(pointcuttedTestSubBean); assertEquals(TestSubBean.class, pointcuttedTestSubBeanUltimateTargetObject.getClass()); Object aspectedTestSubBean = appContext.getBean("aspected-test-sub-bean"); - Object aspectedTestSubBeanUltimateTargetObject = SpringUtils.getUltimateTargetObject(aspectedTestSubBean); + Object aspectedTestSubBeanUltimateTargetObject = ProxyUtil.getSpringUltimateTargetObject(aspectedTestSubBean); assertEquals(aspectedTestSubBean, aspectedTestSubBeanUltimateTargetObject); } }