001: /*
002: * Copyright 2002-2007 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: * http://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:
017: package org.springframework.scripting.bsh;
018:
019: import java.lang.reflect.InvocationHandler;
020: import java.lang.reflect.Method;
021: import java.lang.reflect.Proxy;
022:
023: import bsh.EvalError;
024: import bsh.Interpreter;
025: import bsh.Primitive;
026: import bsh.XThis;
027:
028: import org.springframework.aop.support.AopUtils;
029: import org.springframework.core.NestedRuntimeException;
030: import org.springframework.util.Assert;
031: import org.springframework.util.ClassUtils;
032:
033: /**
034: * Utility methods for handling BeanShell-scripted objects.
035: *
036: * @author Rob Harrop
037: * @author Juergen Hoeller
038: * @since 2.0
039: */
040: public abstract class BshScriptUtils {
041:
042: /**
043: * Create a new BeanShell-scripted object from the given script source.
044: * <p>With this <code>createBshObject</code> variant, the script needs to
045: * declare a full class or return an actual instance of the scripted object.
046: * @param scriptSource the script source text
047: * @return the scripted Java object
048: * @throws EvalError in case of BeanShell parsing failure
049: */
050: public static Object createBshObject(String scriptSource)
051: throws EvalError {
052: return createBshObject(scriptSource, null, null);
053: }
054:
055: /**
056: * Create a new BeanShell-scripted object from the given script source,
057: * using the default ClassLoader.
058: * <p>The script may either be a simple script that needs a corresponding proxy
059: * generated (implementing the specified interfaces), or declare a full class
060: * or return an actual instance of the scripted object (in which case the
061: * specified interfaces, if any, need to be implemented by that class/instance).
062: * @param scriptSource the script source text
063: * @param scriptInterfaces the interfaces that the scripted Java object is
064: * supposed to implement (may be <code>null</code> or empty if the script itself
065: * declares a full class or returns an actual instance of the scripted object)
066: * @return the scripted Java object
067: * @throws EvalError in case of BeanShell parsing failure
068: * @see #createBshObject(String, Class[], ClassLoader)
069: */
070: public static Object createBshObject(String scriptSource,
071: Class[] scriptInterfaces) throws EvalError {
072: return createBshObject(scriptSource, scriptInterfaces,
073: ClassUtils.getDefaultClassLoader());
074: }
075:
076: /**
077: * Create a new BeanShell-scripted object from the given script source.
078: * <p>The script may either be a simple script that needs a corresponding proxy
079: * generated (implementing the specified interfaces), or declare a full class
080: * or return an actual instance of the scripted object (in which case the
081: * specified interfaces, if any, need to be implemented by that class/instance).
082: * @param scriptSource the script source text
083: * @param scriptInterfaces the interfaces that the scripted Java object is
084: * supposed to implement (may be <code>null</code> or empty if the script itself
085: * declares a full class or returns an actual instance of the scripted object)
086: * @param classLoader the ClassLoader to create the script proxy with
087: * @return the scripted Java object
088: * @throws EvalError in case of BeanShell parsing failure
089: */
090: public static Object createBshObject(String scriptSource,
091: Class[] scriptInterfaces, ClassLoader classLoader)
092: throws EvalError {
093:
094: Object result = evaluateBshScript(scriptSource,
095: scriptInterfaces, classLoader);
096: if (result instanceof Class) {
097: Class clazz = (Class) result;
098: try {
099: return clazz.newInstance();
100: } catch (Throwable ex) {
101: throw new IllegalStateException(
102: "Could not instantiate script class ["
103: + clazz.getName() + "]. Root cause is "
104: + ex);
105: }
106: } else {
107: return result;
108: }
109: }
110:
111: /**
112: * Evaluate the specified BeanShell script based on the given script source,
113: * returning the Class defined by the script.
114: * <p>The script may either declare a full class or return an actual instance of
115: * the scripted object (in which case the Class of the object will be returned).
116: * In any other case, the returned Class will be <code>null</code>.
117: * @param scriptSource the script source text
118: * @return the scripted Java class, or <code>null</code> if none could be determined
119: * @throws EvalError in case of BeanShell parsing failure
120: */
121: static Class determineBshObjectType(String scriptSource)
122: throws EvalError {
123: Assert.hasText(scriptSource, "Script source must not be empty");
124: Interpreter interpreter = new Interpreter();
125: Object result = interpreter.eval(scriptSource);
126: if (result instanceof Class) {
127: return (Class) result;
128: } else if (result != null) {
129: return result.getClass();
130: } else {
131: return null;
132: }
133: }
134:
135: /**
136: * Evaluate the specified BeanShell script based on the given script source,
137: * keeping a returned script Class or script Object as-is.
138: * <p>The script may either be a simple script that needs a corresponding proxy
139: * generated (implementing the specified interfaces), or declare a full class
140: * or return an actual instance of the scripted object (in which case the
141: * specified interfaces, if any, need to be implemented by that class/instance).
142: * @param scriptSource the script source text
143: * @param scriptInterfaces the interfaces that the scripted Java object is
144: * supposed to implement (may be <code>null</code> or empty if the script itself
145: * declares a full class or returns an actual instance of the scripted object)
146: * @param classLoader the ClassLoader to create the script proxy with
147: * @return the scripted Java class or Java object
148: * @throws EvalError in case of BeanShell parsing failure
149: */
150: static Object evaluateBshScript(String scriptSource,
151: Class[] scriptInterfaces, ClassLoader classLoader)
152: throws EvalError {
153:
154: Assert.hasText(scriptSource, "Script source must not be empty");
155: Interpreter interpreter = new Interpreter();
156: Object result = interpreter.eval(scriptSource);
157: if (result != null) {
158: return result;
159: } else {
160: // Simple BeanShell script: Let's create a proxy for it, implementing the given interfaces.
161: Assert
162: .notEmpty(
163: scriptInterfaces,
164: "Given script requires a script proxy: At least one script interface is required.");
165: XThis xt = (XThis) interpreter.eval("return this");
166: return Proxy.newProxyInstance(classLoader,
167: scriptInterfaces,
168: new BshObjectInvocationHandler(xt));
169: }
170: }
171:
172: /**
173: * InvocationHandler that invokes a BeanShell script method.
174: */
175: private static class BshObjectInvocationHandler implements
176: InvocationHandler {
177:
178: private final XThis xt;
179:
180: public BshObjectInvocationHandler(XThis xt) {
181: this .xt = xt;
182: }
183:
184: public Object invoke(Object proxy, Method method, Object[] args)
185: throws Throwable {
186: if (AopUtils.isEqualsMethod(method)) {
187: return (isProxyForSameBshObject(args[0]) ? Boolean.TRUE
188: : Boolean.FALSE);
189: }
190: if (AopUtils.isHashCodeMethod(method)) {
191: return new Integer(this .xt.hashCode());
192: }
193: if (AopUtils.isToStringMethod(method)) {
194: return "BeanShell object [" + this .xt + "]";
195: }
196: try {
197: Object result = this .xt.invokeMethod(method.getName(),
198: args);
199: if (result == Primitive.NULL
200: || result == Primitive.VOID) {
201: return null;
202: }
203: if (result instanceof Primitive) {
204: return ((Primitive) result).getValue();
205: }
206: return result;
207: } catch (EvalError ex) {
208: throw new BshExecutionException(ex);
209: }
210: }
211:
212: private boolean isProxyForSameBshObject(Object other) {
213: if (!Proxy.isProxyClass(other.getClass())) {
214: return false;
215: }
216: InvocationHandler ih = Proxy.getInvocationHandler(other);
217: return (ih instanceof BshObjectInvocationHandler && this .xt
218: .equals(((BshObjectInvocationHandler) ih).xt));
219: }
220: }
221:
222: /**
223: * Exception to be thrown on script execution failure.
224: */
225: public static class BshExecutionException extends
226: NestedRuntimeException {
227:
228: private BshExecutionException(EvalError ex) {
229: super ("BeanShell script execution failed", ex);
230: }
231: }
232:
233: }
|