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.forms.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.JavaScriptFlowHelper;
027: import org.apache.cocoon.components.flow.javascript.fom.FOM_JavaScriptFlowHelper;
028: import org.mozilla.javascript.Context;
029: import org.mozilla.javascript.Function;
030: import org.mozilla.javascript.JavaScriptException;
031: import org.mozilla.javascript.Script;
032: import org.mozilla.javascript.Scriptable;
033: import org.mozilla.javascript.ScriptableObject;
034: import org.w3c.dom.Element;
035:
036: /**
037: * Helper methods to use JavaScript in various locations of the Cocoon Forms configuration files
038: * such as event listeners and bindings.
039: *
040: * @version $Id: JavaScriptHelper.java 518747 2007-03-15 20:30:03Z antonio $
041: */
042: public class JavaScriptHelper {
043:
044: /**
045: * A shared root scope, avoiding to recreate a new one each time.
046: */
047: private static Scriptable _rootScope;
048:
049: /**
050: * Build a script with the content of a DOM element.
051: *
052: * @param element the element containing the script
053: * @return the compiled script
054: * @throws IOException
055: */
056: public static Script buildScript(Element element)
057: throws IOException {
058: String jsText = DomHelper.getElementText(element);
059: String sourceName = DomHelper.getSystemIdLocation(element);
060:
061: Context ctx = Context.enter();
062: Script script;
063: try {
064:
065: script = ctx.compileReader(
066: // To use rhino1.5r4-continuations-R26.jar as a workaround for COCOON-1579: Uncomment the next line.
067: // getRootScope(null), //scope
068: new StringReader(jsText), // in
069: sourceName == null ? "<unknown>" : sourceName, // sourceName
070: DomHelper.getLineLocation(element), // lineNo
071: null // securityDomain
072: );
073: } finally {
074: Context.exit();
075: }
076: return script;
077: }
078:
079: /**
080: * Build a function with the content of a DOM element.
081: *
082: * @param element the element containing the function body
083: * @param name the name of the function
084: * @param argumentNames names of the function arguments
085: * @return the compiled function
086: * @throws IOException
087: */
088: public static Function buildFunction(Element element, String name,
089: String[] argumentNames) throws IOException {
090: // Enclose the script text with a function declaration
091: StringBuffer buffer = new StringBuffer("function ")
092: .append(name).append("(");
093: for (int i = 0; i < argumentNames.length; i++) {
094: if (i > 0) {
095: buffer.append(',');
096: }
097: buffer.append(argumentNames[i]);
098: }
099: buffer.append(") {\n")
100: .append(DomHelper.getElementText(element))
101: .append("\n}");
102:
103: String jsText = buffer.toString();
104: String sourceName = DomHelper.getSystemIdLocation(element);
105:
106: Context ctx = Context.enter();
107: Function func;
108: try {
109: func = ctx.compileFunction(getRootScope(null), //scope
110: jsText, // in
111: sourceName == null ? "<unknown>" : sourceName, // sourceName
112: DomHelper.getLineLocation(element) - 1, // lineNo, "-1" because we added "function..."
113: null // securityDomain
114: );
115: } finally {
116: Context.exit();
117: }
118: return func;
119: }
120:
121: /**
122: * Get a root scope for building child scopes.
123: *
124: * @return an appropriate root scope
125: */
126: public static Scriptable getRootScope(Map objectModel) {
127: // FIXME: TemplateOMH should be used in 2.2
128: //return TemplateObjectModelHelper.getScope();
129:
130: if (_rootScope == null) {
131: // Create it if never used up to now
132: Context ctx = Context.enter();
133: try {
134: _rootScope = ctx.initStandardObjects(null);
135: try {
136: ScriptableObject.defineClass(_rootScope,
137: FOM_SimpleCocoon.class);
138: } catch (Exception e) {
139: throw new CascadingRuntimeException(
140: "Cannot setup a root context with a cocoon object for javascript",
141: e);
142: }
143: } finally {
144: Context.exit();
145: }
146: }
147: if (objectModel == null) {
148: return _rootScope;
149: } else {
150: Context ctx = Context.enter();
151: try {
152: Scriptable scope = ctx.newObject(_rootScope);
153: FOM_SimpleCocoon cocoon = (FOM_SimpleCocoon) ctx
154: .newObject(scope, "FOM_SimpleCocoon",
155: new Object[] {});
156: cocoon.setObjectModel(objectModel);
157: cocoon.setParentScope(scope);
158: scope.put("cocoon", scope, cocoon);
159: return scope;
160: } catch (Exception e) {
161: throw new CascadingRuntimeException(
162: "Cannot setup a root context with a cocoon object for javascript",
163: e);
164: } finally {
165: Context.exit();
166: }
167: }
168: }
169:
170: /**
171: * Get a parent scope for building a child scope. The request is searched for an existing scope
172: * that can be provided by a flowscript higher in the call stack, giving visibility to flowscript
173: * functions and global (session) variables.
174: *
175: * @param objectModel the object model where the flowscript scope will be searched (can be <code>null</code>).
176: * @return an appropriate parent scope.
177: */
178: public static Scriptable getParentScope(Map objectModel) {
179: // Try to get the flowscript scope
180: Scriptable parentScope = null;
181: if (objectModel != null) {
182: parentScope = FOM_JavaScriptFlowHelper
183: .getFOM_FlowScope(objectModel);
184: }
185:
186: if (parentScope != null) {
187: return parentScope;
188: } else {
189: return getRootScope(objectModel);
190: }
191: }
192:
193: public static Object execScript(Script script, Map values,
194: Map objectModel) throws JavaScriptException {
195: Context ctx = Context.enter();
196: try {
197: Scriptable parentScope = getParentScope(objectModel);
198:
199: // Create a new local scope
200: Scriptable scope;
201: try {
202: scope = ctx.newObject(parentScope);
203: } catch (Exception e) {
204: // Should normally not happen
205: throw new CascadingRuntimeException(
206: "Cannot create script scope", e);
207: }
208: scope.setParentScope(parentScope);
209:
210: // Populate the scope
211: Iterator iter = values.entrySet().iterator();
212: while (iter.hasNext()) {
213: Map.Entry entry = (Map.Entry) iter.next();
214: String key = (String) entry.getKey();
215: Object value = entry.getValue();
216: scope.put(key, scope, Context.toObject(value, scope));
217: }
218:
219: if (objectModel != null) {
220: Object viewData = FlowHelper
221: .getContextObject(objectModel);
222: if (viewData != null) {
223: scope.put("viewData", scope, Context.toObject(
224: viewData, scope));
225: }
226: }
227:
228: Object result = script.exec(ctx, scope);
229: return JavaScriptFlowHelper.unwrap(result);
230: } finally {
231: Context.exit();
232: }
233: }
234:
235: public static Object callFunction(Function func, Object this Object,
236: Object[] arguments, Map objectModel)
237: throws JavaScriptException {
238: Context ctx = Context.enter();
239: try {
240: Scriptable scope = getParentScope(objectModel);
241:
242: if (objectModel != null) {
243: // we always add the viewData even it is null (see bug COCOON-1916)
244: final Object viewData = FlowHelper
245: .getContextObject(objectModel);
246: // Create a new local scope to hold the view data
247: final Scriptable newScope;
248: try {
249: newScope = ctx.newObject(scope);
250: } catch (Exception e) {
251: // Should normally not happen
252: throw new CascadingRuntimeException(
253: "Cannot create function scope", e);
254: }
255: newScope.setParentScope(scope);
256: scope = newScope;
257:
258: if (viewData != null) {
259: scope.put("viewData", scope, Context.toObject(
260: viewData, scope));
261: } else {
262: scope.put("viewData", scope, null);
263: }
264: }
265: func.setParentScope(scope);
266: Object result = func.call(ctx, scope,
267: thisObject == null ? null : Context.toObject(
268: thisObject, scope), arguments);
269: return JavaScriptFlowHelper.unwrap(result);
270: } finally {
271: Context.exit();
272: }
273: }
274: }
|