KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cocoon > components > flow > javascript > fom > FOM_JavaScriptInterpreter


1 /*
2  * Copyright 1999-2004 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.cocoon.components.flow.javascript.fom;
17
18 import org.apache.avalon.framework.activity.Initializable;
19 import org.apache.avalon.framework.configuration.Configurable;
20 import org.apache.avalon.framework.configuration.Configuration;
21 import org.apache.avalon.framework.configuration.ConfigurationException;
22 import org.apache.avalon.framework.service.ServiceManager;
23
24 import org.apache.cocoon.ResourceNotFoundException;
25 import org.apache.cocoon.components.ContextHelper;
26 import org.apache.cocoon.components.flow.CompilingInterpreter;
27 import org.apache.cocoon.components.flow.Interpreter;
28 import org.apache.cocoon.components.flow.InvalidContinuationException;
29 import org.apache.cocoon.components.flow.WebContinuation;
30 import org.apache.cocoon.components.flow.javascript.JSErrorReporter;
31 import org.apache.cocoon.components.flow.javascript.LocationTrackingDebugger;
32 import org.apache.cocoon.components.flow.javascript.ScriptablePointerFactory;
33 import org.apache.cocoon.components.flow.javascript.ScriptablePropertyHandler;
34 import org.apache.cocoon.environment.ObjectModelHelper;
35 import org.apache.cocoon.environment.Redirector;
36 import org.apache.cocoon.environment.Request;
37 import org.apache.cocoon.environment.Session;
38
39 import org.apache.commons.jxpath.JXPathIntrospector;
40 import org.apache.commons.jxpath.ri.JXPathContextReferenceImpl;
41 import org.apache.excalibur.source.Source;
42 import org.apache.excalibur.source.SourceResolver;
43 import org.apache.excalibur.source.SourceValidity;
44 import org.apache.regexp.RE;
45 import org.apache.regexp.RECompiler;
46 import org.apache.regexp.REProgram;
47 import org.mozilla.javascript.BaseFunction;
48 import org.mozilla.javascript.Context;
49 import org.mozilla.javascript.EcmaError;
50 import org.mozilla.javascript.Function;
51 import org.mozilla.javascript.JavaScriptException;
52 import org.mozilla.javascript.NativeJavaClass;
53 import org.mozilla.javascript.NativeJavaPackage;
54 import org.mozilla.javascript.PropertyException;
55 import org.mozilla.javascript.Script;
56 import org.mozilla.javascript.ScriptRuntime;
57 import org.mozilla.javascript.Scriptable;
58 import org.mozilla.javascript.ScriptableObject;
59 import org.mozilla.javascript.WrappedException;
60 import org.mozilla.javascript.Wrapper;
61 import org.mozilla.javascript.continuations.Continuation;
62 import org.mozilla.javascript.tools.debugger.Main;
63 import org.mozilla.javascript.tools.shell.Global;
64
65 import java.awt.Dimension JavaDoc;
66 import java.awt.Toolkit JavaDoc;
67 import java.io.BufferedReader JavaDoc;
68 import java.io.IOException JavaDoc;
69 import java.io.InputStreamReader JavaDoc;
70 import java.io.OutputStream JavaDoc;
71 import java.io.PushbackInputStream JavaDoc;
72 import java.io.Reader JavaDoc;
73 import java.util.ArrayList JavaDoc;
74 import java.util.HashMap JavaDoc;
75 import java.util.HashSet JavaDoc;
76 import java.util.Iterator JavaDoc;
77 import java.util.LinkedList JavaDoc;
78 import java.util.List JavaDoc;
79 import java.util.Map JavaDoc;
80 import java.util.Set JavaDoc;
81 import java.util.StringTokenizer JavaDoc;
82
83 /**
84  * Interface with the JavaScript interpreter.
85  *
86  * @author <a HREF="mailto:ovidiu@apache.org">Ovidiu Predescu</a>
87  * @author <a HREF="mailto:crafterm@apache.org">Marcus Crafter</a>
88  * @since March 25, 2002
89  * @version CVS $Id: FOM_JavaScriptInterpreter.java 290909 2005-09-22 08:53:10Z sylvain $
90  */

91 public class FOM_JavaScriptInterpreter extends CompilingInterpreter
92         implements Configurable, Initializable {
93
94     /**
95      * A long value is stored under this key in each top level JavaScript
96      * thread scope object. When you enter a context any scripts whose
97      * modification time is later than this value will be recompiled and reexecuted,
98      * and this value will be updated to the current time.
99      */

100     private final static String JavaDoc LAST_EXEC_TIME = "__PRIVATE_LAST_EXEC_TIME__";
101
102     /**
103      * Prefix for session/request attribute storing JavaScript global scope object.
104      */

105     private static final String JavaDoc USER_GLOBAL_SCOPE = "FOM JavaScript GLOBAL SCOPE/";
106
107     /**
108      * This is the only optimization level that supports continuations
109      * in the Christoper Oliver's Rhino JavaScript implementation
110      */

111     private static final int OPTIMIZATION_LEVEL = -2;
112
113     /**
114      * When was the last time we checked for script modifications. Used
115      * only if {@link #reloadScripts} is true.
116      */

117     private long lastTimeCheck;
118
119     /**
120      * Shared global scope for scripts and other immutable objects
121      */

122     private Global scope;
123
124     // FIXME: Does not belong here, should be moved into the sitemap or even higher?
125
private CompilingClassLoader classLoader;
126     private MyClassRepository javaClassRepository = new MyClassRepository();
127     private String JavaDoc[] javaSourcePath;
128
129     /**
130      * List of <code>String</code> objects that represent files to be
131      * read in by the JavaScript interpreter.
132      */

133     private List JavaDoc topLevelScripts = new ArrayList JavaDoc();
134
135     private JSErrorReporter errorReporter;
136     private boolean enableDebugger;
137
138     /**
139      * Needed to get things working with JDK 1.3. Can be removed once we
140      * don't support that platform any more.
141      */

142     protected ServiceManager getServiceManager() {
143         return manager;
144     }
145
146     class MyClassRepository implements CompilingClassLoader.ClassRepository {
147         Map JavaDoc javaSource = new HashMap JavaDoc();
148         Map JavaDoc javaClass = new HashMap JavaDoc();
149         Map JavaDoc sourceToClass = new HashMap JavaDoc();
150         Map JavaDoc classToSource = new HashMap JavaDoc();
151
152         public synchronized void addCompiledClass(String JavaDoc className, Source src,
153                     byte[] contents) {
154             javaSource.put(src.getURI(), src.getValidity());
155             javaClass.put(className, contents);
156             String JavaDoc uri = src.getURI();
157             Set JavaDoc set = (Set JavaDoc)sourceToClass.get(uri);
158             if (set == null) {
159                 set = new HashSet JavaDoc();
160                 sourceToClass.put(uri, set);
161             }
162             set.add(className);
163             classToSource.put(className, src.getURI());
164         }
165
166         public synchronized byte[] getCompiledClass(String JavaDoc className) {
167             return (byte[])javaClass.get(className);
168         }
169
170         public synchronized boolean upToDateCheck() throws Exception JavaDoc {
171             SourceResolver sourceResolver = (SourceResolver)
172                 getServiceManager().lookup(SourceResolver.ROLE);
173             try {
174                 List JavaDoc invalid = new LinkedList JavaDoc();
175                 for (Iterator JavaDoc i = javaSource.entrySet().iterator(); i.hasNext();) {
176                     Map.Entry JavaDoc e = (Map.Entry JavaDoc) i.next();
177                     String JavaDoc uri = (String JavaDoc) e.getKey();
178                     SourceValidity validity = (SourceValidity) e.getValue();
179                     int valid = validity.isValid();
180                     if (valid == SourceValidity.UNKNOWN) {
181                         Source newSrc = null;
182                         try {
183                             newSrc = sourceResolver.resolveURI(uri);
184                             valid = newSrc.getValidity().isValid(validity);
185                         } catch (Exception JavaDoc ignored) {
186                             // ignore exception
187
} finally {
188                             if (newSrc != null) {
189                                 sourceResolver.release(newSrc);
190                             }
191                         }
192                     }
193                     if (valid != SourceValidity.VALID) {
194                         invalid.add(uri);
195                     }
196                 }
197
198                 for (Iterator JavaDoc i = invalid.iterator(); i.hasNext();) {
199                     String JavaDoc uri = (String JavaDoc) i.next();
200                     Set JavaDoc set = (Set JavaDoc) sourceToClass.get(uri);
201                     Iterator JavaDoc ii = set.iterator();
202                     while (ii.hasNext()) {
203                         String JavaDoc className = (String JavaDoc) ii.next();
204                         sourceToClass.remove(className);
205                         javaClass.remove(className);
206                         classToSource.remove(className);
207                     }
208                     set.clear();
209                     javaSource.remove(uri);
210                 }
211
212                 return invalid.size() == 0;
213             } finally {
214                 getServiceManager().release(sourceResolver);
215             }
216         }
217     }
218
219     /**
220      * JavaScript debugger: there's only one of these: it can debug multiple
221      * threads executing JS code.
222      */

223     private static Main debugger;
224
225     static synchronized Main getDebugger() {
226         if (debugger == null) {
227             final Main db = new Main("Cocoon Flow Debugger");
228             db.pack();
229             Dimension JavaDoc size = Toolkit.getDefaultToolkit().getScreenSize();
230             size.width *= 0.75;
231             size.height *= 0.75;
232             db.setSize(size);
233             db.setExitAction(new Runnable JavaDoc() {
234                     public void run() {
235                         db.setVisible(false);
236                     }
237                 });
238             db.setOptimizationLevel(OPTIMIZATION_LEVEL);
239             db.setVisible(true);
240             debugger = db;
241             Context.addContextListener(debugger);
242         }
243         return debugger;
244     }
245
246     public void configure(Configuration config) throws ConfigurationException {
247         super.configure(config);
248
249         String JavaDoc loadOnStartup = config.getChild("load-on-startup").getValue(null);
250         if (loadOnStartup != null) {
251             register(loadOnStartup);
252         }
253
254         String JavaDoc debugger = config.getChild("debugger").getValue(null);
255         enableDebugger = "enabled".equalsIgnoreCase(debugger);
256
257         if (reloadScripts) {
258             String JavaDoc classPath = config.getChild("classpath").getValue(null);
259             synchronized (javaClassRepository) {
260                 if (classPath != null) {
261                     StringTokenizer JavaDoc izer = new StringTokenizer JavaDoc(classPath, ";");
262                     int i = 0;
263                     javaSourcePath = new String JavaDoc[izer.countTokens() + 1];
264                     javaSourcePath[javaSourcePath.length - 1] = "";
265                     while (izer.hasMoreTokens()) {
266                         javaSourcePath[i++] = izer.nextToken();
267                     }
268                 } else {
269                     javaSourcePath = new String JavaDoc[]{""};
270                 }
271                 updateSourcePath();
272             }
273         }
274     }
275
276     public void initialize() throws Exception JavaDoc {
277         if (enableDebugger) {
278             if (getLogger().isDebugEnabled()) {
279                 getLogger().debug("Flow debugger enabled, creating");
280             }
281             getDebugger().doBreak();
282         }
283         Context context = Context.enter();
284         context.setOptimizationLevel(OPTIMIZATION_LEVEL);
285         context.setCompileFunctionsWithDynamicScope(true);
286         context.setGeneratingDebug(true);
287         // add support for Rhino objects to JXPath
288
JXPathIntrospector.registerDynamicClass(Scriptable.class,
289                                                 ScriptablePropertyHandler.class);
290         JXPathContextReferenceImpl.addNodePointerFactory(new ScriptablePointerFactory());
291
292         try {
293             scope = new Global(context);
294             // Access to Cocoon internal objects
295
FOM_Cocoon.init(scope);
296             errorReporter = new JSErrorReporter(getLogger());
297         } catch (Exception JavaDoc e) {
298             Context.exit();
299             e.printStackTrace();
300             throw e;
301         }
302     }
303
304     private ClassLoader JavaDoc getClassLoader(boolean needsRefresh) throws Exception JavaDoc {
305         if (!reloadScripts) {
306             return Thread.currentThread().getContextClassLoader();
307         }
308
309         synchronized (javaClassRepository) {
310             boolean reload = needsRefresh || classLoader == null;
311             if (needsRefresh && classLoader != null) {
312                 reload = !javaClassRepository.upToDateCheck();
313             }
314
315             if (reload) {
316                 // FIXME FIXME FIXME Resolver not released!
317
classLoader = new CompilingClassLoader(
318                         Thread.currentThread().getContextClassLoader(),
319                         (SourceResolver) manager.lookup(SourceResolver.ROLE),
320                         javaClassRepository);
321                 classLoader.addSourceListener(
322                         new CompilingClassLoader.SourceListener() {
323                             public void sourceCompiled(Source src) {
324                                 // no action
325
}
326
327                             public void sourceCompilationError(Source src, String JavaDoc msg) {
328                                 if (src != null) {
329                                     throw Context.reportRuntimeError(msg);
330                                 }
331                             }
332                         });
333                 updateSourcePath();
334             }
335             return classLoader;
336         }
337     }
338
339     private void updateSourcePath() {
340         if (classLoader != null) {
341             classLoader.setSourcePath(javaSourcePath);
342         }
343     }
344
345     /**
346      * Returns the JavaScript scope, a Scriptable object, from the user
347      * session instance. Each interpreter instance can have a scope
348      * associated with it.
349      *
350      * @return a <code>ThreadScope</code> value
351      */

352     private ThreadScope getSessionScope() throws Exception JavaDoc {
353         final String JavaDoc scopeID = USER_GLOBAL_SCOPE + getInterpreterID();
354         final Request request = ContextHelper.getRequest(this.avalonContext);
355
356         ThreadScope scope = null;
357
358         // Get/create the scope attached to the current context
359
Session session = request.getSession(false);
360         if (session != null) {
361             scope = (ThreadScope) session.getAttribute(scopeID);
362         } else {
363             scope = (ThreadScope) request.getAttribute(scopeID);
364         }
365
366         if (scope == null) {
367             scope = createThreadScope();
368             // Save scope in the request early to allow recursive Flow calls
369
request.setAttribute(scopeID, scope);
370         }
371
372         return scope;
373     }
374
375     /**
376      * Associates a JavaScript scope, a Scriptable object, with
377      * {@link #getInterpreterID() identifier} of this {@link Interpreter}
378      * instance.
379      *
380      * @param scope a <code>ThreadScope</code> value
381      */

382     private void setSessionScope(ThreadScope scope) throws Exception JavaDoc {
383         if (scope.useSession) {
384             final String JavaDoc scopeID = USER_GLOBAL_SCOPE + getInterpreterID();
385             final Request request = ContextHelper.getRequest(this.avalonContext);
386
387             // FIXME: Where "session scope" should go when session is invalidated?
388
// Attach the scope to the current context
389
try {
390                 Session session = request.getSession(true);
391                 session.setAttribute(scopeID, scope);
392             } catch (IllegalStateException JavaDoc e) {
393                 // Session might be invalidated already.
394
if (getLogger().isDebugEnabled()) {
395                     getLogger().debug("Got '" + e + "' while trying to set session scope.", e);
396                 }
397             }
398         }
399     }
400
401     public static class ThreadScope extends ScriptableObject {
402         private static final String JavaDoc[] BUILTIN_PACKAGES = {"javax", "org", "com"};
403
404         private ClassLoader JavaDoc classLoader;
405
406         /* true if this scope has assigned any global vars */
407         boolean useSession;
408
409         boolean locked = false;
410
411         /**
412          * Initializes new top-level scope.
413          */

414         public ThreadScope(Global scope) throws Exception JavaDoc {
415             final Context context = Context.getCurrentContext();
416
417             final String JavaDoc[] names = { "importClass" };
418             try {
419                 defineFunctionProperties(names,
420                                          ThreadScope.class,
421                                          ScriptableObject.DONTENUM);
422             } catch (PropertyException e) {
423                 throw new Error JavaDoc(); // should never happen
424
}
425
426             setPrototype(scope);
427
428             // We want this to be a new top-level scope, so set its
429
// parent scope to null. This means that any variables created
430
// by assignments will be properties of this.
431
setParentScope(null);
432
433             // Put in the thread scope the Cocoon object, which gives access
434
// to the interpreter object, and some Cocoon objects. See
435
// FOM_Cocoon for more details.
436
final Object JavaDoc[] args = {};
437             FOM_Cocoon cocoon = (FOM_Cocoon) context.newObject(this,
438                                                                "FOM_Cocoon",
439                                                                args);
440             cocoon.setParentScope(this);
441             super.put("cocoon", this, cocoon);
442
443             defineProperty(LAST_EXEC_TIME,
444                            new Long JavaDoc(0),
445                            ScriptableObject.DONTENUM | ScriptableObject.PERMANENT);
446         }
447
448         public String JavaDoc getClassName() {
449             return "ThreadScope";
450         }
451
452         public void setLock(boolean lock) {
453             this.locked = lock;
454         }
455
456         public void put(String JavaDoc name, Scriptable start, Object JavaDoc value) {
457             //Allow setting values to existing variables, or if this is a
458
//java class (used by importClass & importPackage)
459
if (this.locked && !has(name, start) && !(value instanceof NativeJavaClass)) {
460                 // Need to wrap into a runtime exception as Scriptable.put has no throws clause...
461
throw new WrappedException (new JavaScriptException("Implicit declaration of global variable '" + name +
462                   "' forbidden. Please ensure all variables are explicitely declared with the 'var' keyword"));
463             }
464             this.useSession = true;
465             super.put(name, start, value);
466         }
467
468         public void put(int index, Scriptable start, Object JavaDoc value) {
469             // FIXME(SW): do indexed properties have a meaning on the global scope?
470
if (this.locked && !has(index, start)) {
471                 throw new WrappedException(new JavaScriptException("Global scope locked. Cannot set value for index " + index));
472             }
473             this.useSession = true;
474             super.put(index, start, value);
475         }
476
477         // Invoked after script execution
478
void onExec() {
479             this.useSession = false;
480             super.put(LAST_EXEC_TIME, this, new Long JavaDoc(System.currentTimeMillis()));
481         }
482
483         /** Override importClass to allow reloading of classes */
484         public static void importClass(Context ctx,
485                                        Scriptable thisObj,
486                                        Object JavaDoc[] args,
487                                        Function funObj) {
488             for (int i = 0; i < args.length; i++) {
489                 Object JavaDoc clazz = args[i];
490                 if (!(clazz instanceof NativeJavaClass)) {
491                     throw Context.reportRuntimeError("Not a Java class: " +
492                                                      Context.toString(clazz));
493                 }
494                 String JavaDoc s = ((NativeJavaClass) clazz).getClassObject().getName();
495                 String JavaDoc n = s.substring(s.lastIndexOf('.') + 1);
496                 thisObj.put(n, thisObj, clazz);
497             }
498         }
499
500         public void setupPackages(ClassLoader JavaDoc cl) throws Exception JavaDoc {
501             final String JavaDoc JAVA_PACKAGE = "JavaPackage";
502             if (classLoader != cl) {
503                 classLoader = cl;
504                 Scriptable newPackages = new NativeJavaPackage("", cl);
505                 newPackages.setParentScope(this);
506                 newPackages.setPrototype(ScriptableObject.getClassPrototype(this, JAVA_PACKAGE));
507                 super.put("Packages", this, newPackages);
508                 for (int i = 0; i < BUILTIN_PACKAGES.length; i++) {
509                     String JavaDoc pkgName = BUILTIN_PACKAGES[i];
510                     Scriptable pkg = new NativeJavaPackage(pkgName, cl);
511                     pkg.setParentScope(this);
512                     pkg.setPrototype(ScriptableObject.getClassPrototype(this, JAVA_PACKAGE));
513                     super.put(pkgName, this, pkg);
514                 }
515             }
516         }
517
518         public ClassLoader JavaDoc getClassLoader() {
519             return classLoader;
520         }
521     }
522
523     private ThreadScope createThreadScope() throws Exception JavaDoc {
524         return new ThreadScope(scope);
525     }
526
527     /**
528      * Returns a new Scriptable object to be used as the global scope
529      * when running the JavaScript scripts in the context of a request.
530      *
531      * <p>If you want to maintain the state of global variables across
532      * multiple invocations of <code>&lt;map:call
533      * function="..."&gt;</code>, you need to instanciate the session
534      * object which is a property of the cocoon object
535      * <code>var session = cocoon.session</code>. This will place the
536      * newly create Scriptable object in the user's session, where it
537      * will be retrieved from at the next invocation of {@link #callFunction}.</p>
538      *
539      * @exception Exception if an error occurs
540      */

541     private void setupContext(Redirector redirector, Context context,
542                               ThreadScope thrScope)
543     throws Exception JavaDoc {
544         // Try to retrieve the scope object from the session instance. If
545
// no scope is found, we create a new one, but don't place it in
546
// the session.
547
//
548
// When a user script "creates" a session using
549
// cocoon.createSession() in JavaScript, the thrScope is placed in
550
// the session object, where it's later retrieved from here. This
551
// behaviour allows multiple JavaScript functions to share the
552
// same global scope.
553

554         FOM_Cocoon cocoon = (FOM_Cocoon) thrScope.get("cocoon", thrScope);
555         long lastExecTime = ((Long JavaDoc) thrScope.get(LAST_EXEC_TIME,
556                                                  thrScope)).longValue();
557         boolean needsRefresh = false;
558         if (reloadScripts) {
559             long now = System.currentTimeMillis();
560             if (now >= lastTimeCheck + checkTime) {
561                 needsRefresh = true;
562             }
563             lastTimeCheck = now;
564         }
565
566         // We need to setup the FOM_Cocoon object according to the current
567
// request. Everything else remains the same.
568
ClassLoader JavaDoc classLoader = getClassLoader(needsRefresh);
569         Thread.currentThread().setContextClassLoader(classLoader);
570         thrScope.setupPackages(classLoader);
571         cocoon.pushCallContext(this, redirector, manager,
572                                avalonContext, getLogger(), null);
573
574         // Check if we need to compile and/or execute scripts
575
synchronized (compiledScripts) {
576             List JavaDoc execList = new ArrayList JavaDoc();
577             // If we've never executed scripts in this scope or
578
// if reload-scripts is true and the check interval has expired
579
// or if new scripts have been specified in the sitemap,
580
// then create a list of scripts to compile/execute
581
if (lastExecTime == 0 || needsRefresh || needResolve.size() > 0) {
582                 topLevelScripts.addAll(needResolve);
583                 if (lastExecTime != 0 && !needsRefresh) {
584                     execList.addAll(needResolve);
585                 } else {
586                     execList.addAll(topLevelScripts);
587                 }
588                 needResolve.clear();
589             }
590             // Compile all the scripts first. That way you can set breakpoints
591
// in the debugger before they execute.
592
for (int i = 0, size = execList.size(); i < size; i++) {
593                 String JavaDoc sourceURI = (String JavaDoc)execList.get(i);
594                 ScriptSourceEntry entry =
595                     (ScriptSourceEntry)compiledScripts.get(sourceURI);
596                 if (entry == null) {
597                     Source src = this.sourceresolver.resolveURI(sourceURI);
598                     entry = new ScriptSourceEntry(src);
599                     compiledScripts.put(sourceURI, entry);
600                 }
601                 // Compile the script if necessary
602
entry.getScript(context, this.scope, needsRefresh, this);
603             }
604             // Execute the scripts if necessary
605
for (int i = 0, size = execList.size(); i < size; i++) {
606                 String JavaDoc sourceURI = (String JavaDoc) execList.get(i);
607                 ScriptSourceEntry entry =
608                     (ScriptSourceEntry) compiledScripts.get(sourceURI);
609                 long lastMod = entry.getSource().getLastModified();
610                 Script script = entry.getScript(context, this.scope, false, this);
611                 if (lastExecTime == 0 || lastMod > lastExecTime) {
612                     script.exec(context, thrScope);
613                     thrScope.onExec();
614                 }
615             }
616         }
617     }
618
619     /**
620      * Compile filename as JavaScript code
621      *
622      * @param cx Rhino context
623      * @param fileName resource uri
624      * @return compiled script
625      */

626     Script compileScript(Context cx, String JavaDoc fileName) throws Exception JavaDoc {
627         Source src = this.sourceresolver.resolveURI(fileName);
628         if (src != null) {
629             synchronized (compiledScripts) {
630                 ScriptSourceEntry entry =
631                     (ScriptSourceEntry)compiledScripts.get(src.getURI());
632                 Script compiledScript = null;
633                 if (entry == null) {
634                     compiledScripts.put(src.getURI(),
635                             entry = new ScriptSourceEntry(src));
636                 } else {
637                     this.sourceresolver.release(src);
638                 }
639                 compiledScript = entry.getScript(cx, this.scope, false, this);
640                 return compiledScript;
641             }
642         }
643         throw new ResourceNotFoundException(fileName + ": not found");
644     }
645
646     protected Script compileScript(Context cx, Scriptable scope, Source src)
647     throws Exception JavaDoc {
648         PushbackInputStream JavaDoc is = new PushbackInputStream JavaDoc(src.getInputStream(), ENCODING_BUF_SIZE);
649         try {
650             String JavaDoc encoding = findEncoding(is);
651             Reader JavaDoc reader = encoding == null ? new InputStreamReader JavaDoc(is) : new InputStreamReader JavaDoc(is, encoding);
652             reader = new BufferedReader JavaDoc(reader);
653             Script compiledScript = cx.compileReader(scope, reader,
654                     src.getURI(), 1, null);
655             return compiledScript;
656         } finally {
657             is.close();
658         }
659     }
660     
661     // A charset name can be up to 40 characters taken from the printable characters of US-ASCII
662
// (see http://www.iana.org/assignments/character-sets). So reading 100 bytes should be more than enough.
663
private final static int ENCODING_BUF_SIZE = 100;
664     // Match 'encoding = xxxx' on the first line
665
REProgram encodingRE = new RECompiler().compile("^.*encoding\\s*=\\s*([^\\s]*)");
666     
667     /**
668      * Find the encoding of the stream, or null if not specified
669      */

670     String JavaDoc findEncoding(PushbackInputStream JavaDoc is) throws IOException JavaDoc {
671         // Read some bytes
672
byte[] buffer = new byte[ENCODING_BUF_SIZE];
673         int len = is.read(buffer, 0, buffer.length);
674         // and push them back
675
is.unread(buffer, 0, len);
676         
677         // Interpret them as an ASCII string
678
String JavaDoc str = new String JavaDoc(buffer, 0, len, "ASCII");
679         RE re = new RE(encodingRE);
680         if (re.match(str)) {
681             return re.getParen(1);
682         }
683         return null;
684     }
685
686     /**
687      * Calls a JavaScript function, passing <code>params</code> as its
688      * arguments. In addition to this, it makes available the parameters
689      * through the <code>cocoon.parameters</code> JavaScript array
690      * (indexed by the parameter names).
691      *
692      * @param funName a <code>String</code> value
693      * @param params a <code>List</code> value
694      * @param redirector
695      * @exception Exception if an error occurs
696      */

697     public void callFunction(String JavaDoc funName, List JavaDoc params, Redirector redirector)
698     throws Exception JavaDoc {
699         Context context = Context.enter();
700         context.setOptimizationLevel(OPTIMIZATION_LEVEL);
701         context.setGeneratingDebug(true);
702         context.setCompileFunctionsWithDynamicScope(true);
703         context.setErrorReporter(errorReporter);
704         
705         LocationTrackingDebugger locationTracker = new LocationTrackingDebugger();
706         if (!enableDebugger) {
707             //FIXME: add a "tee" debugger that allows both to be used simultaneously
708
context.setDebugger(locationTracker, null);
709         }
710
711         ThreadScope thrScope = getSessionScope();
712         synchronized (thrScope) {
713             ClassLoader JavaDoc savedClassLoader =
714                 Thread.currentThread().getContextClassLoader();
715             FOM_Cocoon cocoon = null;
716             try {
717                 try {
718                     setupContext(redirector, context, thrScope);
719                     cocoon = (FOM_Cocoon) thrScope.get("cocoon", thrScope);
720
721                     // Register the current scope for scripts indirectly called from this function
722
FOM_JavaScriptFlowHelper.setFOM_FlowScope(cocoon.getObjectModel(), thrScope);
723
724                     if (enableDebugger) {
725                         if (!getDebugger().isVisible()) {
726                             // only raise the debugger window if it isn't already visible
727
getDebugger().setVisible(true);
728                         }
729                     }
730
731                     int size = (params != null ? params.size() : 0);
732                     Object JavaDoc[] funArgs = new Object JavaDoc[size];
733                     Scriptable parameters = context.newObject(thrScope);
734                     for (int i = 0; i < size; i++) {
735                         Interpreter.Argument arg = (Interpreter.Argument)params.get(i);
736                         funArgs[i] = arg.value;
737                         if (arg.name == null) {
738                             arg.name = "";
739                         }
740                         parameters.put(arg.name, parameters, arg.value);
741                     }
742                     cocoon.setParameters(parameters);
743
744                     Object JavaDoc fun = ScriptableObject.getProperty(thrScope, funName);
745                     if (fun == Scriptable.NOT_FOUND) {
746                         throw new ResourceNotFoundException("Function \"javascript:" + funName + "()\" not found");
747                     }
748
749                     // Check count of arguments
750
if (fun instanceof BaseFunction) {
751                         if (((BaseFunction)fun).getArity() != 0) {
752                             getLogger().error("Function '" + funName + "' must have no declared arguments! " +
753                                               "Use cocoon.parameters to reach parameters passed from the sitemap into the function.");
754                         }
755                     }
756
757                     thrScope.setLock(true);
758                     ScriptRuntime.call(context, fun, thrScope, funArgs, thrScope);
759                 } catch (JavaScriptException ex) {
760                     throw locationTracker.getException("Error calling flowscript function " + funName, ex);
761 // EvaluatorException ee = Context.reportRuntimeError(
762
// ToolErrorReporter.getMessage("msg.uncaughtJSException",
763
// ex.getMessage()));
764
// Throwable unwrapped = unwrap(ex);
765
// if (unwrapped instanceof ProcessingException) {
766
// throw (ProcessingException) unwrapped;
767
// }
768
// throw new CascadingRuntimeException(ee.getMessage(),
769
// unwrapped);
770
} catch (EcmaError ee) {
771                     throw locationTracker.getException("Error calling flowscript function " + funName, ee);
772 // String msg = ToolErrorReporter.getMessage("msg.uncaughtJSException", ee.toString());
773
// if (ee.getSourceName() != null) {
774
// Context.reportRuntimeError(msg, ee.getSourceName(),
775
// ee.getLineNumber(), ee.getLineSource(),
776
// ee.getColumnNumber());
777
// } else {
778
// Context.reportRuntimeError(msg);
779
// }
780
// throw new CascadingRuntimeException(ee.getMessage(), ee);
781
}
782             } finally {
783                 thrScope.setLock(false);
784                 setSessionScope(thrScope);
785                 if (cocoon != null) {
786                     cocoon.popCallContext();
787                 }
788                 Context.exit();
789                 Thread.currentThread().setContextClassLoader(savedClassLoader);
790             }
791         }
792     }
793
794     public void handleContinuation(String JavaDoc id, List JavaDoc params,
795                                    Redirector redirector) throws Exception JavaDoc
796     {
797         WebContinuation wk = continuationsMgr.lookupWebContinuation(id, getInterpreterID());
798
799         if (wk == null) {
800             /*
801              * Throw an InvalidContinuationException to be handled inside the
802              * <map:handle-errors> sitemap element.
803              */

804             throw new InvalidContinuationException("The continuation ID " + id + " is invalid.");
805         }
806
807         Context context = Context.enter();
808         context.setOptimizationLevel(OPTIMIZATION_LEVEL);
809         context.setGeneratingDebug(true);
810         context.setCompileFunctionsWithDynamicScope(true);
811         LocationTrackingDebugger locationTracker = new LocationTrackingDebugger();
812         if (!enableDebugger) {
813             //FIXME: add a "tee" debugger that allows both to be used simultaneously
814
context.setDebugger(locationTracker, null);
815         }
816
817         // Obtain the continuation object from it, and setup the
818
// FOM_Cocoon object associated in the dynamic scope of the saved
819
// continuation with the environment and context objects.
820
Continuation k = (Continuation)wk.getContinuation();
821         ThreadScope kScope = (ThreadScope)k.getParentScope();
822         synchronized (kScope) {
823             ClassLoader JavaDoc savedClassLoader =
824                 Thread.currentThread().getContextClassLoader();
825             FOM_Cocoon cocoon = null;
826             try {
827                 Thread.currentThread().setContextClassLoader(kScope.getClassLoader());
828                 cocoon = (FOM_Cocoon)kScope.get("cocoon", kScope);
829                 kScope.setLock(true);
830                 cocoon.pushCallContext(this, redirector, manager,
831                                        avalonContext,
832                                        getLogger(), wk);
833
834                 // Register the current scope for scripts indirectly called from this function
835
FOM_JavaScriptFlowHelper.setFOM_FlowScope(cocoon.getObjectModel(), kScope);
836
837                 if (enableDebugger) {
838                     getDebugger().setVisible(true);
839                 }
840                 Scriptable parameters = context.newObject(kScope);
841                 int size = params != null ? params.size() : 0;
842                 for (int i = 0; i < size; i++) {
843                     Interpreter.Argument arg = (Interpreter.Argument)params.get(i);
844                     parameters.put(arg.name, parameters, arg.value);
845                 }
846                 cocoon.setParameters(parameters);
847                 FOM_WebContinuation fom_wk = new FOM_WebContinuation(wk);
848                 fom_wk.setParentScope(kScope);
849                 fom_wk.setPrototype(ScriptableObject.getClassPrototype(kScope,
850                                                                        fom_wk.getClassName()));
851                 Object JavaDoc[] args = new Object JavaDoc[] {k, fom_wk};
852                 try {
853                     ScriptableObject.callMethod(cocoon,
854                                                 "handleContinuation", args);
855                 } catch (JavaScriptException ex) {
856                     throw locationTracker.getException("Error calling continuation", ex);
857 // EvaluatorException ee = Context.reportRuntimeError(
858
// ToolErrorReporter.getMessage("msg.uncaughtJSException",
859
// ex.getMessage()));
860
// Throwable unwrapped = unwrap(ex);
861
// if (unwrapped instanceof ProcessingException) {
862
// throw (ProcessingException)unwrapped;
863
// }
864
// throw new CascadingRuntimeException(ee.getMessage(),
865
// unwrapped);
866
} catch (EcmaError ee) {
867                     throw locationTracker.getException("Error calling continuation", ee);
868 // String msg = ToolErrorReporter.getMessage("msg.uncaughtJSException", ee.toString());
869
// if (ee.getSourceName() != null) {
870
// Context.reportRuntimeError(msg, ee.getSourceName(),
871
// ee.getLineNumber(), ee.getLineSource(),
872
// ee.getColumnNumber());
873
// } else {
874
// Context.reportRuntimeError(msg);
875
// }
876
// throw new CascadingRuntimeException(ee.getMessage(), ee);
877
}
878             } finally {
879                 kScope.setLock(false);
880                 setSessionScope(kScope);
881                 if (cocoon != null) {
882                     cocoon.popCallContext();
883                 }
884                 Context.exit();
885                 Thread.currentThread().setContextClassLoader(savedClassLoader);
886             }
887         }
888     }
889
890     private Throwable JavaDoc unwrap(JavaScriptException e) {
891         Object JavaDoc value = e.getValue();
892         while (value instanceof Wrapper) {
893             value = ((Wrapper)value).unwrap();
894         }
895         if (value instanceof Throwable JavaDoc) {
896             return (Throwable JavaDoc)value;
897         }
898         return e;
899     }
900
901     public void forwardTo(Scriptable scope, FOM_Cocoon cocoon, String JavaDoc uri,
902                           Object JavaDoc bizData, FOM_WebContinuation fom_wk,
903                           Redirector redirector)
904     throws Exception JavaDoc {
905         setupView(scope, cocoon, fom_wk);
906         super.forwardTo(uri, bizData,
907                         fom_wk == null ? null : fom_wk.getWebContinuation(),
908                         redirector);
909     }
910
911     // package access as this is called by FOM_Cocoon
912
void process(Scriptable scope, FOM_Cocoon cocoon, String JavaDoc uri,
913                  Object JavaDoc bizData, OutputStream JavaDoc out)
914     throws Exception JavaDoc {
915         setupView(scope, cocoon, null);
916         super.process(uri, bizData, out);
917     }
918
919     private void setupView(Scriptable scope, FOM_Cocoon cocoon, FOM_WebContinuation kont) {
920         Map JavaDoc objectModel = ContextHelper.getObjectModel(this.avalonContext);
921
922         // Make the JS live-connect objects available to the view layer
923
FOM_JavaScriptFlowHelper.setPackages(objectModel,
924                (Scriptable)ScriptableObject.getProperty(scope, "Packages"));
925         FOM_JavaScriptFlowHelper.setJavaPackage(objectModel,
926                (Scriptable)ScriptableObject.getProperty(scope, "java"));
927
928         // Make the FOM objects available to the view layer
929
FOM_JavaScriptFlowHelper.setFOM_Request(objectModel,
930                                             cocoon.jsGet_request());
931         FOM_JavaScriptFlowHelper.setFOM_Response(objectModel,
932                                              cocoon.jsGet_response());
933         Request request = ObjectModelHelper.getRequest(objectModel);
934         Scriptable session = null;
935         if (request.getSession(false) != null) {
936             session = cocoon.jsGet_session();
937         }
938         FOM_JavaScriptFlowHelper.setFOM_Session(objectModel, session);
939
940         FOM_JavaScriptFlowHelper.setFOM_Context(objectModel,
941                                                 cocoon.jsGet_context());
942         if (kont != null) {
943             FOM_JavaScriptFlowHelper.setFOM_WebContinuation(objectModel, kont);
944         }
945     }
946 }
947
Popular Tags