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