From 64fd0b081d9367a764b88f1894f3f92198e85b07 Mon Sep 17 00:00:00 2001 From: David Syer Date: Fri, 17 Jun 2011 12:14:01 +0000 Subject: [PATCH] SPR-6268: Add proxy-target-class to --- .../config/ScriptBeanDefinitionParser.java | 46 +++--- .../config/ScriptingDefaultsParser.java | 7 + .../support/ScriptFactoryPostProcessor.java | 146 +++++++++++------- .../scripting/config/spring-lang-3.1.xsd | 44 +++++- .../config/ScriptingDefaultsTests.java | 12 +- .../scripting/groovy/ConcreteMessenger.java | 37 +++++ .../groovy/GroovyScriptFactoryTests.java | 81 ++++++---- .../scripting/groovy/Messenger.groovy | 4 +- .../groovy-with-xsd-proxy-target-class.xml | 14 ++ .../jruby-with-xsd-proxy-target-class.xml | 17 ++ ...scriptingDefaultsProxyTargetClassTests.xml | 17 ++ 11 files changed, 305 insertions(+), 120 deletions(-) create mode 100644 org.springframework.context/src/test/java/org/springframework/scripting/groovy/ConcreteMessenger.java create mode 100644 org.springframework.context/src/test/java/org/springframework/scripting/groovy/groovy-with-xsd-proxy-target-class.xml create mode 100644 org.springframework.context/src/test/java/org/springframework/scripting/groovy/jruby-with-xsd-proxy-target-class.xml create mode 100644 org.springframework.context/src/test/resources/org/springframework/scripting/config/scriptingDefaultsProxyTargetClassTests.xml diff --git a/org.springframework.context/src/main/java/org/springframework/scripting/config/ScriptBeanDefinitionParser.java b/org.springframework.context/src/main/java/org/springframework/scripting/config/ScriptBeanDefinitionParser.java index 34d67c5ef1e..9e21e527778 100644 --- a/org.springframework.context/src/main/java/org/springframework/scripting/config/ScriptBeanDefinitionParser.java +++ b/org.springframework.context/src/main/java/org/springframework/scripting/config/ScriptBeanDefinitionParser.java @@ -70,9 +70,10 @@ class ScriptBeanDefinitionParser extends AbstractBeanDefinitionParser { private static final String SCRIPT_INTERFACES_ATTRIBUTE = "script-interfaces"; private static final String REFRESH_CHECK_DELAY_ATTRIBUTE = "refresh-check-delay"; - - private static final String CUSTOMIZER_REF_ATTRIBUTE = "customizer-ref"; + private static final String PROXY_TARGET_CLASS_ATTRIBUTE = "proxy-target-class"; + + private static final String CUSTOMIZER_REF_ATTRIBUTE = "customizer-ref"; /** * The {@link org.springframework.scripting.ScriptFactory} class that this @@ -80,7 +81,6 @@ class ScriptBeanDefinitionParser extends AbstractBeanDefinitionParser { */ private final String scriptFactoryClassName; - /** * Create a new instance of this parser, creating bean definitions for the * supplied {@link org.springframework.scripting.ScriptFactory} class. @@ -90,7 +90,6 @@ class ScriptBeanDefinitionParser extends AbstractBeanDefinitionParser { this.scriptFactoryClassName = scriptFactoryClassName; } - /** * Parses the dynamic object element and returns the resulting bean definition. * Registers a {@link ScriptFactoryPostProcessor} if needed. @@ -110,7 +109,8 @@ class ScriptBeanDefinitionParser extends AbstractBeanDefinitionParser { GenericBeanDefinition bd = new GenericBeanDefinition(); bd.setBeanClassName(this.scriptFactoryClassName); bd.setSource(parserContext.extractSource(element)); - + bd.setAttribute(ScriptFactoryPostProcessor.LANGUAGE_ATTRIBUTE, element.getLocalName()); + // Determine bean scope. String scope = element.getAttribute(SCOPE_ATTRIBUTE); if (StringUtils.hasLength(scope)) { @@ -123,8 +123,7 @@ class ScriptBeanDefinitionParser extends AbstractBeanDefinitionParser { // Only "byType" and "byName" supported, but maybe other default inherited... if (autowireMode == GenericBeanDefinition.AUTOWIRE_AUTODETECT) { autowireMode = GenericBeanDefinition.AUTOWIRE_BY_TYPE; - } - else if (autowireMode == GenericBeanDefinition.AUTOWIRE_CONSTRUCTOR) { + } else if (autowireMode == GenericBeanDefinition.AUTOWIRE_CONSTRUCTOR) { autowireMode = GenericBeanDefinition.AUTOWIRE_NO; } bd.setAutowireMode(autowireMode); @@ -134,31 +133,34 @@ class ScriptBeanDefinitionParser extends AbstractBeanDefinitionParser { bd.setDependencyCheck(parserContext.getDelegate().getDependencyCheck(dependencyCheck)); // Retrieve the defaults for bean definitions within this parser context - BeanDefinitionDefaults beanDefinitionDefaults = - parserContext.getDelegate().getBeanDefinitionDefaults(); + BeanDefinitionDefaults beanDefinitionDefaults = parserContext.getDelegate().getBeanDefinitionDefaults(); // Determine init method and destroy method. String initMethod = element.getAttribute(INIT_METHOD_ATTRIBUTE); if (StringUtils.hasLength(initMethod)) { bd.setInitMethodName(initMethod); - } - else if (beanDefinitionDefaults.getInitMethodName() != null) { + } else if (beanDefinitionDefaults.getInitMethodName() != null) { bd.setInitMethodName(beanDefinitionDefaults.getInitMethodName()); } String destroyMethod = element.getAttribute(DESTROY_METHOD_ATTRIBUTE); if (StringUtils.hasLength(destroyMethod)) { bd.setDestroyMethodName(destroyMethod); - } - else if (beanDefinitionDefaults.getDestroyMethodName() != null) { + } else if (beanDefinitionDefaults.getDestroyMethodName() != null) { bd.setDestroyMethodName(beanDefinitionDefaults.getDestroyMethodName()); } // Attach any refresh metadata. String refreshCheckDelay = element.getAttribute(REFRESH_CHECK_DELAY_ATTRIBUTE); if (StringUtils.hasText(refreshCheckDelay)) { - bd.setAttribute( - ScriptFactoryPostProcessor.REFRESH_CHECK_DELAY_ATTRIBUTE, new Long(refreshCheckDelay)); + bd.setAttribute(ScriptFactoryPostProcessor.REFRESH_CHECK_DELAY_ATTRIBUTE, new Long(refreshCheckDelay)); + } + + // Attach any proxy target class metadata. + String proxyTargetClass = element.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE); + if (StringUtils.hasText(proxyTargetClass)) { + Boolean flag = new Boolean(proxyTargetClass); + bd.setAttribute(ScriptFactoryPostProcessor.PROXY_TARGET_CLASS_ATTRIBUTE, flag); } // Add constructor arguments. @@ -168,14 +170,13 @@ class ScriptBeanDefinitionParser extends AbstractBeanDefinitionParser { if (element.hasAttribute(SCRIPT_INTERFACES_ATTRIBUTE)) { cav.addIndexedArgumentValue(constructorArgNum++, element.getAttribute(SCRIPT_INTERFACES_ATTRIBUTE)); } - + // This is used for Groovy. It's a bean reference to a customizer bean. if (element.hasAttribute(CUSTOMIZER_REF_ATTRIBUTE)) { String customizerBeanName = element.getAttribute(CUSTOMIZER_REF_ATTRIBUTE); if (!StringUtils.hasText(customizerBeanName)) { parserContext.getReaderContext().error("Attribute 'customizer-ref' has empty value", element); - } - else { + } else { cav.addIndexedArgumentValue(constructorArgNum++, new RuntimeBeanReference(customizerBeanName)); } } @@ -197,15 +198,12 @@ class ScriptBeanDefinitionParser extends AbstractBeanDefinitionParser { if (hasScriptSource && !elements.isEmpty()) { readerContext.error("Only one of 'script-source' and 'inline-script' should be specified.", element); return null; - } - else if (hasScriptSource) { + } else if (hasScriptSource) { return element.getAttribute(SCRIPT_SOURCE_ATTRIBUTE); - } - else if (!elements.isEmpty()) { + } else if (!elements.isEmpty()) { Element inlineElement = (Element) elements.get(0); return "inline:" + DomUtils.getTextValue(inlineElement); - } - else { + } else { readerContext.error("Must specify either 'script-source' or 'inline-script'.", element); return null; } diff --git a/org.springframework.context/src/main/java/org/springframework/scripting/config/ScriptingDefaultsParser.java b/org.springframework.context/src/main/java/org/springframework/scripting/config/ScriptingDefaultsParser.java index 14ca12e2e0e..8f91d49083c 100644 --- a/org.springframework.context/src/main/java/org/springframework/scripting/config/ScriptingDefaultsParser.java +++ b/org.springframework.context/src/main/java/org/springframework/scripting/config/ScriptingDefaultsParser.java @@ -19,6 +19,7 @@ package org.springframework.scripting.config; import org.w3c.dom.Element; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.TypedStringValue; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.util.StringUtils; @@ -31,6 +32,8 @@ public class ScriptingDefaultsParser implements BeanDefinitionParser { private static final String REFRESH_CHECK_DELAY_ATTRIBUTE = "refresh-check-delay"; + private static final String PROXY_TARGET_CLASS_ATTRIBUTE = "proxy-target-class"; + public BeanDefinition parse(Element element, ParserContext parserContext) { BeanDefinition bd = @@ -39,6 +42,10 @@ public class ScriptingDefaultsParser implements BeanDefinitionParser { if (StringUtils.hasText(refreshCheckDelay)) { bd.getPropertyValues().add("defaultRefreshCheckDelay", new Long(refreshCheckDelay)); } + String proxyTargetClass = element.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE); + if (StringUtils.hasText(proxyTargetClass)) { + bd.getPropertyValues().add("defaultProxyTargetClass", new TypedStringValue(proxyTargetClass, Boolean.class)); + } return null; } diff --git a/org.springframework.context/src/main/java/org/springframework/scripting/support/ScriptFactoryPostProcessor.java b/org.springframework.context/src/main/java/org/springframework/scripting/support/ScriptFactoryPostProcessor.java index 28c6bc6a62e..2bf5a2e58f5 100644 --- a/org.springframework.context/src/main/java/org/springframework/scripting/support/ScriptFactoryPostProcessor.java +++ b/org.springframework.context/src/main/java/org/springframework/scripting/support/ScriptFactoryPostProcessor.java @@ -45,6 +45,7 @@ import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter; import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionValidationException; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.context.ResourceLoaderAware; @@ -135,8 +136,8 @@ import org.springframework.util.StringUtils; * @author Mark Fisher * @since 2.0 */ -public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProcessorAdapter - implements BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware, DisposableBean, Ordered { +public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements + BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware, DisposableBean, Ordered { /** * The {@link org.springframework.core.io.Resource}-style prefix that denotes @@ -146,19 +147,26 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces */ public static final String INLINE_SCRIPT_PREFIX = "inline:"; - public static final String REFRESH_CHECK_DELAY_ATTRIBUTE = - Conventions.getQualifiedAttributeName(ScriptFactoryPostProcessor.class, "refreshCheckDelay"); + public static final String REFRESH_CHECK_DELAY_ATTRIBUTE = Conventions.getQualifiedAttributeName( + ScriptFactoryPostProcessor.class, "refreshCheckDelay"); + + public static final String PROXY_TARGET_CLASS_ATTRIBUTE = Conventions.getQualifiedAttributeName( + ScriptFactoryPostProcessor.class, "proxyTargetClass"); + + public static final String LANGUAGE_ATTRIBUTE = Conventions.getQualifiedAttributeName( + ScriptFactoryPostProcessor.class, "language"); private static final String SCRIPT_FACTORY_NAME_PREFIX = "scriptFactory."; private static final String SCRIPTED_OBJECT_NAME_PREFIX = "scriptedObject."; - /** Logger available to subclasses */ protected final Log logger = LogFactory.getLog(getClass()); private long defaultRefreshCheckDelay = -1; + private boolean defaultProxyTargetClass = false; + private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); private ConfigurableBeanFactory beanFactory; @@ -170,7 +178,6 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces /** Map from bean name String to ScriptSource object */ private final Map scriptSourceCache = new HashMap(); - /** * Set the delay between refresh checks, in milliseconds. * Default is -1, indicating no refresh checks at all. @@ -183,14 +190,22 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces this.defaultRefreshCheckDelay = defaultRefreshCheckDelay; } + /** + * Flag to signal that refreshable proxies should be created to proxy the target class not its interfaces. + * @param defaultProxyTargetClass the flag value to set + */ + public void setDefaultProxyTargetClass(boolean defaultProxyTargetClass) { + this.defaultProxyTargetClass = defaultProxyTargetClass; + } + public void setBeanClassLoader(ClassLoader classLoader) { this.beanClassLoader = classLoader; } public void setBeanFactory(BeanFactory beanFactory) { if (!(beanFactory instanceof ConfigurableBeanFactory)) { - throw new IllegalStateException("ScriptFactoryPostProcessor doesn't work with a BeanFactory " + - "which does not implement ConfigurableBeanFactory: " + beanFactory.getClass()); + throw new IllegalStateException("ScriptFactoryPostProcessor doesn't work with a BeanFactory " + + "which does not implement ConfigurableBeanFactory: " + beanFactory.getClass()); } this.beanFactory = (ConfigurableBeanFactory) beanFactory; @@ -217,7 +232,6 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces return Integer.MIN_VALUE; } - @Override public Class predictBeanType(Class beanClass, String beanName) { // We only apply special treatment to ScriptFactory implementations here. @@ -233,18 +247,15 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces prepareScriptBeans(bd, scriptFactoryBeanName, scriptedObjectBeanName); ScriptFactory scriptFactory = this.scriptBeanFactory.getBean(scriptFactoryBeanName, ScriptFactory.class); - ScriptSource scriptSource = - getScriptSource(scriptFactoryBeanName, scriptFactory.getScriptSourceLocator()); + ScriptSource scriptSource = getScriptSource(scriptFactoryBeanName, scriptFactory.getScriptSourceLocator()); Class[] interfaces = scriptFactory.getScriptInterfaces(); Class scriptedType = scriptFactory.getScriptedObjectType(scriptSource); if (scriptedType != null) { return scriptedType; - } - else if (!ObjectUtils.isEmpty(interfaces)) { + } else if (!ObjectUtils.isEmpty(interfaces)) { return (interfaces.length == 1 ? interfaces[0] : createCompositeInterface(interfaces)); - } - else { + } else { if (bd.isSingleton()) { Object bean = this.scriptBeanFactory.getBean(scriptedObjectBeanName); if (bean != null) { @@ -252,15 +263,14 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces } } } - } - catch (Exception ex) { - if (ex instanceof BeanCreationException && - ((BeanCreationException) ex).getMostSpecificCause() instanceof BeanCurrentlyInCreationException) { + } catch (Exception ex) { + if (ex instanceof BeanCreationException + && ((BeanCreationException) ex).getMostSpecificCause() instanceof BeanCurrentlyInCreationException) { if (logger.isTraceEnabled()) { - logger.trace("Could not determine scripted object type for bean '" + beanName + "': " + ex.getMessage()); + logger.trace("Could not determine scripted object type for bean '" + beanName + "': " + + ex.getMessage()); } - } - else { + } else { if (logger.isDebugEnabled()) { logger.debug("Could not determine scripted object type for bean '" + beanName + "'", ex); } @@ -283,8 +293,7 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces prepareScriptBeans(bd, scriptFactoryBeanName, scriptedObjectBeanName); ScriptFactory scriptFactory = this.scriptBeanFactory.getBean(scriptFactoryBeanName, ScriptFactory.class); - ScriptSource scriptSource = - getScriptSource(scriptFactoryBeanName, scriptFactory.getScriptSourceLocator()); + ScriptSource scriptSource = getScriptSource(scriptFactoryBeanName, scriptFactory.getScriptSourceLocator()); boolean isFactoryBean = false; try { Class scriptedObjectType = scriptFactory.getScriptedObjectType(scriptSource); @@ -292,19 +301,25 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces if (scriptedObjectType != null) { isFactoryBean = FactoryBean.class.isAssignableFrom(scriptedObjectType); } - } - catch (Exception ex) { - throw new BeanCreationException( - beanName, "Could not determine scripted object type for " + scriptFactory, ex); + } catch (Exception ex) { + throw new BeanCreationException(beanName, "Could not determine scripted object type for " + scriptFactory, + ex); } long refreshCheckDelay = resolveRefreshCheckDelay(bd); if (refreshCheckDelay >= 0) { Class[] interfaces = scriptFactory.getScriptInterfaces(); - RefreshableScriptTargetSource ts = new RefreshableScriptTargetSource( - this.scriptBeanFactory, scriptedObjectBeanName, scriptFactory, scriptSource, isFactoryBean); + RefreshableScriptTargetSource ts = new RefreshableScriptTargetSource(this.scriptBeanFactory, + scriptedObjectBeanName, scriptFactory, scriptSource, isFactoryBean); + boolean proxyTargetClass = resolveProxyTargetClass(bd); + String language = (String) bd.getAttribute(LANGUAGE_ATTRIBUTE); + if (proxyTargetClass && (language==null || !language.equals("groovy"))) { + throw new BeanDefinitionValidationException( + "Cannot use proxyTargetClass=true with script beans where language is not groovy (found " + + language + ")"); + } ts.setRefreshCheckDelay(refreshCheckDelay); - return createRefreshableProxy(ts, interfaces); + return createRefreshableProxy(ts, interfaces, proxyTargetClass); } if (isFactoryBean) { @@ -313,7 +328,6 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces return this.scriptBeanFactory.getBean(scriptedObjectBeanName); } - /** * Prepare the script beans in the internal BeanFactory that this * post-processor uses. Each original bean definition will be split @@ -322,18 +336,18 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces * @param scriptFactoryBeanName the name of the internal ScriptFactory bean * @param scriptedObjectBeanName the name of the internal scripted object bean */ - protected void prepareScriptBeans( - BeanDefinition bd, String scriptFactoryBeanName, String scriptedObjectBeanName) { + protected void prepareScriptBeans(BeanDefinition bd, String scriptFactoryBeanName, String scriptedObjectBeanName) { // Avoid recreation of the script bean definition in case of a prototype. synchronized (this.scriptBeanFactory) { if (!this.scriptBeanFactory.containsBeanDefinition(scriptedObjectBeanName)) { - this.scriptBeanFactory.registerBeanDefinition( - scriptFactoryBeanName, createScriptFactoryBeanDefinition(bd)); - ScriptFactory scriptFactory = this.scriptBeanFactory.getBean(scriptFactoryBeanName, ScriptFactory.class); - ScriptSource scriptSource = - getScriptSource(scriptFactoryBeanName, scriptFactory.getScriptSourceLocator()); + this.scriptBeanFactory.registerBeanDefinition(scriptFactoryBeanName, + createScriptFactoryBeanDefinition(bd)); + ScriptFactory scriptFactory = this.scriptBeanFactory + .getBean(scriptFactoryBeanName, ScriptFactory.class); + ScriptSource scriptSource = getScriptSource(scriptFactoryBeanName, + scriptFactory.getScriptSourceLocator()); Class[] interfaces = scriptFactory.getScriptInterfaces(); Class[] scriptedInterfaces = interfaces; @@ -342,8 +356,8 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces scriptedInterfaces = ObjectUtils.addObjectToArray(interfaces, configInterface); } - BeanDefinition objectBd = createScriptedObjectBeanDefinition( - bd, scriptFactoryBeanName, scriptSource, scriptedInterfaces); + BeanDefinition objectBd = createScriptedObjectBeanDefinition(bd, scriptFactoryBeanName, scriptSource, + scriptedInterfaces); long refreshCheckDelay = resolveRefreshCheckDelay(bd); if (refreshCheckDelay >= 0) { objectBd.setScope(BeanDefinition.SCOPE_PROTOTYPE); @@ -369,18 +383,31 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces Object attributeValue = beanDefinition.getAttribute(REFRESH_CHECK_DELAY_ATTRIBUTE); if (attributeValue instanceof Number) { refreshCheckDelay = ((Number) attributeValue).longValue(); - } - else if (attributeValue instanceof String) { + } else if (attributeValue instanceof String) { refreshCheckDelay = Long.parseLong((String) attributeValue); - } - else if (attributeValue != null) { - throw new BeanDefinitionStoreException( - "Invalid refresh check delay attribute [" + REFRESH_CHECK_DELAY_ATTRIBUTE + - "] with value [" + attributeValue + "]: needs to be of type Number or String"); + } else if (attributeValue != null) { + throw new BeanDefinitionStoreException("Invalid refresh check delay attribute [" + + REFRESH_CHECK_DELAY_ATTRIBUTE + "] with value [" + attributeValue + + "]: needs to be of type Number or String"); } return refreshCheckDelay; } + protected boolean resolveProxyTargetClass(BeanDefinition beanDefinition) { + boolean proxyTargetClass = this.defaultProxyTargetClass; + Object attributeValue = beanDefinition.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE); + if (attributeValue instanceof Boolean) { + proxyTargetClass = ((Boolean) attributeValue).booleanValue(); + } else if (attributeValue instanceof String) { + proxyTargetClass = new Boolean((String) attributeValue); + } else if (attributeValue != null) { + throw new BeanDefinitionStoreException("Invalid refresh check delay attribute [" + + REFRESH_CHECK_DELAY_ATTRIBUTE + "] with value [" + attributeValue + + "]: needs to be of type Number or String"); + } + return proxyTargetClass; + } + /** * Create a ScriptFactory bean definition based on the given script definition, * extracting only the definition data that is relevant for the ScriptFactory @@ -425,13 +452,12 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces * @param resourceLoader the ResourceLoader to use (if necessary) * @return the ScriptSource instance */ - protected ScriptSource convertToScriptSource( - String beanName, String scriptSourceLocator, ResourceLoader resourceLoader) { + protected ScriptSource convertToScriptSource(String beanName, String scriptSourceLocator, + ResourceLoader resourceLoader) { if (scriptSourceLocator.startsWith(INLINE_SCRIPT_PREFIX)) { return new StaticScriptSource(scriptSourceLocator.substring(INLINE_SCRIPT_PREFIX.length()), beanName); - } - else { + } else { return new ResourceScriptSource(resourceLoader.getResource(scriptSourceLocator)); } } @@ -457,7 +483,7 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces String propertyName = pv.getName(); Class propertyType = BeanUtils.findPropertyType(propertyName, interfaces); String setterName = "set" + StringUtils.capitalize(propertyName); - Signature signature = new Signature(setterName, Type.VOID_TYPE, new Type[] {Type.getType(propertyType)}); + Signature signature = new Signature(setterName, Type.VOID_TYPE, new Type[] { Type.getType(propertyType) }); maker.add(signature, new Type[0]); } if (bd instanceof AbstractBeanDefinition) { @@ -498,8 +524,8 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces * @return the extracted ScriptFactory bean definition * @see org.springframework.scripting.ScriptFactory#getScriptedObject */ - protected BeanDefinition createScriptedObjectBeanDefinition( - BeanDefinition bd, String scriptFactoryBeanName, ScriptSource scriptSource, Class[] interfaces) { + protected BeanDefinition createScriptedObjectBeanDefinition(BeanDefinition bd, String scriptFactoryBeanName, + ScriptSource scriptSource, Class[] interfaces) { GenericBeanDefinition objectBd = new GenericBeanDefinition(bd); objectBd.setFactoryBeanName(scriptFactoryBeanName); @@ -518,23 +544,27 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces * @return the generated proxy * @see RefreshableScriptTargetSource */ - protected Object createRefreshableProxy(TargetSource ts, Class[] interfaces) { + protected Object createRefreshableProxy(TargetSource ts, Class[] interfaces, boolean proxyTargetClass) { ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setTargetSource(ts); + ClassLoader classLoader = this.beanClassLoader; if (interfaces == null) { interfaces = ClassUtils.getAllInterfacesForClass(ts.getTargetClass(), this.beanClassLoader); } proxyFactory.setInterfaces(interfaces); + if (proxyTargetClass) { + classLoader = null; // Force use of Class.getClassLoader() + proxyFactory.setProxyTargetClass(proxyTargetClass); + } DelegatingIntroductionInterceptor introduction = new DelegatingIntroductionInterceptor(ts); introduction.suppressInterface(TargetSource.class); proxyFactory.addAdvice(introduction); - return proxyFactory.getProxy(this.beanClassLoader); + return proxyFactory.getProxy(classLoader); } - /** * Destroy the inner bean factory (used for scripts) on shutdown. */ diff --git a/org.springframework.context/src/main/resources/org/springframework/scripting/config/spring-lang-3.1.xsd b/org.springframework.context/src/main/resources/org/springframework/scripting/config/spring-lang-3.1.xsd index 6659a769449..edce1324f4d 100644 --- a/org.springframework.context/src/main/resources/org/springframework/scripting/config/spring-lang-3.1.xsd +++ b/org.springframework.context/src/main/resources/org/springframework/scripting/config/spring-lang-3.1.xsd @@ -30,28 +30,49 @@ - + + + + + + + + - + + + + + + + + - + + + + + + + + @@ -77,7 +98,6 @@ - - + + + + + + + + + + diff --git a/org.springframework.context/src/test/java/org/springframework/scripting/config/ScriptingDefaultsTests.java b/org.springframework.context/src/test/java/org/springframework/scripting/config/ScriptingDefaultsTests.java index faccf0f190b..2aa12752903 100644 --- a/org.springframework.context/src/test/java/org/springframework/scripting/config/ScriptingDefaultsTests.java +++ b/org.springframework.context/src/test/java/org/springframework/scripting/config/ScriptingDefaultsTests.java @@ -21,6 +21,7 @@ import java.lang.reflect.Field; import junit.framework.TestCase; import org.springframework.aop.framework.Advised; +import org.springframework.aop.support.AopUtils; import org.springframework.aop.target.dynamic.AbstractRefreshableTargetSource; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; @@ -32,7 +33,10 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; public class ScriptingDefaultsTests extends TestCase { private static final String CONFIG = - "org/springframework/scripting/config/scriptingDefaultsTests.xml"; + "org/springframework/scripting/config/scriptingDefaultsTests.xml"; + + private static final String PROXY_CONFIG = + "org/springframework/scripting/config/scriptingDefaultsProxyTargetClassTests.xml"; public void testDefaultRefreshCheckDelay() throws Exception { @@ -73,4 +77,10 @@ public class ScriptingDefaultsTests extends TestCase { assertEquals(otherBean, testBean.getOtherBean()); } + public void testDefaultProxyTargetClass() { + ApplicationContext context = new ClassPathXmlApplicationContext(PROXY_CONFIG); + Object testBean = context.getBean("testBean"); + assertTrue(AopUtils.isCglibProxy(testBean)); + } + } diff --git a/org.springframework.context/src/test/java/org/springframework/scripting/groovy/ConcreteMessenger.java b/org.springframework.context/src/test/java/org/springframework/scripting/groovy/ConcreteMessenger.java new file mode 100644 index 00000000000..6aab4f5df39 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/scripting/groovy/ConcreteMessenger.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * 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 org.springframework.scripting.groovy; + +import org.springframework.scripting.ConfigurableMessenger; + +/** + * @author Dave Syer + * + */ +public class ConcreteMessenger implements ConfigurableMessenger { + + private String message; + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + +} diff --git a/org.springframework.context/src/test/java/org/springframework/scripting/groovy/GroovyScriptFactoryTests.java b/org.springframework.context/src/test/java/org/springframework/scripting/groovy/GroovyScriptFactoryTests.java index b187a9b39a2..985b2531e66 100644 --- a/org.springframework.context/src/test/java/org/springframework/scripting/groovy/GroovyScriptFactoryTests.java +++ b/org.springframework.context/src/test/java/org/springframework/scripting/groovy/GroovyScriptFactoryTests.java @@ -16,7 +16,13 @@ package org.springframework.scripting.groovy; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import groovy.lang.DelegatingMetaClass; import groovy.lang.GroovyObject; @@ -178,8 +184,7 @@ public class GroovyScriptFactoryTests { try { new ClassPathXmlApplicationContext("org/springframework/scripting/groovy/groovyBrokenContext.xml"); fail("Should throw exception for broken script file"); - } - catch (NestedRuntimeException ex) { + } catch (NestedRuntimeException ex) { assertTrue("Wrong root cause: " + ex, ex.contains(ScriptCompilationException.class)); } } @@ -194,12 +199,12 @@ public class GroovyScriptFactoryTests { script.suggestedClassName(); mock.setReturnValue("someName"); mock.replay(); - GroovyScriptFactory factory = new GroovyScriptFactory(ScriptFactoryPostProcessor.INLINE_SCRIPT_PREFIX + badScript); + GroovyScriptFactory factory = new GroovyScriptFactory(ScriptFactoryPostProcessor.INLINE_SCRIPT_PREFIX + + badScript); try { - factory.getScriptedObject(script, new Class[]{}); + factory.getScriptedObject(script, new Class[] {}); fail("Must have thrown a ScriptCompilationException (no public no-arg ctor in scripted class)."); - } - catch (ScriptCompilationException expected) { + } catch (ScriptCompilationException expected) { assertTrue(expected.contains(InstantiationException.class)); } mock.verify(); @@ -215,12 +220,12 @@ public class GroovyScriptFactoryTests { script.suggestedClassName(); mock.setReturnValue("someName"); mock.replay(); - GroovyScriptFactory factory = new GroovyScriptFactory(ScriptFactoryPostProcessor.INLINE_SCRIPT_PREFIX + badScript); + GroovyScriptFactory factory = new GroovyScriptFactory(ScriptFactoryPostProcessor.INLINE_SCRIPT_PREFIX + + badScript); try { - factory.getScriptedObject(script, new Class[]{}); + factory.getScriptedObject(script, new Class[] {}); fail("Must have thrown a ScriptCompilationException (no oublic no-arg ctor in scripted class)."); - } - catch (ScriptCompilationException expected) { + } catch (ScriptCompilationException expected) { assertTrue(expected.contains(IllegalAccessException.class)); } mock.verify(); @@ -254,8 +259,7 @@ public class GroovyScriptFactoryTests { try { new GroovyScriptFactory(null); fail("Must have thrown exception by this point."); - } - catch (IllegalArgumentException expected) { + } catch (IllegalArgumentException expected) { } } @@ -264,8 +268,7 @@ public class GroovyScriptFactoryTests { try { new GroovyScriptFactory(""); fail("Must have thrown exception by this point."); - } - catch (IllegalArgumentException expected) { + } catch (IllegalArgumentException expected) { } } @@ -274,8 +277,7 @@ public class GroovyScriptFactoryTests { try { new GroovyScriptFactory("\n "); fail("Must have thrown exception by this point."); - } - catch (IllegalArgumentException expected) { + } catch (IllegalArgumentException expected) { } } @@ -284,8 +286,7 @@ public class GroovyScriptFactoryTests { try { new ClassPathXmlApplicationContext("lwspBadGroovyContext.xml", getClass()); fail("Must have thrown a BeanCreationException ('inline:' prefix was preceded by whitespace"); - } - catch (BeanCreationException expected) { + } catch (BeanCreationException expected) { assertTrue(expected.contains(FileNotFoundException.class)); } } @@ -312,12 +313,12 @@ public class GroovyScriptFactoryTests { try { factory.getScriptedObject(null, null); fail("Must have thrown a NullPointerException as per contract ('null' ScriptSource supplied"); - } - catch (NullPointerException expected) { + } catch (NullPointerException expected) { } } - @Ignore // see http://build.springframework.org/browse/SPR-TRUNKQUICK-908 + @Ignore + // see http://build.springframework.org/browse/SPR-TRUNKQUICK-908 @Test public void testResourceScriptFromTag() throws Exception { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("groovy-with-xsd.xml", getClass()); @@ -375,6 +376,32 @@ public class GroovyScriptFactoryTests { assertTrue(ctx.getBeansOfType(Messenger.class).values().contains(messenger)); } + @Test + // Test for SPR-6268 + public void testRefreshableFromTagProxyTargetClass() throws Exception { + ApplicationContext ctx = new ClassPathXmlApplicationContext("groovy-with-xsd-proxy-target-class.xml", + getClass()); + assertTrue(Arrays.asList(ctx.getBeanNamesForType(Messenger.class)).contains("refreshableMessenger")); + + Messenger messenger = (Messenger) ctx.getBean("refreshableMessenger"); + + assertTrue(AopUtils.isAopProxy(messenger)); + assertTrue(messenger instanceof Refreshable); + assertEquals("Hello World!", messenger.getMessage()); + + assertTrue(ctx.getBeansOfType(ConcreteMessenger.class).values().contains(messenger)); + } + + @Test + // Test for SPR-6268 + public void testProxyTargetClassNotAllowedIfNotGroovy() throws Exception { + try { + new ClassPathXmlApplicationContext("jruby-with-xsd-proxy-target-class.xml", getClass()); + } catch (BeanCreationException e) { + assertTrue(e.getMessage().contains("Cannot use proxyTargetClass=true")); + } + } + @Test public void testAnonymousScriptDetected() throws Exception { ApplicationContext ctx = new ClassPathXmlApplicationContext("groovy-with-xsd.xml", getClass()); @@ -404,8 +431,7 @@ public class GroovyScriptFactoryTests { try { ctx.getBean("bean3"); fail("Should have thrown BeanCreationException"); - } - catch (BeanCreationException ex) { + } catch (BeanCreationException ex) { // expected assertTrue(ex.contains(UnsatisfiedDependencyException.class)); } @@ -424,8 +450,7 @@ public class GroovyScriptFactoryTests { private void testMetaClass(final String xmlFile) { // expect the exception we threw in the custom metaclass to show it got invoked try { - ApplicationContext ctx = - new ClassPathXmlApplicationContext(xmlFile); + ApplicationContext ctx = new ClassPathXmlApplicationContext(xmlFile); Calculator calc = (Calculator) ctx.getBean("delegatingCalculator"); calc.add(1, 2); fail("expected IllegalStateException"); @@ -454,7 +479,6 @@ public class GroovyScriptFactoryTests { assertEquals("test", result); } - public static class TestCustomizer implements GroovyObjectCustomizer { public void customize(GroovyObject goo) { @@ -462,8 +486,7 @@ public class GroovyScriptFactoryTests { public Object invokeMethod(Object arg0, String mName, Object[] arg2) { if (mName.indexOf("Missing") != -1) { throw new IllegalStateException("Gotcha"); - } - else { + } else { return super.invokeMethod(arg0, mName, arg2); } } diff --git a/org.springframework.context/src/test/java/org/springframework/scripting/groovy/Messenger.groovy b/org.springframework.context/src/test/java/org/springframework/scripting/groovy/Messenger.groovy index 6f411a695c5..1d10ef39e52 100644 --- a/org.springframework.context/src/test/java/org/springframework/scripting/groovy/Messenger.groovy +++ b/org.springframework.context/src/test/java/org/springframework/scripting/groovy/Messenger.groovy @@ -2,7 +2,5 @@ package org.springframework.scripting.groovy; import org.springframework.scripting.ConfigurableMessenger -class GroovyMessenger implements ConfigurableMessenger { - - def String message; +class GroovyMessenger extends ConcreteMessenger { } diff --git a/org.springframework.context/src/test/java/org/springframework/scripting/groovy/groovy-with-xsd-proxy-target-class.xml b/org.springframework.context/src/test/java/org/springframework/scripting/groovy/groovy-with-xsd-proxy-target-class.xml new file mode 100644 index 00000000000..973b317a16d --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/scripting/groovy/groovy-with-xsd-proxy-target-class.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/org.springframework.context/src/test/java/org/springframework/scripting/groovy/jruby-with-xsd-proxy-target-class.xml b/org.springframework.context/src/test/java/org/springframework/scripting/groovy/jruby-with-xsd-proxy-target-class.xml new file mode 100644 index 00000000000..7083ecf7b77 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/scripting/groovy/jruby-with-xsd-proxy-target-class.xml @@ -0,0 +1,17 @@ + + + + + + + + + + diff --git a/org.springframework.context/src/test/resources/org/springframework/scripting/config/scriptingDefaultsProxyTargetClassTests.xml b/org.springframework.context/src/test/resources/org/springframework/scripting/config/scriptingDefaultsProxyTargetClassTests.xml new file mode 100644 index 00000000000..f51d1563ca9 --- /dev/null +++ b/org.springframework.context/src/test/resources/org/springframework/scripting/config/scriptingDefaultsProxyTargetClassTests.xml @@ -0,0 +1,17 @@ + + + + + + + +