001/*
002 * Copyright 2002-2019 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<>();
147
148        private final Map<String, DeferredProperty> deferredProperties = new HashMap<>();
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        @Override
186        public void setMetaClass(MetaClass metaClass) {
187                this.metaClass = metaClass;
188        }
189
190        @Override
191        public MetaClass getMetaClass() {
192                return this.metaClass;
193        }
194
195        /**
196         * Set the binding, i.e. the Groovy variables available in the scope
197         * of a {@code GroovyBeanDefinitionReader} closure.
198         */
199        public void setBinding(Binding binding) {
200                this.binding = binding;
201        }
202
203        /**
204         * Return a specified binding for Groovy variables, if any.
205         */
206        public Binding getBinding() {
207                return this.binding;
208        }
209
210
211        // TRADITIONAL BEAN DEFINITION READER METHODS
212
213        /**
214         * Load bean definitions from the specified Groovy script or XML file.
215         * <p>Note that {@code ".xml"} files will be parsed as XML content; all other kinds
216         * of resources will be parsed as Groovy scripts.
217         * @param resource the resource descriptor for the Groovy script or XML file
218         * @return the number of bean definitions found
219         * @throws BeanDefinitionStoreException in case of loading or parsing errors
220         */
221        @Override
222        public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
223                return loadBeanDefinitions(new EncodedResource(resource));
224        }
225
226        /**
227         * Load bean definitions from the specified Groovy script or XML file.
228         * <p>Note that {@code ".xml"} files will be parsed as XML content; all other kinds
229         * of resources will be parsed as Groovy scripts.
230         * @param encodedResource the resource descriptor for the Groovy script or XML file,
231         * allowing specification of an encoding to use for parsing the file
232         * @return the number of bean definitions found
233         * @throws BeanDefinitionStoreException in case of loading or parsing errors
234         */
235        public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
236                // Check for XML files and redirect them to the "standard" XmlBeanDefinitionReader
237                String filename = encodedResource.getResource().getFilename();
238                if (StringUtils.endsWithIgnoreCase(filename, ".xml")) {
239                        return this.standardXmlBeanDefinitionReader.loadBeanDefinitions(encodedResource);
240                }
241
242                if (logger.isTraceEnabled()) {
243                        logger.trace("Loading Groovy bean definitions from " + encodedResource);
244                }
245
246                @SuppressWarnings("serial")
247                Closure<Object> beans = new Closure<Object>(this) {
248                        @Override
249                        public Object call(Object... args) {
250                                invokeBeanDefiningClosure((Closure<?>) args[0]);
251                                return null;
252                        }
253                };
254                Binding binding = new Binding() {
255                        @Override
256                        public void setVariable(String name, Object value) {
257                                if (currentBeanDefinition != null) {
258                                        applyPropertyToBeanDefinition(name, value);
259                                }
260                                else {
261                                        super.setVariable(name, value);
262                                }
263                        }
264                };
265                binding.setVariable("beans", beans);
266
267                int countBefore = getRegistry().getBeanDefinitionCount();
268                try {
269                        GroovyShell shell = new GroovyShell(getBeanClassLoader(), binding);
270                        shell.evaluate(encodedResource.getReader(), "beans");
271                }
272                catch (Throwable ex) {
273                        throw new BeanDefinitionParsingException(new Problem("Error evaluating Groovy script: " + ex.getMessage(),
274                                        new Location(encodedResource.getResource()), null, ex));
275                }
276
277                int count = getRegistry().getBeanDefinitionCount() - countBefore;
278                if (logger.isDebugEnabled()) {
279                        logger.debug("Loaded " + count + " bean definitions from " + encodedResource);
280                }
281                return count;
282        }
283
284
285        // METHODS FOR CONSUMPTION IN A GROOVY CLOSURE
286
287        /**
288         * Defines a set of beans for the given block or closure.
289         * @param closure the block or closure
290         * @return this {@code GroovyBeanDefinitionReader} instance
291         */
292        public GroovyBeanDefinitionReader beans(Closure<?> closure) {
293                return invokeBeanDefiningClosure(closure);
294        }
295
296        /**
297         * Define an inner bean definition.
298         * @param type the bean type
299         * @return the bean definition
300         */
301        public GenericBeanDefinition bean(Class<?> type) {
302                GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
303                beanDefinition.setBeanClass(type);
304                return beanDefinition;
305        }
306
307        /**
308         * Define an inner bean definition.
309         * @param type the bean type
310         * @param args the constructors arguments and closure configurer
311         * @return the bean definition
312         */
313        public AbstractBeanDefinition bean(Class<?> type, Object...args) {
314                GroovyBeanDefinitionWrapper current = this.currentBeanDefinition;
315                try {
316                        Closure<?> callable = null;
317                        Collection<Object> constructorArgs = null;
318                        if (!ObjectUtils.isEmpty(args)) {
319                                int index = args.length;
320                                Object lastArg = args[index - 1];
321                                if (lastArg instanceof Closure<?>) {
322                                        callable = (Closure<?>) lastArg;
323                                        index--;
324                                }
325                                constructorArgs = resolveConstructorArguments(args, 0, index);
326                        }
327                        this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(null, type, constructorArgs);
328                        if (callable != null) {
329                                callable.call(this.currentBeanDefinition);
330                        }
331                        return this.currentBeanDefinition.getBeanDefinition();
332                }
333                finally {
334                        this.currentBeanDefinition = current;
335                }
336        }
337
338        /**
339         * Define a Spring XML namespace definition to use.
340         * @param definition the namespace definition
341         */
342        public void xmlns(Map<String, String> definition) {
343                if (!definition.isEmpty()) {
344                        for (Map.Entry<String,String> entry : definition.entrySet()) {
345                                String namespace = entry.getKey();
346                                String uri = entry.getValue();
347                                if (uri == null) {
348                                        throw new IllegalArgumentException("Namespace definition must supply a non-null URI");
349                                }
350                                NamespaceHandler namespaceHandler =
351                                                this.groovyDslXmlBeanDefinitionReader.getNamespaceHandlerResolver().resolve(uri);
352                                if (namespaceHandler == null) {
353                                        throw new BeanDefinitionParsingException(new Problem("No namespace handler found for URI: " + uri,
354                                                        new Location(new DescriptiveResource(("Groovy")))));
355                                }
356                                this.namespaces.put(namespace, uri);
357                        }
358                }
359        }
360
361        /**
362         * Import Spring bean definitions from either XML or Groovy sources into the
363         * current bean builder instance.
364         * @param resourcePattern the resource pattern
365         */
366        public void importBeans(String resourcePattern) throws IOException {
367                loadBeanDefinitions(resourcePattern);
368        }
369
370
371        // INTERNAL HANDLING OF GROOVY CLOSURES AND PROPERTIES
372
373        /**
374         * This method overrides method invocation to create beans for each method name that
375         * takes a class argument.
376         */
377        @Override
378        public Object invokeMethod(String name, Object arg) {
379                Object[] args = (Object[])arg;
380                if ("beans".equals(name) && args.length == 1 && args[0] instanceof Closure) {
381                        return beans((Closure<?>) args[0]);
382                }
383                else if ("ref".equals(name)) {
384                        String refName;
385                        if (args[0] == null) {
386                                throw new IllegalArgumentException("Argument to ref() is not a valid bean or was not found");
387                        }
388                        if (args[0] instanceof RuntimeBeanReference) {
389                                refName = ((RuntimeBeanReference) args[0]).getBeanName();
390                        }
391                        else {
392                                refName = args[0].toString();
393                        }
394                        boolean parentRef = false;
395                        if (args.length > 1 && args[1] instanceof Boolean) {
396                                parentRef = (Boolean) args[1];
397                        }
398                        return new RuntimeBeanReference(refName, parentRef);
399                }
400                else if (this.namespaces.containsKey(name) && args.length > 0 && args[0] instanceof Closure) {
401                        GroovyDynamicElementReader reader = createDynamicElementReader(name);
402                        reader.invokeMethod("doCall", args);
403                }
404                else if (args.length > 0 && args[0] instanceof Closure) {
405                        // abstract bean definition
406                        return invokeBeanDefiningMethod(name, args);
407                }
408                else if (args.length > 0 &&
409                                (args[0] instanceof Class || args[0] instanceof RuntimeBeanReference || args[0] instanceof Map)) {
410                        return invokeBeanDefiningMethod(name, args);
411                }
412                else if (args.length > 1 && args[args.length -1] instanceof Closure) {
413                        return invokeBeanDefiningMethod(name, args);
414                }
415                MetaClass mc = DefaultGroovyMethods.getMetaClass(getRegistry());
416                if (!mc.respondsTo(getRegistry(), name, args).isEmpty()){
417                        return mc.invokeMethod(getRegistry(), name, args);
418                }
419                return this;
420        }
421
422        private boolean addDeferredProperty(String property, Object newValue) {
423                if (newValue instanceof List || newValue instanceof Map) {
424                        this.deferredProperties.put(this.currentBeanDefinition.getBeanName() + '.' + property,
425                                        new DeferredProperty(this.currentBeanDefinition, property, newValue));
426                        return true;
427                }
428                return false;
429        }
430
431        private void finalizeDeferredProperties() {
432                for (DeferredProperty dp : this.deferredProperties.values()) {
433                        if (dp.value instanceof List) {
434                                dp.value = manageListIfNecessary((List<?>) dp.value);
435                        }
436                        else if (dp.value instanceof Map) {
437                                dp.value = manageMapIfNecessary((Map<?, ?>) dp.value);
438                        }
439                        dp.apply();
440                }
441                this.deferredProperties.clear();
442        }
443
444        /**
445         * When a method argument is only a closure it is a set of bean definitions.
446         * @param callable the closure argument
447         * @return this {@code GroovyBeanDefinitionReader} instance
448         */
449        protected GroovyBeanDefinitionReader invokeBeanDefiningClosure(Closure<?> callable) {
450                callable.setDelegate(this);
451                callable.call();
452                finalizeDeferredProperties();
453                return this;
454        }
455
456        /**
457         * This method is called when a bean definition node is called.
458         * @param beanName the name of the bean to define
459         * @param args the arguments to the bean. The first argument is the class name, the last
460         * argument is sometimes a closure. All the arguments in between are constructor arguments.
461         * @return the bean definition wrapper
462         */
463        private GroovyBeanDefinitionWrapper invokeBeanDefiningMethod(String beanName, Object[] args) {
464                boolean hasClosureArgument = (args[args.length - 1] instanceof Closure);
465                if (args[0] instanceof Class) {
466                        Class<?> beanClass = (Class<?>) args[0];
467                        if (hasClosureArgument) {
468                                if (args.length - 1 != 1) {
469                                        this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(
470                                                        beanName, beanClass, resolveConstructorArguments(args, 1, args.length - 1));
471                                }
472                                else {
473                                        this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName, beanClass);
474                                }
475                        }
476                        else  {
477                                this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(
478                                                beanName, beanClass, resolveConstructorArguments(args, 1, args.length));
479                        }
480                }
481                else if (args[0] instanceof RuntimeBeanReference) {
482                        this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName);
483                        this.currentBeanDefinition.getBeanDefinition().setFactoryBeanName(((RuntimeBeanReference) args[0]).getBeanName());
484                }
485                else if (args[0] instanceof Map) {
486                        // named constructor arguments
487                        if (args.length > 1 && args[1] instanceof Class) {
488                                List<Object> constructorArgs =
489                                                resolveConstructorArguments(args, 2, hasClosureArgument ? args.length - 1 : args.length);
490                                this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName, (Class<?>) args[1], constructorArgs);
491                                Map<?, ?> namedArgs = (Map<?, ?>) args[0];
492                                for (Object o : namedArgs.keySet()) {
493                                        String propName = (String) o;
494                                        setProperty(propName, namedArgs.get(propName));
495                                }
496                        }
497                        // factory method syntax
498                        else {
499                                this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName);
500                                // First arg is the map containing factoryBean : factoryMethod
501                                Map.Entry<?, ?> factoryBeanEntry = ((Map<?, ?>) args[0]).entrySet().iterator().next();
502                                // If we have a closure body, that will be the last argument.
503                                // In between are the constructor args
504                                int constructorArgsTest = (hasClosureArgument ? 2 : 1);
505                                // If we have more than this number of args, we have constructor args
506                                if (args.length > constructorArgsTest){
507                                        // factory-method requires args
508                                        int endOfConstructArgs = (hasClosureArgument ? args.length - 1 : args.length);
509                                        this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName, null,
510                                                        resolveConstructorArguments(args, 1, endOfConstructArgs));
511                                }
512                                else {
513                                        this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName);
514                                }
515                                this.currentBeanDefinition.getBeanDefinition().setFactoryBeanName(factoryBeanEntry.getKey().toString());
516                                this.currentBeanDefinition.getBeanDefinition().setFactoryMethodName(factoryBeanEntry.getValue().toString());
517                        }
518
519                }
520                else if (args[0] instanceof Closure) {
521                        this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName);
522                        this.currentBeanDefinition.getBeanDefinition().setAbstract(true);
523                }
524                else {
525                        List<Object> constructorArgs =
526                                        resolveConstructorArguments(args, 0, hasClosureArgument ? args.length - 1 : args.length);
527                        this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName, null, constructorArgs);
528                }
529
530                if (hasClosureArgument) {
531                        Closure<?> callable = (Closure<?>) args[args.length - 1];
532                        callable.setDelegate(this);
533                        callable.setResolveStrategy(Closure.DELEGATE_FIRST);
534                        callable.call(this.currentBeanDefinition);
535                }
536
537                GroovyBeanDefinitionWrapper beanDefinition = this.currentBeanDefinition;
538                this.currentBeanDefinition = null;
539                beanDefinition.getBeanDefinition().setAttribute(GroovyBeanDefinitionWrapper.class.getName(), beanDefinition);
540                getRegistry().registerBeanDefinition(beanName, beanDefinition.getBeanDefinition());
541                return beanDefinition;
542        }
543
544        protected List<Object> resolveConstructorArguments(Object[] args, int start, int end) {
545                Object[] constructorArgs = Arrays.copyOfRange(args, start, end);
546                for (int i = 0; i < constructorArgs.length; i++) {
547                        if (constructorArgs[i] instanceof GString) {
548                                constructorArgs[i] = constructorArgs[i].toString();
549                        }
550                        else if (constructorArgs[i] instanceof List) {
551                                constructorArgs[i] = manageListIfNecessary((List<?>) constructorArgs[i]);
552                        }
553                        else if (constructorArgs[i] instanceof Map){
554                                constructorArgs[i] = manageMapIfNecessary((Map<?, ?>) constructorArgs[i]);
555                        }
556                }
557                return Arrays.asList(constructorArgs);
558        }
559
560        /**
561         * Checks whether there are any {@link RuntimeBeanReference RuntimeBeanReferences}
562         * inside the {@link Map} and converts it to a {@link ManagedMap} if necessary.
563         * @param map the original Map
564         * @return either the original map or a managed copy of it
565         */
566        private Object manageMapIfNecessary(Map<?, ?> map) {
567                boolean containsRuntimeRefs = false;
568                for (Object element : map.values()) {
569                        if (element instanceof RuntimeBeanReference) {
570                                containsRuntimeRefs = true;
571                                break;
572                        }
573                }
574                if (containsRuntimeRefs) {
575                        Map<Object, Object> managedMap = new ManagedMap<>();
576                        managedMap.putAll(map);
577                        return managedMap;
578                }
579                return map;
580        }
581
582        /**
583         * Checks whether there are any {@link RuntimeBeanReference RuntimeBeanReferences}
584         * inside the {@link List} and converts it to a {@link ManagedList} if necessary.
585         * @param list the original List
586         * @return either the original list or a managed copy of it
587         */
588        private Object manageListIfNecessary(List<?> list) {
589                boolean containsRuntimeRefs = false;
590                for (Object element : list) {
591                        if (element instanceof RuntimeBeanReference) {
592                                containsRuntimeRefs = true;
593                                break;
594                        }
595                }
596                if (containsRuntimeRefs) {
597                        List<Object> managedList = new ManagedList<>();
598                        managedList.addAll(list);
599                        return managedList;
600                }
601                return list;
602        }
603
604        /**
605         * This method overrides property setting in the scope of the {@code GroovyBeanDefinitionReader}
606         * to set properties on the current bean definition.
607         */
608        @Override
609        public void setProperty(String name, Object value) {
610                if (this.currentBeanDefinition != null) {
611                        applyPropertyToBeanDefinition(name, value);
612                }
613        }
614
615        protected void applyPropertyToBeanDefinition(String name, Object value) {
616                if (value instanceof GString) {
617                        value = value.toString();
618                }
619                if (addDeferredProperty(name, value)) {
620                        return;
621                }
622                else if (value instanceof Closure) {
623                        GroovyBeanDefinitionWrapper current = this.currentBeanDefinition;
624                        try {
625                                Closure<?> callable = (Closure<?>) value;
626                                Class<?> parameterType = callable.getParameterTypes()[0];
627                                if (Object.class == parameterType) {
628                                        this.currentBeanDefinition = new GroovyBeanDefinitionWrapper("");
629                                        callable.call(this.currentBeanDefinition);
630                                }
631                                else {
632                                        this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(null, parameterType);
633                                        callable.call((Object) null);
634                                }
635
636                                value = this.currentBeanDefinition.getBeanDefinition();
637                        }
638                        finally {
639                                this.currentBeanDefinition = current;
640                        }
641                }
642                this.currentBeanDefinition.addProperty(name, value);
643        }
644
645        /**
646         * This method overrides property retrieval in the scope of the
647         * {@code GroovyBeanDefinitionReader}. A property retrieval will either:
648         * <ul>
649         * <li>Retrieve a variable from the bean builder's binding if it exists
650         * <li>Retrieve a RuntimeBeanReference for a specific bean if it exists
651         * <li>Otherwise just delegate to MetaClass.getProperty which will resolve
652         * properties from the {@code GroovyBeanDefinitionReader} itself
653         * </ul>
654         */
655        @Override
656        public Object getProperty(String name) {
657                Binding binding = getBinding();
658                if (binding != null && binding.hasVariable(name)) {
659                        return binding.getVariable(name);
660                }
661                else {
662                        if (this.namespaces.containsKey(name)) {
663                                return createDynamicElementReader(name);
664                        }
665                        if (getRegistry().containsBeanDefinition(name)) {
666                                GroovyBeanDefinitionWrapper beanDefinition = (GroovyBeanDefinitionWrapper)
667                                                getRegistry().getBeanDefinition(name).getAttribute(GroovyBeanDefinitionWrapper.class.getName());
668                                if (beanDefinition != null) {
669                                        return new GroovyRuntimeBeanReference(name, beanDefinition, false);
670                                }
671                                else {
672                                        return new RuntimeBeanReference(name, false);
673                                }
674                        }
675                        // This is to deal with the case where the property setter is the last
676                        // statement in a closure (hence the return value)
677                        else if (this.currentBeanDefinition != null) {
678                                MutablePropertyValues pvs = this.currentBeanDefinition.getBeanDefinition().getPropertyValues();
679                                if (pvs.contains(name)) {
680                                        return pvs.get(name);
681                                }
682                                else {
683                                        DeferredProperty dp = this.deferredProperties.get(this.currentBeanDefinition.getBeanName() + name);
684                                        if (dp != null) {
685                                                return dp.value;
686                                        }
687                                        else {
688                                                return getMetaClass().getProperty(this, name);
689                                        }
690                                }
691                        }
692                        else {
693                                return getMetaClass().getProperty(this, name);
694                        }
695                }
696        }
697
698        private GroovyDynamicElementReader createDynamicElementReader(String namespace) {
699                XmlReaderContext readerContext = this.groovyDslXmlBeanDefinitionReader.createReaderContext(
700                                new DescriptiveResource("Groovy"));
701                BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
702                boolean decorating = (this.currentBeanDefinition != null);
703                if (!decorating) {
704                        this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(namespace);
705                }
706                return new GroovyDynamicElementReader(namespace, this.namespaces, delegate, this.currentBeanDefinition, decorating) {
707                        @Override
708                        protected void afterInvocation() {
709                                if (!this.decorating) {
710                                        currentBeanDefinition = null;
711                                }
712                        }
713                };
714        }
715
716
717        /**
718         * This class is used to defer the adding of a property to a bean definition
719         * until later. This is for a case where you assign a property to a list that
720         * may not contain bean references at that point of assignment, but may later;
721         * hence, it would need to be managed.
722         */
723        private static class DeferredProperty {
724
725                private final GroovyBeanDefinitionWrapper beanDefinition;
726
727                private final String name;
728
729                public Object value;
730
731                public DeferredProperty(GroovyBeanDefinitionWrapper beanDefinition, String name, Object value) {
732                        this.beanDefinition = beanDefinition;
733                        this.name = name;
734                        this.value = value;
735                }
736
737                public void apply() {
738                        this.beanDefinition.addProperty(this.name, this.value);
739                }
740        }
741
742
743        /**
744         * A RuntimeBeanReference that takes care of adding new properties to runtime references.
745         */
746        private class GroovyRuntimeBeanReference extends RuntimeBeanReference implements GroovyObject {
747
748                private final GroovyBeanDefinitionWrapper beanDefinition;
749
750                private MetaClass metaClass;
751
752                public GroovyRuntimeBeanReference(String beanName, GroovyBeanDefinitionWrapper beanDefinition, boolean toParent) {
753                        super(beanName, toParent);
754                        this.beanDefinition = beanDefinition;
755                        this.metaClass = InvokerHelper.getMetaClass(this);
756                }
757
758                @Override
759                public MetaClass getMetaClass() {
760                        return this.metaClass;
761                }
762
763                @Override
764                public Object getProperty(String property) {
765                        if (property.equals("beanName")) {
766                                return getBeanName();
767                        }
768                        else if (property.equals("source")) {
769                                return getSource();
770                        }
771                        else if (this.beanDefinition != null) {
772                                return new GroovyPropertyValue(
773                                                property, this.beanDefinition.getBeanDefinition().getPropertyValues().get(property));
774                        }
775                        else {
776                                return this.metaClass.getProperty(this, property);
777                        }
778                }
779
780                @Override
781                public Object invokeMethod(String name, Object args) {
782                        return this.metaClass.invokeMethod(this, name, args);
783                }
784
785                @Override
786                public void setMetaClass(MetaClass metaClass) {
787                        this.metaClass = metaClass;
788                }
789
790                @Override
791                public void setProperty(String property, Object newValue) {
792                        if (!addDeferredProperty(property, newValue)) {
793                                this.beanDefinition.getBeanDefinition().getPropertyValues().add(property, newValue);
794                        }
795                }
796
797
798                /**
799                 * Wraps a bean definition property and ensures that any RuntimeBeanReference
800                 * additions to it are deferred for resolution later.
801                 */
802                private class GroovyPropertyValue extends GroovyObjectSupport {
803
804                        private final String propertyName;
805
806                        private final Object propertyValue;
807
808                        public GroovyPropertyValue(String propertyName, Object propertyValue) {
809                                this.propertyName = propertyName;
810                                this.propertyValue = propertyValue;
811                        }
812
813                        @SuppressWarnings("unused")
814                        public void leftShift(Object value) {
815                                InvokerHelper.invokeMethod(this.propertyValue, "leftShift", value);
816                                updateDeferredProperties(value);
817                        }
818
819                        @SuppressWarnings("unused")
820                        public boolean add(Object value) {
821                                boolean retVal = (Boolean) InvokerHelper.invokeMethod(this.propertyValue, "add", value);
822                                updateDeferredProperties(value);
823                                return retVal;
824                        }
825
826                        @SuppressWarnings("unused")
827                        public boolean addAll(Collection<?> values) {
828                                boolean retVal = (Boolean) InvokerHelper.invokeMethod(this.propertyValue, "addAll", values);
829                                for (Object value : values) {
830                                        updateDeferredProperties(value);
831                                }
832                                return retVal;
833                        }
834
835                        @Override
836                        public Object invokeMethod(String name, Object args) {
837                                return InvokerHelper.invokeMethod(this.propertyValue, name, args);
838                        }
839
840                        @Override
841                        public Object getProperty(String name) {
842                                return InvokerHelper.getProperty(this.propertyValue, name);
843                        }
844
845                        @Override
846                        public void setProperty(String name, Object value) {
847                                InvokerHelper.setProperty(this.propertyValue, name, value);
848                        }
849
850                        private void updateDeferredProperties(Object value) {
851                                if (value instanceof RuntimeBeanReference) {
852                                        deferredProperties.put(beanDefinition.getBeanName(),
853                                                        new DeferredProperty(beanDefinition, this.propertyName, this.propertyValue));
854                                }
855                        }
856                }
857        }
858
859}