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.components.flow.javascript.fom;
018:
019: import org.apache.avalon.framework.activity.Initializable;
020: import org.apache.avalon.framework.configuration.Configuration;
021: import org.apache.avalon.framework.configuration.ConfigurationException;
022: import org.apache.avalon.framework.service.ServiceManager;
023:
024: import org.apache.cocoon.ResourceNotFoundException;
025: import org.apache.cocoon.components.ContextHelper;
026: import org.apache.cocoon.components.flow.CompilingInterpreter;
027: import org.apache.cocoon.components.flow.Interpreter;
028: import org.apache.cocoon.components.flow.InvalidContinuationException;
029: import org.apache.cocoon.components.flow.WebContinuation;
030: import org.apache.cocoon.components.flow.javascript.JSErrorReporter;
031: import org.apache.cocoon.components.flow.javascript.LocationTrackingDebugger;
032: import org.apache.cocoon.components.flow.javascript.ScriptablePointerFactory;
033: import org.apache.cocoon.components.flow.javascript.ScriptablePropertyHandler;
034: import org.apache.cocoon.environment.ObjectModelHelper;
035: import org.apache.cocoon.environment.Redirector;
036: import org.apache.cocoon.environment.Request;
037: import org.apache.cocoon.environment.Session;
038:
039: import org.apache.commons.jxpath.JXPathIntrospector;
040: import org.apache.commons.jxpath.ri.JXPathContextReferenceImpl;
041: import org.apache.excalibur.source.Source;
042: import org.apache.excalibur.source.SourceResolver;
043: import org.apache.excalibur.source.SourceValidity;
044: import org.apache.regexp.RE;
045: import org.apache.regexp.RECompiler;
046: import org.apache.regexp.REProgram;
047: import org.mozilla.javascript.BaseFunction;
048: import org.mozilla.javascript.Context;
049: import org.mozilla.javascript.EcmaError;
050: import org.mozilla.javascript.Function;
051: import org.mozilla.javascript.JavaScriptException;
052: import org.mozilla.javascript.NativeJavaClass;
053: import org.mozilla.javascript.NativeJavaPackage;
054: import org.mozilla.javascript.Script;
055: import org.mozilla.javascript.ScriptRuntime;
056: import org.mozilla.javascript.Scriptable;
057: import org.mozilla.javascript.ScriptableObject;
058: import org.mozilla.javascript.WrappedException;
059: import org.mozilla.javascript.continuations.Continuation;
060: import org.mozilla.javascript.tools.debugger.Main;
061: import org.mozilla.javascript.tools.shell.Global;
062:
063: import java.awt.Dimension;
064: import java.awt.Toolkit;
065: import java.io.BufferedReader;
066: import java.io.IOException;
067: import java.io.InputStreamReader;
068: import java.io.OutputStream;
069: import java.io.PushbackInputStream;
070: import java.io.Reader;
071: import java.io.StringReader;
072: import java.util.ArrayList;
073: import java.util.HashMap;
074: import java.util.HashSet;
075: import java.util.Iterator;
076: import java.util.LinkedList;
077: import java.util.List;
078: import java.util.Map;
079: import java.util.Set;
080: import java.util.StringTokenizer;
081:
082: /**
083: * Interface with the JavaScript interpreter.
084: *
085: * @author <a href="mailto:ovidiu@apache.org">Ovidiu Predescu</a>
086: * @author <a href="mailto:crafterm@apache.org">Marcus Crafter</a>
087: * @since March 25, 2002
088: * @version CVS $Id: FOM_JavaScriptInterpreter.java 494773 2007-01-10 09:24:02Z cziegeler $
089: */
090: public class FOM_JavaScriptInterpreter extends CompilingInterpreter
091: implements Initializable {
092:
093: /**
094: * A long value is stored under this key in each top level JavaScript
095: * thread scope object. When you enter a context any scripts whose
096: * modification time is later than this value will be recompiled and reexecuted,
097: * and this value will be updated to the current time.
098: */
099: private final static String LAST_EXEC_TIME = "__PRIVATE_LAST_EXEC_TIME__";
100:
101: /**
102: * Prefix for session/request attribute storing JavaScript global scope object.
103: */
104: private static final String USER_GLOBAL_SCOPE = "FOM JavaScript GLOBAL SCOPE/";
105:
106: /**
107: * This is the only optimization level that supports continuations
108: * in the Christoper Oliver's Rhino JavaScript implementation
109: */
110: private static final int OPTIMIZATION_LEVEL = -2;
111:
112: /**
113: * When was the last time we checked for script modifications. Used
114: * only if {@link #reloadScripts} is true.
115: */
116: private long lastTimeCheck;
117:
118: /**
119: * Shared global scope for scripts and other immutable objects
120: */
121: private Global scope;
122:
123: // FIXME: Does not belong here, should be moved into the sitemap or even higher?
124: private CompilingClassLoader classLoader;
125: private MyClassRepository javaClassRepository = new MyClassRepository();
126: private String[] javaSourcePath;
127:
128: /**
129: * List of <code>String</code> objects that represent files to be
130: * read in by the JavaScript interpreter.
131: */
132: private List topLevelScripts = new ArrayList();
133:
134: private JSErrorReporter errorReporter;
135: private boolean enableDebugger;
136:
137: /**
138: * Needed to get things working with JDK 1.3. Can be removed once we
139: * don't support that platform any more.
140: */
141: protected ServiceManager getServiceManager() {
142: return manager;
143: }
144:
145: class MyClassRepository implements
146: CompilingClassLoader.ClassRepository {
147: Map javaSource = new HashMap();
148: Map javaClass = new HashMap();
149: Map sourceToClass = new HashMap();
150: Map classToSource = new HashMap();
151:
152: public synchronized void addCompiledClass(String className,
153: Source src, byte[] contents) {
154: javaSource.put(src.getURI(), src.getValidity());
155: javaClass.put(className, contents);
156: String uri = src.getURI();
157: Set set = (Set) sourceToClass.get(uri);
158: if (set == null) {
159: set = new HashSet();
160: sourceToClass.put(uri, set);
161: }
162: set.add(className);
163: classToSource.put(className, src.getURI());
164: }
165:
166: public synchronized byte[] getCompiledClass(String className) {
167: return (byte[]) javaClass.get(className);
168: }
169:
170: public synchronized boolean upToDateCheck() throws Exception {
171: SourceResolver sourceResolver = (SourceResolver) getServiceManager()
172: .lookup(SourceResolver.ROLE);
173: try {
174: List invalid = new LinkedList();
175: for (Iterator i = javaSource.entrySet().iterator(); i
176: .hasNext();) {
177: Map.Entry e = (Map.Entry) i.next();
178: String uri = (String) e.getKey();
179: SourceValidity validity = (SourceValidity) e
180: .getValue();
181: int valid = validity.isValid();
182: if (valid == SourceValidity.UNKNOWN) {
183: Source newSrc = null;
184: try {
185: newSrc = sourceResolver.resolveURI(uri);
186: valid = newSrc.getValidity().isValid(
187: validity);
188: } catch (Exception ignored) {
189: // ignore exception
190: } finally {
191: if (newSrc != null) {
192: sourceResolver.release(newSrc);
193: }
194: }
195: }
196: if (valid != SourceValidity.VALID) {
197: invalid.add(uri);
198: }
199: }
200:
201: for (Iterator i = invalid.iterator(); i.hasNext();) {
202: String uri = (String) i.next();
203: Set set = (Set) sourceToClass.get(uri);
204: Iterator ii = set.iterator();
205: while (ii.hasNext()) {
206: String className = (String) ii.next();
207: sourceToClass.remove(className);
208: javaClass.remove(className);
209: classToSource.remove(className);
210: }
211: set.clear();
212: javaSource.remove(uri);
213: }
214:
215: return invalid.size() == 0;
216: } finally {
217: getServiceManager().release(sourceResolver);
218: }
219: }
220: }
221:
222: /**
223: * JavaScript debugger: there's only one of these: it can debug multiple
224: * threads executing JS code.
225: */
226: private static Main debugger;
227:
228: static synchronized Main getDebugger() {
229: if (debugger == null) {
230: final Main db = new Main("Cocoon Flow Debugger");
231: db.pack();
232: Dimension size = Toolkit.getDefaultToolkit()
233: .getScreenSize();
234: size.width *= 0.75;
235: size.height *= 0.75;
236: db.setSize(size);
237: db.setExitAction(new Runnable() {
238: public void run() {
239: db.setVisible(false);
240: }
241: });
242: db.setOptimizationLevel(OPTIMIZATION_LEVEL);
243: db.setVisible(true);
244: debugger = db;
245: Context.addContextListener(debugger);
246: }
247: return debugger;
248: }
249:
250: public void configure(Configuration config)
251: throws ConfigurationException {
252: super .configure(config);
253:
254: String loadOnStartup = config.getChild("load-on-startup")
255: .getValue(null);
256: if (loadOnStartup != null) {
257: register(loadOnStartup);
258: }
259:
260: String debugger = config.getChild("debugger").getValue(null);
261: enableDebugger = "enabled".equalsIgnoreCase(debugger);
262:
263: if (reloadScripts) {
264: String classPath = config.getChild("classpath").getValue(
265: null);
266: synchronized (javaClassRepository) {
267: if (classPath != null) {
268: StringTokenizer izer = new StringTokenizer(
269: classPath, ";");
270: int i = 0;
271: javaSourcePath = new String[izer.countTokens() + 1];
272: javaSourcePath[javaSourcePath.length - 1] = "";
273: while (izer.hasMoreTokens()) {
274: javaSourcePath[i++] = izer.nextToken();
275: }
276: } else {
277: javaSourcePath = new String[] { "" };
278: }
279: updateSourcePath();
280: }
281: }
282: }
283:
284: public void initialize() throws Exception {
285: if (enableDebugger) {
286: if (getLogger().isDebugEnabled()) {
287: getLogger().debug("Flow debugger enabled, creating");
288: }
289: getDebugger().doBreak();
290: }
291: Context context = Context.enter();
292: context.setOptimizationLevel(OPTIMIZATION_LEVEL);
293: context.setCompileFunctionsWithDynamicScope(true);
294: context.setGeneratingDebug(true);
295: // add support for Rhino objects to JXPath
296: JXPathIntrospector.registerDynamicClass(Scriptable.class,
297: ScriptablePropertyHandler.class);
298: JXPathContextReferenceImpl
299: .addNodePointerFactory(new ScriptablePointerFactory());
300:
301: try {
302: scope = new Global(context);
303: // Access to Cocoon internal objects
304: FOM_Cocoon.init(scope);
305: errorReporter = new JSErrorReporter(getLogger());
306: } catch (Exception e) {
307: Context.exit();
308: e.printStackTrace();
309: throw e;
310: }
311: }
312:
313: private ClassLoader getClassLoader(boolean needsRefresh)
314: throws Exception {
315: if (!reloadScripts) {
316: return Thread.currentThread().getContextClassLoader();
317: }
318:
319: synchronized (javaClassRepository) {
320: boolean reload = needsRefresh || classLoader == null;
321: if (needsRefresh && classLoader != null) {
322: reload = !javaClassRepository.upToDateCheck();
323: }
324:
325: if (reload) {
326: // FIXME FIXME FIXME Resolver not released!
327: classLoader = new CompilingClassLoader(Thread
328: .currentThread().getContextClassLoader(),
329: (SourceResolver) manager
330: .lookup(SourceResolver.ROLE),
331: javaClassRepository);
332: classLoader
333: .addSourceListener(new CompilingClassLoader.SourceListener() {
334: public void sourceCompiled(Source src) {
335: // no action
336: }
337:
338: public void sourceCompilationError(
339: Source src, String msg) {
340: if (src != null) {
341: throw Context
342: .reportRuntimeError(msg);
343: }
344: }
345: });
346: updateSourcePath();
347: }
348: return classLoader;
349: }
350: }
351:
352: private void updateSourcePath() {
353: if (classLoader != null) {
354: classLoader.setSourcePath(javaSourcePath);
355: }
356: }
357:
358: /**
359: * Returns the JavaScript scope, a Scriptable object, from the user
360: * session instance. Each interpreter instance can have a scope
361: * associated with it.
362: *
363: * @return a <code>ThreadScope</code> value
364: */
365: private ThreadScope getSessionScope() throws Exception {
366: final String scopeID = USER_GLOBAL_SCOPE + getInterpreterID();
367: final Request request = ContextHelper
368: .getRequest(this .avalonContext);
369:
370: ThreadScope scope = null;
371:
372: // Get/create the scope attached to the current context
373: Session session = request.getSession(false);
374: if (session != null) {
375: scope = (ThreadScope) session.getAttribute(scopeID);
376: } else {
377: scope = (ThreadScope) request.getAttribute(scopeID);
378: }
379:
380: if (scope == null) {
381: scope = createThreadScope();
382: // Save scope in the request early to allow recursive Flow calls
383: request.setAttribute(scopeID, scope);
384: }
385:
386: return scope;
387: }
388:
389: /**
390: * Associates a JavaScript scope, a Scriptable object, with
391: * {@link #getInterpreterID() identifier} of this {@link Interpreter}
392: * instance.
393: *
394: * @param scope a <code>ThreadScope</code> value
395: */
396: private void setSessionScope(ThreadScope scope) throws Exception {
397: if (scope.useSession) {
398: final String scopeID = USER_GLOBAL_SCOPE
399: + getInterpreterID();
400: final Request request = ContextHelper
401: .getRequest(this .avalonContext);
402:
403: // FIXME: Where "session scope" should go when session is invalidated?
404: // Attach the scope to the current context
405: try {
406: Session session = request.getSession(true);
407: session.setAttribute(scopeID, scope);
408: } catch (IllegalStateException e) {
409: // Session might be invalidated already.
410: if (getLogger().isDebugEnabled()) {
411: getLogger()
412: .debug(
413: "Got '"
414: + e
415: + "' while trying to set session scope.",
416: e);
417: }
418: }
419: }
420: }
421:
422: public static class ThreadScope extends ScriptableObject {
423: private static final String[] BUILTIN_PACKAGES = { "javax",
424: "org", "com" };
425:
426: private ClassLoader classLoader;
427:
428: /* true if this scope has assigned any global vars */
429: boolean useSession;
430:
431: boolean locked = false;
432:
433: /**
434: * Initializes new top-level scope.
435: */
436: public ThreadScope(Global scope) throws Exception {
437: final Context context = Context.getCurrentContext();
438:
439: final String[] names = { "importClass" };
440: defineFunctionProperties(names, ThreadScope.class,
441: ScriptableObject.DONTENUM);
442:
443: setPrototype(scope);
444:
445: // We want this to be a new top-level scope, so set its
446: // parent scope to null. This means that any variables created
447: // by assignments will be properties of this.
448: setParentScope(null);
449:
450: // Put in the thread scope the Cocoon object, which gives access
451: // to the interpreter object, and some Cocoon objects. See
452: // FOM_Cocoon for more details.
453: final Object[] args = {};
454: FOM_Cocoon cocoon = (FOM_Cocoon) context.newObject(this ,
455: "FOM_Cocoon", args);
456: cocoon.setParentScope(this );
457: super .put("cocoon", this , cocoon);
458:
459: defineProperty(LAST_EXEC_TIME, new Long(0),
460: ScriptableObject.DONTENUM
461: | ScriptableObject.PERMANENT);
462: }
463:
464: public String getClassName() {
465: return "ThreadScope";
466: }
467:
468: public void setLock(boolean lock) {
469: this .locked = lock;
470: }
471:
472: public void put(String name, Scriptable start, Object value) {
473: //Allow setting values to existing variables, or if this is a
474: //java class (used by importClass & importPackage)
475: if (this .locked && !has(name, start)
476: && !(value instanceof NativeJavaClass)
477: && !(value instanceof Function)) {
478: // Need to wrap into a runtime exception as Scriptable.put has no throws clause...
479: throw new WrappedException(
480: new JavaScriptException(
481: "Implicit declaration of global variable '"
482: + name
483: + "' forbidden. Please ensure all variables are explicitely declared with the 'var' keyword"));
484: }
485: this .useSession = true;
486: super .put(name, start, value);
487: }
488:
489: public void put(int index, Scriptable start, Object value) {
490: // FIXME(SW): do indexed properties have a meaning on the global scope?
491: if (this .locked && !has(index, start)) {
492: throw new WrappedException(new JavaScriptException(
493: "Global scope locked. Cannot set value for index "
494: + index));
495: }
496: this .useSession = true;
497: super .put(index, start, value);
498: }
499:
500: // Invoked after script execution
501: void onExec() {
502: this .useSession = false;
503: super .put(LAST_EXEC_TIME, this , new Long(System
504: .currentTimeMillis()));
505: }
506:
507: /** Override importClass to allow reloading of classes */
508: public static void importClass(Context ctx, Scriptable this Obj,
509: Object[] args, Function funObj) {
510: for (int i = 0; i < args.length; i++) {
511: Object clazz = args[i];
512: if (!(clazz instanceof NativeJavaClass)) {
513: throw Context
514: .reportRuntimeError("Not a Java class: "
515: + Context.toString(clazz));
516: }
517: String s = ((NativeJavaClass) clazz).getClassObject()
518: .getName();
519: String n = s.substring(s.lastIndexOf('.') + 1);
520: this Obj.put(n, this Obj, clazz);
521: }
522: }
523:
524: public void setupPackages(ClassLoader cl) throws Exception {
525: final String JAVA_PACKAGE = "JavaPackage";
526: if (classLoader != cl) {
527: classLoader = cl;
528: Scriptable newPackages = new NativeJavaPackage("", cl);
529: newPackages.setParentScope(this );
530: newPackages.setPrototype(ScriptableObject
531: .getClassPrototype(this , JAVA_PACKAGE));
532: super .put("Packages", this , newPackages);
533: for (int i = 0; i < BUILTIN_PACKAGES.length; i++) {
534: String pkgName = BUILTIN_PACKAGES[i];
535: Scriptable pkg = new NativeJavaPackage(pkgName, cl);
536: pkg.setParentScope(this );
537: pkg.setPrototype(ScriptableObject
538: .getClassPrototype(this , JAVA_PACKAGE));
539: super .put(pkgName, this , pkg);
540: }
541: }
542: }
543:
544: public ClassLoader getClassLoader() {
545: return classLoader;
546: }
547: }
548:
549: private ThreadScope createThreadScope() throws Exception {
550: return new ThreadScope(scope);
551: }
552:
553: /**
554: * Returns a new Scriptable object to be used as the global scope
555: * when running the JavaScript scripts in the context of a request.
556: *
557: * <p>If you want to maintain the state of global variables across
558: * multiple invocations of <code><map:call
559: * function="..."></code>, you need to instanciate the session
560: * object which is a property of the cocoon object
561: * <code>var session = cocoon.session</code>. This will place the
562: * newly create Scriptable object in the user's session, where it
563: * will be retrieved from at the next invocation of {@link #callFunction}.</p>
564: *
565: * @exception Exception if an error occurs
566: */
567: private void setupContext(Redirector redirector, Context context,
568: ThreadScope thrScope) throws Exception {
569: // Try to retrieve the scope object from the session instance. If
570: // no scope is found, we create a new one, but don't place it in
571: // the session.
572: //
573: // When a user script "creates" a session using
574: // cocoon.createSession() in JavaScript, the thrScope is placed in
575: // the session object, where it's later retrieved from here. This
576: // behaviour allows multiple JavaScript functions to share the
577: // same global scope.
578:
579: FOM_Cocoon cocoon = (FOM_Cocoon) thrScope.get("cocoon",
580: thrScope);
581: long lastExecTime = ((Long) thrScope.get(LAST_EXEC_TIME,
582: thrScope)).longValue();
583: boolean needsRefresh = false;
584: if (reloadScripts) {
585: long now = System.currentTimeMillis();
586: if (now >= lastTimeCheck + checkTime) {
587: needsRefresh = true;
588: }
589: lastTimeCheck = now;
590: }
591:
592: // We need to setup the FOM_Cocoon object according to the current
593: // request. Everything else remains the same.
594: ClassLoader classLoader = getClassLoader(needsRefresh);
595: Thread.currentThread().setContextClassLoader(classLoader);
596: thrScope.setupPackages(classLoader);
597: cocoon.pushCallContext(this , redirector, manager,
598: avalonContext, getLogger(), null);
599:
600: // Check if we need to compile and/or execute scripts
601: synchronized (compiledScripts) {
602: List execList = new ArrayList();
603: // If we've never executed scripts in this scope or
604: // if reload-scripts is true and the check interval has expired
605: // or if new scripts have been specified in the sitemap,
606: // then create a list of scripts to compile/execute
607: if (lastExecTime == 0 || needsRefresh
608: || needResolve.size() > 0) {
609: topLevelScripts.addAll(needResolve);
610: if (lastExecTime != 0 && !needsRefresh) {
611: execList.addAll(needResolve);
612: } else {
613: execList.addAll(topLevelScripts);
614: }
615: needResolve.clear();
616: }
617: // Compile all the scripts first. That way you can set breakpoints
618: // in the debugger before they execute.
619: for (int i = 0, size = execList.size(); i < size; i++) {
620: String sourceURI = (String) execList.get(i);
621: ScriptSourceEntry entry = (ScriptSourceEntry) compiledScripts
622: .get(sourceURI);
623: if (entry == null) {
624: Source src = this .sourceresolver
625: .resolveURI(sourceURI);
626: entry = new ScriptSourceEntry(src);
627: compiledScripts.put(sourceURI, entry);
628: }
629: // Compile the script if necessary
630: entry
631: .getScript(context, this .scope, needsRefresh,
632: this );
633: }
634: // Execute the scripts if necessary
635: for (int i = 0, size = execList.size(); i < size; i++) {
636: String sourceURI = (String) execList.get(i);
637: ScriptSourceEntry entry = (ScriptSourceEntry) compiledScripts
638: .get(sourceURI);
639: long lastMod = 0;
640: if (reloadScripts && lastExecTime != 0) {
641: lastMod = entry.getSource().getLastModified();
642: }
643: Script script = entry.getScript(context, this .scope,
644: false, this );
645: if (lastExecTime == 0 || lastMod > lastExecTime) {
646: script.exec(context, thrScope);
647: thrScope.onExec();
648: }
649: }
650: }
651: }
652:
653: /**
654: * Compile filename as JavaScript code
655: *
656: * @param cx Rhino context
657: * @param fileName resource uri
658: * @return compiled script
659: */
660: Script compileScript(Context cx, String fileName) throws Exception {
661: Source src = this .sourceresolver.resolveURI(fileName);
662: if (src != null) {
663: synchronized (compiledScripts) {
664: ScriptSourceEntry entry = (ScriptSourceEntry) compiledScripts
665: .get(src.getURI());
666: Script compiledScript = null;
667: if (entry == null) {
668: compiledScripts.put(src.getURI(),
669: entry = new ScriptSourceEntry(src));
670: } else {
671: this .sourceresolver.release(src);
672: }
673: boolean needsRefresh = reloadScripts
674: && (entry.getCompileTime() + checkTime < System
675: .currentTimeMillis());
676: compiledScript = entry.getScript(cx, this .scope,
677: needsRefresh, this );
678: return compiledScript;
679: }
680: }
681: throw new ResourceNotFoundException(fileName + ": not found");
682: }
683:
684: protected Script compileScript(Context cx, Scriptable scope,
685: Source src) throws Exception {
686: PushbackInputStream is = new PushbackInputStream(src
687: .getInputStream(), ENCODING_BUF_SIZE);
688: try {
689: String encoding = findEncoding(is);
690: Reader reader = encoding == null ? new InputStreamReader(is)
691: : new InputStreamReader(is, encoding);
692: reader = new BufferedReader(reader);
693: Script compiledScript = cx.compileReader(scope, reader, src
694: .getURI(), 1, null);
695: return compiledScript;
696: } finally {
697: is.close();
698: }
699: }
700:
701: // A charset name can be up to 40 characters taken from the printable characters of US-ASCII
702: // (see http://www.iana.org/assignments/character-sets). So reading 100 bytes should be more than enough.
703: private final static int ENCODING_BUF_SIZE = 100;
704: // Match 'encoding = xxxx' on the first line
705: REProgram encodingRE = new RECompiler()
706: .compile("^.*encoding\\s*=\\s*([^\\s]*)");
707:
708: /**
709: * Find the encoding of the stream, or null if not specified
710: */
711: String findEncoding(PushbackInputStream is) throws IOException {
712: // Read some bytes
713: byte[] buffer = new byte[ENCODING_BUF_SIZE];
714: int len = is.read(buffer, 0, buffer.length);
715: // and push them back
716: is.unread(buffer, 0, len);
717:
718: // Interpret them as an ASCII string
719: String str = new String(buffer, 0, len, "ASCII");
720: RE re = new RE(encodingRE);
721: if (re.match(str)) {
722: return re.getParen(1);
723: }
724: return null;
725: }
726:
727: /**
728: * Calls a JavaScript function, passing <code>params</code> as its
729: * arguments. In addition to this, it makes available the parameters
730: * through the <code>cocoon.parameters</code> JavaScript array
731: * (indexed by the parameter names).
732: *
733: * @param funName a <code>String</code> value
734: * @param params a <code>List</code> value
735: * @param redirector
736: * @exception Exception if an error occurs
737: */
738: public void callFunction(String funName, List params,
739: Redirector redirector) throws Exception {
740: Context context = Context.enter();
741: context.setOptimizationLevel(OPTIMIZATION_LEVEL);
742: context.setGeneratingDebug(true);
743: context.setCompileFunctionsWithDynamicScope(true);
744: context.setErrorReporter(errorReporter);
745:
746: LocationTrackingDebugger locationTracker = new LocationTrackingDebugger();
747: if (!enableDebugger) {
748: //FIXME: add a "tee" debugger that allows both to be used simultaneously
749: context.setDebugger(locationTracker, null);
750: }
751:
752: ThreadScope thrScope = getSessionScope();
753: synchronized (thrScope) {
754: ClassLoader savedClassLoader = Thread.currentThread()
755: .getContextClassLoader();
756: FOM_Cocoon cocoon = null;
757: try {
758: try {
759: setupContext(redirector, context, thrScope);
760: cocoon = (FOM_Cocoon) thrScope.get("cocoon",
761: thrScope);
762:
763: // Register the current scope for scripts indirectly called from this function
764: FOM_JavaScriptFlowHelper.setFOM_FlowScope(cocoon
765: .getObjectModel(), thrScope);
766:
767: if (enableDebugger) {
768: if (!getDebugger().isVisible()) {
769: // only raise the debugger window if it isn't already visible
770: getDebugger().setVisible(true);
771: }
772: }
773:
774: int size = (params != null ? params.size() : 0);
775: Object[] funArgs = new Object[size];
776: Scriptable parameters = context.newObject(thrScope);
777: for (int i = 0; i < size; i++) {
778: Interpreter.Argument arg = (Interpreter.Argument) params
779: .get(i);
780: funArgs[i] = arg.value;
781: if (arg.name == null) {
782: arg.name = "";
783: }
784: parameters.put(arg.name, parameters, arg.value);
785: }
786: cocoon.setParameters(parameters);
787:
788: Object fun;
789: try {
790: fun = context.compileReader(thrScope,
791: new StringReader(funName), null, 1,
792: null).exec(context, thrScope);
793: } catch (EcmaError ee) {
794: throw new ResourceNotFoundException(
795: "Function \"javascript:" + funName
796: + "()\" not found");
797: }
798:
799: // Check count of arguments
800: if (fun instanceof BaseFunction) {
801: if (((BaseFunction) fun).getArity() != 0) {
802: getLogger()
803: .error(
804: "Function '"
805: + funName
806: + "' must have no declared arguments! "
807: + "Use cocoon.parameters to reach parameters passed from the sitemap into the function.");
808: }
809: }
810:
811: thrScope.setLock(true);
812: ScriptRuntime.call(context, fun, thrScope, funArgs,
813: thrScope);
814: } catch (JavaScriptException ex) {
815: throw locationTracker.getException(
816: "Error calling flowscript function "
817: + funName, ex);
818: } catch (EcmaError ee) {
819: throw locationTracker.getException(
820: "Error calling flowscript function "
821: + funName, ee);
822: }
823: } finally {
824: thrScope.setLock(false);
825: setSessionScope(thrScope);
826: if (cocoon != null) {
827: cocoon.popCallContext();
828: }
829: Context.exit();
830: Thread.currentThread().setContextClassLoader(
831: savedClassLoader);
832: }
833: }
834: }
835:
836: public void handleContinuation(String id, List params,
837: Redirector redirector) throws Exception {
838: WebContinuation wk = continuationsMgr.lookupWebContinuation(id,
839: getInterpreterID());
840:
841: if (wk == null) {
842: /*
843: * Throw an InvalidContinuationException to be handled inside the
844: * <map:handle-errors> sitemap element.
845: */
846: throw new InvalidContinuationException(
847: "The continuation ID " + id + " is invalid.");
848: }
849:
850: Context context = Context.enter();
851: context.setOptimizationLevel(OPTIMIZATION_LEVEL);
852: context.setGeneratingDebug(true);
853: context.setCompileFunctionsWithDynamicScope(true);
854: LocationTrackingDebugger locationTracker = new LocationTrackingDebugger();
855: if (!enableDebugger) {
856: //FIXME: add a "tee" debugger that allows both to be used simultaneously
857: context.setDebugger(locationTracker, null);
858: }
859:
860: // Obtain the continuation object from it, and setup the
861: // FOM_Cocoon object associated in the dynamic scope of the saved
862: // continuation with the environment and context objects.
863: Continuation k = (Continuation) wk.getContinuation();
864: ThreadScope kScope = (ThreadScope) k.getParentScope();
865: synchronized (kScope) {
866: ClassLoader savedClassLoader = Thread.currentThread()
867: .getContextClassLoader();
868: FOM_Cocoon cocoon = null;
869: try {
870: Thread.currentThread().setContextClassLoader(
871: kScope.getClassLoader());
872: cocoon = (FOM_Cocoon) kScope.get("cocoon", kScope);
873: kScope.setLock(true);
874: cocoon.pushCallContext(this , redirector, manager,
875: avalonContext, getLogger(), wk);
876:
877: // Register the current scope for scripts indirectly called from this function
878: FOM_JavaScriptFlowHelper.setFOM_FlowScope(cocoon
879: .getObjectModel(), kScope);
880:
881: if (enableDebugger) {
882: getDebugger().setVisible(true);
883: }
884: Scriptable parameters = context.newObject(kScope);
885: int size = params != null ? params.size() : 0;
886: for (int i = 0; i < size; i++) {
887: Interpreter.Argument arg = (Interpreter.Argument) params
888: .get(i);
889: parameters.put(arg.name, parameters, arg.value);
890: }
891: cocoon.setParameters(parameters);
892: FOM_WebContinuation fom_wk = new FOM_WebContinuation(wk);
893: fom_wk.setParentScope(kScope);
894: fom_wk.setPrototype(ScriptableObject.getClassPrototype(
895: kScope, fom_wk.getClassName()));
896: Object[] args = new Object[] { k, fom_wk };
897: try {
898: ScriptableObject.callMethod(cocoon,
899: "handleContinuation", args);
900: } catch (JavaScriptException ex) {
901: throw locationTracker.getException(
902: "Error calling continuation", ex);
903: } catch (EcmaError ee) {
904: throw locationTracker.getException(
905: "Error calling continuation", ee);
906: }
907: } finally {
908: kScope.setLock(false);
909: setSessionScope(kScope);
910: if (cocoon != null) {
911: cocoon.popCallContext();
912: }
913: Context.exit();
914: Thread.currentThread().setContextClassLoader(
915: savedClassLoader);
916: }
917: }
918: }
919:
920: public void forwardTo(Scriptable scope, FOM_Cocoon cocoon,
921: String uri, Object bizData, FOM_WebContinuation fom_wk,
922: Redirector redirector) throws Exception {
923: setupView(scope, cocoon, fom_wk);
924: super .forwardTo(uri, bizData, fom_wk == null ? null : fom_wk
925: .getWebContinuation(), redirector);
926: }
927:
928: // package access as this is called by FOM_Cocoon
929: void process(Scriptable scope, FOM_Cocoon cocoon, String uri,
930: Object bizData, OutputStream out) throws Exception {
931: setupView(scope, cocoon, null);
932: super .process(uri, bizData, out);
933: }
934:
935: private void setupView(Scriptable scope, FOM_Cocoon cocoon,
936: FOM_WebContinuation kont) {
937: Map objectModel = ContextHelper
938: .getObjectModel(this .avalonContext);
939:
940: // Make the JS live-connect objects available to the view layer
941: FOM_JavaScriptFlowHelper.setPackages(objectModel,
942: (Scriptable) ScriptableObject.getProperty(scope,
943: "Packages"));
944: FOM_JavaScriptFlowHelper.setJavaPackage(objectModel,
945: (Scriptable) ScriptableObject
946: .getProperty(scope, "java"));
947:
948: // Make the FOM objects available to the view layer
949: FOM_JavaScriptFlowHelper.setFOM_Request(objectModel, cocoon
950: .jsGet_request());
951: FOM_JavaScriptFlowHelper.setFOM_Response(objectModel, cocoon
952: .jsGet_response());
953: Request request = ObjectModelHelper.getRequest(objectModel);
954: Scriptable session = null;
955: if (request.getSession(false) != null) {
956: session = cocoon.jsGet_session();
957: }
958: FOM_JavaScriptFlowHelper.setFOM_Session(objectModel, session);
959:
960: FOM_JavaScriptFlowHelper.setFOM_Context(objectModel, cocoon
961: .jsGet_context());
962: if (kont != null) {
963: FOM_JavaScriptFlowHelper.setFOM_WebContinuation(
964: objectModel, kont);
965: }
966: }
967: }
|