1 /* 2 * Copyright 2002-2008 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.springframework.scripting.groovy; 18 19 import java.io.IOException; 20 21 import groovy.lang.GroovyClassLoader; 22 import groovy.lang.GroovyObject; 23 import groovy.lang.MetaClass; 24 import groovy.lang.Script; 25 import org.codehaus.groovy.control.CompilationFailedException; 26 27 import org.springframework.beans.BeansException; 28 import org.springframework.beans.factory.BeanClassLoaderAware; 29 import org.springframework.beans.factory.BeanFactory; 30 import org.springframework.beans.factory.BeanFactoryAware; 31 import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 32 import org.springframework.scripting.ScriptCompilationException; 33 import org.springframework.scripting.ScriptFactory; 34 import org.springframework.scripting.ScriptSource; 35 import org.springframework.util.Assert; 36 import org.springframework.util.ClassUtils; 37 38 /** 39 * {@link org.springframework.scripting.ScriptFactory} implementation 40 * for a Groovy script. 41 * 42 * <p>Typically used in combination with a 43 * {@link org.springframework.scripting.support.ScriptFactoryPostProcessor}; 44 * see the latter's javadoc} for a configuration example. 45 * 46 * @author Juergen Hoeller 47 * @author Rob Harrop 48 * @author Rod Johnson 49 * @since 2.0 50 * @see groovy.lang.GroovyClassLoader 51 * @see org.springframework.scripting.support.ScriptFactoryPostProcessor 52 */ 53 public class GroovyScriptFactory implements ScriptFactory, BeanFactoryAware, BeanClassLoaderAware { 54 55 private final String scriptSourceLocator; 56 57 private final GroovyObjectCustomizer groovyObjectCustomizer; 58 59 private GroovyClassLoader groovyClassLoader; 60 61 private Class scriptClass; 62 63 private Class scriptResultClass; 64 65 private CachedResultHolder cachedResult; 66 67 private final Object scriptClassMonitor = new Object(); 68 69 private boolean wasModifiedForTypeCheck = false; 70 71 72 /** 73 * Create a new GroovyScriptFactory for the given script source. 74 * <p>We don't need to specify script interfaces here, since 75 * a Groovy script defines its Java interfaces itself. 76 * @param scriptSourceLocator a locator that points to the source of the script. 77 * Interpreted by the post-processor that actually creates the script. 78 */ GroovyScriptFactory(String scriptSourceLocator)79 public GroovyScriptFactory(String scriptSourceLocator) { 80 this(scriptSourceLocator, null); 81 } 82 83 /** 84 * Create a new GroovyScriptFactory for the given script source, 85 * specifying a strategy interface that can create a custom MetaClass 86 * to supply missing methods and otherwise change the behavior of the object. 87 * <p>We don't need to specify script interfaces here, since 88 * a Groovy script defines its Java interfaces itself. 89 * @param scriptSourceLocator a locator that points to the source of the script. 90 * Interpreted by the post-processor that actually creates the script. 91 * @param groovyObjectCustomizer a customizer that can set a custom metaclass 92 * or make other changes to the GroovyObject created by this factory 93 * (may be <code>null</code>) 94 */ GroovyScriptFactory(String scriptSourceLocator, GroovyObjectCustomizer groovyObjectCustomizer)95 public GroovyScriptFactory(String scriptSourceLocator, GroovyObjectCustomizer groovyObjectCustomizer) { 96 Assert.hasText(scriptSourceLocator, "'scriptSourceLocator' must not be empty"); 97 this.scriptSourceLocator = scriptSourceLocator; 98 this.groovyObjectCustomizer = groovyObjectCustomizer; 99 } 100 101 setBeanFactory(BeanFactory beanFactory)102 public void setBeanFactory(BeanFactory beanFactory) throws BeansException { 103 if (beanFactory instanceof ConfigurableListableBeanFactory) { 104 ((ConfigurableListableBeanFactory) beanFactory).ignoreDependencyType(MetaClass.class); 105 } 106 } 107 setBeanClassLoader(ClassLoader classLoader)108 public void setBeanClassLoader(ClassLoader classLoader) { 109 this.groovyClassLoader = new GroovyClassLoader(classLoader); 110 } 111 112 /** 113 * Return the GroovyClassLoader used by this script factory. 114 */ getGroovyClassLoader()115 public GroovyClassLoader getGroovyClassLoader() { 116 synchronized (this.scriptClassMonitor) { 117 if (this.groovyClassLoader == null) { 118 this.groovyClassLoader = new GroovyClassLoader(ClassUtils.getDefaultClassLoader()); 119 } 120 return this.groovyClassLoader; 121 } 122 } 123 124 getScriptSourceLocator()125 public String getScriptSourceLocator() { 126 return this.scriptSourceLocator; 127 } 128 129 /** 130 * Groovy scripts determine their interfaces themselves, 131 * hence we don't need to explicitly expose interfaces here. 132 * @return <code>null</code> always 133 */ getScriptInterfaces()134 public Class[] getScriptInterfaces() { 135 return null; 136 } 137 138 /** 139 * Groovy scripts do not need a config interface, 140 * since they expose their setters as public methods. 141 */ requiresConfigInterface()142 public boolean requiresConfigInterface() { 143 return false; 144 } 145 146 147 /** 148 * Loads and parses the Groovy script via the GroovyClassLoader. 149 * @see groovy.lang.GroovyClassLoader 150 */ getScriptedObject(ScriptSource scriptSource, Class[] actualInterfaces)151 public Object getScriptedObject(ScriptSource scriptSource, Class[] actualInterfaces) 152 throws IOException, ScriptCompilationException { 153 154 try { 155 Class scriptClassToExecute = null; 156 157 synchronized (this.scriptClassMonitor) { 158 this.wasModifiedForTypeCheck = false; 159 160 if (this.cachedResult != null) { 161 Object result = this.cachedResult.object; 162 this.cachedResult = null; 163 return result; 164 } 165 166 if (this.scriptClass == null || scriptSource.isModified()) { 167 // New script content... 168 this.scriptClass = getGroovyClassLoader().parseClass( 169 scriptSource.getScriptAsString(), scriptSource.suggestedClassName()); 170 171 if (Script.class.isAssignableFrom(this.scriptClass)) { 172 // A Groovy script, probably creating an instance: let's execute it. 173 Object result = executeScript(scriptSource, this.scriptClass); 174 this.scriptResultClass = (result != null ? result.getClass() : null); 175 return result; 176 } 177 else { 178 this.scriptResultClass = this.scriptClass; 179 } 180 } 181 scriptClassToExecute = this.scriptClass; 182 } 183 184 // Process re-execution outside of the synchronized block. 185 return executeScript(scriptSource, scriptClassToExecute); 186 } 187 catch (CompilationFailedException ex) { 188 throw new ScriptCompilationException(scriptSource, ex); 189 } 190 } 191 getScriptedObjectType(ScriptSource scriptSource)192 public Class getScriptedObjectType(ScriptSource scriptSource) 193 throws IOException, ScriptCompilationException { 194 195 try { 196 synchronized (this.scriptClassMonitor) { 197 if (this.scriptClass == null || scriptSource.isModified()) { 198 // New script content... 199 this.wasModifiedForTypeCheck = true; 200 this.scriptClass = getGroovyClassLoader().parseClass( 201 scriptSource.getScriptAsString(), scriptSource.suggestedClassName()); 202 203 if (Script.class.isAssignableFrom(this.scriptClass)) { 204 // A Groovy script, probably creating an instance: let's execute it. 205 Object result = executeScript(scriptSource, this.scriptClass); 206 this.scriptResultClass = (result != null ? result.getClass() : null); 207 this.cachedResult = new CachedResultHolder(result); 208 } 209 else { 210 this.scriptResultClass = this.scriptClass; 211 } 212 } 213 return this.scriptResultClass; 214 } 215 } 216 catch (CompilationFailedException ex) { 217 throw new ScriptCompilationException(scriptSource, ex); 218 } 219 } 220 requiresScriptedObjectRefresh(ScriptSource scriptSource)221 public boolean requiresScriptedObjectRefresh(ScriptSource scriptSource) { 222 synchronized (this.scriptClassMonitor) { 223 return (scriptSource.isModified() || this.wasModifiedForTypeCheck); 224 } 225 } 226 227 228 /** 229 * Instantiate the given Groovy script class and run it if necessary. 230 * @param scriptSource the source for the underlying script 231 * @param scriptClass the Groovy script class 232 * @return the result object (either an instance of the script class 233 * or the result of running the script instance) 234 * @throws ScriptCompilationException in case of instantiation failure 235 */ executeScript(ScriptSource scriptSource, Class scriptClass)236 protected Object executeScript(ScriptSource scriptSource, Class scriptClass) throws ScriptCompilationException { 237 try { 238 GroovyObject goo = (GroovyObject) scriptClass.newInstance(); 239 240 if (this.groovyObjectCustomizer != null) { 241 // Allow metaclass and other customization. 242 this.groovyObjectCustomizer.customize(goo); 243 } 244 245 if (goo instanceof Script) { 246 // A Groovy script, probably creating an instance: let's execute it. 247 return ((Script) goo).run(); 248 } 249 else { 250 // An instance of the scripted class: let's return it as-is. 251 return goo; 252 } 253 } 254 catch (InstantiationException ex) { 255 throw new ScriptCompilationException( 256 scriptSource, "Could not instantiate Groovy script class: " + scriptClass.getName(), ex); 257 } 258 catch (IllegalAccessException ex) { 259 throw new ScriptCompilationException( 260 scriptSource, "Could not access Groovy script constructor: " + scriptClass.getName(), ex); 261 } 262 } 263 264 265 @Override toString()266 public String toString() { 267 return "GroovyScriptFactory: script source locator [" + this.scriptSourceLocator + "]"; 268 } 269 270 271 /** 272 * Wrapper that holds a temporarily cached result object. 273 */ 274 private static class CachedResultHolder { 275 276 public final Object object; 277 CachedResultHolder(Object object)278 public CachedResultHolder(Object object) { 279 this.object = object; 280 } 281 } 282 283 } 284