001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.woody.util;
018:
019: import java.io.IOException;
020: import java.io.StringReader;
021: import java.util.Iterator;
022: import java.util.Map;
023:
024: import org.apache.avalon.framework.CascadingRuntimeException;
025: import org.apache.cocoon.components.flow.FlowHelper;
026: import org.apache.cocoon.components.flow.javascript.fom.FOM_JavaScriptFlowHelper;
027: import org.mozilla.javascript.Context;
028: import org.mozilla.javascript.Function;
029: import org.mozilla.javascript.JavaScriptException;
030: import org.mozilla.javascript.Script;
031: import org.mozilla.javascript.Scriptable;
032: import org.w3c.dom.Element;
033:
034: /**
035: * Helper methods to use JavaScript in various locations of the Woody configuration files
036: * such as event listeners and bindings.
037: *
038: * @author <a href="http://www.apache.org/~sylvain/">Sylvain Wallez</a>
039: * @version CVS $Id: JavaScriptHelper.java 433543 2006-08-22 06:22:54Z crossley $
040: */
041: public class JavaScriptHelper {
042:
043: /**
044: * A shared root scope, avoiding to recreate a new one each time.
045: */
046: private static Scriptable _rootScope = null;
047:
048: /**
049: * Build a script with the content of a DOM element.
050: *
051: * @param element the element containing the script
052: * @return the compiled script
053: * @throws IOException
054: */
055: public static Script buildScript(Element element)
056: throws IOException {
057: String jsText = DomHelper.getElementText(element);
058: String sourceName = DomHelper.getSystemIdLocation(element);
059:
060: Context ctx = Context.enter();
061: Script script;
062: try {
063: script = ctx.compileReader(getRootScope(), //scope
064: new StringReader(jsText), // in
065: sourceName == null ? "<unknown>" : sourceName, // sourceName
066: DomHelper.getLineLocation(element), // lineNo
067: null // securityDomain
068: );
069: } finally {
070: Context.exit();
071: }
072: return script;
073: }
074:
075: /**
076: * Build a function with the content of a DOM element.
077: *
078: * @param element the element containing the function body
079: * @param argumentNames names of the function arguments
080: * @return the compiled function
081: * @throws IOException
082: */
083: public static Function buildFunction(Element element,
084: String[] argumentNames) throws IOException {
085: // Enclose the script text with a function declaration
086: StringBuffer buffer = new StringBuffer("function foo(");
087: for (int i = 0; i < argumentNames.length; i++) {
088: if (i > 0) {
089: buffer.append(',');
090: }
091: buffer.append(argumentNames[i]);
092: }
093: buffer.append(") {\n")
094: .append(DomHelper.getElementText(element))
095: .append("\n}");
096:
097: String jsText = buffer.toString();
098: String sourceName = DomHelper.getSystemIdLocation(element);
099:
100: Context ctx = Context.enter();
101: Function func;
102: try {
103: func = ctx.compileFunction(getRootScope(), //scope
104: jsText, // in
105: sourceName == null ? "<unknown>" : sourceName, // sourceName
106: DomHelper.getLineLocation(element) - 1, // lineNo, "-1" because we added "function..."
107: null // securityDomain
108: );
109: } finally {
110: Context.exit();
111: }
112: return func;
113: }
114:
115: /**
116: * Get a root scope for building child scopes.
117: *
118: * @return an appropriate root scope
119: */
120: public static Scriptable getRootScope() {
121: if (_rootScope == null) {
122: // Create it if never used up to now
123: Context ctx = Context.enter();
124: try {
125: _rootScope = ctx.initStandardObjects(null);
126: } finally {
127: Context.exit();
128: }
129: }
130: return _rootScope;
131: }
132:
133: /**
134: * Get a parent scope for building a child scope. The request is searched for an existing scope
135: * that can be provided by a flowscript higher in the call stack, giving visibility to flowscript
136: * functions and global (session) variables.
137: *
138: * @param objectModel an objectModel where the flowscript scope will be searched (can be <code>null</code>).
139: * @return an appropriate parent scope.
140: */
141: public static Scriptable getParentScope(Map objectModel) {
142: // Try to get the flowscript scope
143: Scriptable parentScope = null;
144: if (objectModel != null) {
145: parentScope = FOM_JavaScriptFlowHelper
146: .getFOM_FlowScope(objectModel);
147: }
148:
149: if (parentScope != null) {
150: return parentScope;
151: } else {
152: return getRootScope();
153: }
154: }
155:
156: public static Object execScript(Script script, Map values,
157: Map objectModel) throws JavaScriptException {
158: Context ctx = Context.enter();
159: try {
160: Scriptable parentScope = getParentScope(objectModel);
161:
162: // Create a new local scope
163: Scriptable scope;
164: try {
165: scope = ctx.newObject(parentScope);
166: } catch (Exception e) {
167: // Should normally not happen
168: throw new CascadingRuntimeException(
169: "Cannont create script scope", e);
170: }
171: scope.setParentScope(parentScope);
172:
173: // Populate the scope
174: Iterator iter = values.entrySet().iterator();
175: while (iter.hasNext()) {
176: Map.Entry entry = (Map.Entry) iter.next();
177: String key = (String) entry.getKey();
178: Object value = entry.getValue();
179: scope.put(key, scope, Context.toObject(value, scope));
180: }
181:
182: if (objectModel != null) {
183: Object viewData = FlowHelper
184: .getContextObject(objectModel);
185: if (viewData != null) {
186: scope.put("viewData", scope, Context.toObject(
187: viewData, scope));
188: }
189: }
190:
191: Object result = script.exec(ctx, scope);
192: return FlowHelper.unwrap(result);
193: } finally {
194: Context.exit();
195: }
196: }
197:
198: public static Object callFunction(Function func, Object this Object,
199: Object[] arguments, Map objectModel)
200: throws JavaScriptException {
201: Context ctx = Context.enter();
202: try {
203: Scriptable scope = getParentScope(objectModel);
204:
205: if (objectModel != null) {
206: Object viewData = FlowHelper
207: .getContextObject(objectModel);
208: if (viewData != null) {
209: // Create a new local scope to hold the view data
210: Scriptable newScope;
211: try {
212: newScope = ctx.newObject(scope);
213: } catch (Exception e) {
214: // Should normally not happen
215: throw new CascadingRuntimeException(
216: "Cannont create function scope", e);
217: }
218: newScope.setParentScope(scope);
219: scope = newScope;
220:
221: scope.put("viewData", scope, Context.toObject(
222: viewData, scope));
223: }
224: }
225: Object result = func.call(ctx, scope,
226: thisObject == null ? null : Context.toObject(
227: thisObject, scope), arguments);
228: return FlowHelper.unwrap(result);
229: } finally {
230: Context.exit();
231: }
232: }
233: }
|