Java tutorial
/* * Copyright 2002-2011 the original author or authors. * * Licensed 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.red5.server.script.rhino; import java.io.IOException; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.regex.PatternSyntaxException; import javax.script.Bindings; import javax.script.Compilable; import javax.script.CompiledScript; import javax.script.Invocable; import javax.script.ScriptContext; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scripting.ScriptCompilationException; import org.springframework.util.ClassUtils; /** * Utility methods for handling Rhino / Javascript objects. * * @author Paul Gregoire * @since 0.6 */ public class RhinoScriptUtils { private static final Logger log = LoggerFactory.getLogger(RhinoScriptUtils.class); // ScriptEngine manager private static ScriptEngineManager mgr = new ScriptEngineManager(); // Javascript wrapper private static final String jsWrapper = "function Wrapper(obj){return new JSAdapter(){ __has__ : function(name){return true;}, __get__ : function(name){if(name in obj){return obj[name];}else if(typeof(obj['doesNotUnderstand']) == 'function'){return function(){return obj.doesNotUnderstand(name, arguments);}}else{return undefined;}}};}"; /** * Create a new Rhino-scripted object from the given script source. * * @param scriptSource * the script source text * @param interfaces * the interfaces that the scripted Java object is supposed to * implement * @param extendedClass * @return the scripted Java object * @throws ScriptCompilationException * in case of Rhino parsing failure * @throws java.io.IOException */ @SuppressWarnings("rawtypes") public static Object createRhinoObject(String scriptSource, Class[] interfaces, Class extendedClass) throws ScriptCompilationException, IOException, Exception { if (log.isDebugEnabled()) { log.debug("Script Engine Manager: " + mgr.getClass().getName()); } ScriptEngine engine = mgr.getEngineByExtension("js"); if (null == engine) { log.warn("Javascript is not supported in this build"); } // set engine scope namespace Bindings nameSpace = engine.getBindings(ScriptContext.ENGINE_SCOPE); // add the logger to the script nameSpace.put("log", log); // compile the wrapper script CompiledScript wrapper = ((Compilable) engine).compile(jsWrapper); nameSpace.put("Wrapper", wrapper); // get the function name ie. class name / ctor String funcName = RhinoScriptUtils.getFunctionName(scriptSource); if (log.isDebugEnabled()) { log.debug("New script: " + funcName); } // set the 'filename' nameSpace.put(ScriptEngine.FILENAME, funcName); if (null != interfaces) { nameSpace.put("interfaces", interfaces); } if (null != extendedClass) { if (log.isDebugEnabled()) { log.debug("Extended: " + extendedClass.getName()); } nameSpace.put("supa", extendedClass.newInstance()); } // // compile the script CompiledScript script = ((Compilable) engine).compile(scriptSource); // eval the script with the associated namespace Object o = null; try { o = script.eval(); } catch (Exception e) { log.error("Problem evaluating script", e); } if (log.isDebugEnabled()) { log.debug("Result of script call: " + o); } // script didnt return anything we can use so try the wrapper if (null == o) { wrapper.eval(); } else { wrapper.eval(); o = ((Invocable) engine).invokeFunction("Wrapper", new Object[] { engine.get(funcName) }); if (log.isDebugEnabled()) { log.debug("Result of invokeFunction: " + o); } } return Proxy.newProxyInstance(ClassUtils.getDefaultClassLoader(), interfaces, new RhinoObjectInvocationHandler(engine, o)); } /** * InvocationHandler that invokes a Rhino script method. */ private static class RhinoObjectInvocationHandler implements InvocationHandler { private final ScriptEngine engine; private final Object instance; public RhinoObjectInvocationHandler(ScriptEngine engine, Object instance) { this.engine = engine; this.instance = instance; } @SuppressWarnings({ "unchecked", "rawtypes" }) public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object o = null; // ensure a set of args are available if (args == null || args.length == 0) { args = new Object[] { "" }; } String name = method.getName(); if (log.isDebugEnabled()) { log.debug("Calling: " + name); } try { @SuppressWarnings("unused") Method apiMethod = null; Invocable invocable = (Invocable) engine; if (null == instance) { o = invocable.invokeFunction(name, args); } else { try { o = invocable.invokeMethod(instance, name, args); } catch (NoSuchMethodException nex) { log.debug("Method not found: " + name); try { // try to invoke it directly, this will work if the // function is in the engine context // ie. the script has been already evaluated o = invocable.invokeFunction(name, args); } catch (Exception ex) { log.debug("Function not found: " + name); Class[] interfaces = (Class[]) engine.get("interfaces"); for (Class clazz : interfaces) { // java6 style o = invocable.getInterface(engine.get((String) engine.get("className")), clazz); if (null != o) { log.debug("Interface return type: " + o.getClass().getName()); break; } } } } } if (log.isDebugEnabled()) { log.debug("Invocable result: " + o); } } catch (NoSuchMethodException nex) { log.warn("Method not found"); } catch (Throwable t) { log.warn("{}", t); } return o; } } /** * Uses a regex to get the first "function" name, this name is used to * create an instance of the javascript object. * * @param scriptSource * @return */ private static String getFunctionName(String scriptSource) { String ret = "undefined"; try { ret = scriptSource.replaceAll("[\\S\\w\\s]*?function ([\\w]+)\\(\\)[\\S\\w\\s]+", "$1"); // if undefined then look for the first var if (ret.equals("undefined") || ret.length() > 64) { ret = scriptSource.replaceAll("[\\S\\w\\s]*?var ([\\w]+)[\\S\\w\\s]+", "$1"); } } catch (PatternSyntaxException ex) { log.error("Syntax error in the regular expression"); } catch (IllegalArgumentException ex) { log.error("Syntax error in the replacement text (unescaped $ signs?)"); } catch (IndexOutOfBoundsException ex) { log.error("Non-existent backreference used the replacement text"); } log.debug("Got a function name: " + ret); return ret; } }