001: /*
002: * Copyright 2002-2007 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: * http://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:
017: package org.springframework.scripting.support;
018:
019: import java.util.HashMap;
020: import java.util.Iterator;
021: import java.util.Map;
022:
023: import net.sf.cglib.asm.Type;
024: import net.sf.cglib.core.Signature;
025: import net.sf.cglib.proxy.InterfaceMaker;
026: import org.apache.commons.logging.Log;
027: import org.apache.commons.logging.LogFactory;
028:
029: import org.springframework.aop.TargetSource;
030: import org.springframework.aop.framework.AopInfrastructureBean;
031: import org.springframework.aop.framework.ProxyFactory;
032: import org.springframework.aop.support.DelegatingIntroductionInterceptor;
033: import org.springframework.beans.BeanUtils;
034: import org.springframework.beans.PropertyValue;
035: import org.springframework.beans.factory.BeanClassLoaderAware;
036: import org.springframework.beans.factory.BeanDefinitionStoreException;
037: import org.springframework.beans.factory.BeanFactory;
038: import org.springframework.beans.factory.BeanFactoryAware;
039: import org.springframework.beans.factory.DisposableBean;
040: import org.springframework.beans.factory.config.BeanDefinition;
041: import org.springframework.beans.factory.config.BeanPostProcessor;
042: import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
043: import org.springframework.beans.factory.support.AbstractBeanFactory;
044: import org.springframework.beans.factory.support.DefaultListableBeanFactory;
045: import org.springframework.beans.factory.support.RootBeanDefinition;
046: import org.springframework.context.ResourceLoaderAware;
047: import org.springframework.core.Conventions;
048: import org.springframework.core.Ordered;
049: import org.springframework.core.io.DefaultResourceLoader;
050: import org.springframework.core.io.ResourceLoader;
051: import org.springframework.scripting.ScriptFactory;
052: import org.springframework.scripting.ScriptSource;
053: import org.springframework.util.ClassUtils;
054: import org.springframework.util.ObjectUtils;
055: import org.springframework.util.StringUtils;
056:
057: /**
058: * {@link org.springframework.beans.factory.config.BeanPostProcessor} that
059: * handles {@link org.springframework.scripting.ScriptFactory} definitions,
060: * replacing each factory with the actual scripted Java object generated by it.
061: *
062: * <p>This is similar to the
063: * {@link org.springframework.beans.factory.FactoryBean} mechanism, but is
064: * specifically tailored for scripts and not built into Spring's core
065: * container itself but rather implemented as an extension.
066: *
067: * <p><b>NOTE:</b> The most important characteristic of this post-processor
068: * is that constructor arguments are applied to the
069: * {@link org.springframework.scripting.ScriptFactory} instance
070: * while bean property values are applied to the generated scripted object.
071: * Typically, constructor arguments include a script source locator and
072: * potentially script interfaces, while bean property values include
073: * references and config values to inject into the scripted object itself.
074: *
075: * <p>The following {@link ScriptFactoryPostProcessor} will automatically
076: * be applied to the two
077: * {@link org.springframework.scripting.ScriptFactory} definitions below.
078: * At runtime, the actual scripted objects will be exposed for
079: * "bshMessenger" and "groovyMessenger", rather than the
080: * {@link org.springframework.scripting.ScriptFactory} instances. Both of
081: * those are supposed to be castable to the example's <code>Messenger</code>
082: * interfaces here.
083: *
084: * <pre class="code"><bean class="org.springframework.scripting.support.ScriptFactoryPostProcessor"/>
085: *
086: * <bean id="bshMessenger" class="org.springframework.scripting.bsh.BshScriptFactory">
087: * <constructor-arg value="classpath:mypackage/Messenger.bsh"/>
088: * <constructor-arg value="mypackage.Messenger"/>
089: * <property name="message" value="Hello World!"/>
090: * </bean>
091: *
092: * <bean id="groovyMessenger" class="org.springframework.scripting.bsh.GroovyScriptFactory">
093: * <constructor-arg value="classpath:mypackage/Messenger.groovy"/>
094: * <property name="message" value="Hello World!"/>
095: * </bean></pre>
096: *
097: * <p><b>NOTE:</b> Please note that the above excerpt from a Spring
098: * XML bean definition file uses just the <bean/>-style syntax
099: * (in an effort to illustrate using the {@link ScriptFactoryPostProcessor} itself).
100: * In reality, you would never create a <bean/> definition for a
101: * {@link ScriptFactoryPostProcessor} explicitly; rather you would import the
102: * tags from the <code>'lang'</code> namespace and simply create scripted
103: * beans using the tags in that namespace... as part of doing so, a
104: * {@link ScriptFactoryPostProcessor} will implicitly be created for you.
105: *
106: * <p>The Spring reference documentation contains numerous examples of using
107: * tags in the <code>'lang'</code> namespace; by way of an example, find below
108: * a Groovy-backed bean defined using the <code>'lang:groovy'</code> tag.
109: *
110: * <pre class="code">
111: * <?xml version="1.0" encoding="UTF-8"?>
112: * <beans xmlns="http://www.springframework.org/schema/beans"
113: * xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
114: * xmlns:lang="http://www.springframework.org/schema/lang">
115: *
116: * <!-- this is the bean definition for the Groovy-backed Messenger implementation -->
117: * <lang:groovy id="messenger" script-source="classpath:Messenger.groovy">
118: * <lang:property name="message" value="I Can Do The Frug" />
119: * </lang:groovy>
120: *
121: * <!-- an otherwise normal bean that will be injected by the Groovy-backed Messenger -->
122: * <bean id="bookingService" class="x.y.DefaultBookingService">
123: * <property name="messenger" ref="messenger" />
124: * </bean>
125: *
126: * </beans></pre>
127: *
128: * @author Juergen Hoeller
129: * @author Rob Harrop
130: * @author Rick Evans
131: * @since 2.0
132: */
133: public class ScriptFactoryPostProcessor extends
134: InstantiationAwareBeanPostProcessorAdapter implements
135: BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware,
136: DisposableBean, Ordered {
137:
138: /**
139: * The {@link org.springframework.core.io.Resource}-style prefix that denotes
140: * an inline script.
141: * <p>An inline script is a script that is defined right there in the (typically XML)
142: * configuration, as opposed to being defined in an external file.
143: */
144: public static final String INLINE_SCRIPT_PREFIX = "inline:";
145:
146: public static final String REFRESH_CHECK_DELAY_ATTRIBUTE = Conventions
147: .getQualifiedAttributeName(
148: ScriptFactoryPostProcessor.class,
149: "refreshCheckDelay");
150:
151: private static final String SCRIPT_FACTORY_NAME_PREFIX = "scriptFactory.";
152:
153: private static final String SCRIPTED_OBJECT_NAME_PREFIX = "scriptedObject.";
154:
155: /** Logger available to subclasses */
156: protected final Log logger = LogFactory.getLog(getClass());
157:
158: private long defaultRefreshCheckDelay = -1;
159:
160: private ClassLoader beanClassLoader = ClassUtils
161: .getDefaultClassLoader();
162:
163: private AbstractBeanFactory beanFactory;
164:
165: private ResourceLoader resourceLoader = new DefaultResourceLoader();
166:
167: final DefaultListableBeanFactory scriptBeanFactory = new DefaultListableBeanFactory();
168:
169: /** Map from bean name String to ScriptSource object */
170: private final Map scriptSourceCache = new HashMap();
171:
172: /**
173: * Set the delay between refresh checks, in milliseconds.
174: * Default is -1, indicating no refresh checks at all.
175: * <p>Note that an actual refresh will only happen when
176: * the {@link org.springframework.scripting.ScriptSource} indicates
177: * that it has been modified.
178: * @see org.springframework.scripting.ScriptSource#isModified()
179: */
180: public void setDefaultRefreshCheckDelay(
181: long defaultRefreshCheckDelay) {
182: this .defaultRefreshCheckDelay = defaultRefreshCheckDelay;
183: }
184:
185: public void setBeanClassLoader(ClassLoader classLoader) {
186: this .beanClassLoader = classLoader;
187: }
188:
189: public void setBeanFactory(BeanFactory beanFactory) {
190: if (!(beanFactory instanceof AbstractBeanFactory)) {
191: throw new IllegalStateException(
192: "ScriptFactoryPostProcessor must run in AbstractBeanFactory, not in "
193: + beanFactory);
194: }
195: this .beanFactory = (AbstractBeanFactory) beanFactory;
196:
197: // Required so that references (up container hierarchies) are correctly resolved.
198: this .scriptBeanFactory.setParentBeanFactory(this .beanFactory);
199:
200: // Required so that all BeanPostProcessors, Scopes, etc become available.
201: this .scriptBeanFactory.copyConfigurationFrom(this .beanFactory);
202:
203: // Filter out BeanPostProcessors that are part of the AOP infrastructure,
204: // since those are only meant to apply to beans defined in the original factory.
205: for (Iterator it = this .scriptBeanFactory
206: .getBeanPostProcessors().iterator(); it.hasNext();) {
207: BeanPostProcessor postProcessor = (BeanPostProcessor) it
208: .next();
209: if (postProcessor instanceof AopInfrastructureBean) {
210: it.remove();
211: }
212: }
213: }
214:
215: public void setResourceLoader(ResourceLoader resourceLoader) {
216: this .resourceLoader = resourceLoader;
217: }
218:
219: public int getOrder() {
220: return Integer.MIN_VALUE;
221: }
222:
223: public Class predictBeanType(Class beanClass, String beanName) {
224: // We only apply special treatment to ScriptFactory implementations here.
225: if (!ScriptFactory.class.isAssignableFrom(beanClass)) {
226: return null;
227: }
228:
229: RootBeanDefinition bd = this .beanFactory
230: .getMergedBeanDefinition(beanName);
231: String scriptFactoryBeanName = SCRIPT_FACTORY_NAME_PREFIX
232: + beanName;
233: String scriptedObjectBeanName = SCRIPTED_OBJECT_NAME_PREFIX
234: + beanName;
235: prepareScriptBeans(bd, scriptFactoryBeanName,
236: scriptedObjectBeanName);
237:
238: ScriptFactory scriptFactory = (ScriptFactory) this .scriptBeanFactory
239: .getBean(scriptFactoryBeanName, ScriptFactory.class);
240: ScriptSource scriptSource = getScriptSource(
241: scriptFactoryBeanName, scriptFactory
242: .getScriptSourceLocator());
243: Class[] interfaces = scriptFactory.getScriptInterfaces();
244:
245: Class scriptedType = null;
246: try {
247: scriptedType = scriptFactory
248: .getScriptedObjectType(scriptSource);
249: } catch (Exception ex) {
250: if (logger.isDebugEnabled()) {
251: logger.debug(
252: "Could not determine the scripted object type for script factory ["
253: + scriptFactory + "]", ex);
254: }
255: }
256:
257: if (scriptedType != null) {
258: return scriptedType;
259: } else if (ObjectUtils.isEmpty(interfaces)) {
260: if (bd.isSingleton()) {
261: return this .scriptBeanFactory.getBean(
262: scriptedObjectBeanName).getClass();
263: } else {
264: return null;
265: }
266: } else {
267: return (interfaces.length == 1 ? interfaces[0]
268: : createCompositeInterface(interfaces));
269: }
270: }
271:
272: public Object postProcessBeforeInstantiation(Class beanClass,
273: String beanName) {
274: // We only apply special treatment to ScriptFactory implementations here.
275: if (!ScriptFactory.class.isAssignableFrom(beanClass)) {
276: return null;
277: }
278:
279: RootBeanDefinition bd = this .beanFactory
280: .getMergedBeanDefinition(beanName);
281: String scriptFactoryBeanName = SCRIPT_FACTORY_NAME_PREFIX
282: + beanName;
283: String scriptedObjectBeanName = SCRIPTED_OBJECT_NAME_PREFIX
284: + beanName;
285: prepareScriptBeans(bd, scriptFactoryBeanName,
286: scriptedObjectBeanName);
287:
288: long refreshCheckDelay = resolveRefreshCheckDelay(bd);
289: if (refreshCheckDelay >= 0) {
290: ScriptFactory scriptFactory = (ScriptFactory) this .scriptBeanFactory
291: .getBean(scriptFactoryBeanName, ScriptFactory.class);
292: ScriptSource scriptSource = getScriptSource(
293: scriptFactoryBeanName, scriptFactory
294: .getScriptSourceLocator());
295: Class[] interfaces = scriptFactory.getScriptInterfaces();
296: RefreshableScriptTargetSource ts = new RefreshableScriptTargetSource(
297: this .scriptBeanFactory, scriptedObjectBeanName,
298: scriptSource);
299: ts.setRefreshCheckDelay(refreshCheckDelay);
300: return createRefreshableProxy(ts, interfaces);
301: }
302:
303: return this .scriptBeanFactory.getBean(scriptedObjectBeanName);
304: }
305:
306: /**
307: * Prepare the script beans in the internal BeanFactory that this
308: * post-processor uses. Each original bean definition will be split
309: * into a ScriptFactory definition and a scripted object definition.
310: * @param bd the original bean definition in the main BeanFactory
311: * @param scriptFactoryBeanName the name of the internal ScriptFactory bean
312: * @param scriptedObjectBeanName the name of the internal scripted object bean
313: */
314: protected void prepareScriptBeans(RootBeanDefinition bd,
315: String scriptFactoryBeanName, String scriptedObjectBeanName) {
316:
317: // Avoid recreation of the script bean definition in case of a prototype.
318: synchronized (this .scriptBeanFactory) {
319: if (!this .scriptBeanFactory
320: .containsBeanDefinition(scriptedObjectBeanName)) {
321:
322: this .scriptBeanFactory.registerBeanDefinition(
323: scriptFactoryBeanName,
324: createScriptFactoryBeanDefinition(bd));
325: ScriptFactory scriptFactory = (ScriptFactory) this .scriptBeanFactory
326: .getBean(scriptFactoryBeanName,
327: ScriptFactory.class);
328: ScriptSource scriptSource = getScriptSource(
329: scriptFactoryBeanName, scriptFactory
330: .getScriptSourceLocator());
331: Class[] interfaces = scriptFactory
332: .getScriptInterfaces();
333:
334: Class[] scriptedInterfaces = interfaces;
335: if (scriptFactory.requiresConfigInterface()
336: && !bd.getPropertyValues().isEmpty()) {
337: Class configInterface = createConfigInterface(bd,
338: interfaces);
339: scriptedInterfaces = (Class[]) ObjectUtils
340: .addObjectToArray(interfaces,
341: configInterface);
342: }
343:
344: RootBeanDefinition objectBd = createScriptedObjectBeanDefinition(
345: bd, scriptFactoryBeanName, scriptSource,
346: scriptedInterfaces);
347: long refreshCheckDelay = resolveRefreshCheckDelay(bd);
348: if (refreshCheckDelay >= 0) {
349: objectBd.setSingleton(false);
350: }
351:
352: this .scriptBeanFactory.registerBeanDefinition(
353: scriptedObjectBeanName, objectBd);
354: }
355: }
356: }
357:
358: /**
359: * Get the refresh check delay for the given {@link ScriptFactory} {@link BeanDefinition}.
360: * If the {@link BeanDefinition} has a
361: * {@link org.springframework.core.AttributeAccessor metadata attribute}
362: * under the key {@link #REFRESH_CHECK_DELAY_ATTRIBUTE} which is a valid {@link Number}
363: * type, then this value is used. Otherwise, the the {@link #defaultRefreshCheckDelay}
364: * value is used.
365: * @param beanDefinition the BeanDefinition to check
366: * @return the refresh check delay
367: */
368: protected long resolveRefreshCheckDelay(
369: BeanDefinition beanDefinition) {
370: long refreshCheckDelay = this .defaultRefreshCheckDelay;
371: Object attributeValue = beanDefinition
372: .getAttribute(REFRESH_CHECK_DELAY_ATTRIBUTE);
373: if (attributeValue instanceof Number) {
374: refreshCheckDelay = ((Number) attributeValue).longValue();
375: } else if (attributeValue instanceof String) {
376: refreshCheckDelay = Long.parseLong((String) attributeValue);
377: } else if (attributeValue != null) {
378: throw new BeanDefinitionStoreException(
379: "Invalid refresh check delay attribute ["
380: + REFRESH_CHECK_DELAY_ATTRIBUTE
381: + "] with value [" + attributeValue
382: + "]: needs to be of type Number or String");
383: }
384: return refreshCheckDelay;
385: }
386:
387: /**
388: * Create a ScriptFactory bean definition based on the given script definition,
389: * extracting only the definition data that is relevant for the ScriptFactory
390: * (that is, only bean class and constructor arguments).
391: * @param bd the full script bean definition
392: * @return the extracted ScriptFactory bean definition
393: * @see org.springframework.scripting.ScriptFactory
394: */
395: protected RootBeanDefinition createScriptFactoryBeanDefinition(
396: RootBeanDefinition bd) {
397: RootBeanDefinition scriptBd = new RootBeanDefinition();
398: scriptBd.setBeanClassName(bd.getBeanClassName());
399: scriptBd.getConstructorArgumentValues().addArgumentValues(
400: bd.getConstructorArgumentValues());
401: return scriptBd;
402: }
403:
404: /**
405: * Obtain a ScriptSource for the given bean, lazily creating it
406: * if not cached already.
407: * @param beanName the name of the scripted bean
408: * @param scriptSourceLocator the script source locator associated with the bean
409: * @return the corresponding ScriptSource instance
410: * @see #convertToScriptSource
411: */
412: protected ScriptSource getScriptSource(String beanName,
413: String scriptSourceLocator) {
414: synchronized (this .scriptSourceCache) {
415: ScriptSource scriptSource = (ScriptSource) this .scriptSourceCache
416: .get(beanName);
417: if (scriptSource == null) {
418: scriptSource = convertToScriptSource(
419: scriptSourceLocator, this .resourceLoader);
420: this .scriptSourceCache.put(beanName, scriptSource);
421: }
422: return scriptSource;
423: }
424: }
425:
426: /**
427: * Convert the given script source locator to a ScriptSource instance.
428: * <p>By default, supported locators are Spring resource locations
429: * (such as "file:C:/myScript.bsh" or "classpath:myPackage/myScript.bsh")
430: * and inline scripts ("inline:myScriptText...").
431: * @param scriptSourceLocator the script source locator
432: * @param resourceLoader the ResourceLoader to use (if necessary)
433: * @return the ScriptSource instance
434: */
435: protected ScriptSource convertToScriptSource(
436: String scriptSourceLocator, ResourceLoader resourceLoader) {
437: if (scriptSourceLocator.startsWith(INLINE_SCRIPT_PREFIX)) {
438: return new StaticScriptSource(scriptSourceLocator
439: .substring(INLINE_SCRIPT_PREFIX.length()));
440: } else {
441: return new ResourceScriptSource(resourceLoader
442: .getResource(scriptSourceLocator));
443: }
444: }
445:
446: /**
447: * Create a config interface for the given bean definition, defining setter
448: * methods for the defined property values as well as an init method and
449: * a destroy method (if defined).
450: * <p>This implementation creates the interface via CGLIB's InterfaceMaker,
451: * determining the property types from the given interfaces (as far as possible).
452: * @param bd the bean definition (property values etc) to create a
453: * config interface for
454: * @param interfaces the interfaces to check against (might define
455: * getters corresponding to the setters we're supposed to generate)
456: * @return the config interface
457: * @see net.sf.cglib.proxy.InterfaceMaker
458: * @see org.springframework.beans.BeanUtils#findPropertyType
459: */
460: protected Class createConfigInterface(RootBeanDefinition bd,
461: Class[] interfaces) {
462: InterfaceMaker maker = new InterfaceMaker();
463: PropertyValue[] pvs = bd.getPropertyValues()
464: .getPropertyValues();
465: for (int i = 0; i < pvs.length; i++) {
466: String propertyName = pvs[i].getName();
467: Class propertyType = BeanUtils.findPropertyType(
468: propertyName, interfaces);
469: String setterName = "set"
470: + StringUtils.capitalize(propertyName);
471: Signature signature = new Signature(setterName,
472: Type.VOID_TYPE, new Type[] { Type
473: .getType(propertyType) });
474: maker.add(signature, new Type[0]);
475: }
476: if (bd.getInitMethodName() != null) {
477: Signature signature = new Signature(bd.getInitMethodName(),
478: Type.VOID_TYPE, new Type[0]);
479: maker.add(signature, new Type[0]);
480: }
481: if (bd.getDestroyMethodName() != null) {
482: Signature signature = new Signature(bd
483: .getDestroyMethodName(), Type.VOID_TYPE,
484: new Type[0]);
485: maker.add(signature, new Type[0]);
486: }
487: return maker.create();
488: }
489:
490: /**
491: * Create a composite interface Class for the given interfaces,
492: * implementing the given interfaces in one single Class.
493: * <p>The default implementation builds a JDK proxy class
494: * for the given interfaces.
495: * @param interfaces the interfaces to merge
496: * @return the merged interface as Class
497: * @see java.lang.reflect.Proxy#getProxyClass
498: */
499: protected Class createCompositeInterface(Class[] interfaces) {
500: return ClassUtils.createCompositeInterface(interfaces,
501: this .beanClassLoader);
502: }
503:
504: /**
505: * Create a bean definition for the scripted object, based on the given script
506: * definition, extracting the definition data that is relevant for the scripted
507: * object (that is, everything but bean class and constructor arguments).
508: * @param bd the full script bean definition
509: * @param scriptFactoryBeanName the name of the internal ScriptFactory bean
510: * @param scriptSource the ScriptSource for the scripted bean
511: * @param interfaces the interfaces that the scripted bean is supposed to implement
512: * @return the extracted ScriptFactory bean definition
513: * @see org.springframework.scripting.ScriptFactory#getScriptedObject
514: */
515: protected RootBeanDefinition createScriptedObjectBeanDefinition(
516: RootBeanDefinition bd, String scriptFactoryBeanName,
517: ScriptSource scriptSource, Class[] interfaces) {
518:
519: RootBeanDefinition objectBd = new RootBeanDefinition(bd);
520: objectBd.setFactoryBeanName(scriptFactoryBeanName);
521: objectBd.setFactoryMethodName("getScriptedObject");
522: objectBd.getConstructorArgumentValues().clear();
523: objectBd.getConstructorArgumentValues()
524: .addIndexedArgumentValue(0, scriptSource);
525: objectBd.getConstructorArgumentValues()
526: .addIndexedArgumentValue(1, interfaces);
527: return objectBd;
528: }
529:
530: /**
531: * Create a refreshable proxy for the given AOP TargetSource.
532: * @param ts the refreshable TargetSource
533: * @param interfaces the proxy interfaces (may be <code>null</code> to
534: * indicate proxying of all interfaces implemented by the target class)
535: * @return the generated proxy
536: * @see RefreshableScriptTargetSource
537: */
538: protected Object createRefreshableProxy(TargetSource ts,
539: Class[] interfaces) {
540: ProxyFactory proxyFactory = new ProxyFactory();
541: proxyFactory.setTargetSource(ts);
542:
543: if (interfaces == null) {
544: interfaces = ClassUtils.getAllInterfacesForClass(ts
545: .getTargetClass());
546: }
547: proxyFactory.setInterfaces(interfaces);
548:
549: DelegatingIntroductionInterceptor introduction = new DelegatingIntroductionInterceptor(
550: ts);
551: introduction.suppressInterface(TargetSource.class);
552: proxyFactory.addAdvice(introduction);
553:
554: return proxyFactory.getProxy(this .beanClassLoader);
555: }
556:
557: /**
558: * Destroy the inner bean factory (used for scripts) on shutdown.
559: */
560: public void destroy() {
561: this.scriptBeanFactory.destroySingletons();
562: }
563:
564: }
|