001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.beans.factory.groovy;
018
019import java.io.IOException;
020import java.util.Arrays;
021import java.util.Collection;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025
026import groovy.lang.Binding;
027import groovy.lang.Closure;
028import groovy.lang.GString;
029import groovy.lang.GroovyObject;
030import groovy.lang.GroovyObjectSupport;
031import groovy.lang.GroovyShell;
032import groovy.lang.GroovySystem;
033import groovy.lang.MetaClass;
034import org.codehaus.groovy.runtime.DefaultGroovyMethods;
035import org.codehaus.groovy.runtime.InvokerHelper;
036
037import org.springframework.beans.MutablePropertyValues;
038import org.springframework.beans.factory.BeanDefinitionStoreException;
039import org.springframework.beans.factory.config.RuntimeBeanReference;
040import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
041import org.springframework.beans.factory.parsing.Location;
042import org.springframework.beans.factory.parsing.Problem;
043import org.springframework.beans.factory.support.AbstractBeanDefinition;
044import org.springframework.beans.factory.support.AbstractBeanDefinitionReader;
045import org.springframework.beans.factory.support.BeanDefinitionRegistry;
046import org.springframework.beans.factory.support.GenericBeanDefinition;
047import org.springframework.beans.factory.support.ManagedList;
048import org.springframework.beans.factory.support.ManagedMap;
049import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
050import org.springframework.beans.factory.xml.NamespaceHandler;
051import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
052import org.springframework.beans.factory.xml.XmlReaderContext;
053import org.springframework.core.io.DescriptiveResource;
054import org.springframework.core.io.Resource;
055import org.springframework.core.io.support.EncodedResource;
056import org.springframework.util.ObjectUtils;
057import org.springframework.util.StringUtils;
058
059/**
060 * A Groovy-based reader for Spring bean definitions: like a Groovy builder,
061 * but more of a DSL for Spring configuration.
062 *
063 * <p>This bean definition reader also understands XML bean definition files,
064 * allowing for seamless mixing and matching with Groovy bean definition files.
065 *
066 * <p>Typically applied to a
067 * {@link org.springframework.beans.factory.support.DefaultListableBeanFactory}
068 * or a {@link org.springframework.context.support.GenericApplicationContext},
069 * but can be used against any {@link BeanDefinitionRegistry} implementation.
070 *
071 * <h3>Example Syntax</h3>
072 * <pre class="code">
073 * import org.hibernate.SessionFactory
074 * import org.apache.commons.dbcp.BasicDataSource
075 *
076 * def reader = new GroovyBeanDefinitionReader(myApplicationContext)
077 * reader.beans {
078 *     dataSource(BasicDataSource) {                  // <--- invokeMethod
079 *         driverClassName = "org.hsqldb.jdbcDriver"
080 *         url = "jdbc:hsqldb:mem:grailsDB"
081 *         username = "sa"                            // <-- setProperty
082 *         password = ""
083 *         settings = [mynew:"setting"]
084 *     }
085 *     sessionFactory(SessionFactory) {
086 *         dataSource = dataSource                    // <-- getProperty for retrieving references
087 *     }
088 *     myService(MyService) {
089 *         nestedBean = { AnotherBean bean ->         // <-- setProperty with closure for nested bean
090 *             dataSource = dataSource
091 *         }
092 *     }
093 * }</pre>
094 *
095 * <p>You can also load resources containing beans defined in a Groovy script using
096 * either the {@link #loadBeanDefinitions(Resource...)} or
097 * {@link #loadBeanDefinitions(String...)} method, with a script looking similar to
098 * the following.
099 *
100 * <pre class="code">
101 * import org.hibernate.SessionFactory
102 * import org.apache.commons.dbcp.BasicDataSource
103 *
104 * beans {
105 *     dataSource(BasicDataSource) {
106 *         driverClassName = "org.hsqldb.jdbcDriver"
107 *         url = "jdbc:hsqldb:mem:grailsDB"
108 *         username = "sa"
109 *         password = ""
110 *         settings = [mynew:"setting"]
111 *     }
112 *     sessionFactory(SessionFactory) {
113 *         dataSource = dataSource
114 *     }
115 *     myService(MyService) {
116 *         nestedBean = { AnotherBean bean ->
117 *             dataSource = dataSource
118 *         }
119 *     }
120 * }</pre>
121 *
122 * @author Jeff Brown
123 * @author Graeme Rocher
124 * @author Juergen Hoeller
125 * @author Sam Brannen
126 * @since 4.0
127 * @see BeanDefinitionRegistry
128 * @see org.springframework.beans.factory.support.DefaultListableBeanFactory
129 * @see org.springframework.context.support.GenericApplicationContext
130 * @see org.springframework.context.support.GenericGroovyApplicationContext
131 */
132public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader implements GroovyObject {
133
134        /**
135         * Standard {@code XmlBeanDefinitionReader} created with default
136         * settings for loading bean definitions from XML files.
137         */
138        private final XmlBeanDefinitionReader standardXmlBeanDefinitionReader;
139
140        /**
141         * Groovy DSL {@code XmlBeanDefinitionReader} for loading bean definitions
142         * via the Groovy DSL, typically configured with XML validation disabled.
143         */
144        private final XmlBeanDefinitionReader groovyDslXmlBeanDefinitionReader;
145
146        private final Map<String, String> namespaces = new HashMap<String, String>();
147
148        private final Map<String, DeferredProperty> deferredProperties = new HashMap<String, DeferredProperty>();
149
150        private MetaClass metaClass = GroovySystem.getMetaClassRegistry().getMetaClass(getClass());
151
152        private Binding binding;
153
154        private GroovyBeanDefinitionWrapper currentBeanDefinition;
155
156
157        /**
158         * Create a new {@code GroovyBeanDefinitionReader} for the given
159         * {@link BeanDefinitionRegistry}.
160         * @param registry the {@code BeanDefinitionRegistry} to load bean definitions into
161         */
162        public GroovyBeanDefinitionReader(BeanDefinitionRegistry registry) {
163                super(registry);
164                this.standardXmlBeanDefinitionReader = new XmlBeanDefinitionReader(registry);
165                this.groovyDslXmlBeanDefinitionReader = new XmlBeanDefinitionReader(registry);
166                this.groovyDslXmlBeanDefinitionReader.setValidating(false);
167        }
168
169        /**
170         * Create a new {@code GroovyBeanDefinitionReader} based on the given
171         * {@link XmlBeanDefinitionReader}, loading bean definitions into its
172         * {@code BeanDefinitionRegistry} and delegating Groovy DSL loading to it.
173         * <p>The supplied {@code XmlBeanDefinitionReader} should typically
174         * be pre-configured with XML validation disabled.
175         * @param xmlBeanDefinitionReader the {@code XmlBeanDefinitionReader} to
176         * derive the registry from and to delegate Groovy DSL loading to
177         */
178        public GroovyBeanDefinitionReader(XmlBeanDefinitionReader xmlBeanDefinitionReader) {
179                super(xmlBeanDefinitionReader.getRegistry());
180                this.standardXmlBeanDefinitionReader = new XmlBeanDefinitionReader(xmlBeanDefinitionReader.getRegistry());
181                this.groovyDslXmlBeanDefinitionReader = xmlBeanDefinitionReader;
182        }
183
184
185        public void setMetaClass(MetaClass metaClass) {
186                this.metaClass = metaClass;
187        }
188
189        public MetaClass getMetaClass() {
190                return this.metaClass;
191        }
192
193        /**
194         * Set the binding, i.e. the Groovy variables available in the scope
195         * of a {@code GroovyBeanDefinitionReader} closure.
196         */
197        public void setBinding(Binding binding) {
198                this.binding = binding;
199        }
200
201        /**
202         * Return a specified binding for Groovy variables, if any.
203         */
204        public Binding getBinding() {
205                return this.binding;
206        }
207
208
209        // TRADITIONAL BEAN DEFINITION READER METHODS
210
211        /**
212         * Load bean definitions from the specified Groovy script or XML file.
213         * <p>Note that {@code ".xml"} files will be parsed as XML content; all other kinds
214         * of resources will be parsed as Groovy scripts.
215         * @param resource the resource descriptor for the Groovy script or XML file
216         * @return the number of bean definitions found
217         * @throws BeanDefinitionStoreException in case of loading or parsing errors
218         */
219        public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
220                return loadBeanDefinitions(new EncodedResource(resource));
221        }
222
223        /**
224         * Load bean definitions from the specified Groovy script or XML file.
225         * <p>Note that {@code ".xml"} files will be parsed as XML content; all other kinds
226         * of resources will be parsed as Groovy scripts.
227         * @param encodedResource the resource descriptor for the Groovy script or XML file,
228         * allowing specification of an encoding to use for parsing the file
229         * @return the number of bean definitions found
230         * @throws BeanDefinitionStoreException in case of loading or parsing errors
231         */
232        public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
233                // Check for XML files and redirect them to the "standard" XmlBeanDefinitionReader
234                String filename = encodedResource.getResource().getFilename();
235                if (StringUtils.endsWithIgnoreCase(filename, ".xml")) {
236                        return this.standardXmlBeanDefinitionReader.loadBeanDefinitions(encodedResource);
237                }
238
239                Closure beans = new Closure(this) {
240                        @Override
241                        public Object call(Object[] args) {
242                                invokeBeanDefiningClosure((Closure) args[0]);
243                                return null;
244                        }
245                };
246                Binding binding = new Binding() {
247                        @Override
248                        public void setVariable(String name, Object value) {
249                                if (currentBeanDefinition != null) {
250                                        applyPropertyToBeanDefinition(name, value);
251                                }
252                                else {
253                                        super.setVariable(name, value);
254                                }
255                        }
256                };
257                binding.setVariable("beans", beans);
258
259                int countBefore = getRegistry().getBeanDefinitionCount();
260                try {
261                        GroovyShell shell = new GroovyShell(getResourceLoader().getClassLoader(), binding);
262                        shell.evaluate(encodedResource.getReader(), "beans");
263                }
264                catch (Throwable ex) {
265                        throw new BeanDefinitionParsingException(new Problem("Error evaluating Groovy script: " + ex.getMessage(),
266                                        new Location(encodedResource.getResource()), null, ex));
267                }
268                return getRegistry().getBeanDefinitionCount() - countBefore;
269        }
270
271
272        // METHODS FOR CONSUMPTION IN A GROOVY CLOSURE
273
274        /**
275         * Defines a set of beans for the given block or closure.
276         * @param closure the block or closure
277         * @return this {@code GroovyBeanDefinitionReader} instance
278         */
279        public GroovyBeanDefinitionReader beans(Closure closure) {
280                return invokeBeanDefiningClosure(closure);
281        }
282
283        /**
284         * Define an inner bean definition.
285         * @param type the bean type
286         * @return the bean definition
287         */
288        public GenericBeanDefinition bean(Class<?> type) {
289                GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
290                beanDefinition.setBeanClass(type);
291                return beanDefinition;
292        }
293
294        /**
295         * Define an inner bean definition.
296         * @param type the bean type
297         * @param args the constructors arguments and closure configurer
298         * @return the bean definition
299         */
300        public AbstractBeanDefinition bean(Class<?> type, Object...args) {
301                GroovyBeanDefinitionWrapper current = this.currentBeanDefinition;
302                try {
303                        Closure callable = null;
304                        Collection constructorArgs = null;
305                        if (!ObjectUtils.isEmpty(args)) {
306                                int index = args.length;
307                                Object lastArg = args[index - 1];
308                                if (lastArg instanceof Closure) {
309                                        callable = (Closure) lastArg;
310                                        index--;
311                                }
312                                if (index > -1) {
313                                        constructorArgs = resolveConstructorArguments(args, 0, index);
314                                }
315                        }
316                        this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(null, type, constructorArgs);
317                        if (callable != null) {
318                                callable.call(this.currentBeanDefinition);
319                        }
320                        return this.currentBeanDefinition.getBeanDefinition();
321                }
322                finally {
323                        this.currentBeanDefinition = current;
324                }
325        }
326
327        /**
328         * Define a Spring XML namespace definition to use.
329         * @param definition the namespace definition
330         */
331        public void xmlns(Map<String, String> definition) {
332                if (!definition.isEmpty()) {
333                        for (Map.Entry<String,String> entry : definition.entrySet()) {
334                                String namespace = entry.getKey();
335                                String uri = entry.getValue();
336                                if (uri == null) {
337                                        throw new IllegalArgumentException("Namespace definition must supply a non-null URI");
338                                }
339                                NamespaceHandler namespaceHandler =
340                                                this.groovyDslXmlBeanDefinitionReader.getNamespaceHandlerResolver().resolve(uri);
341                                if (namespaceHandler == null) {
342                                        throw new BeanDefinitionParsingException(new Problem("No namespace handler found for URI: " + uri,
343                                                        new Location(new DescriptiveResource(("Groovy")))));
344                                }
345                                this.namespaces.put(namespace, uri);
346                        }
347                }
348        }
349
350        /**
351         * Import Spring bean definitions from either XML or Groovy sources into the
352         * current bean builder instance.
353         * @param resourcePattern the resource pattern
354         */
355        public void importBeans(String resourcePattern) throws IOException {
356                loadBeanDefinitions(resourcePattern);
357        }
358
359
360        // INTERNAL HANDLING OF GROOVY CLOSURES AND PROPERTIES
361
362        /**
363         * This method overrides method invocation to create beans for each method name that
364         * takes a class argument.
365         */
366        public Object invokeMethod(String name, Object arg) {
367                Object[] args = (Object[])arg;
368                if ("beans".equals(name) && args.length == 1 && args[0] instanceof Closure) {
369                        return beans((Closure) args[0]);
370                }
371                else if ("ref".equals(name)) {
372                        String refName;
373                        if (args[0] == null) {
374                                throw new IllegalArgumentException("Argument to ref() is not a valid bean or was not found");
375                        }
376                        if (args[0] instanceof RuntimeBeanReference) {
377                                refName = ((RuntimeBeanReference) args[0]).getBeanName();
378                        }
379                        else {
380                                refName = args[0].toString();
381                        }
382                        boolean parentRef = false;
383                        if (args.length > 1 && args[1] instanceof Boolean) {
384                                parentRef = (Boolean) args[1];
385                        }
386                        return new RuntimeBeanReference(refName, parentRef);
387                }
388                else if (this.namespaces.containsKey(name) && args.length > 0 && args[0] instanceof Closure) {
389                        GroovyDynamicElementReader reader = createDynamicElementReader(name);
390                        reader.invokeMethod("doCall", args);
391                }
392                else if (args.length > 0 && args[0] instanceof Closure) {
393                        // abstract bean definition
394                        return invokeBeanDefiningMethod(name, args);
395                }
396                else if (args.length > 0 &&
397                                (args[0] instanceof Class || args[0] instanceof RuntimeBeanReference || args[0] instanceof Map)) {
398                        return invokeBeanDefiningMethod(name, args);
399                }
400                else if (args.length > 1 && args[args.length -1] instanceof Closure) {
401                        return invokeBeanDefiningMethod(name, args);
402                }
403                MetaClass mc = DefaultGroovyMethods.getMetaClass(getRegistry());
404                if (!mc.respondsTo(getRegistry(), name, args).isEmpty()){
405                        return mc.invokeMethod(getRegistry(), name, args);
406                }
407                return this;
408        }
409
410        private boolean addDeferredProperty(String property, Object newValue) {
411                if (newValue instanceof List || newValue instanceof Map) {
412                        this.deferredProperties.put(this.currentBeanDefinition.getBeanName() + '.' + property,
413                                        new DeferredProperty(this.currentBeanDefinition, property, newValue));
414                        return true;
415                }
416                return false;
417        }
418
419        private void finalizeDeferredProperties() {
420                for (DeferredProperty dp : this.deferredProperties.values()) {
421                        if (dp.value instanceof List) {
422                                dp.value = manageListIfNecessary((List) dp.value);
423                        }
424                        else if (dp.value instanceof Map) {
425                                dp.value = manageMapIfNecessary((Map) dp.value);
426                        }
427                        dp.apply();
428                }
429                this.deferredProperties.clear();
430        }
431
432        /**
433         * When a method argument is only a closure it is a set of bean definitions.
434         * @param callable the closure argument
435         * @return this {@code GroovyBeanDefinitionReader} instance
436         */
437        protected GroovyBeanDefinitionReader invokeBeanDefiningClosure(Closure callable) {
438                callable.setDelegate(this);
439                callable.call();
440                finalizeDeferredProperties();
441                return this;
442        }
443
444        /**
445         * This method is called when a bean definition node is called.
446         * @param beanName the name of the bean to define
447         * @param args the arguments to the bean. The first argument is the class name, the last
448         * argument is sometimes a closure. All the arguments in between are constructor arguments.
449         * @return the bean definition wrapper
450         */
451        private GroovyBeanDefinitionWrapper invokeBeanDefiningMethod(String beanName, Object[] args) {
452                boolean hasClosureArgument = (args[args.length - 1] instanceof Closure);
453                if (args[0] instanceof Class) {
454                        Class<?> beanClass = (Class<?>) args[0];
455                        if (args.length >= 1) {
456                                if (hasClosureArgument) {
457                                        if (args.length - 1 != 1) {
458                                                this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(
459                                                                beanName, beanClass, resolveConstructorArguments(args, 1, args.length - 1));
460                                        }
461                                        else {
462                                                this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName, beanClass);
463                                        }
464                                }
465                                else  {
466                                        this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(
467                                                        beanName, beanClass, resolveConstructorArguments(args, 1, args.length));
468                                }
469
470                        }
471                }
472                else if (args[0] instanceof RuntimeBeanReference) {
473                        this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName);
474                        this.currentBeanDefinition.getBeanDefinition().setFactoryBeanName(((RuntimeBeanReference) args[0]).getBeanName());
475                }
476                else if (args[0] instanceof Map) {
477                        // named constructor arguments
478                        if (args.length > 1 && args[1] instanceof Class) {
479                                List constructorArgs = resolveConstructorArguments(args, 2, hasClosureArgument ? args.length - 1 : args.length);
480                                this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName, (Class)args[1], constructorArgs);
481                                Map namedArgs = (Map)args[0];
482                                for (Object o : namedArgs.keySet()) {
483                                        String propName = (String) o;
484                                        setProperty(propName, namedArgs.get(propName));
485                                }
486                        }
487                        // factory method syntax
488                        else {
489                                this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName);
490                                //First arg is the map containing factoryBean : factoryMethod
491                                Map.Entry factoryBeanEntry = (Map.Entry) ((Map) args[0]).entrySet().iterator().next();
492                                // If we have a closure body, that will be the last argument.
493                                // In between are the constructor args
494                                int constructorArgsTest = (hasClosureArgument ? 2 : 1);
495                                // If we have more than this number of args, we have constructor args
496                                if (args.length > constructorArgsTest){
497                                        // factory-method requires args
498                                        int endOfConstructArgs = (hasClosureArgument ? args.length - 1 : args.length);
499                                        this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName, null,
500                                                        resolveConstructorArguments(args, 1, endOfConstructArgs));
501                                }
502                                else {
503                                        this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName);
504                                }
505                                this.currentBeanDefinition.getBeanDefinition().setFactoryBeanName(factoryBeanEntry.getKey().toString());
506                                this.currentBeanDefinition.getBeanDefinition().setFactoryMethodName(factoryBeanEntry.getValue().toString());
507                        }
508
509                }
510                else if (args[0] instanceof Closure) {
511                        this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName);
512                        this.currentBeanDefinition.getBeanDefinition().setAbstract(true);
513                }
514                else {
515                        List constructorArgs = resolveConstructorArguments(args, 0, hasClosureArgument ? args.length - 1 : args.length);
516                        this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName, null, constructorArgs);
517                }
518
519                if (hasClosureArgument) {
520                        Closure callable = (Closure) args[args.length - 1];
521                        callable.setDelegate(this);
522                        callable.setResolveStrategy(Closure.DELEGATE_FIRST);
523                        callable.call(this.currentBeanDefinition);
524                }
525
526                GroovyBeanDefinitionWrapper beanDefinition = this.currentBeanDefinition;
527                this.currentBeanDefinition = null;
528                beanDefinition.getBeanDefinition().setAttribute(GroovyBeanDefinitionWrapper.class.getName(), beanDefinition);
529                getRegistry().registerBeanDefinition(beanName, beanDefinition.getBeanDefinition());
530                return beanDefinition;
531        }
532
533        protected List<Object> resolveConstructorArguments(Object[] args, int start, int end) {
534                Object[] constructorArgs = Arrays.copyOfRange(args, start, end);
535                for (int i = 0; i < constructorArgs.length; i++) {
536                        if (constructorArgs[i] instanceof GString) {
537                                constructorArgs[i] = constructorArgs[i].toString();
538                        }
539                        else if (constructorArgs[i] instanceof List) {
540                                constructorArgs[i] = manageListIfNecessary((List) constructorArgs[i]);
541                        }
542                        else if (constructorArgs[i] instanceof Map){
543                                constructorArgs[i] = manageMapIfNecessary((Map) constructorArgs[i]);
544                        }
545                }
546                return Arrays.asList(constructorArgs);
547        }
548
549        /**
550         * Checks whether there are any {@link RuntimeBeanReference}s inside the {@link Map}
551         * and converts it to a {@link ManagedMap} if necessary.
552         * @param map the original Map
553         * @return either the original map or a managed copy of it
554         */
555        private Object manageMapIfNecessary(Map<?, ?> map) {
556                boolean containsRuntimeRefs = false;
557                for (Object element : map.values()) {
558                        if (element instanceof RuntimeBeanReference) {
559                                containsRuntimeRefs = true;
560                                break;
561                        }
562                }
563                if (containsRuntimeRefs) {
564                        Map<Object, Object> managedMap = new ManagedMap<Object, Object>();
565                        managedMap.putAll(map);
566                        return managedMap;
567                }
568                return map;
569        }
570
571        /**
572         * Checks whether there are any {@link RuntimeBeanReference}s inside the {@link List}
573         * and converts it to a {@link ManagedList} if necessary.
574         * @param list the original List
575         * @return either the original list or a managed copy of it
576         */
577        private Object manageListIfNecessary(List<?> list) {
578                boolean containsRuntimeRefs = false;
579                for (Object element : list) {
580                        if (element instanceof RuntimeBeanReference) {
581                                containsRuntimeRefs = true;
582                                break;
583                        }
584                }
585                if (containsRuntimeRefs) {
586                        List<Object> managedList = new ManagedList<Object>();
587                        managedList.addAll(list);
588                        return managedList;
589                }
590                return list;
591        }
592
593        /**
594         * This method overrides property setting in the scope of the {@code GroovyBeanDefinitionReader}
595         * to set properties on the current bean definition.
596         */
597        public void setProperty(String name, Object value) {
598                if (this.currentBeanDefinition != null) {
599                        applyPropertyToBeanDefinition(name, value);
600                }
601        }
602
603        protected void applyPropertyToBeanDefinition(String name, Object value) {
604                if (value instanceof GString) {
605                        value = value.toString();
606                }
607                if (addDeferredProperty(name, value)) {
608                        return;
609                }
610                else if (value instanceof Closure) {
611                        GroovyBeanDefinitionWrapper current = this.currentBeanDefinition;
612                        try {
613                                Closure callable = (Closure) value;
614                                Class<?> parameterType = callable.getParameterTypes()[0];
615                                if (Object.class == parameterType) {
616                                        this.currentBeanDefinition = new GroovyBeanDefinitionWrapper("");
617                                        callable.call(this.currentBeanDefinition);
618                                }
619                                else {
620                                        this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(null, parameterType);
621                                        callable.call((Object) null);
622                                }
623
624                                value = this.currentBeanDefinition.getBeanDefinition();
625                        }
626                        finally {
627                                this.currentBeanDefinition = current;
628                        }
629                }
630                this.currentBeanDefinition.addProperty(name, value);
631        }
632
633        /**
634         * This method overrides property retrieval in the scope of the
635         * {@code GroovyBeanDefinitionReader}. A property retrieval will either:
636         * <ul>
637         * <li>Retrieve a variable from the bean builder's binding if it exists
638         * <li>Retrieve a RuntimeBeanReference for a specific bean if it exists
639         * <li>Otherwise just delegate to MetaClass.getProperty which will resolve
640         * properties from the {@code GroovyBeanDefinitionReader} itself
641         * </ul>
642         */
643        public Object getProperty(String name) {
644                Binding binding = getBinding();
645                if (binding != null && binding.hasVariable(name)) {
646                        return binding.getVariable(name);
647                }
648                else {
649                        if (this.namespaces.containsKey(name)) {
650                                return createDynamicElementReader(name);
651                        }
652                        if (getRegistry().containsBeanDefinition(name)) {
653                                GroovyBeanDefinitionWrapper beanDefinition = (GroovyBeanDefinitionWrapper)
654                                                getRegistry().getBeanDefinition(name).getAttribute(GroovyBeanDefinitionWrapper.class.getName());
655                                if (beanDefinition != null) {
656                                        return new GroovyRuntimeBeanReference(name, beanDefinition, false);
657                                }
658                                else {
659                                        return new RuntimeBeanReference(name, false);
660                                }
661                        }
662                        // This is to deal with the case where the property setter is the last
663                        // statement in a closure (hence the return value)
664                        else if (this.currentBeanDefinition != null) {
665                                MutablePropertyValues pvs = this.currentBeanDefinition.getBeanDefinition().getPropertyValues();
666                                if (pvs.contains(name)) {
667                                        return pvs.get(name);
668                                }
669                                else {
670                                        DeferredProperty dp = this.deferredProperties.get(this.currentBeanDefinition.getBeanName() + name);
671                                        if (dp != null) {
672                                                return dp.value;
673                                        }
674                                        else {
675                                                return getMetaClass().getProperty(this, name);
676                                        }
677                                }
678                        }
679                        else {
680                                return getMetaClass().getProperty(this, name);
681                        }
682                }
683        }
684
685        private GroovyDynamicElementReader createDynamicElementReader(String namespace) {
686                XmlReaderContext readerContext = this.groovyDslXmlBeanDefinitionReader.createReaderContext(
687                                new DescriptiveResource("Groovy"));
688                BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
689                boolean decorating = (this.currentBeanDefinition != null);
690                if (!decorating) {
691                        this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(namespace);
692                }
693                return new GroovyDynamicElementReader(namespace, this.namespaces, delegate, this.currentBeanDefinition, decorating) {
694                        @Override
695                        protected void afterInvocation() {
696                                if (!this.decorating) {
697                                        currentBeanDefinition = null;
698                                }
699                        }
700                };
701        }
702
703
704        /**
705         * This class is used to defer the adding of a property to a bean definition
706         * until later. This is for a case where you assign a property to a list that
707         * may not contain bean references at that point of assignment, but may later;
708         * hence, it would need to be managed.
709         */
710        private static class DeferredProperty {
711
712                private final GroovyBeanDefinitionWrapper beanDefinition;
713
714                private final String name;
715
716                public Object value;
717
718                public DeferredProperty(GroovyBeanDefinitionWrapper beanDefinition, String name, Object value) {
719                        this.beanDefinition = beanDefinition;
720                        this.name = name;
721                        this.value = value;
722                }
723
724                public void apply() {
725                        this.beanDefinition.addProperty(this.name, this.value);
726                }
727        }
728
729
730        /**
731         * A RuntimeBeanReference that takes care of adding new properties to runtime references.
732         */
733        private class GroovyRuntimeBeanReference extends RuntimeBeanReference implements GroovyObject {
734
735                private final GroovyBeanDefinitionWrapper beanDefinition;
736
737                private MetaClass metaClass;
738
739                public GroovyRuntimeBeanReference(String beanName, GroovyBeanDefinitionWrapper beanDefinition, boolean toParent) {
740                        super(beanName, toParent);
741                        this.beanDefinition = beanDefinition;
742                        this.metaClass = InvokerHelper.getMetaClass(this);
743                }
744
745                public MetaClass getMetaClass() {
746                        return this.metaClass;
747                }
748
749                public Object getProperty(String property) {
750                        if (property.equals("beanName")) {
751                                return getBeanName();
752                        }
753                        else if (property.equals("source")) {
754                                return getSource();
755                        }
756                        else if (this.beanDefinition != null) {
757                                return new GroovyPropertyValue(
758                                                property, this.beanDefinition.getBeanDefinition().getPropertyValues().get(property));
759                        }
760                        else {
761                                return this.metaClass.getProperty(this, property);
762                        }
763                }
764
765                public Object invokeMethod(String name, Object args) {
766                        return this.metaClass.invokeMethod(this, name, args);
767                }
768
769                public void setMetaClass(MetaClass metaClass) {
770                        this.metaClass = metaClass;
771                }
772
773                public void setProperty(String property, Object newValue) {
774                        if (!addDeferredProperty(property, newValue)) {
775                                this.beanDefinition.getBeanDefinition().getPropertyValues().add(property, newValue);
776                        }
777                }
778
779
780                /**
781                 * Wraps a bean definition property and ensures that any RuntimeBeanReference
782                 * additions to it are deferred for resolution later.
783                 */
784                private class GroovyPropertyValue extends GroovyObjectSupport {
785
786                        private final String propertyName;
787
788                        private final Object propertyValue;
789
790                        public GroovyPropertyValue(String propertyName, Object propertyValue) {
791                                this.propertyName = propertyName;
792                                this.propertyValue = propertyValue;
793                        }
794
795                        public void leftShift(Object value) {
796                                InvokerHelper.invokeMethod(this.propertyValue, "leftShift", value);
797                                updateDeferredProperties(value);
798                        }
799
800                        public boolean add(Object value) {
801                                boolean retVal = (Boolean) InvokerHelper.invokeMethod(this.propertyValue, "add", value);
802                                updateDeferredProperties(value);
803                                return retVal;
804                        }
805
806                        public boolean addAll(Collection values) {
807                                boolean retVal = (Boolean) InvokerHelper.invokeMethod(this.propertyValue, "addAll", values);
808                                for (Object value : values) {
809                                        updateDeferredProperties(value);
810                                }
811                                return retVal;
812                        }
813
814                        @Override
815                        public Object invokeMethod(String name, Object args) {
816                                return InvokerHelper.invokeMethod(this.propertyValue, name, args);
817                        }
818
819                        @Override
820                        public Object getProperty(String name) {
821                                return InvokerHelper.getProperty(this.propertyValue, name);
822                        }
823
824                        @Override
825                        public void setProperty(String name, Object value) {
826                                InvokerHelper.setProperty(this.propertyValue, name, value);
827                        }
828
829                        private void updateDeferredProperties(Object value) {
830                                if (value instanceof RuntimeBeanReference) {
831                                        deferredProperties.put(beanDefinition.getBeanName(),
832                                                        new DeferredProperty(beanDefinition, this.propertyName, this.propertyValue));
833                                }
834                        }
835                }
836        }
837
838}