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.config;
018:
019: import java.util.List;
020:
021: import org.w3c.dom.Element;
022:
023: import org.springframework.beans.factory.config.ConstructorArgumentValues;
024: import org.springframework.beans.factory.config.RuntimeBeanReference;
025: import org.springframework.beans.factory.support.AbstractBeanDefinition;
026: import org.springframework.beans.factory.support.BeanDefinitionDefaults;
027: import org.springframework.beans.factory.support.GenericBeanDefinition;
028: import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
029: import org.springframework.beans.factory.xml.ParserContext;
030: import org.springframework.beans.factory.xml.XmlReaderContext;
031: import org.springframework.scripting.support.ScriptFactoryPostProcessor;
032: import org.springframework.util.StringUtils;
033: import org.springframework.util.xml.DomUtils;
034:
035: /**
036: * BeanDefinitionParser implementation for the '<code><lang:groovy/></code>',
037: * '<code><lang:jruby/></code>' and '<code><lang:bsh/></code>' tags.
038: * Allows for objects written using dynamic languages to be easily exposed with
039: * the {@link org.springframework.beans.factory.BeanFactory}.
040: *
041: * <p>The script for each object can be specified either as a reference to the Resource
042: * containing it (using the '<code>script-source</code>' attribute) or inline in the XML configuration
043: * itself (using the '<code>inline-script</code>' attribute.
044: *
045: * <p>By default, dynamic objects created with these tags are <strong>not</strong> refreshable.
046: * To enable refreshing, specify the refresh check delay for each object (in milliseconds) using the
047: * '<code>refresh-check-delay</code>' attribute.
048: *
049: * @author Rob Harrop
050: * @author Rod Johnson
051: * @author Juergen Hoeller
052: * @author Mark Fisher
053: * @since 2.0
054: */
055: class ScriptBeanDefinitionParser extends AbstractBeanDefinitionParser {
056:
057: private static final String SCRIPT_SOURCE_ATTRIBUTE = "script-source";
058:
059: private static final String INLINE_SCRIPT_ELEMENT = "inline-script";
060:
061: private static final String SCOPE_ATTRIBUTE = "scope";
062:
063: private static final String AUTOWIRE_ATTRIBUTE = "autowire";
064:
065: private static final String DEPENDENCY_CHECK_ATTRIBUTE = "dependency-check";
066:
067: private static final String INIT_METHOD_ATTRIBUTE = "init-method";
068:
069: private static final String DESTROY_METHOD_ATTRIBUTE = "destroy-method";
070:
071: private static final String SCRIPT_INTERFACES_ATTRIBUTE = "script-interfaces";
072:
073: private static final String REFRESH_CHECK_DELAY_ATTRIBUTE = "refresh-check-delay";
074:
075: private static final String CUSTOMIZER_REF_ATTRIBUTE = "customizer-ref";
076:
077: /**
078: * The {@link org.springframework.scripting.ScriptFactory} class that this
079: * parser instance will create bean definitions for.
080: */
081: private final String scriptFactoryClassName;
082:
083: /**
084: * Create a new instance of this parser, creating bean definitions for the
085: * supplied {@link org.springframework.scripting.ScriptFactory} class.
086: * @param scriptFactoryClassName the ScriptFactory class to operate on
087: */
088: public ScriptBeanDefinitionParser(String scriptFactoryClassName) {
089: this .scriptFactoryClassName = scriptFactoryClassName;
090: }
091:
092: /**
093: * Parses the dynamic object element and returns the resulting bean definition.
094: * Registers a {@link ScriptFactoryPostProcessor} if needed.
095: */
096: protected AbstractBeanDefinition parseInternal(Element element,
097: ParserContext parserContext) {
098: // Resolve the script source.
099: String value = resolveScriptSource(element, parserContext
100: .getReaderContext());
101: if (value == null) {
102: return null;
103: }
104:
105: // Set up infrastructure.
106: LangNamespaceUtils
107: .registerScriptFactoryPostProcessorIfNecessary(parserContext
108: .getRegistry());
109:
110: // Create script factory bean definition.
111: GenericBeanDefinition bd = new GenericBeanDefinition();
112: bd.setBeanClassName(this .scriptFactoryClassName);
113: bd.setSource(parserContext.extractSource(element));
114:
115: // Determine bean scope.
116: String scope = element.getAttribute(SCOPE_ATTRIBUTE);
117: if (StringUtils.hasLength(scope)) {
118: bd.setScope(scope);
119: }
120:
121: // Determine autowire mode.
122: String autowire = element.getAttribute(AUTOWIRE_ATTRIBUTE);
123: int autowireMode = parserContext.getDelegate().getAutowireMode(
124: autowire);
125: // Only "byType" and "byName" supported, but maybe other default inherited...
126: if (autowireMode == GenericBeanDefinition.AUTOWIRE_AUTODETECT) {
127: autowireMode = GenericBeanDefinition.AUTOWIRE_BY_TYPE;
128: } else if (autowireMode == GenericBeanDefinition.AUTOWIRE_CONSTRUCTOR) {
129: autowireMode = GenericBeanDefinition.AUTOWIRE_NO;
130: }
131: bd.setAutowireMode(autowireMode);
132:
133: // Determine dependency check setting.
134: String dependencyCheck = element
135: .getAttribute(DEPENDENCY_CHECK_ATTRIBUTE);
136: bd.setDependencyCheck(parserContext.getDelegate()
137: .getDependencyCheck(dependencyCheck));
138:
139: // Retrieve the defaults for bean definitions within this parser context
140: BeanDefinitionDefaults beanDefinitionDefaults = parserContext
141: .getDelegate().getBeanDefinitionDefaults();
142:
143: // Determine init method and destroy method.
144: String initMethod = element.getAttribute(INIT_METHOD_ATTRIBUTE);
145: if (StringUtils.hasLength(initMethod)) {
146: bd.setInitMethodName(initMethod);
147: } else if (beanDefinitionDefaults.getInitMethodName() != null) {
148: bd.setInitMethodName(beanDefinitionDefaults
149: .getInitMethodName());
150: }
151:
152: String destroyMethod = element
153: .getAttribute(DESTROY_METHOD_ATTRIBUTE);
154: if (StringUtils.hasLength(destroyMethod)) {
155: bd.setDestroyMethodName(destroyMethod);
156: } else if (beanDefinitionDefaults.getDestroyMethodName() != null) {
157: bd.setDestroyMethodName(beanDefinitionDefaults
158: .getDestroyMethodName());
159: }
160:
161: // Attach any refresh metadata.
162: String refreshCheckDelay = element
163: .getAttribute(REFRESH_CHECK_DELAY_ATTRIBUTE);
164: if (StringUtils.hasText(refreshCheckDelay)) {
165: bd
166: .setAttribute(
167: ScriptFactoryPostProcessor.REFRESH_CHECK_DELAY_ATTRIBUTE,
168: new Long(refreshCheckDelay));
169: }
170:
171: // Add constructor arguments.
172: ConstructorArgumentValues cav = bd
173: .getConstructorArgumentValues();
174: int constructorArgNum = 0;
175: cav.addIndexedArgumentValue(constructorArgNum++, value);
176: if (element.hasAttribute(SCRIPT_INTERFACES_ATTRIBUTE)) {
177: cav.addIndexedArgumentValue(constructorArgNum++, element
178: .getAttribute(SCRIPT_INTERFACES_ATTRIBUTE));
179: }
180:
181: // This is used for Groovy. It's a bean reference to a customizer bean.
182: if (element.hasAttribute(CUSTOMIZER_REF_ATTRIBUTE)) {
183: String customizerBeanName = element
184: .getAttribute(CUSTOMIZER_REF_ATTRIBUTE);
185: cav.addIndexedArgumentValue(constructorArgNum++,
186: new RuntimeBeanReference(customizerBeanName));
187: }
188:
189: // Add any property definitions that need adding.
190: parserContext.getDelegate().parsePropertyElements(element, bd);
191:
192: return bd;
193: }
194:
195: /**
196: * Resolves the script source from either the '<code>script-source</code>' attribute or
197: * the '<code>inline-script</code>' element. Logs and {@link XmlReaderContext#error} and
198: * returns <code>null</code> if neither or both of these values are specified.
199: */
200: private String resolveScriptSource(Element element,
201: XmlReaderContext readerContext) {
202: boolean hasScriptSource = element
203: .hasAttribute(SCRIPT_SOURCE_ATTRIBUTE);
204: List elements = DomUtils.getChildElementsByTagName(element,
205: INLINE_SCRIPT_ELEMENT);
206: if (hasScriptSource && !elements.isEmpty()) {
207: readerContext
208: .error(
209: "Only one of 'script-source' and 'inline-script' should be specified.",
210: element);
211: return null;
212: } else if (hasScriptSource) {
213: return element.getAttribute(SCRIPT_SOURCE_ATTRIBUTE);
214: } else if (!elements.isEmpty()) {
215: Element inlineElement = (Element) elements.get(0);
216: return "inline:" + DomUtils.getTextValue(inlineElement);
217: } else {
218: readerContext
219: .error(
220: "Must specify either 'script-source' or 'inline-script'.",
221: element);
222: return null;
223: }
224: }
225:
226: }
|