/*
 * Copyright 2002-2019 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
 *
 *      https://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.beans.factory.groovy;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import groovy.lang.Binding;
import groovy.lang.Closure;
import groovy.lang.GString;
import groovy.lang.GroovyObject;
import groovy.lang.GroovyObjectSupport;
import groovy.lang.GroovyShell;
import groovy.lang.GroovySystem;
import groovy.lang.MetaClass;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.codehaus.groovy.runtime.InvokerHelper;

import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.parsing.Location;
import org.springframework.beans.factory.parsing.Problem;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
import org.springframework.beans.factory.xml.NamespaceHandler;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.beans.factory.xml.XmlReaderContext;
import org.springframework.core.io.DescriptiveResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

A Groovy-based reader for Spring bean definitions: like a Groovy builder, but more of a DSL for Spring configuration.

This bean definition reader also understands XML bean definition files, allowing for seamless mixing and matching with Groovy bean definition files.

Typically applied to a DefaultListableBeanFactory or a GenericApplicationContext, but can be used against any BeanDefinitionRegistry implementation.

Example Syntax

import org.hibernate.SessionFactory
import org.apache.commons.dbcp.BasicDataSource
def reader = new GroovyBeanDefinitionReader(myApplicationContext)
reader.beans {
    dataSource(BasicDataSource) {                  // <--- invokeMethod
        driverClassName = "org.hsqldb.jdbcDriver"
        url = "jdbc:hsqldb:mem:grailsDB"
        username = "sa"                            // <-- setProperty
        password = ""
        settings = [mynew:"setting"]
    }
    sessionFactory(SessionFactory) {
        dataSource = dataSource                    // <-- getProperty for retrieving references
    }
    myService(MyService) {
        nestedBean = { AnotherBean bean ->         // <-- setProperty with closure for nested bean
            dataSource = dataSource
        }
    }
}

You can also load resources containing beans defined in a Groovy script using either the AbstractBeanDefinitionReader.loadBeanDefinitions(Resource...) or AbstractBeanDefinitionReader.loadBeanDefinitions(String...) method, with a script looking similar to the following.

import org.hibernate.SessionFactory
import org.apache.commons.dbcp.BasicDataSource
beans {
    dataSource(BasicDataSource) {
        driverClassName = "org.hsqldb.jdbcDriver"
        url = "jdbc:hsqldb:mem:grailsDB"
        username = "sa"
        password = ""
        settings = [mynew:"setting"]
    }
    sessionFactory(SessionFactory) {
        dataSource = dataSource
    }
    myService(MyService) {
        nestedBean = { AnotherBean bean ->
            dataSource = dataSource
        }
    }
}
Author:Jeff Brown, Graeme Rocher, Juergen Hoeller, Sam Brannen
See Also:
Since:4.0
/** * A Groovy-based reader for Spring bean definitions: like a Groovy builder, * but more of a DSL for Spring configuration. * * <p>This bean definition reader also understands XML bean definition files, * allowing for seamless mixing and matching with Groovy bean definition files. * * <p>Typically applied to a * {@link org.springframework.beans.factory.support.DefaultListableBeanFactory} * or a {@link org.springframework.context.support.GenericApplicationContext}, * but can be used against any {@link BeanDefinitionRegistry} implementation. * * <h3>Example Syntax</h3> * <pre class="code"> * import org.hibernate.SessionFactory * import org.apache.commons.dbcp.BasicDataSource * * def reader = new GroovyBeanDefinitionReader(myApplicationContext) * reader.beans { * dataSource(BasicDataSource) { // <--- invokeMethod * driverClassName = "org.hsqldb.jdbcDriver" * url = "jdbc:hsqldb:mem:grailsDB" * username = "sa" // <-- setProperty * password = "" * settings = [mynew:"setting"] * } * sessionFactory(SessionFactory) { * dataSource = dataSource // <-- getProperty for retrieving references * } * myService(MyService) { * nestedBean = { AnotherBean bean -> // <-- setProperty with closure for nested bean * dataSource = dataSource * } * } * }</pre> * * <p>You can also load resources containing beans defined in a Groovy script using * either the {@link #loadBeanDefinitions(Resource...)} or * {@link #loadBeanDefinitions(String...)} method, with a script looking similar to * the following. * * <pre class="code"> * import org.hibernate.SessionFactory * import org.apache.commons.dbcp.BasicDataSource * * beans { * dataSource(BasicDataSource) { * driverClassName = "org.hsqldb.jdbcDriver" * url = "jdbc:hsqldb:mem:grailsDB" * username = "sa" * password = "" * settings = [mynew:"setting"] * } * sessionFactory(SessionFactory) { * dataSource = dataSource * } * myService(MyService) { * nestedBean = { AnotherBean bean -> * dataSource = dataSource * } * } * }</pre> * * @author Jeff Brown * @author Graeme Rocher * @author Juergen Hoeller * @author Sam Brannen * @since 4.0 * @see BeanDefinitionRegistry * @see org.springframework.beans.factory.support.DefaultListableBeanFactory * @see org.springframework.context.support.GenericApplicationContext * @see org.springframework.context.support.GenericGroovyApplicationContext */
public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader implements GroovyObject {
Standard XmlBeanDefinitionReader created with default settings for loading bean definitions from XML files.
/** * Standard {@code XmlBeanDefinitionReader} created with default * settings for loading bean definitions from XML files. */
private final XmlBeanDefinitionReader standardXmlBeanDefinitionReader;
Groovy DSL XmlBeanDefinitionReader for loading bean definitions via the Groovy DSL, typically configured with XML validation disabled.
/** * Groovy DSL {@code XmlBeanDefinitionReader} for loading bean definitions * via the Groovy DSL, typically configured with XML validation disabled. */
private final XmlBeanDefinitionReader groovyDslXmlBeanDefinitionReader; private final Map<String, String> namespaces = new HashMap<>(); private final Map<String, DeferredProperty> deferredProperties = new HashMap<>(); private MetaClass metaClass = GroovySystem.getMetaClassRegistry().getMetaClass(getClass()); private Binding binding; private GroovyBeanDefinitionWrapper currentBeanDefinition;
Create a new GroovyBeanDefinitionReader for the given BeanDefinitionRegistry.
Params:
  • registry – the BeanDefinitionRegistry to load bean definitions into
/** * Create a new {@code GroovyBeanDefinitionReader} for the given * {@link BeanDefinitionRegistry}. * @param registry the {@code BeanDefinitionRegistry} to load bean definitions into */
public GroovyBeanDefinitionReader(BeanDefinitionRegistry registry) { super(registry); this.standardXmlBeanDefinitionReader = new XmlBeanDefinitionReader(registry); this.groovyDslXmlBeanDefinitionReader = new XmlBeanDefinitionReader(registry); this.groovyDslXmlBeanDefinitionReader.setValidating(false); }
Create a new GroovyBeanDefinitionReader based on the given XmlBeanDefinitionReader, loading bean definitions into its BeanDefinitionRegistry and delegating Groovy DSL loading to it.

The supplied XmlBeanDefinitionReader should typically be pre-configured with XML validation disabled.

Params:
  • xmlBeanDefinitionReader – the XmlBeanDefinitionReader to derive the registry from and to delegate Groovy DSL loading to
/** * Create a new {@code GroovyBeanDefinitionReader} based on the given * {@link XmlBeanDefinitionReader}, loading bean definitions into its * {@code BeanDefinitionRegistry} and delegating Groovy DSL loading to it. * <p>The supplied {@code XmlBeanDefinitionReader} should typically * be pre-configured with XML validation disabled. * @param xmlBeanDefinitionReader the {@code XmlBeanDefinitionReader} to * derive the registry from and to delegate Groovy DSL loading to */
public GroovyBeanDefinitionReader(XmlBeanDefinitionReader xmlBeanDefinitionReader) { super(xmlBeanDefinitionReader.getRegistry()); this.standardXmlBeanDefinitionReader = new XmlBeanDefinitionReader(xmlBeanDefinitionReader.getRegistry()); this.groovyDslXmlBeanDefinitionReader = xmlBeanDefinitionReader; } @Override public void setMetaClass(MetaClass metaClass) { this.metaClass = metaClass; } @Override public MetaClass getMetaClass() { return this.metaClass; }
Set the binding, i.e. the Groovy variables available in the scope of a GroovyBeanDefinitionReader closure.
/** * Set the binding, i.e. the Groovy variables available in the scope * of a {@code GroovyBeanDefinitionReader} closure. */
public void setBinding(Binding binding) { this.binding = binding; }
Return a specified binding for Groovy variables, if any.
/** * Return a specified binding for Groovy variables, if any. */
public Binding getBinding() { return this.binding; } // TRADITIONAL BEAN DEFINITION READER METHODS
Load bean definitions from the specified Groovy script or XML file.

Note that ".xml" files will be parsed as XML content; all other kinds of resources will be parsed as Groovy scripts.

Params:
  • resource – the resource descriptor for the Groovy script or XML file
Throws:
Returns:the number of bean definitions found
/** * Load bean definitions from the specified Groovy script or XML file. * <p>Note that {@code ".xml"} files will be parsed as XML content; all other kinds * of resources will be parsed as Groovy scripts. * @param resource the resource descriptor for the Groovy script or XML file * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors */
@Override public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); }
Load bean definitions from the specified Groovy script or XML file.

Note that ".xml" files will be parsed as XML content; all other kinds of resources will be parsed as Groovy scripts.

Params:
  • encodedResource – the resource descriptor for the Groovy script or XML file, allowing specification of an encoding to use for parsing the file
Throws:
Returns:the number of bean definitions found
/** * Load bean definitions from the specified Groovy script or XML file. * <p>Note that {@code ".xml"} files will be parsed as XML content; all other kinds * of resources will be parsed as Groovy scripts. * @param encodedResource the resource descriptor for the Groovy script or XML file, * allowing specification of an encoding to use for parsing the file * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors */
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { // Check for XML files and redirect them to the "standard" XmlBeanDefinitionReader String filename = encodedResource.getResource().getFilename(); if (StringUtils.endsWithIgnoreCase(filename, ".xml")) { return this.standardXmlBeanDefinitionReader.loadBeanDefinitions(encodedResource); } if (logger.isTraceEnabled()) { logger.trace("Loading Groovy bean definitions from " + encodedResource); } @SuppressWarnings("serial") Closure<Object> beans = new Closure<Object>(this) { @Override public Object call(Object... args) { invokeBeanDefiningClosure((Closure<?>) args[0]); return null; } }; Binding binding = new Binding() { @Override public void setVariable(String name, Object value) { if (currentBeanDefinition != null) { applyPropertyToBeanDefinition(name, value); } else { super.setVariable(name, value); } } }; binding.setVariable("beans", beans); int countBefore = getRegistry().getBeanDefinitionCount(); try { GroovyShell shell = new GroovyShell(getBeanClassLoader(), binding); shell.evaluate(encodedResource.getReader(), "beans"); } catch (Throwable ex) { throw new BeanDefinitionParsingException(new Problem("Error evaluating Groovy script: " + ex.getMessage(), new Location(encodedResource.getResource()), null, ex)); } int count = getRegistry().getBeanDefinitionCount() - countBefore; if (logger.isDebugEnabled()) { logger.debug("Loaded " + count + " bean definitions from " + encodedResource); } return count; } // METHODS FOR CONSUMPTION IN A GROOVY CLOSURE
Defines a set of beans for the given block or closure.
Params:
  • closure – the block or closure
Returns:this GroovyBeanDefinitionReader instance
/** * Defines a set of beans for the given block or closure. * @param closure the block or closure * @return this {@code GroovyBeanDefinitionReader} instance */
public GroovyBeanDefinitionReader beans(Closure<?> closure) { return invokeBeanDefiningClosure(closure); }
Define an inner bean definition.
Params:
  • type – the bean type
Returns:the bean definition
/** * Define an inner bean definition. * @param type the bean type * @return the bean definition */
public GenericBeanDefinition bean(Class<?> type) { GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(type); return beanDefinition; }
Define an inner bean definition.
Params:
  • type – the bean type
  • args – the constructors arguments and closure configurer
Returns:the bean definition
/** * Define an inner bean definition. * @param type the bean type * @param args the constructors arguments and closure configurer * @return the bean definition */
public AbstractBeanDefinition bean(Class<?> type, Object...args) { GroovyBeanDefinitionWrapper current = this.currentBeanDefinition; try { Closure<?> callable = null; Collection<Object> constructorArgs = null; if (!ObjectUtils.isEmpty(args)) { int index = args.length; Object lastArg = args[index - 1]; if (lastArg instanceof Closure<?>) { callable = (Closure<?>) lastArg; index--; } constructorArgs = resolveConstructorArguments(args, 0, index); } this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(null, type, constructorArgs); if (callable != null) { callable.call(this.currentBeanDefinition); } return this.currentBeanDefinition.getBeanDefinition(); } finally { this.currentBeanDefinition = current; } }
Define a Spring XML namespace definition to use.
Params:
  • definition – the namespace definition
/** * Define a Spring XML namespace definition to use. * @param definition the namespace definition */
public void xmlns(Map<String, String> definition) { if (!definition.isEmpty()) { for (Map.Entry<String,String> entry : definition.entrySet()) { String namespace = entry.getKey(); String uri = entry.getValue(); if (uri == null) { throw new IllegalArgumentException("Namespace definition must supply a non-null URI"); } NamespaceHandler namespaceHandler = this.groovyDslXmlBeanDefinitionReader.getNamespaceHandlerResolver().resolve(uri); if (namespaceHandler == null) { throw new BeanDefinitionParsingException(new Problem("No namespace handler found for URI: " + uri, new Location(new DescriptiveResource(("Groovy"))))); } this.namespaces.put(namespace, uri); } } }
Import Spring bean definitions from either XML or Groovy sources into the current bean builder instance.
Params:
  • resourcePattern – the resource pattern
/** * Import Spring bean definitions from either XML or Groovy sources into the * current bean builder instance. * @param resourcePattern the resource pattern */
public void importBeans(String resourcePattern) throws IOException { loadBeanDefinitions(resourcePattern); } // INTERNAL HANDLING OF GROOVY CLOSURES AND PROPERTIES
This method overrides method invocation to create beans for each method name that takes a class argument.
/** * This method overrides method invocation to create beans for each method name that * takes a class argument. */
@Override public Object invokeMethod(String name, Object arg) { Object[] args = (Object[])arg; if ("beans".equals(name) && args.length == 1 && args[0] instanceof Closure) { return beans((Closure<?>) args[0]); } else if ("ref".equals(name)) { String refName; if (args[0] == null) { throw new IllegalArgumentException("Argument to ref() is not a valid bean or was not found"); } if (args[0] instanceof RuntimeBeanReference) { refName = ((RuntimeBeanReference) args[0]).getBeanName(); } else { refName = args[0].toString(); } boolean parentRef = false; if (args.length > 1 && args[1] instanceof Boolean) { parentRef = (Boolean) args[1]; } return new RuntimeBeanReference(refName, parentRef); } else if (this.namespaces.containsKey(name) && args.length > 0 && args[0] instanceof Closure) { GroovyDynamicElementReader reader = createDynamicElementReader(name); reader.invokeMethod("doCall", args); } else if (args.length > 0 && args[0] instanceof Closure) { // abstract bean definition return invokeBeanDefiningMethod(name, args); } else if (args.length > 0 && (args[0] instanceof Class || args[0] instanceof RuntimeBeanReference || args[0] instanceof Map)) { return invokeBeanDefiningMethod(name, args); } else if (args.length > 1 && args[args.length -1] instanceof Closure) { return invokeBeanDefiningMethod(name, args); } MetaClass mc = DefaultGroovyMethods.getMetaClass(getRegistry()); if (!mc.respondsTo(getRegistry(), name, args).isEmpty()){ return mc.invokeMethod(getRegistry(), name, args); } return this; } private boolean addDeferredProperty(String property, Object newValue) { if (newValue instanceof List || newValue instanceof Map) { this.deferredProperties.put(this.currentBeanDefinition.getBeanName() + '.' + property, new DeferredProperty(this.currentBeanDefinition, property, newValue)); return true; } return false; } private void finalizeDeferredProperties() { for (DeferredProperty dp : this.deferredProperties.values()) { if (dp.value instanceof List) { dp.value = manageListIfNecessary((List<?>) dp.value); } else if (dp.value instanceof Map) { dp.value = manageMapIfNecessary((Map<?, ?>) dp.value); } dp.apply(); } this.deferredProperties.clear(); }
When a method argument is only a closure it is a set of bean definitions.
Params:
  • callable – the closure argument
Returns:this GroovyBeanDefinitionReader instance
/** * When a method argument is only a closure it is a set of bean definitions. * @param callable the closure argument * @return this {@code GroovyBeanDefinitionReader} instance */
protected GroovyBeanDefinitionReader invokeBeanDefiningClosure(Closure<?> callable) { callable.setDelegate(this); callable.call(); finalizeDeferredProperties(); return this; }
This method is called when a bean definition node is called.
Params:
  • beanName – the name of the bean to define
  • args – the arguments to the bean. The first argument is the class name, the last argument is sometimes a closure. All the arguments in between are constructor arguments.
Returns:the bean definition wrapper
/** * This method is called when a bean definition node is called. * @param beanName the name of the bean to define * @param args the arguments to the bean. The first argument is the class name, the last * argument is sometimes a closure. All the arguments in between are constructor arguments. * @return the bean definition wrapper */
private GroovyBeanDefinitionWrapper invokeBeanDefiningMethod(String beanName, Object[] args) { boolean hasClosureArgument = (args[args.length - 1] instanceof Closure); if (args[0] instanceof Class) { Class<?> beanClass = (Class<?>) args[0]; if (hasClosureArgument) { if (args.length - 1 != 1) { this.currentBeanDefinition = new GroovyBeanDefinitionWrapper( beanName, beanClass, resolveConstructorArguments(args, 1, args.length - 1)); } else { this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName, beanClass); } } else { this.currentBeanDefinition = new GroovyBeanDefinitionWrapper( beanName, beanClass, resolveConstructorArguments(args, 1, args.length)); } } else if (args[0] instanceof RuntimeBeanReference) { this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName); this.currentBeanDefinition.getBeanDefinition().setFactoryBeanName(((RuntimeBeanReference) args[0]).getBeanName()); } else if (args[0] instanceof Map) { // named constructor arguments if (args.length > 1 && args[1] instanceof Class) { List<Object> constructorArgs = resolveConstructorArguments(args, 2, hasClosureArgument ? args.length - 1 : args.length); this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName, (Class<?>) args[1], constructorArgs); Map<?, ?> namedArgs = (Map<?, ?>) args[0]; for (Object o : namedArgs.keySet()) { String propName = (String) o; setProperty(propName, namedArgs.get(propName)); } } // factory method syntax else { this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName); // First arg is the map containing factoryBean : factoryMethod Map.Entry<?, ?> factoryBeanEntry = ((Map<?, ?>) args[0]).entrySet().iterator().next(); // If we have a closure body, that will be the last argument. // In between are the constructor args int constructorArgsTest = (hasClosureArgument ? 2 : 1); // If we have more than this number of args, we have constructor args if (args.length > constructorArgsTest){ // factory-method requires args int endOfConstructArgs = (hasClosureArgument ? args.length - 1 : args.length); this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName, null, resolveConstructorArguments(args, 1, endOfConstructArgs)); } else { this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName); } this.currentBeanDefinition.getBeanDefinition().setFactoryBeanName(factoryBeanEntry.getKey().toString()); this.currentBeanDefinition.getBeanDefinition().setFactoryMethodName(factoryBeanEntry.getValue().toString()); } } else if (args[0] instanceof Closure) { this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName); this.currentBeanDefinition.getBeanDefinition().setAbstract(true); } else { List<Object> constructorArgs = resolveConstructorArguments(args, 0, hasClosureArgument ? args.length - 1 : args.length); this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName, null, constructorArgs); } if (hasClosureArgument) { Closure<?> callable = (Closure<?>) args[args.length - 1]; callable.setDelegate(this); callable.setResolveStrategy(Closure.DELEGATE_FIRST); callable.call(this.currentBeanDefinition); } GroovyBeanDefinitionWrapper beanDefinition = this.currentBeanDefinition; this.currentBeanDefinition = null; beanDefinition.getBeanDefinition().setAttribute(GroovyBeanDefinitionWrapper.class.getName(), beanDefinition); getRegistry().registerBeanDefinition(beanName, beanDefinition.getBeanDefinition()); return beanDefinition; } protected List<Object> resolveConstructorArguments(Object[] args, int start, int end) { Object[] constructorArgs = Arrays.copyOfRange(args, start, end); for (int i = 0; i < constructorArgs.length; i++) { if (constructorArgs[i] instanceof GString) { constructorArgs[i] = constructorArgs[i].toString(); } else if (constructorArgs[i] instanceof List) { constructorArgs[i] = manageListIfNecessary((List<?>) constructorArgs[i]); } else if (constructorArgs[i] instanceof Map){ constructorArgs[i] = manageMapIfNecessary((Map<?, ?>) constructorArgs[i]); } } return Arrays.asList(constructorArgs); }
Checks whether there are any RuntimeBeanReferences inside the Map and converts it to a ManagedMap if necessary.
Params:
  • map – the original Map
Returns:either the original map or a managed copy of it
/** * Checks whether there are any {@link RuntimeBeanReference RuntimeBeanReferences} * inside the {@link Map} and converts it to a {@link ManagedMap} if necessary. * @param map the original Map * @return either the original map or a managed copy of it */
private Object manageMapIfNecessary(Map<?, ?> map) { boolean containsRuntimeRefs = false; for (Object element : map.values()) { if (element instanceof RuntimeBeanReference) { containsRuntimeRefs = true; break; } } if (containsRuntimeRefs) { Map<Object, Object> managedMap = new ManagedMap<>(); managedMap.putAll(map); return managedMap; } return map; }
Checks whether there are any RuntimeBeanReferences inside the List and converts it to a ManagedList if necessary.
Params:
  • list – the original List
Returns:either the original list or a managed copy of it
/** * Checks whether there are any {@link RuntimeBeanReference RuntimeBeanReferences} * inside the {@link List} and converts it to a {@link ManagedList} if necessary. * @param list the original List * @return either the original list or a managed copy of it */
private Object manageListIfNecessary(List<?> list) { boolean containsRuntimeRefs = false; for (Object element : list) { if (element instanceof RuntimeBeanReference) { containsRuntimeRefs = true; break; } } if (containsRuntimeRefs) { List<Object> managedList = new ManagedList<>(); managedList.addAll(list); return managedList; } return list; }
This method overrides property setting in the scope of the GroovyBeanDefinitionReader to set properties on the current bean definition.
/** * This method overrides property setting in the scope of the {@code GroovyBeanDefinitionReader} * to set properties on the current bean definition. */
@Override public void setProperty(String name, Object value) { if (this.currentBeanDefinition != null) { applyPropertyToBeanDefinition(name, value); } } protected void applyPropertyToBeanDefinition(String name, Object value) { if (value instanceof GString) { value = value.toString(); } if (addDeferredProperty(name, value)) { return; } else if (value instanceof Closure) { GroovyBeanDefinitionWrapper current = this.currentBeanDefinition; try { Closure<?> callable = (Closure<?>) value; Class<?> parameterType = callable.getParameterTypes()[0]; if (Object.class == parameterType) { this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(""); callable.call(this.currentBeanDefinition); } else { this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(null, parameterType); callable.call((Object) null); } value = this.currentBeanDefinition.getBeanDefinition(); } finally { this.currentBeanDefinition = current; } } this.currentBeanDefinition.addProperty(name, value); }
This method overrides property retrieval in the scope of the GroovyBeanDefinitionReader. A property retrieval will either:
  • Retrieve a variable from the bean builder's binding if it exists
  • Retrieve a RuntimeBeanReference for a specific bean if it exists
  • Otherwise just delegate to MetaClass.getProperty which will resolve properties from the GroovyBeanDefinitionReader itself
/** * This method overrides property retrieval in the scope of the * {@code GroovyBeanDefinitionReader}. A property retrieval will either: * <ul> * <li>Retrieve a variable from the bean builder's binding if it exists * <li>Retrieve a RuntimeBeanReference for a specific bean if it exists * <li>Otherwise just delegate to MetaClass.getProperty which will resolve * properties from the {@code GroovyBeanDefinitionReader} itself * </ul> */
@Override public Object getProperty(String name) { Binding binding = getBinding(); if (binding != null && binding.hasVariable(name)) { return binding.getVariable(name); } else { if (this.namespaces.containsKey(name)) { return createDynamicElementReader(name); } if (getRegistry().containsBeanDefinition(name)) { GroovyBeanDefinitionWrapper beanDefinition = (GroovyBeanDefinitionWrapper) getRegistry().getBeanDefinition(name).getAttribute(GroovyBeanDefinitionWrapper.class.getName()); if (beanDefinition != null) { return new GroovyRuntimeBeanReference(name, beanDefinition, false); } else { return new RuntimeBeanReference(name, false); } } // This is to deal with the case where the property setter is the last // statement in a closure (hence the return value) else if (this.currentBeanDefinition != null) { MutablePropertyValues pvs = this.currentBeanDefinition.getBeanDefinition().getPropertyValues(); if (pvs.contains(name)) { return pvs.get(name); } else { DeferredProperty dp = this.deferredProperties.get(this.currentBeanDefinition.getBeanName() + name); if (dp != null) { return dp.value; } else { return getMetaClass().getProperty(this, name); } } } else { return getMetaClass().getProperty(this, name); } } } private GroovyDynamicElementReader createDynamicElementReader(String namespace) { XmlReaderContext readerContext = this.groovyDslXmlBeanDefinitionReader.createReaderContext( new DescriptiveResource("Groovy")); BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext); boolean decorating = (this.currentBeanDefinition != null); if (!decorating) { this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(namespace); } return new GroovyDynamicElementReader(namespace, this.namespaces, delegate, this.currentBeanDefinition, decorating) { @Override protected void afterInvocation() { if (!this.decorating) { currentBeanDefinition = null; } } }; }
This class is used to defer the adding of a property to a bean definition until later. This is for a case where you assign a property to a list that may not contain bean references at that point of assignment, but may later; hence, it would need to be managed.
/** * This class is used to defer the adding of a property to a bean definition * until later. This is for a case where you assign a property to a list that * may not contain bean references at that point of assignment, but may later; * hence, it would need to be managed. */
private static class DeferredProperty { private final GroovyBeanDefinitionWrapper beanDefinition; private final String name; public Object value; public DeferredProperty(GroovyBeanDefinitionWrapper beanDefinition, String name, Object value) { this.beanDefinition = beanDefinition; this.name = name; this.value = value; } public void apply() { this.beanDefinition.addProperty(this.name, this.value); } }
A RuntimeBeanReference that takes care of adding new properties to runtime references.
/** * A RuntimeBeanReference that takes care of adding new properties to runtime references. */
private class GroovyRuntimeBeanReference extends RuntimeBeanReference implements GroovyObject { private final GroovyBeanDefinitionWrapper beanDefinition; private MetaClass metaClass; public GroovyRuntimeBeanReference(String beanName, GroovyBeanDefinitionWrapper beanDefinition, boolean toParent) { super(beanName, toParent); this.beanDefinition = beanDefinition; this.metaClass = InvokerHelper.getMetaClass(this); } @Override public MetaClass getMetaClass() { return this.metaClass; } @Override public Object getProperty(String property) { if (property.equals("beanName")) { return getBeanName(); } else if (property.equals("source")) { return getSource(); } else if (this.beanDefinition != null) { return new GroovyPropertyValue( property, this.beanDefinition.getBeanDefinition().getPropertyValues().get(property)); } else { return this.metaClass.getProperty(this, property); } } @Override public Object invokeMethod(String name, Object args) { return this.metaClass.invokeMethod(this, name, args); } @Override public void setMetaClass(MetaClass metaClass) { this.metaClass = metaClass; } @Override public void setProperty(String property, Object newValue) { if (!addDeferredProperty(property, newValue)) { this.beanDefinition.getBeanDefinition().getPropertyValues().add(property, newValue); } }
Wraps a bean definition property and ensures that any RuntimeBeanReference additions to it are deferred for resolution later.
/** * Wraps a bean definition property and ensures that any RuntimeBeanReference * additions to it are deferred for resolution later. */
private class GroovyPropertyValue extends GroovyObjectSupport { private final String propertyName; private final Object propertyValue; public GroovyPropertyValue(String propertyName, Object propertyValue) { this.propertyName = propertyName; this.propertyValue = propertyValue; } @SuppressWarnings("unused") public void leftShift(Object value) { InvokerHelper.invokeMethod(this.propertyValue, "leftShift", value); updateDeferredProperties(value); } @SuppressWarnings("unused") public boolean add(Object value) { boolean retVal = (Boolean) InvokerHelper.invokeMethod(this.propertyValue, "add", value); updateDeferredProperties(value); return retVal; } @SuppressWarnings("unused") public boolean addAll(Collection<?> values) { boolean retVal = (Boolean) InvokerHelper.invokeMethod(this.propertyValue, "addAll", values); for (Object value : values) { updateDeferredProperties(value); } return retVal; } @Override public Object invokeMethod(String name, Object args) { return InvokerHelper.invokeMethod(this.propertyValue, name, args); } @Override public Object getProperty(String name) { return InvokerHelper.getProperty(this.propertyValue, name); } @Override public void setProperty(String name, Object value) { InvokerHelper.setProperty(this.propertyValue, name, value); } private void updateDeferredProperties(Object value) { if (value instanceof RuntimeBeanReference) { deferredProperties.put(beanDefinition.getBeanName(), new DeferredProperty(beanDefinition, this.propertyName, this.propertyValue)); } } } } }