/* * 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.cocoon.woody.util; import java.io.IOException; import java.io.StringReader; import java.util.Iterator; import java.util.Map; import org.apache.avalon.framework.CascadingRuntimeException; import org.apache.cocoon.components.flow.FlowHelper; import org.apache.cocoon.components.flow.javascript.fom.FOM_JavaScriptFlowHelper; import org.mozilla.javascript.Context; import org.mozilla.javascript.Function; import org.mozilla.javascript.JavaScriptException; import org.mozilla.javascript.Script; import org.mozilla.javascript.Scriptable; import org.w3c.dom.Element; /** * Helper methods to use JavaScript in various locations of the Woody configuration files * such as event listeners and bindings. * * @author <a href="http://www.apache.org/~sylvain/">Sylvain Wallez</a> * @version CVS $Id$ */ public class JavaScriptHelper { /** * A shared root scope, avoiding to recreate a new one each time. */ private static Scriptable _rootScope = null; /** * Build a script with the content of a DOM element. * * @param element the element containing the script * @return the compiled script * @throws IOException */ public static Script buildScript(Element element) throws IOException { String jsText = DomHelper.getElementText(element); String sourceName = DomHelper.getSystemIdLocation(element); Context ctx = Context.enter(); Script script; try { script = ctx.compileReader( getRootScope(), //scope new StringReader(jsText), // in sourceName == null ? "<unknown>" : sourceName, // sourceName DomHelper.getLineLocation(element), // lineNo null // securityDomain ); } finally { Context.exit(); } return script; } /** * Build a function with the content of a DOM element. * * @param element the element containing the function body * @param argumentNames names of the function arguments * @return the compiled function * @throws IOException */ public static Function buildFunction(Element element, String[] argumentNames) throws IOException { // Enclose the script text with a function declaration StringBuffer buffer = new StringBuffer("function foo("); for (int i = 0; i < argumentNames.length; i++) { if (i > 0) { buffer.append(','); } buffer.append(argumentNames[i]); } buffer.append(") {\n").append(DomHelper.getElementText(element)).append("\n}"); String jsText = buffer.toString(); String sourceName = DomHelper.getSystemIdLocation(element); Context ctx = Context.enter(); Function func; try { func = ctx.compileFunction( getRootScope(), //scope jsText, // in sourceName == null ? "<unknown>" : sourceName, // sourceName DomHelper.getLineLocation(element) - 1, // lineNo, "-1" because we added "function..." null // securityDomain ); } finally { Context.exit(); } return func; } /** * Get a root scope for building child scopes. * * @return an appropriate root scope */ public static Scriptable getRootScope() { if (_rootScope == null) { // Create it if never used up to now Context ctx = Context.enter(); try { _rootScope = ctx.initStandardObjects(null); } finally { Context.exit(); } } return _rootScope; } /** * Get a parent scope for building a child scope. The request is searched for an existing scope * that can be provided by a flowscript higher in the call stack, giving visibility to flowscript * functions and global (session) variables. * * @param objectModel an objectModel where the flowscript scope will be searched (can be <code>null</code>). * @return an appropriate parent scope. */ public static Scriptable getParentScope(Map objectModel) { // Try to get the flowscript scope Scriptable parentScope = null; if (objectModel != null) { parentScope = FOM_JavaScriptFlowHelper.getFOM_FlowScope(objectModel); } if (parentScope != null) { return parentScope; } else { return getRootScope(); } } public static Object execScript(Script script, Map values, Map objectModel) throws JavaScriptException { Context ctx = Context.enter(); try { Scriptable parentScope = getParentScope(objectModel); // Create a new local scope Scriptable scope; try { scope = ctx.newObject(parentScope); } catch (Exception e) { // Should normally not happen throw new CascadingRuntimeException("Cannont create script scope", e); } scope.setParentScope(parentScope); // Populate the scope Iterator iter = values.entrySet().iterator(); while(iter.hasNext()) { Map.Entry entry = (Map.Entry)iter.next(); String key = (String)entry.getKey(); Object value = entry.getValue(); scope.put(key, scope, Context.toObject(value, scope)); } if (objectModel != null) { Object viewData = FlowHelper.getContextObject(objectModel); if (viewData != null) { scope.put("viewData", scope, Context.toObject(viewData, scope)); } } Object result = script.exec(ctx, scope); return FlowHelper.unwrap(result); } finally { Context.exit(); } } public static Object callFunction(Function func, Object thisObject, Object[] arguments, Map objectModel) throws JavaScriptException { Context ctx = Context.enter(); try { Scriptable scope = getParentScope(objectModel); if (objectModel != null) { Object viewData = FlowHelper.getContextObject(objectModel); if (viewData != null) { // Create a new local scope to hold the view data Scriptable newScope; try { newScope = ctx.newObject(scope); } catch (Exception e) { // Should normally not happen throw new CascadingRuntimeException("Cannont create function scope", e); } newScope.setParentScope(scope); scope = newScope; scope.put("viewData", scope, Context.toObject(viewData, scope)); } } Object result = func.call(ctx, scope, thisObject == null? null: Context.toObject(thisObject, scope), arguments); return FlowHelper.unwrap(result); } finally { Context.exit(); } } }