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}