1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.scripting.support;
18
19 import java.util.HashMap;
20 import java.util.Iterator;
21 import java.util.Map;
22
23 import org.apache.commons.logging.Log;
24 import org.apache.commons.logging.LogFactory;
25
26 import org.springframework.aop.TargetSource;
27 import org.springframework.aop.framework.AopInfrastructureBean;
28 import org.springframework.aop.framework.ProxyFactory;
29 import org.springframework.aop.support.DelegatingIntroductionInterceptor;
30 import org.springframework.asm.Type;
31 import org.springframework.beans.BeanUtils;
32 import org.springframework.beans.PropertyValue;
33 import org.springframework.beans.factory.BeanClassLoaderAware;
34 import org.springframework.beans.factory.BeanCreationException;
35 import org.springframework.beans.factory.BeanCurrentlyInCreationException;
36 import org.springframework.beans.factory.BeanDefinitionStoreException;
37 import org.springframework.beans.factory.BeanFactory;
38 import org.springframework.beans.factory.BeanFactoryAware;
39 import org.springframework.beans.factory.DisposableBean;
40 import org.springframework.beans.factory.FactoryBean;
41 import org.springframework.beans.factory.config.BeanDefinition;
42 import org.springframework.beans.factory.config.BeanPostProcessor;
43 import org.springframework.beans.factory.config.ConfigurableBeanFactory;
44 import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
45 import org.springframework.beans.factory.support.AbstractBeanDefinition;
46 import org.springframework.beans.factory.support.BeanDefinitionValidationException;
47 import org.springframework.beans.factory.support.DefaultListableBeanFactory;
48 import org.springframework.beans.factory.support.GenericBeanDefinition;
49 import org.springframework.cglib.core.Signature;
50 import org.springframework.cglib.proxy.InterfaceMaker;
51 import org.springframework.context.ResourceLoaderAware;
52 import org.springframework.core.Conventions;
53 import org.springframework.core.Ordered;
54 import org.springframework.core.io.DefaultResourceLoader;
55 import org.springframework.core.io.ResourceLoader;
56 import org.springframework.scripting.ScriptFactory;
57 import org.springframework.scripting.ScriptSource;
58 import org.springframework.util.ClassUtils;
59 import org.springframework.util.ObjectUtils;
60 import org.springframework.util.StringUtils;
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139 public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements
140 BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware, DisposableBean, Ordered {
141
142
143
144
145
146
147
148 public static final String INLINE_SCRIPT_PREFIX = "inline:";
149
150 public static final String REFRESH_CHECK_DELAY_ATTRIBUTE = Conventions.getQualifiedAttributeName(
151 ScriptFactoryPostProcessor.class, "refreshCheckDelay");
152
153 public static final String PROXY_TARGET_CLASS_ATTRIBUTE = Conventions.getQualifiedAttributeName(
154 ScriptFactoryPostProcessor.class, "proxyTargetClass");
155
156 public static final String LANGUAGE_ATTRIBUTE = Conventions.getQualifiedAttributeName(
157 ScriptFactoryPostProcessor.class, "language");
158
159 private static final String SCRIPT_FACTORY_NAME_PREFIX = "scriptFactory.";
160
161 private static final String SCRIPTED_OBJECT_NAME_PREFIX = "scriptedObject.";
162
163
164 protected final Log logger = LogFactory.getLog(getClass());
165
166 private long defaultRefreshCheckDelay = -1;
167
168 private boolean defaultProxyTargetClass = false;
169
170 private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
171
172 private ConfigurableBeanFactory beanFactory;
173
174 private ResourceLoader resourceLoader = new DefaultResourceLoader();
175
176 final DefaultListableBeanFactory scriptBeanFactory = new DefaultListableBeanFactory();
177
178
179 private final Map<String, ScriptSource> scriptSourceCache = new HashMap<String, ScriptSource>();
180
181
182
183
184
185
186
187
188
189 public void setDefaultRefreshCheckDelay(long defaultRefreshCheckDelay) {
190 this.defaultRefreshCheckDelay = defaultRefreshCheckDelay;
191 }
192
193
194
195
196
197 public void setDefaultProxyTargetClass(boolean defaultProxyTargetClass) {
198 this.defaultProxyTargetClass = defaultProxyTargetClass;
199 }
200
201 @Override
202 public void setBeanClassLoader(ClassLoader classLoader) {
203 this.beanClassLoader = classLoader;
204 }
205
206 @Override
207 public void setBeanFactory(BeanFactory beanFactory) {
208 if (!(beanFactory instanceof ConfigurableBeanFactory)) {
209 throw new IllegalStateException("ScriptFactoryPostProcessor doesn't work with a BeanFactory "
210 + "which does not implement ConfigurableBeanFactory: " + beanFactory.getClass());
211 }
212 this.beanFactory = (ConfigurableBeanFactory) beanFactory;
213
214
215 this.scriptBeanFactory.setParentBeanFactory(this.beanFactory);
216
217
218 this.scriptBeanFactory.copyConfigurationFrom(this.beanFactory);
219
220
221
222 for (Iterator<BeanPostProcessor> it = this.scriptBeanFactory.getBeanPostProcessors().iterator(); it.hasNext();) {
223 if (it.next() instanceof AopInfrastructureBean) {
224 it.remove();
225 }
226 }
227 }
228
229 @Override
230 public void setResourceLoader(ResourceLoader resourceLoader) {
231 this.resourceLoader = resourceLoader;
232 }
233
234 @Override
235 public int getOrder() {
236 return Integer.MIN_VALUE;
237 }
238
239 @Override
240 public Class<?> predictBeanType(Class<?> beanClass, String beanName) {
241
242 if (!ScriptFactory.class.isAssignableFrom(beanClass)) {
243 return null;
244 }
245
246 BeanDefinition bd = this.beanFactory.getMergedBeanDefinition(beanName);
247
248 try {
249 String scriptFactoryBeanName = SCRIPT_FACTORY_NAME_PREFIX + beanName;
250 String scriptedObjectBeanName = SCRIPTED_OBJECT_NAME_PREFIX + beanName;
251 prepareScriptBeans(bd, scriptFactoryBeanName, scriptedObjectBeanName);
252
253 ScriptFactory scriptFactory = this.scriptBeanFactory.getBean(scriptFactoryBeanName, ScriptFactory.class);
254 ScriptSource scriptSource = getScriptSource(scriptFactoryBeanName, scriptFactory.getScriptSourceLocator());
255 Class<?>[] interfaces = scriptFactory.getScriptInterfaces();
256
257 Class<?> scriptedType = scriptFactory.getScriptedObjectType(scriptSource);
258 if (scriptedType != null) {
259 return scriptedType;
260 }
261 else if (!ObjectUtils.isEmpty(interfaces)) {
262 return (interfaces.length == 1 ? interfaces[0] : createCompositeInterface(interfaces));
263 }
264 else {
265 if (bd.isSingleton()) {
266 Object bean = this.scriptBeanFactory.getBean(scriptedObjectBeanName);
267 if (bean != null) {
268 return bean.getClass();
269 }
270 }
271 }
272 }
273 catch (Exception ex) {
274 if (ex instanceof BeanCreationException
275 && ((BeanCreationException) ex).getMostSpecificCause() instanceof BeanCurrentlyInCreationException) {
276 if (logger.isTraceEnabled()) {
277 logger.trace("Could not determine scripted object type for bean '" + beanName + "': "
278 + ex.getMessage());
279 }
280 }
281 else {
282 if (logger.isDebugEnabled()) {
283 logger.debug("Could not determine scripted object type for bean '" + beanName + "'", ex);
284 }
285 }
286 }
287
288 return null;
289 }
290
291 @Override
292 public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
293
294 if (!ScriptFactory.class.isAssignableFrom(beanClass)) {
295 return null;
296 }
297
298 BeanDefinition bd = this.beanFactory.getMergedBeanDefinition(beanName);
299 String scriptFactoryBeanName = SCRIPT_FACTORY_NAME_PREFIX + beanName;
300 String scriptedObjectBeanName = SCRIPTED_OBJECT_NAME_PREFIX + beanName;
301 prepareScriptBeans(bd, scriptFactoryBeanName, scriptedObjectBeanName);
302
303 ScriptFactory scriptFactory = this.scriptBeanFactory.getBean(scriptFactoryBeanName, ScriptFactory.class);
304 ScriptSource scriptSource = getScriptSource(scriptFactoryBeanName, scriptFactory.getScriptSourceLocator());
305 boolean isFactoryBean = false;
306 try {
307 Class<?> scriptedObjectType = scriptFactory.getScriptedObjectType(scriptSource);
308
309 if (scriptedObjectType != null) {
310 isFactoryBean = FactoryBean.class.isAssignableFrom(scriptedObjectType);
311 }
312 }
313 catch (Exception ex) {
314 throw new BeanCreationException(beanName,
315 "Could not determine scripted object type for " + scriptFactory, ex);
316 }
317
318 long refreshCheckDelay = resolveRefreshCheckDelay(bd);
319 if (refreshCheckDelay >= 0) {
320 Class<?>[] interfaces = scriptFactory.getScriptInterfaces();
321 RefreshableScriptTargetSource ts = new RefreshableScriptTargetSource(this.scriptBeanFactory,
322 scriptedObjectBeanName, scriptFactory, scriptSource, isFactoryBean);
323 boolean proxyTargetClass = resolveProxyTargetClass(bd);
324 String language = (String) bd.getAttribute(LANGUAGE_ATTRIBUTE);
325 if (proxyTargetClass && (language == null || !language.equals("groovy"))) {
326 throw new BeanDefinitionValidationException(
327 "Cannot use proxyTargetClass=true with script beans where language is not 'groovy': '" +
328 language + "'");
329 }
330 ts.setRefreshCheckDelay(refreshCheckDelay);
331 return createRefreshableProxy(ts, interfaces, proxyTargetClass);
332 }
333
334 if (isFactoryBean) {
335 scriptedObjectBeanName = BeanFactory.FACTORY_BEAN_PREFIX + scriptedObjectBeanName;
336 }
337 return this.scriptBeanFactory.getBean(scriptedObjectBeanName);
338 }
339
340
341
342
343
344
345
346
347
348 protected void prepareScriptBeans(BeanDefinition bd, String scriptFactoryBeanName, String scriptedObjectBeanName) {
349
350
351 synchronized (this.scriptBeanFactory) {
352 if (!this.scriptBeanFactory.containsBeanDefinition(scriptedObjectBeanName)) {
353
354 this.scriptBeanFactory.registerBeanDefinition(scriptFactoryBeanName,
355 createScriptFactoryBeanDefinition(bd));
356 ScriptFactory scriptFactory = this.scriptBeanFactory
357 .getBean(scriptFactoryBeanName, ScriptFactory.class);
358 ScriptSource scriptSource = getScriptSource(scriptFactoryBeanName,
359 scriptFactory.getScriptSourceLocator());
360 Class<?>[] interfaces = scriptFactory.getScriptInterfaces();
361
362 Class<?>[] scriptedInterfaces = interfaces;
363 if (scriptFactory.requiresConfigInterface() && !bd.getPropertyValues().isEmpty()) {
364 Class<?> configInterface = createConfigInterface(bd, interfaces);
365 scriptedInterfaces = ObjectUtils.addObjectToArray(interfaces, configInterface);
366 }
367
368 BeanDefinition objectBd = createScriptedObjectBeanDefinition(bd, scriptFactoryBeanName, scriptSource,
369 scriptedInterfaces);
370 long refreshCheckDelay = resolveRefreshCheckDelay(bd);
371 if (refreshCheckDelay >= 0) {
372 objectBd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
373 }
374
375 this.scriptBeanFactory.registerBeanDefinition(scriptedObjectBeanName, objectBd);
376 }
377 }
378 }
379
380
381
382
383
384
385
386
387
388
389
390 protected long resolveRefreshCheckDelay(BeanDefinition beanDefinition) {
391 long refreshCheckDelay = this.defaultRefreshCheckDelay;
392 Object attributeValue = beanDefinition.getAttribute(REFRESH_CHECK_DELAY_ATTRIBUTE);
393 if (attributeValue instanceof Number) {
394 refreshCheckDelay = ((Number) attributeValue).longValue();
395 }
396 else if (attributeValue instanceof String) {
397 refreshCheckDelay = Long.parseLong((String) attributeValue);
398 }
399 else if (attributeValue != null) {
400 throw new BeanDefinitionStoreException("Invalid refresh check delay attribute [" +
401 REFRESH_CHECK_DELAY_ATTRIBUTE + "] with value '" + attributeValue +
402 "': needs to be of type Number or String");
403 }
404 return refreshCheckDelay;
405 }
406
407 protected boolean resolveProxyTargetClass(BeanDefinition beanDefinition) {
408 boolean proxyTargetClass = this.defaultProxyTargetClass;
409 Object attributeValue = beanDefinition.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE);
410 if (attributeValue instanceof Boolean) {
411 proxyTargetClass = (Boolean) attributeValue;
412 }
413 else if (attributeValue instanceof String) {
414 proxyTargetClass = Boolean.valueOf((String) attributeValue);
415 }
416 else if (attributeValue != null) {
417 throw new BeanDefinitionStoreException("Invalid proxy target class attribute [" +
418 PROXY_TARGET_CLASS_ATTRIBUTE + "] with value '" + attributeValue +
419 "': needs to be of type Boolean or String");
420 }
421 return proxyTargetClass;
422 }
423
424
425
426
427
428
429
430
431
432 protected BeanDefinition createScriptFactoryBeanDefinition(BeanDefinition bd) {
433 GenericBeanDefinition scriptBd = new GenericBeanDefinition();
434 scriptBd.setBeanClassName(bd.getBeanClassName());
435 scriptBd.getConstructorArgumentValues().addArgumentValues(bd.getConstructorArgumentValues());
436 return scriptBd;
437 }
438
439
440
441
442
443
444
445
446
447 protected ScriptSource getScriptSource(String beanName, String scriptSourceLocator) {
448 synchronized (this.scriptSourceCache) {
449 ScriptSource scriptSource = this.scriptSourceCache.get(beanName);
450 if (scriptSource == null) {
451 scriptSource = convertToScriptSource(beanName, scriptSourceLocator, this.resourceLoader);
452 this.scriptSourceCache.put(beanName, scriptSource);
453 }
454 return scriptSource;
455 }
456 }
457
458
459
460
461
462
463
464
465
466
467
468 protected ScriptSource convertToScriptSource(String beanName, String scriptSourceLocator,
469 ResourceLoader resourceLoader) {
470
471 if (scriptSourceLocator.startsWith(INLINE_SCRIPT_PREFIX)) {
472 return new StaticScriptSource(scriptSourceLocator.substring(INLINE_SCRIPT_PREFIX.length()), beanName);
473 }
474 else {
475 return new ResourceScriptSource(resourceLoader.getResource(scriptSourceLocator));
476 }
477 }
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493 protected Class<?> createConfigInterface(BeanDefinition bd, Class<?>[] interfaces) {
494 InterfaceMaker maker = new InterfaceMaker();
495 PropertyValue[] pvs = bd.getPropertyValues().getPropertyValues();
496 for (PropertyValue pv : pvs) {
497 String propertyName = pv.getName();
498 Class<?> propertyType = BeanUtils.findPropertyType(propertyName, interfaces);
499 String setterName = "set" + StringUtils.capitalize(propertyName);
500 Signature signature = new Signature(setterName, Type.VOID_TYPE, new Type[] {Type.getType(propertyType)});
501 maker.add(signature, new Type[0]);
502 }
503 if (bd instanceof AbstractBeanDefinition) {
504 AbstractBeanDefinition abd = (AbstractBeanDefinition) bd;
505 if (abd.getInitMethodName() != null) {
506 Signature signature = new Signature(abd.getInitMethodName(), Type.VOID_TYPE, new Type[0]);
507 maker.add(signature, new Type[0]);
508 }
509 if (abd.getDestroyMethodName() != null) {
510 Signature signature = new Signature(abd.getDestroyMethodName(), Type.VOID_TYPE, new Type[0]);
511 maker.add(signature, new Type[0]);
512 }
513 }
514 return maker.create();
515 }
516
517
518
519
520
521
522
523
524
525
526 protected Class<?> createCompositeInterface(Class<?>[] interfaces) {
527 return ClassUtils.createCompositeInterface(interfaces, this.beanClassLoader);
528 }
529
530
531
532
533
534
535
536
537
538
539
540
541 protected BeanDefinition createScriptedObjectBeanDefinition(BeanDefinition bd, String scriptFactoryBeanName,
542 ScriptSource scriptSource, Class<?>[] interfaces) {
543
544 GenericBeanDefinition objectBd = new GenericBeanDefinition(bd);
545 objectBd.setFactoryBeanName(scriptFactoryBeanName);
546 objectBd.setFactoryMethodName("getScriptedObject");
547 objectBd.getConstructorArgumentValues().clear();
548 objectBd.getConstructorArgumentValues().addIndexedArgumentValue(0, scriptSource);
549 objectBd.getConstructorArgumentValues().addIndexedArgumentValue(1, interfaces);
550 return objectBd;
551 }
552
553
554
555
556
557
558
559
560
561 protected Object createRefreshableProxy(TargetSource ts, Class<?>[] interfaces, boolean proxyTargetClass) {
562 ProxyFactory proxyFactory = new ProxyFactory();
563 proxyFactory.setTargetSource(ts);
564 ClassLoader classLoader = this.beanClassLoader;
565
566 if (interfaces == null) {
567 interfaces = ClassUtils.getAllInterfacesForClass(ts.getTargetClass(), this.beanClassLoader);
568 }
569 proxyFactory.setInterfaces(interfaces);
570 if (proxyTargetClass) {
571 classLoader = null;
572 proxyFactory.setProxyTargetClass(proxyTargetClass);
573 }
574
575 DelegatingIntroductionInterceptor introduction = new DelegatingIntroductionInterceptor(ts);
576 introduction.suppressInterface(TargetSource.class);
577 proxyFactory.addAdvice(introduction);
578
579 return proxyFactory.getProxy(classLoader);
580 }
581
582
583
584
585 @Override
586 public void destroy() {
587 this.scriptBeanFactory.destroySingletons();
588 }
589
590 }