View Javadoc

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.struts.flow.core.javascript.fom;
17  
18  import java.beans.IntrospectionException;
19  import java.beans.Introspector;
20  import java.beans.PropertyDescriptor;
21  import java.io.OutputStream;
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Collections;
25  import java.util.Enumeration;
26  import java.util.HashMap;
27  import java.util.HashSet;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Set;
31  
32  import org.apache.struts.flow.Constants;
33  
34  //import org.apache.avalon.framework.context.Context;
35  //import org.apache.avalon.framework.context.ContextException;
36  //import org.apache.avalon.framework.logger.Logger;
37  //import org.apache.avalon.framework.service.ServiceManager;
38  //import org.apache.cocoon.components.ContextHelper;
39  //import org.apache.cocoon.components.LifecycleHelper;
40  import org.apache.struts.flow.core.ContinuationsManager;
41  import org.apache.struts.flow.core.WebContinuation;
42  import org.apache.struts.flow.core.javascript.ConversionHelper;
43  import org.apache.struts.flow.core.Interpreter.Argument;
44  //import org.apache.cocoon.environment.ObjectModelHelper;
45  //import org.apache.cocoon.environment.Redirector;
46  //import org.apache.cocoon.environment.Request;
47  //import org.apache.cocoon.environment.Response;
48  //import org.apache.cocoon.environment.Session;
49  //import org.apache.cocoon.util.ClassUtils;
50  import org.mozilla.javascript.JavaScriptException;
51  import org.mozilla.javascript.NativeJavaClass;
52  import org.mozilla.javascript.NativeJavaObject;
53  import org.mozilla.javascript.Script;
54  import org.mozilla.javascript.Scriptable;
55  import org.mozilla.javascript.ScriptableObject;
56  import org.mozilla.javascript.Undefined;
57  import org.mozilla.javascript.Wrapper;
58  import org.mozilla.javascript.continuations.Continuation;
59  
60  import org.apache.commons.chain.web.WebContext;
61  import org.apache.struts.flow.core.Factory;
62  import org.apache.struts.flow.core.Logger;
63  
64  /***
65   * Implementation of FOM (Flow Object Model).
66   *
67   * @since 2.1
68   * @author <a href="mailto:coliver.at.apache.org">Christopher Oliver</a>
69   * @author <a href="mailto:reinhard.at.apache.org">Reinhard P\u00F6tz</a>
70   * @version CVS $Id: FOM_Flow.java 292491 2005-09-29 17:44:16Z bloritsch $
71   */
72  public class FOM_Flow extends ScriptableObject {
73  
74      class CallContext {
75          CallContext caller;
76          FOM_JavaScriptInterpreter interpreter;
77          WebContext webctx;
78          Logger logger;
79          Scriptable context;
80          Scriptable parameters;
81          Scriptable log;
82          WebContinuation lastContinuation;
83          FOM_WebContinuation fwk;
84          PageLocalScopeImpl currentPageLocal;
85  
86          public CallContext(CallContext caller,
87                             FOM_JavaScriptInterpreter interp,
88                             WebContext webctx,
89                             Logger logger,
90                             WebContinuation lastContinuation) {
91              this.caller = caller;
92              this.interpreter = interp;
93              this.webctx = webctx;
94              this.logger = logger;
95              this.lastContinuation = lastContinuation;
96              if (lastContinuation != null) {
97                  fwk = new FOM_WebContinuation(lastContinuation);
98                  Scriptable scope = FOM_Flow.this.getParentScope();
99                  fwk.setParentScope(scope);
100                 fwk.setPrototype(getClassPrototype(scope, fwk.getClassName()));
101                 this.currentPageLocal = fwk.getPageLocal();
102             }
103             if (this.currentPageLocal != null) {
104                 // "clone" the page local scope
105                 this.currentPageLocal = this.currentPageLocal.duplicate();
106             } else {
107                 this.currentPageLocal = new PageLocalScopeImpl(getTopLevelScope(FOM_Flow.this));
108             }
109             pageLocal.setDelegate(this.currentPageLocal);
110         }
111 
112         public FOM_WebContinuation getLastContinuation() {
113             return fwk;
114         }
115 
116         public void setLastContinuation(FOM_WebContinuation fwk) {
117             this.fwk = fwk;
118             if (fwk != null) {
119                 pageLocal.setDelegate(fwk.getPageLocal());
120                 this.lastContinuation = fwk.getWebContinuation();
121             } else {
122                 this.lastContinuation = null;
123             }
124         }
125 
126         public WebContext getWebContext() {
127             return webctx;
128         }
129         
130         public Scriptable getContext() {
131             if (context != null) {
132                 return context;
133             }
134             context = org.mozilla.javascript.Context.toObject(webctx, getParentScope());
135             return context;
136         }
137 
138         public Scriptable getLog() {
139             if (log != null) {
140                 return log;
141             }
142             log = org.mozilla.javascript.Context.toObject(logger, getParentScope());
143             return log;
144         }
145 
146         public Scriptable getParameters() {
147             return parameters;
148         }
149 
150         public void setParameters(Scriptable parameters) {
151             this.parameters = parameters;
152         }
153     }
154 
155     private CallContext currentCall;
156     protected PageLocalScopeHolder pageLocal;
157 
158     public String getClassName() {
159         return "FOM_Flow";
160     }
161 
162 
163     // Called by FOM_JavaScriptInterpreter
164     static void init(Scriptable scope) throws Exception {
165         //FIXME(SW) what is the exact purpose of defineClass() ??
166         defineClass(scope, FOM_Flow.class);
167 //        defineClass(scope, FOM_Request.class);
168 //        defineClass(scope, FOM_Response.class);
169 //        defineClass(scope, FOM_Cookie.class);
170 //        defineClass(scope, FOM_Session.class);
171 //        defineClass(scope, FOM_Context.class);
172 //        defineClass(scope, FOM_Log.class);
173         defineClass(scope, FOM_WebContinuation.class);
174         defineClass(scope, PageLocalImpl.class);
175     }
176 
177     void pushCallContext(FOM_JavaScriptInterpreter interp,
178                          WebContext webctx,
179                          Logger logger,
180                          WebContinuation lastContinuation) {
181         if (pageLocal == null) {
182             pageLocal = new PageLocalScopeHolder(getTopLevelScope(this));
183         }
184         
185         this.currentCall = new CallContext(currentCall, interp, webctx,
186                                            logger, lastContinuation);
187     }
188 
189     void popCallContext() {
190         // Clear the scope attribute
191         //FOM_JavaScriptFlowHelper.setFOM_FlowScope(this.getObjectModel(), null);
192 
193         this.currentCall = this.currentCall.caller;
194         // reset current page locals
195         if (this.currentCall != null) {
196             pageLocal.setDelegate(this.currentCall.currentPageLocal);
197         } else {
198             pageLocal.setDelegate(null);
199         }
200     }
201 
202 
203     public FOM_WebContinuation jsGet_continuation() {
204         // FIXME: This method can return invalid continuation! Is it OK to do so?
205         return currentCall.getLastContinuation();
206     }
207 
208     public void jsSet_continuation(Object obj) {
209         FOM_WebContinuation fwk = (FOM_WebContinuation)ConversionHelper.jsobjectToObject(obj);
210         currentCall.setLastContinuation(fwk);
211     }
212 
213     public FOM_WebContinuation jsFunction_forward(String uri,
214                                                    Object obj,
215                                                    Object wk)
216         throws Exception {
217             
218         WebContext ctx = currentCall.getWebContext();
219         FOM_WebContinuation fom_wk = (FOM_WebContinuation)ConversionHelper.jsobjectToObject(wk);
220         
221         if (fom_wk != null) {
222             // save page locals
223             fom_wk.setPageLocal(pageLocal.getDelegate());
224             ctx.put(Constants.CONTINUATION_ID_KEY, fom_wk.jsGet_id());
225         }
226         
227         ctx.put(Constants.FORWARD_NAME_KEY, uri);
228         ctx.put(Constants.BIZ_DATA_KEY, ConversionHelper.jsobjectToObject(obj));
229         return fom_wk;
230     }
231 
232     public Scriptable jsFunction_createPageLocal() {
233         return pageLocal.createPageLocal();
234     }
235 
236 /*
237 
238  NOTE (SM): These are the hooks to the future FOM Event Model that will be
239  designed in the future. It has been postponed because we think
240  there are more important things to do at the moment, but these
241  are left here to indicate that they are planned.
242 
243     public void jsFunction_addEventListener(String eventName,
244                                             Object function) {
245         // what is this?
246     }
247 
248     public void jsFunction_removeEventListener(String eventName,
249                                                Object function) {
250         // what is this?
251     }
252 
253 */
254 
255 
256     /***
257      * Load the script file specified as argument.
258      *
259      * @param filename a <code>String</code> value
260      * @return an <code>Object</code> value
261      * @exception JavaScriptException if an error occurs
262      */
263     public Object jsFunction_load( String filename )
264         throws Exception {
265         org.mozilla.javascript.Context cx =
266             org.mozilla.javascript.Context.getCurrentContext();
267         Scriptable scope = getParentScope();
268         Script script = getInterpreter().compileScript(cx, filename);
269         return script.exec( cx, scope );
270     }
271 
272 
273     /***
274      * Base JS wrapper for Cocoon's request/session/context objects.
275      * <p>
276      * FIXME(SW): The only thing added to the regular Java object is the fact that
277      * attributes can be accessed as properties. Do we want to keep this?
278      */
279     private static abstract class AttributeHolderJavaObject extends NativeJavaObject {
280         
281         private static Map classProps = new HashMap();
282         private Set propNames;
283 
284         public AttributeHolderJavaObject(Scriptable scope, Object object, Class clazz) {
285             super(scope, object, clazz);
286             this.propNames = getProperties(object.getClass());
287         }
288         
289         /*** Compute the names of JavaBean properties so that we can filter them our in get() */
290         private static Set getProperties(Class clazz) {
291             Set result = (Set)classProps.get(clazz);
292             if (result == null) {
293                 try {
294                     PropertyDescriptor[] descriptors = Introspector.getBeanInfo(clazz).getPropertyDescriptors();
295                     result = new HashSet();
296                     for (int i = 0; i < descriptors.length; i++) {
297                         result.add(descriptors[i].getName());
298                     }
299                 } catch (IntrospectionException e) {
300                     // Cannot introspect: just consider there are no properties
301                     result = Collections.EMPTY_SET;
302                 }
303                 classProps.put(clazz, result);
304             }
305             return result;
306         }
307         
308         
309         protected abstract Enumeration getAttributeNames();
310         protected abstract Object getAttribute(String name);
311         
312         public Object[] getIds() {
313             // Get class Ids
314             Object [] classIds = super.getIds();
315             
316             // and add attribute names
317             ArrayList idList = new ArrayList(Arrays.asList(classIds));
318             Enumeration iter = getAttributeNames();
319             while(iter.hasMoreElements()) {
320                 idList.add(iter.nextElement());
321             }
322             return idList.toArray();
323         }
324         
325         public boolean has(String name, Scriptable start) {
326             return super.has(name, start) || getAttribute(name) != null;
327         }
328         
329         public Object get(String name, Scriptable start) {
330             Object result;
331             // Filter out JavaBean properties. We only want methods of the underlying object.
332             if (this.propNames.contains(name)) {
333                 result = NOT_FOUND;
334             } else {
335                 result = super.get(name, start);
336             }
337             if (result == NOT_FOUND) {
338                 result = getAttribute(name);
339                 if (result != null) {
340                     result = org.mozilla.javascript.Context.javaToJS(result, start);
341                 } else {
342                     result = NOT_FOUND;
343                 }
344             }
345             return result;
346         }
347     }
348 
349     
350     public Scriptable jsGet_log() {
351         return currentCall.getLog();
352     }
353 
354     public Scriptable jsGet_context() {
355         return currentCall.getContext();
356     }
357 
358     /***
359      * Get Sitemap parameters
360      *
361      * @return a <code>Scriptable</code> value whose properties represent
362      * the Sitemap parameters from <map:call>
363      */
364     public Scriptable jsGet_parameters() {
365         return getParameters();
366     }
367 
368     public Scriptable getParameters() {
369         return currentCall.getParameters();
370     }
371 
372     void setParameters(Scriptable value) {
373         currentCall.setParameters(value);
374     }
375     
376     /***
377      *  Converts a JavaScript object to a HashMap
378      */
379     public Map jsFunction_jsobjectToMap(Scriptable jsobject) {
380         return ConversionHelper.jsobjectToMap(jsobject);
381     }
382 
383     // Make everything available to JavaScript objects implemented in Java:
384 
385     /***
386      * Get the current context
387      * @return The context
388      */
389     public WebContext getWebContext() {
390         return currentCall.webctx;
391     }
392 
393     private Logger getLogger() {
394         return currentCall.logger;
395     }
396 
397     private FOM_JavaScriptInterpreter getInterpreter() {
398         return currentCall.interpreter;
399     }
400 
401     /***
402      * Required by FOM_WebContinuation. This way we do not make whole Interpreter public
403      * @return interpreter Id associated with this FOM.
404      */
405     public String getInterpreterId() {
406         return getInterpreter().getInterpreterID();
407     }
408 
409     /***
410      * Perform the behavior of <map:call continuation="blah">
411      * This can be used in cases where the continuation id is not encoded
412      * in the request in a form convenient to access in the sitemap.
413      * Your script can extract the id from the request and then call
414      * this method to process it as normal.
415      * @param kontId The continuation id
416      * @param parameters Any parameters you want to pass to the continuation (may be null)
417      */
418     public void handleContinuation(String kontId, Scriptable parameters)
419         throws Exception {
420         List list = null;
421         if (parameters == null || parameters == Undefined.instance) {
422             parameters = getParameters();
423         }
424         Object[] ids = parameters.getIds();
425         list = new ArrayList();
426         for (int i = 0; i < ids.length; i++) {
427             String name = ids[i].toString();
428             Argument arg = new Argument(name,
429                                         org.mozilla.javascript.Context.toString(getProperty(parameters, name)));
430             list.add(arg);
431         }
432         getInterpreter().handleContinuation(kontId, list, this.currentCall.webctx);
433     }
434 
435     /***
436      * Return this continuation if it is valid, or first valid parent
437      */
438     private FOM_WebContinuation findValidParent(FOM_WebContinuation wk) {
439         if (wk != null) {
440             WebContinuation wc = wk.getWebContinuation();
441             while (wc != null && wc.disposed()) {
442                 wc = wc.getParentContinuation();
443             }
444             if (wc != null) {
445                 return new FOM_WebContinuation(wc);
446             }
447         }
448 
449         return null;
450     }
451 
452     /***
453      * Create a Bookmark WebContinuation from a JS Continuation with the last
454      * continuation of sendPageAndWait as its parent.
455      * PageLocal variables will be shared with the continuation of
456      * the next call to sendPageAndWait().
457      * @param k The JS continuation
458      * @param ttl Lifetime for this continuation (zero means no limit)
459      */
460     public FOM_WebContinuation jsFunction_makeWebContinuation(Object k,
461                                                               Object ttl)
462         throws Exception {
463         double d = org.mozilla.javascript.Context.toNumber(ttl);
464         FOM_WebContinuation result =
465             makeWebContinuation((Continuation)ConversionHelper.jsobjectToObject(k),
466                                 findValidParent(jsGet_continuation()),
467                                 (int)d);
468         result.setPageLocal(pageLocal.getDelegate());
469         currentCall.setLastContinuation(result);
470         return result;
471     }
472 
473     /***
474      * Create a Web Continuation from a JS Continuation
475      * @param k The JS continuation (may be null - null will be returned in that case)
476      * @param parent The parent of this continuation (may be null)
477      * @param timeToLive Lifetime for this continuation (zero means no limit)
478      */
479     public FOM_WebContinuation makeWebContinuation(Continuation k,
480                                                    FOM_WebContinuation parent,
481                                                    int timeToLive)
482         throws Exception {
483         if (k == null) {
484             return null;
485         }
486         WebContinuation wk;
487         ContinuationsManager contMgr;
488         contMgr = Factory.getContinuationsManager();
489         wk = contMgr.createWebContinuation(ConversionHelper.jsobjectToObject(k),
490                                            (parent == null ? null : parent.getWebContinuation()),
491                                            timeToLive,
492                                            getInterpreter().getInterpreterID(),
493                                            null,
494                                            currentCall.getWebContext());
495         FOM_WebContinuation result = new FOM_WebContinuation(wk);
496         result.setParentScope(getParentScope());
497         result.setPrototype(getClassPrototype(getParentScope(),
498                                               result.getClassName()));
499         return result;
500     }
501 }