001/* 002 * Copyright 2002-2016 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.bsh; 018 019import java.io.IOException; 020 021import bsh.EvalError; 022 023import org.springframework.beans.factory.BeanClassLoaderAware; 024import org.springframework.scripting.ScriptCompilationException; 025import org.springframework.scripting.ScriptFactory; 026import org.springframework.scripting.ScriptSource; 027import org.springframework.util.Assert; 028import org.springframework.util.ClassUtils; 029 030/** 031 * {@link org.springframework.scripting.ScriptFactory} implementation 032 * for a BeanShell script. 033 * 034 * <p>Typically used in combination with a 035 * {@link org.springframework.scripting.support.ScriptFactoryPostProcessor}; 036 * see the latter's javadoc for a configuration example. 037 * 038 * @author Juergen Hoeller 039 * @author Rob Harrop 040 * @since 2.0 041 * @see BshScriptUtils 042 * @see org.springframework.scripting.support.ScriptFactoryPostProcessor 043 */ 044public class BshScriptFactory implements ScriptFactory, BeanClassLoaderAware { 045 046 private final String scriptSourceLocator; 047 048 private final Class<?>[] scriptInterfaces; 049 050 private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); 051 052 private Class<?> scriptClass; 053 054 private final Object scriptClassMonitor = new Object(); 055 056 private boolean wasModifiedForTypeCheck = false; 057 058 059 /** 060 * Create a new BshScriptFactory for the given script source. 061 * <p>With this {@code BshScriptFactory} variant, the script needs to 062 * declare a full class or return an actual instance of the scripted object. 063 * @param scriptSourceLocator a locator that points to the source of the script. 064 * Interpreted by the post-processor that actually creates the script. 065 */ 066 public BshScriptFactory(String scriptSourceLocator) { 067 Assert.hasText(scriptSourceLocator, "'scriptSourceLocator' must not be empty"); 068 this.scriptSourceLocator = scriptSourceLocator; 069 this.scriptInterfaces = null; 070 } 071 072 /** 073 * Create a new BshScriptFactory for the given script source. 074 * <p>The script may either be a simple script that needs a corresponding proxy 075 * generated (implementing the specified interfaces), or declare a full class 076 * or return an actual instance of the scripted object (in which case the 077 * specified interfaces, if any, need to be implemented by that class/instance). 078 * @param scriptSourceLocator a locator that points to the source of the script. 079 * Interpreted by the post-processor that actually creates the script. 080 * @param scriptInterfaces the Java interfaces that the scripted object 081 * is supposed to implement (may be {@code null}) 082 */ 083 public BshScriptFactory(String scriptSourceLocator, Class<?>... scriptInterfaces) { 084 Assert.hasText(scriptSourceLocator, "'scriptSourceLocator' must not be empty"); 085 this.scriptSourceLocator = scriptSourceLocator; 086 this.scriptInterfaces = scriptInterfaces; 087 } 088 089 090 @Override 091 public void setBeanClassLoader(ClassLoader classLoader) { 092 this.beanClassLoader = classLoader; 093 } 094 095 096 @Override 097 public String getScriptSourceLocator() { 098 return this.scriptSourceLocator; 099 } 100 101 @Override 102 public Class<?>[] getScriptInterfaces() { 103 return this.scriptInterfaces; 104 } 105 106 /** 107 * BeanShell scripts do require a config interface. 108 */ 109 @Override 110 public boolean requiresConfigInterface() { 111 return true; 112 } 113 114 /** 115 * Load and parse the BeanShell script via {@link BshScriptUtils}. 116 * @see BshScriptUtils#createBshObject(String, Class[], ClassLoader) 117 */ 118 @Override 119 public Object getScriptedObject(ScriptSource scriptSource, Class<?>... actualInterfaces) 120 throws IOException, ScriptCompilationException { 121 122 Class<?> clazz; 123 124 try { 125 synchronized (this.scriptClassMonitor) { 126 boolean requiresScriptEvaluation = (this.wasModifiedForTypeCheck && this.scriptClass == null); 127 this.wasModifiedForTypeCheck = false; 128 129 if (scriptSource.isModified() || requiresScriptEvaluation) { 130 // New script content: Let's check whether it evaluates to a Class. 131 Object result = BshScriptUtils.evaluateBshScript( 132 scriptSource.getScriptAsString(), actualInterfaces, this.beanClassLoader); 133 if (result instanceof Class) { 134 // A Class: We'll cache the Class here and create an instance 135 // outside of the synchronized block. 136 this.scriptClass = (Class<?>) result; 137 } 138 else { 139 // Not a Class: OK, we'll simply create BeanShell objects 140 // through evaluating the script for every call later on. 141 // For this first-time check, let's simply return the 142 // already evaluated object. 143 return result; 144 } 145 } 146 clazz = this.scriptClass; 147 } 148 } 149 catch (EvalError ex) { 150 this.scriptClass = null; 151 throw new ScriptCompilationException(scriptSource, ex); 152 } 153 154 if (clazz != null) { 155 // A Class: We need to create an instance for every call. 156 try { 157 return clazz.newInstance(); 158 } 159 catch (Throwable ex) { 160 throw new ScriptCompilationException( 161 scriptSource, "Could not instantiate script class: " + clazz.getName(), ex); 162 } 163 } 164 else { 165 // Not a Class: We need to evaluate the script for every call. 166 try { 167 return BshScriptUtils.createBshObject( 168 scriptSource.getScriptAsString(), actualInterfaces, this.beanClassLoader); 169 } 170 catch (EvalError ex) { 171 throw new ScriptCompilationException(scriptSource, ex); 172 } 173 } 174 } 175 176 @Override 177 public Class<?> getScriptedObjectType(ScriptSource scriptSource) 178 throws IOException, ScriptCompilationException { 179 180 synchronized (this.scriptClassMonitor) { 181 try { 182 if (scriptSource.isModified()) { 183 // New script content: Let's check whether it evaluates to a Class. 184 this.wasModifiedForTypeCheck = true; 185 this.scriptClass = BshScriptUtils.determineBshObjectType( 186 scriptSource.getScriptAsString(), this.beanClassLoader); 187 } 188 return this.scriptClass; 189 } 190 catch (EvalError ex) { 191 this.scriptClass = null; 192 throw new ScriptCompilationException(scriptSource, ex); 193 } 194 } 195 } 196 197 @Override 198 public boolean requiresScriptedObjectRefresh(ScriptSource scriptSource) { 199 synchronized (this.scriptClassMonitor) { 200 return (scriptSource.isModified() || this.wasModifiedForTypeCheck); 201 } 202 } 203 204 205 @Override 206 public String toString() { 207 return "BshScriptFactory: script source locator [" + this.scriptSourceLocator + "]"; 208 } 209 210}