All Downloads are FREE. Search and download functionalities are using the official Maven repository.

grails.spring.BeanBuilder Maven / Gradle / Ivy

There is a newer version: 2023.0.2
Show newest version
/*
 * Copyright 2004-2024 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 grails.spring;

import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
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.MetaClass;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.parsing.EmptyReaderEventListener;
import org.springframework.beans.factory.parsing.FailFastProblemReporter;
import org.springframework.beans.factory.parsing.Location;
import org.springframework.beans.factory.parsing.NullSourceExtractor;
import org.springframework.beans.factory.parsing.Problem;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.SimpleBeanDefinitionRegistry;
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
import org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver;
import org.springframework.beans.factory.xml.NamespaceHandler;
import org.springframework.beans.factory.xml.NamespaceHandlerResolver;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.beans.factory.xml.XmlReaderContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.Assert;

import org.grails.spring.BeanConfiguration;
import org.grails.spring.DefaultBeanConfiguration;
import org.grails.spring.DefaultRuntimeSpringConfiguration;
import org.grails.spring.RuntimeSpringConfiguration;

/**
 * 

Runtime bean configuration wrapper. Like a Groovy builder, but more of a DSL for * Spring configuration. Allows syntax like:

* *
 *     
 * import org.hibernate.SessionFactory
 * import org.apache.tomcat.jdbc.pool.DataSource
 *
 * BeanBuilder builder = new BeanBuilder()
 * builder.beans {
 *   dataSource(DataSource) {                  // <--- invokeMethod
 *     driverClassName = "org.h2.Driver"
 *     url = "jdbc:h2:mem:grailsDB"
 *     username = "sa"                            // <-- setProperty
 *     password = ""
 *     settings = [mynew:"setting"]
 *   }
 *   sessionFactory(SessionFactory) {
 *     dataSource = dataSource                 // <-- getProperty for retrieving refs
 *   }
 *   myService(MyService) {
 *     nestedBean = { AnotherBean bean ->          // <-- setProperty with closure for nested bean
 *       dataSource = dataSource
 *     }
 *   }
 * }
 * 
 * 
*

* You can also use the Spring IO API to load resources containing beans defined as a Groovy * script using either the constructors or the loadBeans(Resource[] resources) method *

* * @author Graeme Rocher * @since 0.4 * */ public class BeanBuilder extends GroovyObjectSupport { private static final Log logger = LogFactory.getLog(BeanBuilder.class); private static final String CREATE_APPCTX = "createApplicationContext"; private static final String REGISTER_BEANS = "registerBeans"; private static final String BEANS = "beans"; private static final String REF = "ref"; private RuntimeSpringConfiguration springConfig; private BeanConfiguration currentBeanConfig; private final Map deferredProperties = new HashMap<>(); private final ApplicationContext parentCtx; private Map binding = Collections.emptyMap(); private ClassLoader classLoader; private NamespaceHandlerResolver namespaceHandlerResolver; private final Map namespaceHandlers = new HashMap<>(); private XmlBeanDefinitionReader xmlBeanDefinitionReader; private final Map namespaces = new HashMap<>(); private Resource beanBuildResource; private XmlReaderContext readerContext; private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); public BeanBuilder() { this(null, null); } public BeanBuilder(ClassLoader classLoader) { this(null, classLoader); } public BeanBuilder(ApplicationContext parent) { this(parent, null); } public BeanBuilder(ApplicationContext parent, ClassLoader classLoader) { this(parent, null, classLoader); } public BeanBuilder(ApplicationContext parentCtx, RuntimeSpringConfiguration springConfig, ClassLoader classLoader) { this.springConfig = springConfig == null ? createRuntimeSpringConfiguration(parentCtx, classLoader) : springConfig; this.parentCtx = parentCtx; this.classLoader = classLoader; initializeSpringConfig(); } public void setResourcePatternResolver(ResourcePatternResolver resourcePatternResolver) { Assert.notNull(resourcePatternResolver, "The argument [resourcePatternResolver] cannot be null"); this.resourcePatternResolver = resourcePatternResolver; } public void setBeanBuildResource(Resource resource) { this.beanBuildResource = resource; } protected void initializeSpringConfig() { this.xmlBeanDefinitionReader = new XmlBeanDefinitionReader((GenericApplicationContext) this.springConfig.getUnrefreshedApplicationContext()); initializeBeanBuilderForClassLoader(this.classLoader); } public void setClassLoader(ClassLoader classLoader) { this.classLoader = classLoader == null ? getClass().getClassLoader() : classLoader; initializeBeanBuilderForClassLoader(classLoader); } protected void initializeBeanBuilderForClassLoader(ClassLoader classLoader) { this.xmlBeanDefinitionReader.setBeanClassLoader(classLoader); this.namespaceHandlerResolver = new DefaultNamespaceHandlerResolver(this.classLoader); this.readerContext = new XmlReaderContext(this.beanBuildResource, new FailFastProblemReporter(), new EmptyReaderEventListener(), new NullSourceExtractor(), this.xmlBeanDefinitionReader, this.namespaceHandlerResolver); } public void setNamespaceHandlerResolver(NamespaceHandlerResolver namespaceHandlerResolver) { this.namespaceHandlerResolver = namespaceHandlerResolver; } protected RuntimeSpringConfiguration createRuntimeSpringConfiguration(ApplicationContext parent, ClassLoader cl) { return new DefaultRuntimeSpringConfiguration(parent, cl); } public Log getLog() { return logger; } /** * Imports 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) { try { Resource[] resources = this.resourcePatternResolver.getResources(resourcePattern); for (Resource resource : resources) { importBeans(resource); } } catch (IOException e) { logger.error("Error loading beans for resource pattern: " + resourcePattern, e); } } public void importBeans(Resource resource) { String filename = resource.getFilename(); if (filename != null && filename.endsWith(".groovy")) { loadBeans(resource); } else if (filename != null && filename.endsWith(".xml")) { SimpleBeanDefinitionRegistry beanRegistry = new SimpleBeanDefinitionRegistry(); XmlBeanDefinitionReader beanReader = new XmlBeanDefinitionReader(beanRegistry); beanReader.loadBeanDefinitions(resource); String[] beanNames = beanRegistry.getBeanDefinitionNames(); for (String beanName : beanNames) { this.springConfig.addBeanDefinition(beanName, beanRegistry.getBeanDefinition(beanName)); } } } /** * Defines a Spring namespace definition to use. * * @param definition The definition */ public void xmlns(Map definition) { Assert.notNull(this.namespaceHandlerResolver, "You cannot define a Spring namespace without a [namespaceHandlerResolver] set"); if (definition.isEmpty()) { return; } for (Map.Entry entry : definition.entrySet()) { String namespace = entry.getKey(); String uri = entry.getValue() == null ? null : entry.getValue(); Assert.notNull(uri, "Namespace definition cannot supply a null URI"); NamespaceHandler namespaceHandler = this.namespaceHandlerResolver.resolve(uri); if (namespaceHandler == null) { throw new BeanDefinitionParsingException( new Problem("No namespace handler found for URI: " + uri, new Location(this.readerContext.getResource()))); } this.namespaceHandlers.put(namespace, namespaceHandler); this.namespaces.put(namespace, uri); } } /** * Retrieves the parent ApplicationContext * @return The parent ApplicationContext */ public ApplicationContext getParentCtx() { return this.parentCtx; } /** * Retrieves the RuntimeSpringConfiguration instance used the the BeanBuilder * @return The RuntimeSpringConfiguration instance */ public RuntimeSpringConfiguration getSpringConfig() { return this.springConfig; } /** * Retrieves a BeanDefinition for the given name * @param name The bean definition * @return The BeanDefinition instance */ public BeanDefinition getBeanDefinition(String name) { if (!getSpringConfig().containsBean(name)) { return null; } return getSpringConfig().getBeanConfig(name).getBeanDefinition(); } /** * Retrieves all BeanDefinitions for this BeanBuilder * * @return A map of BeanDefinition instances with the bean id as the key */ public Map getBeanDefinitions() { Map beanDefinitions = new HashMap<>(); for (String beanName : getSpringConfig().getBeanNames()) { beanDefinitions.put(beanName, getSpringConfig().getBeanConfig(beanName).getBeanDefinition()); } return beanDefinitions; } /** * Sets the runtime Spring configuration instance to use. This is not necessary to set * and is configured to default value if not, but is useful for integrating with other * spring configuration mechanisms @see org.codehaus.groovy.grails.commons.spring.GrailsRuntimeConfigurator * * @param springConfig The spring config */ public void setSpringConfig(RuntimeSpringConfiguration springConfig) { this.springConfig = springConfig; initializeSpringConfig(); } /** * Takes a resource pattern as (@see org.springframework.core.io.support.PathMatchingResourcePatternResolver) * This allows you load multiple bean resources in this single builder * * eg loadBeans("classpath:*Beans.groovy") * * @param resourcePattern The resource pattern * @throws IOException When the path cannot be matched */ public void loadBeans(String resourcePattern) throws IOException { loadBeans(new PathMatchingResourcePatternResolver().getResources(resourcePattern)); } /** * Loads a single Resource into the bean builder * * @param resource The resource to load */ public void loadBeans(Resource resource) { this.beanBuildResource = resource; loadBeans(new Resource[] { resource }); } /** * Loads a set of given beans * @param resources The resources to load * @throws IOException Thrown if there is an error reading one of the passes resources */ public void loadBeans(Resource[] resources) { @SuppressWarnings("rawtypes") Closure beans = new Closure(this) { private static final long serialVersionUID = -2778328821635253740L; @Override public Object call(Object... args) { invokeBeanDefiningClosure((Closure) args[0]); return null; } }; Binding b = new Binding() { @Override public void setVariable(String name, Object value) { if (BeanBuilder.this.currentBeanConfig == null) { super.setVariable(name, value); } else { setPropertyOnBeanConfig(name, value); } } }; b.setVariable("beans", beans); for (Resource resource : resources) { try { GroovyShell shell = this.classLoader == null ? new GroovyShell(b) : new GroovyShell(this.classLoader, b); shell.evaluate(new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8)); } catch (Throwable e) { throw new BeanDefinitionParsingException( new Problem("Error evaluating bean definition script: " + e.getMessage(), new Location(resource), null, e)); } } } /** * Register a set of beans with the given bean registry. Most * application contexts are bean registries. */ public void registerBeans(BeanDefinitionRegistry registry) { finalizeDeferredProperties(); if (registry instanceof GenericApplicationContext) { GenericApplicationContext ctx = (GenericApplicationContext) registry; ctx.setClassLoader(this.classLoader); ctx.getBeanFactory().setBeanClassLoader(this.classLoader); } this.springConfig.registerBeansWithRegistry(registry); } /** * Registers bean definitions with another instance of RuntimeSpringConfiguration, overriding any beans in the target. * * @param targetSpringConfig The RuntimeSpringConfiguration object */ public void registerBeans(RuntimeSpringConfiguration targetSpringConfig) { this.springConfig.registerBeansWithConfig(targetSpringConfig); } /** * 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 (CREATE_APPCTX.equals(name)) { return createApplicationContext(); } if (REGISTER_BEANS.equals(name) && args.length == 1 && args[0] instanceof GenericApplicationContext) { registerBeans((GenericApplicationContext) args[0]); return null; } if (BEANS.equals(name) && args.length == 1 && args[0] instanceof Closure) { return beans((Closure) args[0]); } if (REF.equals(name)) { String refName; Assert.notNull(args[0], "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) { if (args[1] instanceof Boolean) { parentRef = (Boolean) args[1]; } } return new RuntimeBeanReference(refName, parentRef); } if (this.namespaceHandlers.containsKey(name) && args.length > 0 && (args[0] instanceof Closure)) { createDynamicElementReader(name, true).invokeMethod("doCall", args); return this; } if (args.length > 0 && args[0] instanceof Closure) { // abstract bean definition return invokeBeanDefiningMethod(name, args); } if (args.length > 0 && args[0] instanceof Class || args.length > 0 && args[0] instanceof RuntimeBeanReference || args.length > 0 && args[0] instanceof Map) { return invokeBeanDefiningMethod(name, args); } if (args.length > 1 && args[args.length - 1] instanceof Closure) { return invokeBeanDefiningMethod(name, args); } ApplicationContext ctx = this.springConfig.getUnrefreshedApplicationContext(); MetaClass mc = DefaultGroovyMethods.getMetaClass(ctx); if (!mc.respondsTo(ctx, name, args).isEmpty()) { return mc.invokeMethod(ctx, name, args); } return this; } /** * Defines a set of beans for the given block or closure. * * @param c The block or closure * @return This BeanBuilder instance */ public BeanBuilder beans(Closure c) { return invokeBeanDefiningClosure(c); } /** * Creates an ApplicationContext from the current state of the BeanBuilder * @return The ApplicationContext instance */ public ApplicationContext createApplicationContext() { finalizeDeferredProperties(); return this.springConfig.getApplicationContext(); } protected void finalizeDeferredProperties() { for (DeferredProperty dp : this.deferredProperties.values()) { if (dp.value instanceof List) { dp.value = manageListIfNecessary(dp.value); } else if (dp.value instanceof Map) { dp.value = manageMapIfNecessary(dp.value); } dp.setInBeanConfig(); } this.deferredProperties.clear(); } protected boolean addToDeferred(BeanConfiguration beanConfig, String property, Object newValue) { if (newValue instanceof List) { this.deferredProperties.put(this.currentBeanConfig.getName() + property, new DeferredProperty(this.currentBeanConfig, property, newValue)); return true; } if (newValue instanceof Map) { this.deferredProperties.put(this.currentBeanConfig.getName() + property, new DeferredProperty(this.currentBeanConfig, property, newValue)); return true; } return false; } /** * Called when a bean definition node is called. * * @param name 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 configuration instance */ protected BeanConfiguration invokeBeanDefiningMethod(String name, 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.currentBeanConfig = this.springConfig.addSingletonBean(name, beanClass, resolveConstructorArguments(args, 1, args.length - 1)); } else { this.currentBeanConfig = this.springConfig.addSingletonBean(name, beanClass); } } else { this.currentBeanConfig = this.springConfig.addSingletonBean(name, beanClass, resolveConstructorArguments(args, 1, args.length)); } } else if (args[0] instanceof RuntimeBeanReference) { this.currentBeanConfig = this.springConfig.addSingletonBean(name); this.currentBeanConfig.setFactoryBean(((RuntimeBeanReference) args[0]).getBeanName()); } else if (args[0] instanceof Map) { // named constructor arguments if (args.length > 1 && args[1] instanceof Class) { List constructorArgs = resolveConstructorArguments(args, 2, hasClosureArgument ? args.length - 1 : args.length); this.currentBeanConfig = this.springConfig.addSingletonBean(name, (Class) args[1], constructorArgs); @SuppressWarnings("rawtypes") Map namedArgs = (Map) args[0]; for (Object o : namedArgs.keySet()) { String propName = (String) o; setProperty(propName, namedArgs.get(propName)); } } // factory method syntax else { //First arg is the map containing factoryBean : factoryMethod @SuppressWarnings("rawtypes") Map.Entry factoryBeanEntry = (Map.Entry) ((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.currentBeanConfig = this.springConfig.addSingletonBean(name, null, resolveConstructorArguments(args, 1, endOfConstructArgs)); } else { this.currentBeanConfig = this.springConfig.addSingletonBean(name); } this.currentBeanConfig.setFactoryBean(factoryBeanEntry.getKey().toString()); this.currentBeanConfig.setFactoryMethod(factoryBeanEntry.getValue().toString()); } } else if (args[0] instanceof Closure) { this.currentBeanConfig = this.springConfig.addAbstractBean(name); } else { List constructorArgs = resolveConstructorArguments(args, 0, hasClosureArgument ? args.length - 1 : args.length); this.currentBeanConfig = new DefaultBeanConfiguration(name, null, constructorArgs); this.springConfig.addBeanConfiguration(name, this.currentBeanConfig); } if (hasClosureArgument) { Closure callable = (Closure) args[args.length - 1]; callable.setDelegate(this); callable.setResolveStrategy(Closure.DELEGATE_FIRST); callable.call(new Object[] { this.currentBeanConfig }); } if (this.beanBuildResource != null && this.currentBeanConfig.getResource() == null) { this.currentBeanConfig.setResource(this.beanBuildResource); } BeanConfiguration beanConfig = this.currentBeanConfig; this.currentBeanConfig = null; return beanConfig; } @SuppressWarnings("rawtypes") protected List resolveConstructorArguments(Object[] args, int start, int end) { Object[] constructorArgs = subarray(args, start, end); filterGStringReferences(constructorArgs); for (int i = 0; i < constructorArgs.length; i++) { if (constructorArgs[i] instanceof List) { constructorArgs[i] = manageListIfNecessary(constructorArgs[i]); } else if (constructorArgs[i] instanceof Map) { constructorArgs[i] = manageMapIfNecessary(constructorArgs[i]); } } return Arrays.asList(constructorArgs); } protected Object[] subarray(Object[] args, int i, int j) { Assert.isTrue(j <= args.length, "Upper bound can't be greater than array length"); Object[] b = new Object[j - i]; int n = 0; for (int k = i; k < j; k++, n++) { b[n] = args[k]; } return b; } protected void filterGStringReferences(Object[] constructorArgs) { for (int i = 0; i < constructorArgs.length; i++) { Object constructorArg = constructorArgs[i]; if (constructorArg instanceof GString) { constructorArgs[i] = constructorArg.toString(); } } } /** * When an method's argument is only a closure it is a set of bean definitions. * * @param callable The closure argument * @return This BeanBuilder instance */ protected BeanBuilder invokeBeanDefiningClosure(Closure callable) { callable.setDelegate(this); callable.setResolveStrategy(Closure.OWNER_FIRST); callable.call(); finalizeDeferredProperties(); return this; } /** * Overrides property setting in the scope of the BeanBuilder to set * properties on the current BeanConfiguration. */ @Override public void setProperty(String name, Object value) { if (this.currentBeanConfig != null) { setPropertyOnBeanConfig(name, value); } } /** * Defines an inner bean definition. * * @param type The bean type * @return The bean definition */ public AbstractBeanDefinition bean(Class type) { return this.springConfig.createSingletonBean(type).getBeanDefinition(); } /** * Defines an inner bean definition. * * @param type The bean type * @param args The constructors arguments and closure configurer * @return The bean definition */ @SuppressWarnings("rawtypes") public AbstractBeanDefinition bean(Class type, Object... args) { BeanConfiguration current = this.currentBeanConfig; try { Closure callable = null; Collection constructorArgs = null; if (args != null && args.length > 0) { int index = args.length; Object lastArg = args[index - 1]; if (lastArg instanceof Closure) { callable = (Closure) lastArg; index--; } constructorArgs = resolveConstructorArguments(args, 0, index); } this.currentBeanConfig = constructorArgs == null ? this.springConfig.createSingletonBean(type) : this.springConfig.createSingletonBean(type, constructorArgs); if (callable != null) { callable.call(new Object[] { this.currentBeanConfig }); } return this.currentBeanConfig.getBeanDefinition(); } finally { this.currentBeanConfig = current; } } protected void setPropertyOnBeanConfig(String name, Object value) { if (value instanceof GString) { value = value.toString(); } if (addToDeferred(this.currentBeanConfig, name, value)) { return; } if (value instanceof Closure) { BeanConfiguration current = this.currentBeanConfig; try { Closure callable = (Closure) value; Class parameterType = callable.getParameterTypes()[0]; if (parameterType.equals(Object.class)) { this.currentBeanConfig = this.springConfig.createSingletonBean(""); callable.call(new Object[] { this.currentBeanConfig }); } else { this.currentBeanConfig = this.springConfig.createSingletonBean(parameterType); callable.call(); } value = this.currentBeanConfig.getBeanDefinition(); } finally { this.currentBeanConfig = current; } } this.currentBeanConfig.addProperty(name, value); } /** * Checks whether there are any runtime refs inside a Map and converts * it to a ManagedMap if necessary. * * @param value The current map * @return A ManagedMap or a normal map */ @SuppressWarnings({ "rawtypes", "unchecked" }) protected Object manageMapIfNecessary(Object value) { Map map = (Map) value; boolean containsRuntimeRefs = false; for (Object e : map.values()) { if (e instanceof RuntimeBeanReference) { containsRuntimeRefs = true; break; } } if (containsRuntimeRefs) { Map managedMap = new ManagedMap(); managedMap.putAll(map); return managedMap; } return value; } /** * Checks whether there are any runtime refs inside the list and * converts it to a ManagedList if necessary. * * @param value The object that represents the list * @return Either a new list or a managed one */ @SuppressWarnings({ "rawtypes", "unchecked" }) protected Object manageListIfNecessary(Object value) { List list = (List) value; boolean containsRuntimeRefs = false; for (Object e : list) { if (e instanceof RuntimeBeanReference) { containsRuntimeRefs = true; break; } } if (containsRuntimeRefs) { List tmp = new ManagedList(); tmp.addAll((List) value); value = tmp; } return value; } /** * Overrides property retrieval in the scope of the BeanBuilder to either: * * a) Retrieve a variable from the bean builder's binding if it exists * b) Retrieve a RuntimeBeanReference for a specific bean if it exists * c) Otherwise just delegate to super.getProperty which will resolve properties from the BeanBuilder itself */ @Override public Object getProperty(String name) { if (this.binding.containsKey(name)) { return this.binding.get(name); } if (this.namespaceHandlers.containsKey(name)) { return createDynamicElementReader(name, this.currentBeanConfig != null); } if (this.springConfig.containsBean(name)) { BeanConfiguration beanConfig = this.springConfig.getBeanConfig(name); if (beanConfig != null) { return new ConfigurableRuntimeBeanReference(name, this.springConfig.getBeanConfig(name), false); } 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) if (this.currentBeanConfig != null) { if (this.currentBeanConfig.hasProperty(name)) { return this.currentBeanConfig.getPropertyValue(name); } DeferredProperty dp = this.deferredProperties.get(this.currentBeanConfig.getName() + name); if (dp != null) { return dp.value; } return super.getProperty(name); } return super.getProperty(name); } protected DynamicElementReader createDynamicElementReader(String namespace, boolean decorator) { NamespaceHandler handler = this.namespaceHandlers.get(namespace); ParserContext parserContext = new ParserContext(this.readerContext, new BeanDefinitionParserDelegate(this.readerContext)); DynamicElementReader dynamicElementReader = new DynamicElementReader(namespace, this.namespaces, handler, parserContext) { @Override protected void afterInvocation() { if (!decorator) { BeanBuilder.this.currentBeanConfig = null; } } }; dynamicElementReader.setClassLoader(this.classLoader); if (this.currentBeanConfig != null) { dynamicElementReader.setBeanConfiguration(this.currentBeanConfig); } else if (!decorator) { this.currentBeanConfig = new DefaultBeanConfiguration(namespace); dynamicElementReader.setBeanConfiguration(this.currentBeanConfig); } dynamicElementReader.setBeanDecorator(decorator); return dynamicElementReader; } /** * Sets the binding (the variables available in the scope of the BeanBuilder). * @param b The Binding instance */ @SuppressWarnings("unchecked") public void setBinding(Binding b) { this.binding = b.getVariables(); } /** * Defers 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 asignment, but may later hence it would need to be managed * * @author Graeme Rocher */ private class DeferredProperty { private final BeanConfiguration config; private final String name; private Object value; DeferredProperty(BeanConfiguration config, String name, Object value) { this.config = config; this.name = name; this.value = value; } public void setInBeanConfig() { this.config.addProperty(this.name, this.value); } } /** * Adds new properties to runtime references. * * @author Graeme Rocher * @since 0.4 */ private class ConfigurableRuntimeBeanReference extends RuntimeBeanReference implements GroovyObject { private MetaClass metaClass; private final BeanConfiguration beanConfig; ConfigurableRuntimeBeanReference(String beanName, BeanConfiguration beanConfig, boolean toParent) { super(beanName, toParent); Assert.notNull(beanConfig, "Argument [beanConfig] cannot be null"); this.beanConfig = beanConfig; this.metaClass = InvokerHelper.getMetaClass(this); } public MetaClass getMetaClass() { return this.metaClass; } public void setMetaClass(MetaClass metaClass) { this.metaClass = metaClass; } public Object getProperty(String property) { if (property.equals("beanName")) { return getBeanName(); } if (property.equals("source")) { return getSource(); } if (this.beanConfig != null) { return new WrappedPropertyValue(property, this.beanConfig.getPropertyValue(property)); } return this.metaClass.getProperty(this, property); } public void setProperty(String property, Object newValue) { if (!addToDeferred(this.beanConfig, property, newValue)) { this.beanConfig.setPropertyValue(property, newValue); } } public Object invokeMethod(String name, Object args) { return this.metaClass.invokeMethod(this, name, args); } /** * Wraps a BeanConfiguration property an ensures that any RuntimeReference additions to it are * deferred for resolution later. */ private class WrappedPropertyValue extends GroovyObjectSupport { private final Object propertyValue; private final String propertyName; WrappedPropertyValue(String propertyName, Object propertyValue) { this.propertyValue = propertyValue; this.propertyName = propertyName; } public void leftShift(Object value) { InvokerHelper.invokeMethod(this.propertyValue, "leftShift", value); updateDeferredProperties(value); } public boolean add(Object value) { boolean retval = (Boolean) InvokerHelper.invokeMethod(this.propertyValue, "add", value); updateDeferredProperties(value); return retval; } public boolean addAll(@SuppressWarnings("rawtypes") 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) { BeanBuilder.this.deferredProperties.put(ConfigurableRuntimeBeanReference.this.beanConfig.getName(), new DeferredProperty(ConfigurableRuntimeBeanReference.this.beanConfig, this.propertyName, this.propertyValue)); } } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy