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