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