Introduced ScriptEvaluator strategy interface

Including ScriptEvaluator implementations for JSR-223, Groovy and BeanShell. BeanShell consistently receives the bean ClassLoader now. Also revised ScriptFactory and its implementations for varargs.

Issue: SPR-11007
This commit is contained in:
Juergen Hoeller
2013-10-26 01:17:32 +02:00
committed by unknown
parent 12e896ed8b
commit dfb29f4296
21 changed files with 629 additions and 110 deletions

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2002-2013 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;
import java.util.Map;
/**
* Spring's strategy interface for evaluating a script.
*
* <p>Aside from language-specific implementations, Spring also ships
* a version based on the standard {@code javax.script} package (JSR-223):
* {@link org.springframework.scripting.support.StandardScriptEvaluator}.
*
* @author Juergen Hoeller
* @author Costin Leau
* @since 4.0
*/
public interface ScriptEvaluator {
/**
* Evaluate the given script.
* @param script the ScriptSource for the script to evaluate
* @return the return value of the script, if any
* @throws ScriptCompilationException if the evaluator failed to read,
* compile or evaluate the script
*/
Object evaluate(ScriptSource script) throws ScriptCompilationException;
/**
* Evaluate the given script with the given arguments.
* @param script the ScriptSource for the script to evaluate
* @param arguments the key-value pairs to expose to the script,
* typically as script variables. May be {@code null}.
* @return the return value of the script, if any
* @throws ScriptCompilationException if the evaluator failed to read,
* compile or evaluate the script
*/
Object evaluate(ScriptSource script, Map<String, Object> arguments) throws ScriptCompilationException;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
@@ -49,7 +49,7 @@ public interface ScriptFactory {
* its Java interfaces (such as in the case of Groovy).
* @return the interfaces for the script
*/
Class[] getScriptInterfaces();
Class<?>[] getScriptInterfaces();
/**
* Return whether the script requires a config interface to be
@@ -75,7 +75,7 @@ public interface ScriptFactory {
* @throws IOException if script retrieval failed
* @throws ScriptCompilationException if script compilation failed
*/
Object getScriptedObject(ScriptSource scriptSource, Class[] actualInterfaces)
Object getScriptedObject(ScriptSource scriptSource, Class<?>... actualInterfaces)
throws IOException, ScriptCompilationException;
/**
@@ -91,7 +91,7 @@ public interface ScriptFactory {
* @throws ScriptCompilationException if script compilation failed
* @since 2.0.3
*/
Class getScriptedObjectType(ScriptSource scriptSource)
Class<?> getScriptedObjectType(ScriptSource scriptSource)
throws IOException, ScriptCompilationException;
/**

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2002-2013 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 java.io.StringReader;
import java.util.Map;
import bsh.EvalError;
import bsh.Interpreter;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.scripting.ScriptCompilationException;
import org.springframework.scripting.ScriptEvaluator;
import org.springframework.scripting.ScriptSource;
/**
* BeanShell-based implementation of Spring's {@link ScriptEvaluator} strategy interface.
*
* @author Juergen Hoeller
* @since 4.0
* @see Interpreter#eval(String)
*/
public class BshScriptEvaluator implements ScriptEvaluator, BeanClassLoaderAware {
private ClassLoader classLoader;
/**
* Construct a new BshScriptEvaluator.
*/
public BshScriptEvaluator() {
}
/**
* Construct a new BshScriptEvaluator.
* @param classLoader the ClassLoader to use for the {@link Interpreter}
*/
public BshScriptEvaluator(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public Object evaluate(ScriptSource script) {
return evaluate(script, null);
}
@Override
public Object evaluate(ScriptSource script, Map<String, Object> arguments) {
try {
Interpreter interpreter = new Interpreter();
interpreter.setClassLoader(this.classLoader);
if (arguments != null) {
for (Map.Entry<String, Object> entry : arguments.entrySet()) {
interpreter.set(entry.getKey(), entry.getValue());
}
}
return interpreter.eval(new StringReader(script.getScriptAsString()));
}
catch (IOException ex) {
throw new ScriptCompilationException(script, "Cannot access script", ex);
}
catch (EvalError ex) {
throw new ScriptCompilationException(script, "Evaluation failure", ex);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
@@ -45,11 +45,11 @@ public class BshScriptFactory implements ScriptFactory, BeanClassLoaderAware {
private final String scriptSourceLocator;
private final Class[] scriptInterfaces;
private final Class<?>[] scriptInterfaces;
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
private Class scriptClass;
private Class<?> scriptClass;
private final Object scriptClassMonitor = new Object();
@@ -64,7 +64,9 @@ public class BshScriptFactory implements ScriptFactory, BeanClassLoaderAware {
* Interpreted by the post-processor that actually creates the script.
*/
public BshScriptFactory(String scriptSourceLocator) {
this(scriptSourceLocator, null);
Assert.hasText(scriptSourceLocator, "'scriptSourceLocator' must not be empty");
this.scriptSourceLocator = scriptSourceLocator;
this.scriptInterfaces = null;
}
/**
@@ -78,7 +80,7 @@ public class BshScriptFactory implements ScriptFactory, BeanClassLoaderAware {
* @param scriptInterfaces the Java interfaces that the scripted object
* is supposed to implement (may be {@code null})
*/
public BshScriptFactory(String scriptSourceLocator, Class[] scriptInterfaces) {
public BshScriptFactory(String scriptSourceLocator, Class<?>... scriptInterfaces) {
Assert.hasText(scriptSourceLocator, "'scriptSourceLocator' must not be empty");
this.scriptSourceLocator = scriptSourceLocator;
this.scriptInterfaces = scriptInterfaces;
@@ -96,7 +98,7 @@ public class BshScriptFactory implements ScriptFactory, BeanClassLoaderAware {
}
@Override
public Class[] getScriptInterfaces() {
public Class<?>[] getScriptInterfaces() {
return this.scriptInterfaces;
}
@@ -113,11 +115,11 @@ public class BshScriptFactory implements ScriptFactory, BeanClassLoaderAware {
* @see BshScriptUtils#createBshObject(String, Class[], ClassLoader)
*/
@Override
public Object getScriptedObject(ScriptSource scriptSource, Class[] actualInterfaces)
public Object getScriptedObject(ScriptSource scriptSource, Class<?>... actualInterfaces)
throws IOException, ScriptCompilationException {
try {
Class clazz = null;
Class<?> clazz;
synchronized (this.scriptClassMonitor) {
boolean requiresScriptEvaluation = (this.wasModifiedForTypeCheck && this.scriptClass == null);
@@ -165,7 +167,7 @@ public class BshScriptFactory implements ScriptFactory, BeanClassLoaderAware {
}
@Override
public Class getScriptedObjectType(ScriptSource scriptSource)
public Class<?> getScriptedObjectType(ScriptSource scriptSource)
throws IOException, ScriptCompilationException {
try {
@@ -173,7 +175,8 @@ public class BshScriptFactory implements ScriptFactory, BeanClassLoaderAware {
if (scriptSource.isModified()) {
// New script content: Let's check whether it evaluates to a Class.
this.wasModifiedForTypeCheck = true;
this.scriptClass = BshScriptUtils.determineBshObjectType(scriptSource.getScriptAsString());
this.scriptClass = BshScriptUtils.determineBshObjectType(
scriptSource.getScriptAsString(), this.beanClassLoader);
}
return this.scriptClass;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
@@ -66,7 +66,7 @@ public abstract class BshScriptUtils {
* @throws EvalError in case of BeanShell parsing failure
* @see #createBshObject(String, Class[], ClassLoader)
*/
public static Object createBshObject(String scriptSource, Class[] scriptInterfaces) throws EvalError {
public static Object createBshObject(String scriptSource, Class<?>... scriptInterfaces) throws EvalError {
return createBshObject(scriptSource, scriptInterfaces, ClassUtils.getDefaultClassLoader());
}
@@ -80,11 +80,11 @@ public abstract class BshScriptUtils {
* @param scriptInterfaces the interfaces that the scripted Java object is
* supposed to implement (may be {@code null} or empty if the script itself
* declares a full class or returns an actual instance of the scripted object)
* @param classLoader the ClassLoader to create the script proxy with
* @param classLoader the ClassLoader to use for evaluating the script
* @return the scripted Java object
* @throws EvalError in case of BeanShell parsing failure
*/
public static Object createBshObject(String scriptSource, Class[] scriptInterfaces, ClassLoader classLoader)
public static Object createBshObject(String scriptSource, Class<?>[] scriptInterfaces, ClassLoader classLoader)
throws EvalError {
Object result = evaluateBshScript(scriptSource, scriptInterfaces, classLoader);
@@ -110,12 +110,14 @@ public abstract class BshScriptUtils {
* the scripted object (in which case the Class of the object will be returned).
* In any other case, the returned Class will be {@code null}.
* @param scriptSource the script source text
* @param classLoader the ClassLoader to use for evaluating the script
* @return the scripted Java class, or {@code null} if none could be determined
* @throws EvalError in case of BeanShell parsing failure
*/
static Class determineBshObjectType(String scriptSource) throws EvalError {
static Class<?> determineBshObjectType(String scriptSource, ClassLoader classLoader) throws EvalError {
Assert.hasText(scriptSource, "Script source must not be empty");
Interpreter interpreter = new Interpreter();
interpreter.setClassLoader(classLoader);
Object result = interpreter.eval(scriptSource);
if (result instanceof Class) {
return (Class) result;
@@ -139,15 +141,16 @@ public abstract class BshScriptUtils {
* @param scriptInterfaces the interfaces that the scripted Java object is
* supposed to implement (may be {@code null} or empty if the script itself
* declares a full class or returns an actual instance of the scripted object)
* @param classLoader the ClassLoader to create the script proxy with
* @param classLoader the ClassLoader to use for evaluating the script
* @return the scripted Java class or Java object
* @throws EvalError in case of BeanShell parsing failure
*/
static Object evaluateBshScript(String scriptSource, Class[] scriptInterfaces, ClassLoader classLoader)
static Object evaluateBshScript(String scriptSource, Class<?>[] scriptInterfaces, ClassLoader classLoader)
throws EvalError {
Assert.hasText(scriptSource, "Script source must not be empty");
Interpreter interpreter = new Interpreter();
interpreter.setClassLoader(classLoader);
Object result = interpreter.eval(scriptSource);
if (result != null) {
return result;

View File

@@ -3,6 +3,7 @@
*
* Package providing integration of
* <a href="http://www.beanshell.org">BeanShell</a>
* (and <a href="http://code.google.com/p/beanshell2/">BeanShell2</a>)
* into Spring's scripting infrastructure.
*
*/

View File

@@ -0,0 +1,76 @@
package org.springframework.scripting.groovy;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import org.codehaus.groovy.control.CompilationFailedException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.scripting.ScriptCompilationException;
import org.springframework.scripting.ScriptEvaluator;
import org.springframework.scripting.ScriptSource;
import org.springframework.scripting.support.ResourceScriptSource;
/**
* Groovy-based implementation of Spring's {@link ScriptEvaluator} strategy interface.
*
* @author Juergen Hoeller
* @since 4.0
* @see GroovyShell#evaluate(String, String)
*/
public class GroovyScriptEvaluator implements ScriptEvaluator, BeanClassLoaderAware {
private ClassLoader classLoader;
/**
* Construct a new GroovyScriptEvaluator.
*/
public GroovyScriptEvaluator() {
}
/**
* Construct a new GroovyScriptEvaluator.
* @param classLoader the ClassLoader to use as a parent for the {@link GroovyShell}
*/
public GroovyScriptEvaluator(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public Object evaluate(ScriptSource script) {
return evaluate(script, null);
}
@Override
public Object evaluate(ScriptSource script, Map<String, Object> arguments) {
GroovyShell groovyShell = new GroovyShell(this.classLoader, new Binding(arguments));
try {
String filename = (script instanceof ResourceScriptSource ?
((ResourceScriptSource) script).getResource().getFilename() : null);
if (filename != null) {
return groovyShell.evaluate(script.getScriptAsString(), filename);
}
else {
return groovyShell.evaluate(script.getScriptAsString());
}
}
catch (IOException ex) {
throw new ScriptCompilationException(script, "Cannot access script", ex);
}
catch (CompilationFailedException ex) {
throw new ScriptCompilationException(script, "Evaluation failure", ex);
}
}
}

View File

@@ -43,7 +43,7 @@ import org.springframework.util.ClassUtils;
* {@link org.springframework.scripting.support.ScriptFactoryPostProcessor};
* see the latter's javadoc} for a configuration example.
*
* <p>Note: Spring 4.0 supports Groovy 1.7 and higher.
* <p>Note: Spring 4.0 supports Groovy 1.8 and higher.
*
* @author Juergen Hoeller
* @author Rob Harrop
@@ -60,9 +60,9 @@ public class GroovyScriptFactory implements ScriptFactory, BeanFactoryAware, Bea
private GroovyClassLoader groovyClassLoader;
private Class scriptClass;
private Class<?> scriptClass;
private Class scriptResultClass;
private Class<?> scriptResultClass;
private CachedResultHolder cachedResult;
@@ -137,7 +137,7 @@ public class GroovyScriptFactory implements ScriptFactory, BeanFactoryAware, Bea
* @return {@code null} always
*/
@Override
public Class[] getScriptInterfaces() {
public Class<?>[] getScriptInterfaces() {
return null;
}
@@ -156,11 +156,11 @@ public class GroovyScriptFactory implements ScriptFactory, BeanFactoryAware, Bea
* @see groovy.lang.GroovyClassLoader
*/
@Override
public Object getScriptedObject(ScriptSource scriptSource, Class[] actualInterfaces)
public Object getScriptedObject(ScriptSource scriptSource, Class<?>... actualInterfaces)
throws IOException, ScriptCompilationException {
try {
Class scriptClassToExecute = null;
Class<?> scriptClassToExecute;
synchronized (this.scriptClassMonitor) {
this.wasModifiedForTypeCheck = false;
@@ -198,7 +198,7 @@ public class GroovyScriptFactory implements ScriptFactory, BeanFactoryAware, Bea
}
@Override
public Class getScriptedObjectType(ScriptSource scriptSource)
public Class<?> getScriptedObjectType(ScriptSource scriptSource)
throws IOException, ScriptCompilationException {
try {
@@ -243,7 +243,7 @@ public class GroovyScriptFactory implements ScriptFactory, BeanFactoryAware, Bea
* or the result of running the script instance)
* @throws ScriptCompilationException in case of instantiation failure
*/
protected Object executeScript(ScriptSource scriptSource, Class scriptClass) throws ScriptCompilationException {
protected Object executeScript(ScriptSource scriptSource, Class<?> scriptClass) throws ScriptCompilationException {
try {
GroovyObject goo = (GroovyObject) scriptClass.newInstance();

View File

@@ -49,7 +49,7 @@ public class JRubyScriptFactory implements ScriptFactory, BeanClassLoaderAware {
private final String scriptSourceLocator;
private final Class[] scriptInterfaces;
private final Class<?>[] scriptInterfaces;
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
@@ -61,7 +61,7 @@ public class JRubyScriptFactory implements ScriptFactory, BeanClassLoaderAware {
* @param scriptInterfaces the Java interfaces that the scripted object
* is supposed to implement
*/
public JRubyScriptFactory(String scriptSourceLocator, Class[] scriptInterfaces) {
public JRubyScriptFactory(String scriptSourceLocator, Class<?>... scriptInterfaces) {
Assert.hasText(scriptSourceLocator, "'scriptSourceLocator' must not be empty");
Assert.notEmpty(scriptInterfaces, "'scriptInterfaces' must not be empty");
this.scriptSourceLocator = scriptSourceLocator;
@@ -81,7 +81,7 @@ public class JRubyScriptFactory implements ScriptFactory, BeanClassLoaderAware {
}
@Override
public Class[] getScriptInterfaces() {
public Class<?>[] getScriptInterfaces() {
return this.scriptInterfaces;
}
@@ -98,7 +98,7 @@ public class JRubyScriptFactory implements ScriptFactory, BeanClassLoaderAware {
* @see JRubyScriptUtils#createJRubyObject(String, Class[], ClassLoader)
*/
@Override
public Object getScriptedObject(ScriptSource scriptSource, Class[] actualInterfaces)
public Object getScriptedObject(ScriptSource scriptSource, Class<?>... actualInterfaces)
throws IOException, ScriptCompilationException {
try {
return JRubyScriptUtils.createJRubyObject(
@@ -116,7 +116,7 @@ public class JRubyScriptFactory implements ScriptFactory, BeanClassLoaderAware {
}
@Override
public Class getScriptedObjectType(ScriptSource scriptSource)
public Class<?> getScriptedObjectType(ScriptSource scriptSource)
throws IOException, ScriptCompilationException {
return null;

View File

@@ -63,7 +63,7 @@ public abstract class JRubyScriptUtils {
* @throws JumpException in case of JRuby parsing failure
* @see ClassUtils#getDefaultClassLoader()
*/
public static Object createJRubyObject(String scriptSource, Class[] interfaces) throws JumpException {
public static Object createJRubyObject(String scriptSource, Class<?>... interfaces) throws JumpException {
return createJRubyObject(scriptSource, interfaces, ClassUtils.getDefaultClassLoader());
}
@@ -75,8 +75,7 @@ public abstract class JRubyScriptUtils {
* @return the scripted Java object
* @throws JumpException in case of JRuby parsing failure
*/
@SuppressWarnings("deprecation")
public static Object createJRubyObject(String scriptSource, Class[] interfaces, ClassLoader classLoader) {
public static Object createJRubyObject(String scriptSource, Class<?>[] interfaces, ClassLoader classLoader) {
Ruby ruby = initializeRuntime();
Node scriptRootNode = ruby.parseEval(scriptSource, "", null, 0);
@@ -131,16 +130,16 @@ public abstract class JRubyScriptUtils {
}
else if (child instanceof NewlineNode) {
NewlineNode nn = (NewlineNode) child;
Node found = findClassNode(nn.getNextNode());
if (found instanceof ClassNode) {
return (ClassNode) found;
ClassNode found = findClassNode(nn.getNextNode());
if (found != null) {
return found;
}
}
}
for (Node child : children) {
Node found = findClassNode(child);
if (found instanceof ClassNode) {
return (ClassNode) found;
ClassNode found = findClassNode(child);
if (found != null) {
return found;
}
}
return null;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
@@ -18,12 +18,13 @@ package org.springframework.scripting.support;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.scripting.ScriptSource;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
@@ -49,41 +50,56 @@ public class ResourceScriptSource implements ScriptSource {
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
private final Resource resource;
private EncodedResource resource;
private long lastModified = -1;
private final Object lastModifiedMonitor = new Object();
private String encoding = "UTF-8";
/**
* Create a new ResourceScriptSource for the given resource.
* @param resource the Resource to load the script from
* @param resource the EncodedResource to load the script from
*/
public ResourceScriptSource(Resource resource) {
public ResourceScriptSource(EncodedResource resource) {
Assert.notNull(resource, "Resource must not be null");
this.resource = resource;
}
/**
* Create a new ResourceScriptSource for the given resource.
* @param resource the Resource to load the script from (using UTF-8 encoding)
*/
public ResourceScriptSource(Resource resource) {
Assert.notNull(resource, "Resource must not be null");
this.resource = new EncodedResource(resource, "UTF-8");
}
/**
* Return the {@link org.springframework.core.io.Resource} to load the
* script from.
*/
public final Resource getResource() {
return this.resource;
return this.resource.getResource();
}
/**
* Set the encoding used for reading the script resource.
* <p>The default value for regular Resources is "UTF-8".
* A {@code null} value implies the platform default.
*/
public void setEncoding(String encoding) {
this.resource = new EncodedResource(this.resource.getResource(), encoding);
}
@Override
public String getScriptAsString() throws IOException {
synchronized (this.lastModifiedMonitor) {
this.lastModified = retrieveLastModifiedTime();
}
InputStream stream = this.resource.getInputStream();
Reader reader = (StringUtils.hasText(encoding) ? new InputStreamReader(stream, encoding)
: new InputStreamReader(stream));
Reader reader = this.resource.getReader();
return FileCopyUtils.copyToString(reader);
}
@@ -101,10 +117,11 @@ public class ResourceScriptSource implements ScriptSource {
protected long retrieveLastModifiedTime() {
try {
return getResource().lastModified();
} catch (IOException ex) {
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug(getResource() + " could not be resolved in the file system - "
+ "current timestamp not available for script modification check", ex);
logger.debug(getResource() + " could not be resolved in the file system - " +
"current timestamp not available for script modification check", ex);
}
return 0;
}
@@ -115,18 +132,9 @@ public class ResourceScriptSource implements ScriptSource {
return StringUtils.stripFilenameExtension(getResource().getFilename());
}
/**
* Sets the encoding used for reading the script resource. The default value is "UTF-8".
* A null value, implies the platform default.
*
* @param encoding charset encoding used for reading the script.
*/
public void setEncoding(String encoding) {
this.encoding = encoding;
}
@Override
public String toString() {
return this.resource.toString();
}
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright 2002-2013 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.support;
import java.io.IOException;
import java.util.Map;
import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.scripting.ScriptCompilationException;
import org.springframework.scripting.ScriptEvaluator;
import org.springframework.scripting.ScriptSource;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* {@code javax.script} (JSR-223) based implementation of
* Spring's {@link ScriptEvaluator} strategy interface.
*
* @author Juergen Hoeller
* @author Costin Leau
* @since 4.0
* @see ScriptEngine#eval(String)
*/
public class StandardScriptEvaluator implements ScriptEvaluator, BeanClassLoaderAware {
private volatile ScriptEngineManager scriptEngineManager;
private String language;
/**
* Construct a new StandardScriptEvaluator.
*/
public StandardScriptEvaluator() {
}
/**
* Construct a new StandardScriptEvaluator.
* @param classLoader the class loader to use for script engine detection
*/
public StandardScriptEvaluator(ClassLoader classLoader) {
this.scriptEngineManager = new ScriptEngineManager(classLoader);
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.scriptEngineManager = new ScriptEngineManager(classLoader);
}
/**
* Set the name of language meant for evaluation the scripts (e.g. "Groovy").
*/
public void setLanguage(String language) {
this.language = language;
}
@Override
public Object evaluate(ScriptSource script) {
return evaluate(script, null);
}
@Override
public Object evaluate(ScriptSource script, Map<String, Object> arguments) {
ScriptEngine engine = getScriptEngine(script);
Bindings bindings = (!CollectionUtils.isEmpty(arguments) ? new SimpleBindings(arguments) : null);
try {
return (bindings != null ? engine.eval(script.getScriptAsString(), bindings) :
engine.eval(script.getScriptAsString()));
}
catch (IOException ex) {
throw new ScriptCompilationException(script, "Cannot access script", ex);
}
catch (ScriptException ex) {
throw new ScriptCompilationException(script, "Evaluation failure", ex);
}
}
/**
* Obtain the JSR-223 ScriptEngine to use for the given script.
* @param script the script to evaluate
* @return the ScriptEngine (never {@code null})
*/
protected ScriptEngine getScriptEngine(ScriptSource script) {
if (this.scriptEngineManager == null) {
this.scriptEngineManager = new ScriptEngineManager();
}
if (StringUtils.hasText(this.language)) {
ScriptEngine engine = this.scriptEngineManager.getEngineByName(this.language);
if (engine == null) {
throw new IllegalStateException("No matching engine found for language '" + this.language + "'");
}
return engine;
}
else if (script instanceof ResourceScriptSource) {
Resource resource = ((ResourceScriptSource) script).getResource();
String extension = StringUtils.getFilenameExtension(resource.getFilename());
if (extension == null) {
throw new IllegalStateException(
"No script language defined, and no file extension defined for resource: " + resource);
}
ScriptEngine engine = this.scriptEngineManager.getEngineByExtension(extension);
if (engine == null) {
throw new IllegalStateException("No matching engine found for file extension '" + extension + "'");
}
return engine;
}
else {
throw new IllegalStateException(
"No script language defined, and no resource associated with script: " + script);
}
}
}