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.BeanDefinitionRegistry;
027: import org.springframework.beans.factory.support.RootBeanDefinition;
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: * @since 2.0
053: */
054: class ScriptBeanDefinitionParser extends AbstractBeanDefinitionParser {
055:
056: /**
057: * The unique name under which the internally managed {@link ScriptFactoryPostProcessor} is
058: * registered in the {@link BeanDefinitionRegistry}.
059: */
060: private static final String SCRIPT_FACTORY_POST_PROCESSOR_BEAN_NAME = ".scriptFactoryPostProcessor";
061:
062: private static final String SCRIPT_SOURCE_ATTRIBUTE = "script-source";
063:
064: private static final String INLINE_SCRIPT_ELEMENT = "inline-script";
065:
066: private static final String SCOPE_ATTRIBUTE = "scope";
067:
068: private static final String INIT_METHOD_ATTRIBUTE = "init-method";
069:
070: private static final String DESTROY_METHOD_ATTRIBUTE = "destroy-method";
071:
072: private static final String SCRIPT_INTERFACES_ATTRIBUTE = "script-interfaces";
073:
074: private static final String REFRESH_CHECK_DELAY_ATTRIBUTE = "refresh-check-delay";
075:
076: private static final String CUSTOMIZER_REF_ATTRIBUTE = "customizer-ref";
077:
078: /**
079: * The {@link org.springframework.scripting.ScriptFactory} class that this
080: * parser instance will create bean definitions for.
081: */
082: private final String scriptFactoryClassName;
083:
084: /**
085: * Create a new instance of this parser, creating bean definitions for the
086: * supplied {@link org.springframework.scripting.ScriptFactory} class.
087: * @param scriptFactoryClassName the ScriptFactory class to operate on
088: */
089: public ScriptBeanDefinitionParser(String scriptFactoryClassName) {
090: this .scriptFactoryClassName = scriptFactoryClassName;
091: }
092:
093: /**
094: * Parses the dynamic object element and returns the resulting bean definition.
095: * Registers a {@link ScriptFactoryPostProcessor} if needed.
096: */
097: protected AbstractBeanDefinition parseInternal(Element element,
098: ParserContext parserContext) {
099: // Resolve the script source.
100: String value = resolveScriptSource(element, parserContext
101: .getReaderContext());
102: if (value == null) {
103: return null;
104: }
105:
106: // Set up infrastructure.
107: registerScriptFactoryPostProcessorIfNecessary(parserContext
108: .getRegistry());
109:
110: // Create script factory bean definition.
111: RootBeanDefinition beanDefinition = new RootBeanDefinition();
112: beanDefinition.setBeanClassName(this .scriptFactoryClassName);
113: beanDefinition.setSource(parserContext.extractSource(element));
114:
115: // Determine bean scope.
116: String scope = element.getAttribute(SCOPE_ATTRIBUTE);
117: if (StringUtils.hasLength(scope)) {
118: beanDefinition.setScope(scope);
119: }
120:
121: // Determine init method and destroy method.
122: String initMethod = element.getAttribute(INIT_METHOD_ATTRIBUTE);
123: if (StringUtils.hasLength(initMethod)) {
124: beanDefinition.setInitMethodName(initMethod);
125: }
126: String destroyMethod = element
127: .getAttribute(DESTROY_METHOD_ATTRIBUTE);
128: if (StringUtils.hasLength(destroyMethod)) {
129: beanDefinition.setDestroyMethodName(destroyMethod);
130: }
131:
132: // Attach any refresh metadata.
133: String refreshCheckDelay = element
134: .getAttribute(REFRESH_CHECK_DELAY_ATTRIBUTE);
135: if (StringUtils.hasText(refreshCheckDelay)) {
136: beanDefinition
137: .setAttribute(
138: ScriptFactoryPostProcessor.REFRESH_CHECK_DELAY_ATTRIBUTE,
139: new Long(refreshCheckDelay));
140: }
141:
142: // Add constructor arguments.
143: ConstructorArgumentValues cav = beanDefinition
144: .getConstructorArgumentValues();
145: int constructorArgNum = 0;
146: cav.addIndexedArgumentValue(constructorArgNum++, value);
147: if (element.hasAttribute(SCRIPT_INTERFACES_ATTRIBUTE)) {
148: cav.addIndexedArgumentValue(constructorArgNum++, element
149: .getAttribute(SCRIPT_INTERFACES_ATTRIBUTE));
150: }
151:
152: // This is used for Groovy. It's a bean reference to a customizer bean.
153: if (element.hasAttribute(CUSTOMIZER_REF_ATTRIBUTE)) {
154: String customizerBeanName = element
155: .getAttribute(CUSTOMIZER_REF_ATTRIBUTE);
156: cav.addIndexedArgumentValue(constructorArgNum++,
157: new RuntimeBeanReference(customizerBeanName));
158: }
159:
160: // Add any property definitions that need adding.
161: parserContext.getDelegate().parsePropertyElements(element,
162: beanDefinition);
163:
164: return beanDefinition;
165: }
166:
167: /**
168: * Resolves the script source from either the '<code>script-source</code>' attribute or
169: * the '<code>inline-script</code>' element. Logs and {@link XmlReaderContext#error} and
170: * returns <code>null</code> if neither or both of these values are specified.
171: */
172: private String resolveScriptSource(Element element,
173: XmlReaderContext readerContext) {
174: boolean hasScriptSource = element
175: .hasAttribute(SCRIPT_SOURCE_ATTRIBUTE);
176: List elements = DomUtils.getChildElementsByTagName(element,
177: INLINE_SCRIPT_ELEMENT);
178: if (hasScriptSource && !elements.isEmpty()) {
179: readerContext
180: .error(
181: "Only one of 'script-source' and 'inline-script' should be specified.",
182: element);
183: return null;
184: } else if (hasScriptSource) {
185: return element.getAttribute(SCRIPT_SOURCE_ATTRIBUTE);
186: } else if (!elements.isEmpty()) {
187: Element inlineElement = (Element) elements.get(0);
188: return "inline:" + DomUtils.getTextValue(inlineElement);
189: } else {
190: readerContext
191: .error(
192: "Must specify either 'script-source' or 'inline-script'.",
193: element);
194: return null;
195: }
196: }
197:
198: /**
199: * Registers a {@link ScriptFactoryPostProcessor} bean definition in the supplied
200: * {@link BeanDefinitionRegistry} if the {@link ScriptFactoryPostProcessor} hasn't
201: * already been registered.
202: */
203: private static void registerScriptFactoryPostProcessorIfNecessary(
204: BeanDefinitionRegistry registry) {
205: if (!registry
206: .containsBeanDefinition(SCRIPT_FACTORY_POST_PROCESSOR_BEAN_NAME)) {
207: RootBeanDefinition beanDefinition = new RootBeanDefinition(
208: ScriptFactoryPostProcessor.class);
209: registry.registerBeanDefinition(
210: SCRIPT_FACTORY_POST_PROCESSOR_BEAN_NAME,
211: beanDefinition);
212: }
213: }
214:
215: }
|