001/* 002 * Copyright 2002-2015 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 * https://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 017package org.springframework.scripting.support; 018 019import java.io.IOException; 020import java.util.Map; 021import javax.script.Bindings; 022import javax.script.ScriptEngine; 023import javax.script.ScriptEngineManager; 024import javax.script.ScriptException; 025 026import org.springframework.beans.factory.BeanClassLoaderAware; 027import org.springframework.core.io.Resource; 028import org.springframework.scripting.ScriptCompilationException; 029import org.springframework.scripting.ScriptEvaluator; 030import org.springframework.scripting.ScriptSource; 031import org.springframework.util.CollectionUtils; 032import org.springframework.util.StringUtils; 033 034/** 035 * {@code javax.script} (JSR-223) based implementation of Spring's {@link ScriptEvaluator} 036 * strategy interface. 037 * 038 * @author Juergen Hoeller 039 * @author Costin Leau 040 * @since 4.0 041 * @see ScriptEngine#eval(String) 042 */ 043public class StandardScriptEvaluator implements ScriptEvaluator, BeanClassLoaderAware { 044 045 private volatile ScriptEngineManager scriptEngineManager; 046 047 private String engineName; 048 049 050 /** 051 * Construct a new {@code StandardScriptEvaluator}. 052 */ 053 public StandardScriptEvaluator() { 054 } 055 056 /** 057 * Construct a new {@code StandardScriptEvaluator} for the given class loader. 058 * @param classLoader the class loader to use for script engine detection 059 */ 060 public StandardScriptEvaluator(ClassLoader classLoader) { 061 this.scriptEngineManager = new ScriptEngineManager(classLoader); 062 } 063 064 /** 065 * Construct a new {@code StandardScriptEvaluator} for the given JSR-223 066 * {@link ScriptEngineManager} to obtain script engines from. 067 * @param scriptEngineManager the ScriptEngineManager (or subclass thereof) to use 068 * @since 4.2.2 069 */ 070 public StandardScriptEvaluator(ScriptEngineManager scriptEngineManager) { 071 this.scriptEngineManager = scriptEngineManager; 072 } 073 074 075 /** 076 * Set the name of the language meant for evaluating the scripts (e.g. "Groovy"). 077 * <p>This is effectively an alias for {@link #setEngineName "engineName"}, 078 * potentially (but not yet) providing common abbreviations for certain languages 079 * beyond what the JSR-223 script engine factory exposes. 080 * @see #setEngineName 081 */ 082 public void setLanguage(String language) { 083 this.engineName = language; 084 } 085 086 /** 087 * Set the name of the script engine for evaluating the scripts (e.g. "Groovy"), 088 * as exposed by the JSR-223 script engine factory. 089 * @since 4.2.2 090 * @see #setLanguage 091 */ 092 public void setEngineName(String engineName) { 093 this.engineName = engineName; 094 } 095 096 /** 097 * Set the globally scoped bindings on the underlying script engine manager, 098 * shared by all scripts, as an alternative to script argument bindings. 099 * @since 4.2.2 100 * @see #evaluate(ScriptSource, Map) 101 * @see javax.script.ScriptEngineManager#setBindings(Bindings) 102 * @see javax.script.SimpleBindings 103 */ 104 public void setGlobalBindings(Map<String, Object> globalBindings) { 105 if (globalBindings != null) { 106 this.scriptEngineManager.setBindings(StandardScriptUtils.getBindings(globalBindings)); 107 } 108 } 109 110 @Override 111 public void setBeanClassLoader(ClassLoader classLoader) { 112 if (this.scriptEngineManager == null) { 113 this.scriptEngineManager = new ScriptEngineManager(classLoader); 114 } 115 } 116 117 118 @Override 119 public Object evaluate(ScriptSource script) { 120 return evaluate(script, null); 121 } 122 123 @Override 124 public Object evaluate(ScriptSource script, Map<String, Object> argumentBindings) { 125 ScriptEngine engine = getScriptEngine(script); 126 try { 127 if (CollectionUtils.isEmpty(argumentBindings)) { 128 return engine.eval(script.getScriptAsString()); 129 } 130 else { 131 Bindings bindings = StandardScriptUtils.getBindings(argumentBindings); 132 return engine.eval(script.getScriptAsString(), bindings); 133 } 134 } 135 catch (IOException ex) { 136 throw new ScriptCompilationException(script, "Cannot access script for ScriptEngine", ex); 137 } 138 catch (ScriptException ex) { 139 throw new ScriptCompilationException(script, new StandardScriptEvalException(ex)); 140 } 141 } 142 143 /** 144 * Obtain the JSR-223 ScriptEngine to use for the given script. 145 * @param script the script to evaluate 146 * @return the ScriptEngine (never {@code null}) 147 */ 148 protected ScriptEngine getScriptEngine(ScriptSource script) { 149 if (this.scriptEngineManager == null) { 150 this.scriptEngineManager = new ScriptEngineManager(); 151 } 152 153 if (StringUtils.hasText(this.engineName)) { 154 return StandardScriptUtils.retrieveEngineByName(this.scriptEngineManager, this.engineName); 155 } 156 else if (script instanceof ResourceScriptSource) { 157 Resource resource = ((ResourceScriptSource) script).getResource(); 158 String extension = StringUtils.getFilenameExtension(resource.getFilename()); 159 if (extension == null) { 160 throw new IllegalStateException( 161 "No script language defined, and no file extension defined for resource: " + resource); 162 } 163 ScriptEngine engine = this.scriptEngineManager.getEngineByExtension(extension); 164 if (engine == null) { 165 throw new IllegalStateException("No matching engine found for file extension '" + extension + "'"); 166 } 167 return engine; 168 } 169 else { 170 throw new IllegalStateException( 171 "No script language defined, and no resource associated with script: " + script); 172 } 173 } 174 175}