blob: ff5465d5fd061a42710d9995b57aaa957dc398e4 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.bsf.engines.javascript;
import java.util.Iterator;
import java.util.Vector;
import org.apache.bsf.BSFDeclaredBean;
import org.apache.bsf.BSFException;
import org.apache.bsf.BSFManager;
import org.apache.bsf.util.BSFEngineImpl;
import org.apache.bsf.util.BSFFunctions;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.EvaluatorException;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.ImporterTopLevel;
import org.mozilla.javascript.JavaScriptException;
import org.mozilla.javascript.NativeJavaObject;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.WrappedException;
import org.mozilla.javascript.Wrapper;
/**
* This is the interface to Netscape's Rhino (JavaScript) from the
* Bean Scripting Framework.
* <p>
* The original version of this code was first written by Adam Peller
* for use in LotusXSL. Sanjiva took his code and adapted it for BSF.
*
* @author Adam Peller <peller@lotus.com>
* @author Sanjiva Weerawarana
* @author Matthew J. Duftler
* @author Norris Boyd
*/
public class JavaScriptEngine extends BSFEngineImpl {
/**
* The global script object, where all embedded functions are defined,
* as well as the standard ECMA "core" objects.
*/
private Scriptable global;
/**
* Return an object from an extension.
* @param object Object on which to make the call (ignored).
* @param method The name of the method to call.
* @param args an array of arguments to be
* passed to the extension, which may be either
* Vectors of Nodes, or Strings.
*/
public Object call(Object object, String method, Object[] args)
throws BSFException {
Object retval = null;
Context cx;
try {
cx = Context.enter();
// REMIND: convert arg list Vectors here?
Object fun = global.get(method, global);
// NOTE: Source and line arguments are nonsense in a call().
// Any way to make these arguments *sensible?
if (fun == Scriptable.NOT_FOUND) {
throw new EvaluatorException("function " + method +
" not found.", "none", 0);
}
cx.setOptimizationLevel(-1);
cx.setGeneratingDebug(false);
cx.setGeneratingSource(false);
cx.setOptimizationLevel(0);
cx.setDebugger(null, null);
retval =
((Function) fun).call(cx, global, global, args);
// ScriptRuntime.call(cx, fun, global, args, global);
if (retval instanceof Wrapper) {
retval = ((Wrapper) retval).unwrap();
}
}
catch (Throwable t) {
handleError(t);
}
finally {
Context.exit();
}
return retval;
}
public void declareBean(BSFDeclaredBean bean) throws BSFException {
if ((bean.bean instanceof Number) ||
(bean.bean instanceof String) ||
(bean.bean instanceof Boolean)) {
global.put(bean.name, global, bean.bean);
}
else {
// Must wrap non-scriptable objects before presenting to Rhino
Scriptable wrapped = Context.toObject(bean.bean, global);
global.put(bean.name, global, wrapped);
}
}
/**
* This is used by an application to evaluate a string containing
* some expression.
*/
public Object eval(String source, int lineNo, int columnNo, Object oscript)
throws BSFException {
String scriptText = oscript.toString();
Object retval = null;
Context cx;
try {
cx = Context.enter();
cx.setOptimizationLevel(-1);
cx.setGeneratingDebug(false);
cx.setGeneratingSource(false);
cx.setOptimizationLevel(0);
cx.setDebugger(null, null);
retval = cx.evaluateString(global, scriptText,
source, lineNo,
null);
if (retval instanceof NativeJavaObject) {
retval = ((NativeJavaObject) retval).unwrap();
}
}
catch (Throwable t) { // includes JavaScriptException, rethrows Errors
handleError(t);
}
finally {
Context.exit();
}
return retval;
}
private void handleError(Throwable t) throws BSFException {
if (t instanceof WrappedException) {
t = ((WrappedException) t).getWrappedException();
}
String message = null;
Throwable target = t;
if (t instanceof JavaScriptException) {
message = t.getLocalizedMessage();
// Is it an exception wrapped in a JavaScriptException?
Object value = ((JavaScriptException) t).getValue();
if (value instanceof Throwable) {
// likely a wrapped exception from a LiveConnect call.
// Display its stack trace as a diagnostic
target = (Throwable) value;
}
}
else if (t instanceof EvaluatorException ||
t instanceof SecurityException) {
message = t.getLocalizedMessage();
}
else if (t instanceof RuntimeException) {
message = "Internal Error: " + t.toString();
}
else if (t instanceof StackOverflowError) {
message = "Stack Overflow";
}
if (message == null) {
message = t.toString();
}
if (t instanceof Error && !(t instanceof StackOverflowError)) {
// Re-throw Errors because we're supposed to let the JVM see it
// Don't re-throw StackOverflows, because we know we've
// corrected the situation by aborting the loop and
// a long stacktrace would end up on the user's console
throw (Error) t;
}
else {
throw new BSFException(BSFException.REASON_OTHER_ERROR,
"JavaScript Error: " + message,
target);
}
}
/**
* Initialize the engine.
* Put the manager into the context-manager
* map hashtable too.
*/
public void initialize(BSFManager mgr, String lang, Vector declaredBeans)
throws BSFException {
super.initialize(mgr, lang, declaredBeans);
// Initialize context and global scope object
try {
Context cx = Context.enter();
global = new ImporterTopLevel(cx);
Scriptable bsf = Context.toObject(new BSFFunctions(mgr, this), global);
global.put("bsf", global, bsf);
for(Iterator it = declaredBeans.iterator(); it.hasNext();) {
declareBean((BSFDeclaredBean) it.next());
}
}
catch (Throwable t) {
}
finally {
Context.exit();
}
}
public void undeclareBean(BSFDeclaredBean bean) throws BSFException {
global.delete(bean.name);
}
}