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.groovy;
018:
019: import java.io.IOException;
020:
021: import groovy.lang.GroovyClassLoader;
022: import groovy.lang.GroovyObject;
023: import groovy.lang.MetaClass;
024: import groovy.lang.Script;
025: import org.codehaus.groovy.control.CompilationFailedException;
026:
027: import org.springframework.beans.BeansException;
028: import org.springframework.beans.factory.BeanClassLoaderAware;
029: import org.springframework.beans.factory.BeanFactory;
030: import org.springframework.beans.factory.BeanFactoryAware;
031: import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
032: import org.springframework.scripting.ScriptCompilationException;
033: import org.springframework.scripting.ScriptFactory;
034: import org.springframework.scripting.ScriptSource;
035: import org.springframework.util.Assert;
036: import org.springframework.util.ClassUtils;
037:
038: /**
039: * {@link org.springframework.scripting.ScriptFactory} implementation
040: * for a Groovy script.
041: *
042: * <p>Typically used in combination with a
043: * {@link org.springframework.scripting.support.ScriptFactoryPostProcessor};
044: * see the latter's javadoc} for a configuration example.
045: *
046: * @author Juergen Hoeller
047: * @author Rob Harrop
048: * @author Rod Johnson
049: * @since 2.0
050: * @see groovy.lang.GroovyClassLoader
051: * @see org.springframework.scripting.support.ScriptFactoryPostProcessor
052: */
053: public class GroovyScriptFactory implements ScriptFactory,
054: BeanFactoryAware, BeanClassLoaderAware {
055:
056: private final String scriptSourceLocator;
057:
058: private final GroovyObjectCustomizer groovyObjectCustomizer;
059:
060: private GroovyClassLoader groovyClassLoader = new GroovyClassLoader(
061: ClassUtils.getDefaultClassLoader());
062:
063: private Class scriptClass;
064:
065: private Class scriptResultClass;
066:
067: private final Object scriptClassMonitor = new Object();
068:
069: /**
070: * Create a new GroovyScriptFactory for the given script source.
071: * <p>We don't need to specify script interfaces here, since
072: * a Groovy script defines its Java interfaces itself.
073: * @param scriptSourceLocator a locator that points to the source of the script.
074: * Interpreted by the post-processor that actually creates the script.
075: */
076: public GroovyScriptFactory(String scriptSourceLocator) {
077: this (scriptSourceLocator, null);
078: }
079:
080: /**
081: * Create a new GroovyScriptFactory for the given script source,
082: * specifying a strategy interface that can create a custom MetaClass
083: * to supply missing methods and otherwise change the behavior of the object.
084: * <p>We don't need to specify script interfaces here, since
085: * a Groovy script defines its Java interfaces itself.
086: * @param scriptSourceLocator a locator that points to the source of the script.
087: * Interpreted by the post-processor that actually creates the script.
088: * @param groovyObjectCustomizer a customizer that can set a custom metaclass
089: * or make other changes to the GroovyObject created by this factory
090: * (may be <code>null</code>)
091: */
092: public GroovyScriptFactory(String scriptSourceLocator,
093: GroovyObjectCustomizer groovyObjectCustomizer) {
094: Assert.hasText(scriptSourceLocator,
095: "'scriptSourceLocator' must not be empty");
096: this .scriptSourceLocator = scriptSourceLocator;
097: this .groovyObjectCustomizer = groovyObjectCustomizer;
098: }
099:
100: public void setBeanFactory(BeanFactory beanFactory)
101: throws BeansException {
102: if (beanFactory instanceof ConfigurableListableBeanFactory) {
103: ((ConfigurableListableBeanFactory) beanFactory)
104: .ignoreDependencyType(MetaClass.class);
105: }
106: }
107:
108: public void setBeanClassLoader(ClassLoader classLoader) {
109: this .groovyClassLoader = new GroovyClassLoader(classLoader);
110: }
111:
112: public String getScriptSourceLocator() {
113: return this .scriptSourceLocator;
114: }
115:
116: /**
117: * Groovy scripts determine their interfaces themselves,
118: * hence we don't need to explicitly expose interfaces here.
119: * @return <code>null</code> always
120: */
121: public Class[] getScriptInterfaces() {
122: return null;
123: }
124:
125: /**
126: * Groovy scripts do not need a config interface,
127: * since they expose their setters as public methods.
128: */
129: public boolean requiresConfigInterface() {
130: return false;
131: }
132:
133: /**
134: * Loads and parses the Groovy script via the GroovyClassLoader.
135: * @see groovy.lang.GroovyClassLoader
136: */
137: public Object getScriptedObject(ScriptSource scriptSource,
138: Class[] actualInterfaces) throws IOException,
139: ScriptCompilationException {
140:
141: try {
142: Class scriptClassToExecute = null;
143:
144: synchronized (this .scriptClassMonitor) {
145: if (this .scriptClass == null
146: || scriptSource.isModified()) {
147: this .scriptClass = this .groovyClassLoader
148: .parseClass(scriptSource
149: .getScriptAsString(), scriptSource
150: .toString());
151:
152: if (Script.class.isAssignableFrom(this .scriptClass)) {
153: // A Groovy script, probably creating an instance: let's execute it.
154: Object result = executeScript(scriptSource,
155: this .scriptClass);
156: this .scriptResultClass = (result != null ? result
157: .getClass()
158: : null);
159: return result;
160: } else {
161: this .scriptResultClass = this .scriptClass;
162: }
163: }
164: scriptClassToExecute = this .scriptClass;
165: }
166:
167: // Process re-execution outside of the synchronized block.
168: return executeScript(scriptSource, scriptClassToExecute);
169: } catch (CompilationFailedException ex) {
170: throw new ScriptCompilationException(scriptSource, ex);
171: }
172: }
173:
174: public Class getScriptedObjectType(ScriptSource scriptSource)
175: throws IOException, ScriptCompilationException {
176:
177: synchronized (this .scriptClassMonitor) {
178: if (this .scriptClass == null || scriptSource.isModified()) {
179: this .scriptClass = this .groovyClassLoader.parseClass(
180: scriptSource.getScriptAsString(), scriptSource
181: .toString());
182:
183: if (Script.class.isAssignableFrom(this .scriptClass)) {
184: // A Groovy script, probably creating an instance: let's execute it.
185: Object result = executeScript(scriptSource,
186: this .scriptClass);
187: this .scriptResultClass = (result != null ? result
188: .getClass() : null);
189: } else {
190: this .scriptResultClass = this .scriptClass;
191: }
192: }
193: return this .scriptResultClass;
194: }
195: }
196:
197: /**
198: * Instantiate the given Groovy script class and run it if necessary.
199: * @param scriptSource the source for the underlying script
200: * @param scriptClass the Groovy script class
201: * @return the result object (either an instance of the script class
202: * or the result of running the script instance)
203: * @throws ScriptCompilationException in case of instantiation failure
204: */
205: protected Object executeScript(ScriptSource scriptSource,
206: Class scriptClass) throws ScriptCompilationException {
207: try {
208: GroovyObject goo = (GroovyObject) scriptClass.newInstance();
209:
210: if (this .groovyObjectCustomizer != null) {
211: // Allow metaclass and other customization.
212: this .groovyObjectCustomizer.customize(goo);
213: }
214:
215: if (goo instanceof Script) {
216: // A Groovy script, probably creating an instance: let's execute it.
217: return ((Script) goo).run();
218: } else {
219: // An instance of the scripted class: let's return it as-is.
220: return goo;
221: }
222: } catch (InstantiationException ex) {
223: throw new ScriptCompilationException(scriptSource,
224: "Could not instantiate Groovy script class: "
225: + scriptClass.getName(), ex);
226: } catch (IllegalAccessException ex) {
227: throw new ScriptCompilationException(scriptSource,
228: "Could not access Groovy script constructor: "
229: + scriptClass.getName(), ex);
230: }
231: }
232:
233: public String toString() {
234: return "GroovyScriptFactory: script source locator ["
235: + this .scriptSourceLocator + "]";
236: }
237:
238: }
|