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}