/* * Copyright 2002-2008 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.scripting.bsh; import java.io.IOException; import bsh.EvalError; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.scripting.ScriptCompilationException; import org.springframework.scripting.ScriptFactory; import org.springframework.scripting.ScriptSource; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** * {@link org.springframework.scripting.ScriptFactory} implementation * for a BeanShell script. * * <p>Typically used in combination with a * {@link org.springframework.scripting.support.ScriptFactoryPostProcessor}; * see the latter's javadoc for a configuration example. * * @author Juergen Hoeller * @author Rob Harrop * @since 2.0 * @see BshScriptUtils * @see org.springframework.scripting.support.ScriptFactoryPostProcessor */ public class BshScriptFactory implements ScriptFactory, BeanClassLoaderAware { private final String scriptSourceLocator; private final Class[] scriptInterfaces; private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); private Class scriptClass; private final Object scriptClassMonitor = new Object(); private boolean wasModifiedForTypeCheck = false; /** * Create a new BshScriptFactory for the given script source. * <p>With this <code>BshScriptFactory</code> variant, the script needs to * declare a full class or return an actual instance of the scripted object. * @param scriptSourceLocator a locator that points to the source of the script. * Interpreted by the post-processor that actually creates the script. */ public BshScriptFactory(String scriptSourceLocator) { this(scriptSourceLocator, null); } /** * Create a new BshScriptFactory for the given script source. * <p>The script may either be a simple script that needs a corresponding proxy * generated (implementing the specified interfaces), or declare a full class * or return an actual instance of the scripted object (in which case the * specified interfaces, if any, need to be implemented by that class/instance). * @param scriptSourceLocator a locator that points to the source of the script. * Interpreted by the post-processor that actually creates the script. * @param scriptInterfaces the Java interfaces that the scripted object * is supposed to implement (may be <code>null</code>) */ public BshScriptFactory(String scriptSourceLocator, Class[] scriptInterfaces) { Assert.hasText(scriptSourceLocator, "'scriptSourceLocator' must not be empty"); this.scriptSourceLocator = scriptSourceLocator; this.scriptInterfaces = scriptInterfaces; } public void setBeanClassLoader(ClassLoader classLoader) { this.beanClassLoader = classLoader; } public String getScriptSourceLocator() { return this.scriptSourceLocator; } public Class[] getScriptInterfaces() { return this.scriptInterfaces; } /** * BeanShell scripts do require a config interface. */ public boolean requiresConfigInterface() { return true; } /** * Load and parse the BeanShell script via {@link BshScriptUtils}. * @see BshScriptUtils#createBshObject(String, Class[], ClassLoader) */ public Object getScriptedObject(ScriptSource scriptSource, Class[] actualInterfaces) throws IOException, ScriptCompilationException { try { Class clazz = null; synchronized (this.scriptClassMonitor) { boolean requiresScriptEvaluation = (this.wasModifiedForTypeCheck && this.scriptClass == null); this.wasModifiedForTypeCheck = false; if (scriptSource.isModified() || requiresScriptEvaluation) { // New script content: Let's check whether it evaluates to a Class. Object result = BshScriptUtils.evaluateBshScript( scriptSource.getScriptAsString(), actualInterfaces, this.beanClassLoader); if (result instanceof Class) { // A Class: We'll cache the Class here and create an instance // outside of the synchronized block. this.scriptClass = (Class) result; } else { // Not a Class: OK, we'll simply create BeanShell objects // through evaluating the script for every call later on. // For this first-time check, let's simply return the // already evaluated object. return result; } } clazz = this.scriptClass; } if (clazz != null) { // A Class: We need to create an instance for every call. try { return clazz.newInstance(); } catch (Throwable ex) { throw new ScriptCompilationException( scriptSource, "Could not instantiate script class: " + clazz.getName(), ex); } } else { // Not a Class: We need to evaluate the script for every call. return BshScriptUtils.createBshObject( scriptSource.getScriptAsString(), actualInterfaces, this.beanClassLoader); } } catch (EvalError ex) { throw new ScriptCompilationException(scriptSource, ex); } } public Class getScriptedObjectType(ScriptSource scriptSource) throws IOException, ScriptCompilationException { try { synchronized (this.scriptClassMonitor) { if (scriptSource.isModified()) { // New script content: Let's check whether it evaluates to a Class. this.wasModifiedForTypeCheck = true; this.scriptClass = BshScriptUtils.determineBshObjectType(scriptSource.getScriptAsString()); } return this.scriptClass; } } catch (EvalError ex) { throw new ScriptCompilationException(scriptSource, ex); } } public boolean requiresScriptedObjectRefresh(ScriptSource scriptSource) { synchronized (this.scriptClassMonitor) { return (scriptSource.isModified() || this.wasModifiedForTypeCheck); } } public String toString() { return "BshScriptFactory: script source locator [" + this.scriptSourceLocator + "]"; } }