File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\util\spring\BeanBuilder.java.html

1 /*
2 * Copyright 2004-2005 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 package hudson.util.spring;
17
18 import groovy.lang.Binding;
19 import groovy.lang.Closure;
20 import groovy.lang.GString;
21 import groovy.lang.GroovyObject;
22 import groovy.lang.GroovyObjectSupport;
23 import groovy.lang.GroovyShell;
24 import groovy.lang.MetaClass;
25 import groovy.lang.MissingMethodException;
26 import org.apache.commons.lang.ArrayUtils;
27 import org.codehaus.groovy.control.CompilerConfiguration;
28 import org.codehaus.groovy.runtime.DefaultGroovyMethods;
29 import org.codehaus.groovy.runtime.InvokerHelper;
30 import org.springframework.beans.factory.config.BeanDefinition;
31 import org.springframework.beans.factory.config.RuntimeBeanReference;
32 import org.springframework.beans.factory.support.ManagedList;
33 import org.springframework.beans.factory.support.ManagedMap;
34 import org.springframework.context.ApplicationContext;
35 import org.springframework.context.support.StaticApplicationContext;
36 import org.springframework.core.io.Resource;
37 import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
38 import org.springframework.web.context.WebApplicationContext;
39
40 import java.io.IOException;
41 import java.io.InputStream;
42 import java.util.Arrays;
43 import java.util.HashMap;
44 import java.util.List;
45 import java.util.ListIterator;
46 import java.util.Map;
47 import java.util.Map.Entry;
48
49 /**
50 * <p>Runtime bean configuration wrapper. Like a Groovy builder, but more of a DSL for
51 * Spring configuration. Allows syntax like:</p>
52 *
53 * <pre>
54 * import org.hibernate.SessionFactory
55 * import org.apache.commons.dbcp.BasicDataSource
56 *
57 * BeanBuilder builder = new BeanBuilder()
58 * builder.beans {
59 * dataSource(BasicDataSource) { // <--- invokeMethod
60 * driverClassName = "org.hsqldb.jdbcDriver"
61 * url = "jdbc:hsqldb:mem:grailsDB"
62 * username = "sa" // <-- setProperty
63 * password = ""
64 * settings = [mynew:"setting"]
65 * }
66 * sessionFactory(SessionFactory) {
67 * dataSource = dataSource // <-- getProperty for retrieving refs
68 * }
69 * myService(MyService) {
70 * nestedBean = { AnotherBean bean-> // <-- setProperty with closure for nested bean
71 * dataSource = dataSource
72 * }
73 * }
74 * }
75 * </pre>
76 * <p>
77 * You can also use the Spring IO API to load resources containing beans defined as a Groovy
78 * script using either the constructors or the loadBeans(Resource[] resources) method
79 * </p>
80 *
81 * @author Graeme Rocher
82 * @since 0.4
83 *
84 */
85 public class BeanBuilder extends GroovyObjectSupport {
86 private static final String ANONYMOUS_BEAN = "bean";
87 private RuntimeSpringConfiguration springConfig = new DefaultRuntimeSpringConfiguration();
88 private BeanConfiguration currentBeanConfig;
89 private Map<String,DeferredProperty> deferredProperties = new HashMap<String,DeferredProperty>();
90 private ApplicationContext parentCtx;
91 private Map binding = new HashMap();
92 private ClassLoader classLoader = null;
93
94
95 public BeanBuilder() {
96 super();
97 }
98
99 public BeanBuilder(ClassLoader classLoader) {
100 super();
101 this.classLoader = classLoader;
102 }
103
104 public BeanBuilder(ApplicationContext parent) {
105 super();
106 this.parentCtx = parent;
107 this.springConfig = new DefaultRuntimeSpringConfiguration(parent);
108 }
109
110 public BeanBuilder(ApplicationContext parent,ClassLoader classLoader) {
111 super();
112 this.parentCtx = parent;
113 this.springConfig = new DefaultRuntimeSpringConfiguration(parent);
114 this.classLoader = classLoader;
115 }
116
117 /**
118 * Parses the bean definition groovy script.
119 */
120 public void parse(InputStream script) {
121 parse(script,new Binding());
122 }
123
124 /**
125 * Parses the bean definition groovy script by first exporting the given {@link Binding}.
126 */
127 public void parse(InputStream script, Binding binding) {
128 setBinding(binding);
129 CompilerConfiguration cc = new CompilerConfiguration();
130 cc.setScriptBaseClass(ClosureScript.class.getName());
131 GroovyShell shell = new GroovyShell(classLoader,binding,cc);
132
133 ClosureScript s = (ClosureScript)shell.parse(script);
134 s.setDelegate(this);
135 s.run();
136 }
137
138 /**
139 * Retrieves the parent ApplicationContext
140 * @return The parent ApplicationContext
141 */
142 public ApplicationContext getParentCtx() {
143 return parentCtx;
144 }
145
146 /**
147 * Retrieves the RuntimeSpringConfiguration instance used the the BeanBuilder
148 * @return The RuntimeSpringConfiguration instance
149 */
150 public RuntimeSpringConfiguration getSpringConfig() {
151 return springConfig;
152 }
153
154 /**
155 * Retrieves a BeanDefinition for the given name
156 * @param name The bean definition
157 * @return The BeanDefinition instance
158 */
159 public BeanDefinition getBeanDefinition(String name) {
160 if(!getSpringConfig().containsBean(name))
161 return null;
162 return getSpringConfig().getBeanConfig(name).getBeanDefinition();
163 }
164
165 /**
166 * Retrieves all BeanDefinitions for this BeanBuilder
167 *
168 * @return A map of BeanDefinition instances with the bean id as the key
169 */
170 public Map<String,BeanDefinition> getBeanDefinitions() {
171
172 Map<String,BeanDefinition> beanDefinitions = new HashMap<String,BeanDefinition>();
173 for (String beanName : getSpringConfig().getBeanNames()) {
174 BeanDefinition bd = getSpringConfig()
175 .getBeanConfig(beanName)
176 .getBeanDefinition();
177 beanDefinitions.put(beanName, bd);
178 }
179 return beanDefinitions;
180 }
181
182 /**
183 * Sets the runtime Spring configuration instance to use. This is not necessary to set
184 * and is configured to default value if not, but is useful for integrating with other
185 * spring configuration mechanisms @see org.codehaus.groovy.grails.commons.spring.GrailsRuntimeConfigurator
186 *
187 * @param springConfig The spring config
188 */
189 public void setSpringConfig(RuntimeSpringConfiguration springConfig) {
190 this.springConfig = springConfig;
191 }
192
193
194
195 /**
196 * This class is used to defer the adding of a property to a bean definition until later
197 * This is for a case where you assign a property to a list that may not contain bean references at
198 * that point of asignment, but may later hence it would need to be managed
199 *
200 * @author Graeme Rocher
201 */
202 private static class DeferredProperty {
203 private BeanConfiguration config;
204 private String name;
205 private Object value;
206
207 DeferredProperty(BeanConfiguration config, String name, Object value) {
208 this.config = config;
209 this.name = name;
210 this.value = value;
211 }
212
213 public void setInBeanConfig() {
214 this.config.addProperty(name, value);
215 }
216 }
217
218 /**
219 * A RuntimeBeanReference that takes care of adding new properties to runtime references
220 *
221 * @author Graeme Rocher
222 * @since 0.4
223 *
224 */
225 private class ConfigurableRuntimeBeanReference extends RuntimeBeanReference implements GroovyObject {
226
227 private MetaClass metaClass;
228 private BeanConfiguration beanConfig;
229
230 public ConfigurableRuntimeBeanReference(String beanName, BeanConfiguration beanConfig) {
231 this(beanName, beanConfig, false);
232 }
233
234 public ConfigurableRuntimeBeanReference(String beanName, BeanConfiguration beanConfig, boolean toParent) {
235 super(beanName, toParent);
236 this.beanConfig = beanConfig;
237 if(beanConfig == null)
238 throw new IllegalArgumentException("Argument [beanConfig] cannot be null");
239 this.metaClass = InvokerHelper.getMetaClass(this);
240 }
241
242 public MetaClass getMetaClass() {
243 return this.metaClass;
244 }
245
246 public Object getProperty(String property) {
247 if(property.equals("beanName"))
248 return getBeanName();
249 else if(property.equals("source"))
250 return getSource();
251 else if(this.beanConfig != null) {
252 return new WrappedPropertyValue(property,beanConfig.getPropertyValue(property));
253 }
254 else
255 return this.metaClass.getProperty(this, property);
256 }
257
258
259
260 /**
261 * Wraps a BeanConfiguration property an ensures that any RuntimeReference additions to it are
262 * deferred for resolution later
263 *
264 * @author Graeme Rocher
265 * @since 0.4
266 *
267 */
268 private class WrappedPropertyValue extends GroovyObjectSupport {
269 private Object propertyValue;
270 private String propertyName;
271 public WrappedPropertyValue(String propertyName, Object propertyValue) {
272 this.propertyValue = propertyValue;
273 this.propertyName = propertyName;
274 }
275
276 public void leftShift(Object value) {
277 InvokerHelper.invokeMethod(propertyValue, "leftShift", value);
278 if(value instanceof RuntimeBeanReference) {
279 deferredProperties.put(beanConfig.getName(), new DeferredProperty(beanConfig, propertyName, propertyValue));
280 }
281 }
282 }
283 public Object invokeMethod(String name, Object args) {
284 return this.metaClass.invokeMethod(this, name, args);
285 }
286
287 public void setMetaClass(MetaClass metaClass) {
288 this.metaClass = metaClass;
289 }
290
291 public void setProperty(String property, Object newValue) {
292 if(!addToDeferred(beanConfig,property, newValue)) {
293 beanConfig.setPropertyValue(property, newValue);
294 }
295 }
296 }
297 /**
298 * Takes a resource pattern as (@see org.springframework.core.io.support.PathMatchingResourcePatternResolver)
299 * This allows you load multiple bean resources in this single builder
300 *
301 * eg loadBeans("classpath:*Beans.groovy")
302 *
303 * @param resourcePattern
304 * @throws IOException When the path cannot be matched
305 */
306 public void loadBeans(String resourcePattern) throws IOException {
307 loadBeans(new PathMatchingResourcePatternResolver().getResources(resourcePattern));
308 }
309
310 /**
311 * Loads a single Resource into the bean builder
312 *
313 * @param resource The resource to load
314 * @throws IOException When an error occurs
315 */
316 public void loadBeans(Resource resource) throws IOException {
317 loadBeans(new Resource[]{resource});
318 }
319
320 /**
321 * Loads a set of given beans
322 * @param resources The resources to load
323 * @throws IOException
324 */
325 public void loadBeans(Resource[] resources) throws IOException {
326 Closure beans = new Closure(this){
327 @Override
328 public Object call(Object[] args) {
329 return beans((Closure)args[0]);
330 }
331 };
332 Binding b = new Binding();
333 b.setVariable("beans", beans);
334
335 GroovyShell shell = classLoader != null ? new GroovyShell(classLoader,b) : new GroovyShell(b);
336 for (Resource resource : resources) {
337 shell.evaluate(resource.getInputStream());
338 }
339 }
340
341 public void registerBeans(StaticApplicationContext ctx) {
342 finalizeDeferredProperties();
343 springConfig.registerBeansWithContext(ctx);
344 }
345
346 public RuntimeBeanReference ref(String refName) {
347 return ref(refName,false);
348 }
349
350 public RuntimeBeanReference parentRef(String refName) {
351 return ref(refName,true);
352 }
353
354 public RuntimeBeanReference ref(String refName, boolean parentRef) {
355 return new RuntimeBeanReference(refName, parentRef);
356 }
357
358 /**
359 * This method is invoked by Groovy when a method that's not defined in Java is invoked.
360 * We use that as a syntax for bean definition.
361 */
362 public Object methodMissing(String name, Object arg) {
363 Object[] args = (Object[])arg;
364
365 if(args.length == 0)
366 throw new MissingMethodException(name,getClass(),args);
367
368 if(args[0] instanceof Closure) {
369 // abstract bean definition
370 return invokeBeanDefiningMethod(name, args);
371 }
372 else if(args[0] instanceof Class || args[0] instanceof RuntimeBeanReference || args[0] instanceof Map) {
373 return invokeBeanDefiningMethod(name, args);
374 }
375 else if (args.length > 1 && args[args.length -1] instanceof Closure) {
376 return invokeBeanDefiningMethod(name, args);
377 }
378 WebApplicationContext ctx = springConfig.getUnrefreshedApplicationContext();
379 MetaClass mc = DefaultGroovyMethods.getMetaClass(ctx);
380 if(!mc.respondsTo(ctx, name, args).isEmpty()){
381 return mc.invokeMethod(ctx,name, args);
382 }
383 return this;
384 }
385
386 public WebApplicationContext createApplicationContext() {
387 finalizeDeferredProperties();
388 return springConfig.getApplicationContext();
389 }
390
391 private void finalizeDeferredProperties() {
392 for (DeferredProperty dp : deferredProperties.values()) {
393 if (dp.value instanceof List) {
394 dp.value = manageListIfNecessary((List)dp.value);
395 } else if (dp.value instanceof Map) {
396 dp.value = manageMapIfNecessary((Map)dp.value);
397 }
398 dp.setInBeanConfig();
399 }
400 deferredProperties.clear();
401 }
402
403 private boolean addToDeferred(BeanConfiguration beanConfig,String property, Object newValue) {
404 if(newValue instanceof List) {
405 deferredProperties.put(currentBeanConfig.getName()+property,new DeferredProperty(currentBeanConfig, property, newValue));
406 return true;
407 }
408 else if(newValue instanceof Map) {
409 deferredProperties.put(currentBeanConfig.getName()+property,new DeferredProperty(currentBeanConfig, property, newValue));
410 return true;
411 }
412 return false;
413 }
414 /**
415 * This method is called when a bean definition node is called
416 *
417 * @param name The name of the bean to define
418 * @param args The arguments to the bean. The first argument is the class name, the last argument is sometimes a closure. All
419 * the arguments in between are constructor arguments
420 * @return The bean configuration instance
421 */
422 private BeanConfiguration invokeBeanDefiningMethod(String name, Object[] args) {
423 BeanConfiguration old = currentBeanConfig;
424 try {
425 if(args[0] instanceof Class) {
426 Class beanClass = args[0] instanceof Class ? (Class)args[0] : args[0].getClass();
427
428 if(args.length >= 1) {
429 if(args[args.length-1] instanceof Closure) {
430 if(args.length-1 != 1) {
431 Object[] constructorArgs = ArrayUtils.subarray(args, 1, args.length-1);
432 filterGStringReferences(constructorArgs);
433 if(name.equals(ANONYMOUS_BEAN))
434 currentBeanConfig = springConfig.createSingletonBean(beanClass,Arrays.asList(constructorArgs));
435 else
436 currentBeanConfig = springConfig.addSingletonBean(name, beanClass, Arrays.asList(constructorArgs));
437 }
438 else {
439 if(name.equals(ANONYMOUS_BEAN))
440 currentBeanConfig = springConfig.createSingletonBean(beanClass);
441 else
442 currentBeanConfig = springConfig.addSingletonBean(name, beanClass);
443 }
444 }
445 else {
446 Object[] constructorArgs = ArrayUtils.subarray(args, 1, args.length);
447 filterGStringReferences(constructorArgs);
448 if(name.equals(ANONYMOUS_BEAN))
449 currentBeanConfig = springConfig.createSingletonBean(beanClass,Arrays.asList(constructorArgs));
450 else
451 currentBeanConfig = springConfig.addSingletonBean(name, beanClass, Arrays.asList(constructorArgs));
452 }
453
454 }
455 }
456 else if(args[0] instanceof RuntimeBeanReference) {
457 currentBeanConfig = springConfig.addSingletonBean(name);
458 currentBeanConfig.setFactoryBean(((RuntimeBeanReference)args[0]).getBeanName());
459 }
460 else if(args[0] instanceof Map) {
461 currentBeanConfig = springConfig.addSingletonBean(name);
462 Map.Entry factoryBeanEntry = (Map.Entry)((Map)args[0]).entrySet().iterator().next();
463 currentBeanConfig.setFactoryBean(factoryBeanEntry.getKey().toString());
464 currentBeanConfig.setFactoryMethod(factoryBeanEntry.getValue().toString());
465 }
466 else if(args[0] instanceof Closure) {
467 currentBeanConfig = springConfig.addAbstractBean(name);
468 }
469 else {
470 Object[] constructorArgs;
471 if(args[args.length-1] instanceof Closure) {
472 constructorArgs= ArrayUtils.subarray(args, 0, args.length-1);
473 }
474 else {
475 constructorArgs= ArrayUtils.subarray(args, 0, args.length);
476 }
477 filterGStringReferences(constructorArgs);
478 currentBeanConfig = new DefaultBeanConfiguration(name, null, Arrays.asList(constructorArgs));
479 springConfig.addBeanConfiguration(name,currentBeanConfig);
480 }
481 if(args[args.length-1] instanceof Closure) {
482 Closure callable = (Closure)args[args.length-1];
483 callable.setDelegate(this);
484 callable.setResolveStrategy(Closure.DELEGATE_FIRST);
485 callable.call(new Object[]{currentBeanConfig});
486
487 }
488
489 return currentBeanConfig;
490 } finally {
491 currentBeanConfig = old;
492 }
493 }
494
495 private void filterGStringReferences(Object[] constructorArgs) {
496 for (int i = 0; i < constructorArgs.length; i++) {
497 Object constructorArg = constructorArgs[i];
498 if(constructorArg instanceof GString) constructorArgs[i] = constructorArg.toString();
499 }
500 }
501
502 /**
503 * When an methods argument is only a closure it is a set of bean definitions
504 *
505 * @param callable The closure argument
506 */
507 public BeanBuilder beans(Closure callable) {
508 callable.setDelegate(this);
509 // callable.setResolveStrategy(Closure.DELEGATE_FIRST);
510 callable.call();
511 finalizeDeferredProperties();
512
513 return this;
514 }
515
516 /**
517 * This method overrides property setting in the scope of the BeanBuilder to set
518 * properties on the current BeanConfiguration
519 */
520 @Override
521 public void setProperty(String name, Object value) {
522 if(currentBeanConfig != null) {
523 if(value instanceof GString)value = value.toString();
524 if(addToDeferred(currentBeanConfig, name, value)) {
525 return;
526 }
527 else if(value instanceof Closure) {
528 BeanConfiguration current = currentBeanConfig;
529 try {
530 Closure callable = (Closure)value;
531
532 Class parameterType = callable.getParameterTypes()[0];
533 if(parameterType.equals(Object.class)) {
534 currentBeanConfig = springConfig.createSingletonBean("");
535 callable.call(new Object[]{currentBeanConfig});
536 }
537 else {
538 currentBeanConfig = springConfig.createSingletonBean(parameterType);
539 callable.call(null);
540 }
541
542 value = currentBeanConfig.getBeanDefinition();
543 }
544 finally {
545 currentBeanConfig = current;
546 }
547 }
548 currentBeanConfig.addProperty(name, value);
549 } else {
550 binding.put(name,value);
551 }
552 }
553
554 /**
555 * Checks whether there are any runtime refs inside a Map and converts
556 * it to a ManagedMap if necessary
557 *
558 * @param value The current map
559 * @return A ManagedMap or a normal map
560 */
561 private Object manageMapIfNecessary(Map<Object, Object> value) {
562 boolean containsRuntimeRefs = false;
563 for (Entry<Object, Object> e : value.entrySet()) {
564 Object v = e.getValue();
565 if (v instanceof RuntimeBeanReference) {
566 containsRuntimeRefs = true;
567 }
568 if (v instanceof BeanConfiguration) {
569 BeanConfiguration c = (BeanConfiguration) v;
570 e.setValue(c.getBeanDefinition());
571 containsRuntimeRefs = true;
572 }
573 }
574 if(containsRuntimeRefs) {
575 // return new ManagedMap(map);
576 ManagedMap m = new ManagedMap();
577 m.putAll(value);
578 return m;
579 }
580 return value;
581 }
582
583 /**
584 * Checks whether there are any runtime refs inside the list and
585 * converts it to a ManagedList if necessary
586 *
587 * @param value The object that represents the list
588 * @return Either a new list or a managed one
589 */
590 private Object manageListIfNecessary(List<Object> value) {
591 boolean containsRuntimeRefs = false;
592 for (ListIterator<Object> i = value.listIterator(); i.hasNext();) {
593 Object e = i.next();
594 if(e instanceof RuntimeBeanReference) {
595 containsRuntimeRefs = true;
596 }
597 if (e instanceof BeanConfiguration) {
598 BeanConfiguration c = (BeanConfiguration) e;
599 i.set(c.getBeanDefinition());
600 containsRuntimeRefs = true;
601 }
602 }
603 if(containsRuntimeRefs) {
604 List tmp = new ManagedList();
605 tmp.addAll(value);
606 value = tmp;
607 }
608 return value;
609 }
610
611 /**
612 * This method overrides property retrieval in the scope of the BeanBuilder to either:
613 *
614 * a) Retrieve a variable from the bean builder's binding if it exits
615 * b) Retrieve a RuntimeBeanReference for a specific bean if it exists
616 * c) Otherwise just delegate to super.getProperty which will resolve properties from the BeanBuilder itself
617 */
618 @Override
619 public Object getProperty(String name) {
620 if(binding.containsKey(name)) {
621 return binding.get(name);
622 }
623 else {
624 if(springConfig.containsBean(name)) {
625 BeanConfiguration beanConfig = springConfig.getBeanConfig(name);
626 if(beanConfig != null) {
627 return new ConfigurableRuntimeBeanReference(name, springConfig.getBeanConfig(name) ,false);
628 }
629 else
630 return new RuntimeBeanReference(name,false);
631 }
632 // this is to deal with the case where the property setter is the last
633 // statement in a closure (hence the return value)
634 else if(currentBeanConfig != null) {
635 if(currentBeanConfig.hasProperty(name))
636 return currentBeanConfig.getPropertyValue(name);
637 else {
638 DeferredProperty dp = deferredProperties.get(currentBeanConfig.getName()+name);
639 if(dp!=null) {
640 return dp.value;
641 }
642 else {
643 return super.getProperty(name);
644 }
645 }
646 }
647 else {
648 return super.getProperty(name);
649 }
650 }
651 }
652
653 /**
654 * Sets the binding (the variables available in the scope of the BeanBuilder)
655 * @param b The Binding instance
656 */
657 public void setBinding(Binding b) {
658 this.binding = b.getVariables();
659 }
660
661 }
662