JSR-223 based StandardScriptFactory (including <lang:std> support)
This commit also completes 4.2 schema variants in spring-context. Issue: SPR-5215
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2015 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.
|
||||
@@ -41,8 +41,7 @@ public class ScriptCompilationException extends NestedRuntimeException {
|
||||
/**
|
||||
* Constructor for ScriptCompilationException.
|
||||
* @param msg the detail message
|
||||
* @param cause the root cause (usually from using an underlying
|
||||
* script compiler API)
|
||||
* @param cause the root cause (usually from using an underlying script compiler API)
|
||||
*/
|
||||
public ScriptCompilationException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
@@ -51,23 +50,32 @@ public class ScriptCompilationException extends NestedRuntimeException {
|
||||
/**
|
||||
* Constructor for ScriptCompilationException.
|
||||
* @param scriptSource the source for the offending script
|
||||
* @param cause the root cause (usually from using an underlying
|
||||
* script compiler API)
|
||||
* @param msg the detail message
|
||||
* @since 4.2
|
||||
*/
|
||||
public ScriptCompilationException(ScriptSource scriptSource, Throwable cause) {
|
||||
super("Could not compile script", cause);
|
||||
public ScriptCompilationException(ScriptSource scriptSource, String msg) {
|
||||
super("Could not compile " + scriptSource + ": " + msg);
|
||||
this.scriptSource = scriptSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for ScriptCompilationException.
|
||||
* @param msg the detail message
|
||||
* @param scriptSource the source for the offending script
|
||||
* @param cause the root cause (usually from using an underlying
|
||||
* script compiler API)
|
||||
* @param cause the root cause (usually from using an underlying script compiler API)
|
||||
*/
|
||||
public ScriptCompilationException(ScriptSource scriptSource, Throwable cause) {
|
||||
super("Could not compile " + scriptSource, cause);
|
||||
this.scriptSource = scriptSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for ScriptCompilationException.
|
||||
* @param scriptSource the source for the offending script
|
||||
* @param msg the detail message
|
||||
* @param cause the root cause (usually from using an underlying script compiler API)
|
||||
*/
|
||||
public ScriptCompilationException(ScriptSource scriptSource, String msg, Throwable cause) {
|
||||
super("Could not compile script [" + scriptSource + "]: " + msg, cause);
|
||||
super("Could not compile " + scriptSource + ": " + msg, cause);
|
||||
this.scriptSource = scriptSource;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2015 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.
|
||||
@@ -44,6 +44,7 @@ public class LangNamespaceHandler extends NamespaceHandlerSupport {
|
||||
registerScriptBeanDefinitionParser("groovy", "org.springframework.scripting.groovy.GroovyScriptFactory");
|
||||
registerScriptBeanDefinitionParser("jruby", "org.springframework.scripting.jruby.JRubyScriptFactory");
|
||||
registerScriptBeanDefinitionParser("bsh", "org.springframework.scripting.bsh.BshScriptFactory");
|
||||
registerScriptBeanDefinitionParser("std", "org.springframework.scripting.support.StandardScriptFactory");
|
||||
registerBeanDefinitionParser("defaults", new ScriptingDefaultsParser());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2015 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.
|
||||
@@ -55,6 +55,8 @@ import org.springframework.util.xml.DomUtils;
|
||||
*/
|
||||
class ScriptBeanDefinitionParser extends AbstractBeanDefinitionParser {
|
||||
|
||||
private static final String ENGINE_ATTRIBUTE = "engine";
|
||||
|
||||
private static final String SCRIPT_SOURCE_ATTRIBUTE = "script-source";
|
||||
|
||||
private static final String INLINE_SCRIPT_ELEMENT = "inline-script";
|
||||
@@ -104,6 +106,9 @@ class ScriptBeanDefinitionParser extends AbstractBeanDefinitionParser {
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
|
||||
// Engine attribute only supported for <lang:std>
|
||||
String engine = element.getAttribute(ENGINE_ATTRIBUTE);
|
||||
|
||||
// Resolve the script source.
|
||||
String value = resolveScriptSource(element, parserContext.getReaderContext());
|
||||
if (value == null) {
|
||||
@@ -184,9 +189,13 @@ class ScriptBeanDefinitionParser extends AbstractBeanDefinitionParser {
|
||||
// Add constructor arguments.
|
||||
ConstructorArgumentValues cav = bd.getConstructorArgumentValues();
|
||||
int constructorArgNum = 0;
|
||||
if (StringUtils.hasLength(engine)) {
|
||||
cav.addIndexedArgumentValue(constructorArgNum++, engine);
|
||||
}
|
||||
cav.addIndexedArgumentValue(constructorArgNum++, value);
|
||||
if (element.hasAttribute(SCRIPT_INTERFACES_ATTRIBUTE)) {
|
||||
cav.addIndexedArgumentValue(constructorArgNum++, element.getAttribute(SCRIPT_INTERFACES_ATTRIBUTE));
|
||||
cav.addIndexedArgumentValue(
|
||||
constructorArgNum++, element.getAttribute(SCRIPT_INTERFACES_ATTRIBUTE), "java.lang.Class[]");
|
||||
}
|
||||
|
||||
// This is used for Groovy. It's a bean reference to a customizer bean.
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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 javax.script.Invocable;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineManager;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
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;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.scripting.ScriptFactory} implementation based
|
||||
* on the JSR-223 script engine abstraction (as included in Java 6+).
|
||||
* Supports JavaScript, Groovy, JRuby and other JSR-223 compliant engines.
|
||||
*
|
||||
* <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
|
||||
* @since 4.2
|
||||
* @see ScriptFactoryPostProcessor
|
||||
*/
|
||||
public class StandardScriptFactory implements ScriptFactory, BeanClassLoaderAware {
|
||||
|
||||
private final String scriptEngineName;
|
||||
|
||||
private final String scriptSourceLocator;
|
||||
|
||||
private final Class<?>[] scriptInterfaces;
|
||||
|
||||
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
|
||||
|
||||
private volatile ScriptEngine scriptEngine;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new StandardScriptFactory for the given script source.
|
||||
* @param scriptSourceLocator a locator that points to the source of the script.
|
||||
* Interpreted by the post-processor that actually creates the script.
|
||||
*/
|
||||
public StandardScriptFactory(String scriptSourceLocator) {
|
||||
this(null, scriptSourceLocator, (Class<?>[]) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new StandardScriptFactory for the given script source.
|
||||
* @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
|
||||
*/
|
||||
public StandardScriptFactory(String scriptSourceLocator, Class<?>... scriptInterfaces) {
|
||||
this(null, scriptSourceLocator, scriptInterfaces);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new StandardScriptFactory for the given script source.
|
||||
* @param scriptEngineName the name of the JSR-223 ScriptEngine to use
|
||||
* (explicitly given instead of inferred from the script source)
|
||||
* @param scriptSourceLocator a locator that points to the source of the script.
|
||||
* Interpreted by the post-processor that actually creates the script.
|
||||
*/
|
||||
public StandardScriptFactory(String scriptEngineName, String scriptSourceLocator) {
|
||||
this(scriptEngineName, scriptSourceLocator, (Class<?>[]) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new StandardScriptFactory for the given script source.
|
||||
* @param scriptEngineName the name of the JSR-223 ScriptEngine to use
|
||||
* (explicitly given instead of inferred from the script source)
|
||||
* @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
|
||||
*/
|
||||
public StandardScriptFactory(String scriptEngineName, String scriptSourceLocator, Class<?>... scriptInterfaces) {
|
||||
Assert.hasText(scriptSourceLocator, "'scriptSourceLocator' must not be empty");
|
||||
this.scriptEngineName = scriptEngineName;
|
||||
this.scriptSourceLocator = scriptSourceLocator;
|
||||
this.scriptInterfaces = scriptInterfaces;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setBeanClassLoader(ClassLoader classLoader) {
|
||||
this.beanClassLoader = classLoader;
|
||||
}
|
||||
|
||||
protected ScriptEngine retrieveScriptEngine(ScriptSource scriptSource) {
|
||||
ScriptEngineManager scriptEngineManager = new ScriptEngineManager(this.beanClassLoader);
|
||||
if (this.scriptEngineName != null) {
|
||||
ScriptEngine engine = scriptEngineManager.getEngineByName(this.scriptEngineName);
|
||||
if (engine == null) {
|
||||
throw new IllegalStateException("Script engine named '" + this.scriptEngineName + "' not found");
|
||||
}
|
||||
return engine;
|
||||
}
|
||||
if (scriptSource instanceof ResourceScriptSource) {
|
||||
String filename = ((ResourceScriptSource) scriptSource).getResource().getFilename();
|
||||
if (filename != null) {
|
||||
String extension = StringUtils.getFilenameExtension(filename);
|
||||
if (extension != null) {
|
||||
ScriptEngine engine = scriptEngineManager.getEngineByExtension(extension);
|
||||
if (engine != null) {
|
||||
return engine;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getScriptSourceLocator() {
|
||||
return this.scriptSourceLocator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?>[] getScriptInterfaces() {
|
||||
return this.scriptInterfaces;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresConfigInterface() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load and parse the script via JSR-223's ScriptEngine.
|
||||
*/
|
||||
@Override
|
||||
public Object getScriptedObject(ScriptSource scriptSource, Class<?>... actualInterfaces)
|
||||
throws IOException, ScriptCompilationException {
|
||||
|
||||
Object script;
|
||||
|
||||
try {
|
||||
if (this.scriptEngine == null) {
|
||||
this.scriptEngine = retrieveScriptEngine(scriptSource);
|
||||
if (this.scriptEngine == null) {
|
||||
throw new IllegalStateException("Could not determine script engine for " + scriptSource);
|
||||
}
|
||||
}
|
||||
script = this.scriptEngine.eval(scriptSource.getScriptAsString());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new ScriptCompilationException(scriptSource, ex);
|
||||
}
|
||||
|
||||
if (!ObjectUtils.isEmpty(actualInterfaces)) {
|
||||
boolean adaptationRequired = false;
|
||||
for (Class<?> requestedIfc : actualInterfaces) {
|
||||
if (!requestedIfc.isInstance(script)) {
|
||||
adaptationRequired = true;
|
||||
}
|
||||
}
|
||||
if (adaptationRequired) {
|
||||
Class<?> adaptedIfc;
|
||||
if (actualInterfaces.length == 1) {
|
||||
adaptedIfc = actualInterfaces[0];
|
||||
}
|
||||
else {
|
||||
adaptedIfc = ClassUtils.createCompositeInterface(actualInterfaces, this.beanClassLoader);
|
||||
}
|
||||
if (adaptedIfc != null) {
|
||||
if (!(this.scriptEngine instanceof Invocable)) {
|
||||
throw new ScriptCompilationException(scriptSource,
|
||||
"ScriptEngine must implement Invocable in order to adapt it to an interface: " +
|
||||
this.scriptEngine);
|
||||
}
|
||||
Invocable invocable = (Invocable) this.scriptEngine;
|
||||
if (script != null) {
|
||||
script = invocable.getInterface(script, adaptedIfc);
|
||||
}
|
||||
if (script == null) {
|
||||
script = invocable.getInterface(adaptedIfc);
|
||||
if (script == null) {
|
||||
throw new ScriptCompilationException(scriptSource,
|
||||
"Could not adapt script to interface [" + adaptedIfc.getName() + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (script instanceof Class) {
|
||||
Class<?> scriptClass = (Class<?>) script;
|
||||
try {
|
||||
return scriptClass.newInstance();
|
||||
}
|
||||
catch (InstantiationException ex) {
|
||||
throw new ScriptCompilationException(
|
||||
scriptSource, "Could not instantiate script class: " + scriptClass.getName(), ex);
|
||||
}
|
||||
catch (IllegalAccessException ex) {
|
||||
throw new ScriptCompilationException(
|
||||
scriptSource, "Could not access script constructor: " + scriptClass.getName(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
return script;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getScriptedObjectType(ScriptSource scriptSource)
|
||||
throws IOException, ScriptCompilationException {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresScriptedObjectRefresh(ScriptSource scriptSource) {
|
||||
return scriptSource.isModified();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StandardScriptFactory: script source locator [" + this.scriptSourceLocator + "]";
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user