1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
35
36
37
38
39
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
45
46
47
48
49
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
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
164 static void init(Scriptable scope) throws Exception {
165
166 defineClass(scope, FOM_Flow.class);
167
168
169
170
171
172
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
191
192
193 this.currentCall = this.currentCall.caller;
194
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
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
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
239
240
241
242
243
244
245
246
247
248
249
250
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
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
314 Object [] classIds = super.getIds();
315
316
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
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
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 }