r21168: - Step 2: Update swat for the latest qooxdoo version. The build now copies
[ira/wip.git] / webapps / swat / source / class / swat / main / AbstractModuleFsm.js
1 /*
2  * Copyright:
3  *   (C) 2006 by Derrell Lipman
4  *       All rights reserved
5  *
6  * License:
7  *   LGPL 2.1: http://creativecommons.org/licenses/LGPL/2.1/
8  */
9
10 /**
11  * Common facilities for modules' finite state machines.  Each module's FSM
12  * should extend this class.
13  */
14 qx.OO.defineClass("swat.main.AbstractModuleFsm", qx.core.Object, function()
15 {
16   qx.core.Object.call(this);
17
18   // Create an array for pushing request objects
19   this._requests = [ ];
20 });
21
22
23 qx.Proto.buildFsm = function(module)
24 {
25   throw new Error("Module must overload buildFsm() " +
26                   "to build its custom finite state machine.");
27 };
28
29 qx.Proto.addAwaitRpcResultState = function(module, blockedEvents)
30 {
31   var fsm = module.fsm;
32   var _this = this;
33
34   /*
35    * State: AwaitRpcResult
36    *
37    * Actions upon entry:
38    *  - enable any objects in group "swat.main.fsmUtils.enable_during_rpc"
39    *  - disable any objects in group "swat.main.fsmUtils.disable_during_rpc"
40    *
41    * Actions upon exit:
42    *   - disable any objects in group "swat.main.fsmUtils.enable_during_rpc"
43    *   - enable any objects in group "swat.main.fsmUtils.disable_during_rpc"
44    *
45    * Transition on:
46    *  "completed" (on RPC)
47    *  "failed" (on RPC)
48    *  "execute" on swat.main.fsmUtils.abort_rpc
49    */
50
51   var stateInfo =
52   {
53     "autoActionsBeforeOnentry" :
54     {
55       // The name of a function.
56       "setEnabled" :
57       [
58         {
59           // We want to enable objects in the group
60           // swat.main.fsmUtils.enable_during_rpc
61           "parameters" : [ true ],
62
63           // Call this.getObject(<object>).setEnabled(true) on
64           // state entry, for each <object> in the group called
65           // "swat.main.fsmUtils.enable_during_rpc".
66           "groups"      : [ "swat.main.fsmUtils.enable_during_rpc" ]
67         },
68
69         {
70           // We want to disable objects in the group
71           // swat.main.fsmUtils.disable_during_rpc
72           "parameters" : [ false ],
73
74           // Call this.getObject(<object>).setEnabled(false) on
75           // state entry, for each <object> in the group called
76           // "swat.main.fsmUtils.disable_during_rpc".
77           "groups"      : [ "swat.main.fsmUtils.disable_during_rpc" ]
78         }
79       ]
80     },
81
82     "autoActionsBeforeOnexit" :
83     {
84       // The name of a function.
85       "setEnabled" :
86       [
87         {
88           // We want to re-disable objects we had enabled, in the group
89           // swat.main.fsmUtils.enable_during_rpc
90           "parameters" : [ false ],
91
92           // Call this.getObject(<object>).setEnabled(false) on
93           // state entry, for each <object> in the group called
94           // "swat.main.fsmUtils.enable_during_rpc".
95           "groups"      : [ "swat.main.fsmUtils.enable_during_rpc" ]
96         },
97
98         {
99           // We want to re-enable objects we had disabled, in the group
100           // swat.main.fsmUtils.disable_during_rpc
101           "parameters" : [ true ],
102
103           // Call this.getObject(<object>).setEnabled(true) on
104           // state entry, for each <object> in the group called
105           // "swat.main.fsmUtils.disable_during_rpc".
106           "groups"      : [ "swat.main.fsmUtils.disable_during_rpc" ]
107         }
108       ]
109     },
110
111     "onentry" :
112       function(fsm, event)
113       {
114         var bAuthCompleted = false;
115
116         // See if we just completed an authentication
117         if (fsm.getPreviousState() == "State_Authenticate" &&
118             event.getType() == "complete")
119         {
120           bAuthCompleted = true;
121         }
122
123         // If we didn't just complete an authentication and we're coming
124         // from some other state...
125         if (! bAuthCompleted &&
126             fsm.getPreviousState() != "State_AwaitRpcResult")
127         {
128           // ... then push the previous state onto the state stack
129           fsm.pushState(false);
130         }
131       },
132
133     "events" :
134     {
135       "execute"  :
136       {
137         "swat.main.fsmUtils.abort_rpc" :
138           "Transition_AwaitRpcResult_to_AwaitRpcResult_via_button_abort"
139       },
140
141       "completed" :
142         "Transition_AwaitRpcResult_to_PopStack_via_complete",
143
144       "failed" :
145         qx.util.fsm.FiniteStateMachine.EventHandling.PREDICATE
146     }
147   };
148
149   // If there are blocked events specified...
150   if (blockedEvents)
151   {
152     // ... then add them to the state info events object
153     for (var blockedEvent in blockedEvents)
154     {
155       // Ensure it's not already there.  Avoid programmer headaches.
156       if (stateInfo["events"][blockedEvent])
157       {
158         throw new Error("Attempt to add blocked event " +
159                         blockedEvent + " but it is already handled");
160       }
161
162       // Add the event.
163       stateInfo["events"][blockedEvent] = blockedEvents[blockedEvent];
164     }
165   }
166
167   var state = new qx.util.fsm.State( "State_AwaitRpcResult", stateInfo);
168   fsm.addState(state);
169
170   /*** Transitions that use a PREDICATE appear first ***/
171
172   /*
173    * Transition: AwaitRpcResult to GetAuthInfo
174    *
175    * Cause: "failed" (on RPC) where reason is PermissionDenied
176    */
177   var trans = new qx.util.fsm.Transition(
178     "Transition_AwaitRpcResult_to_Authenticate",
179     {
180       "nextState" :
181         "State_Authenticate",
182
183       "predicate" :
184         function(fsm, event)
185         {
186           var error = event.getData(); // retrieve the JSON-RPC error
187
188           // Did we get get origin=Server, and either
189           // code=NotLoggedIn or code=SessionExpired ? 
190           var origins = swat.main.AbstractModuleFsm.JsonRpc_Origin;
191           var serverErrors = swat.main.AbstractModuleFsm.JsonRpc_ServerError;
192           if (error.origin == origins.Server &&
193               (error.code == serverErrors.NotLoggedIn ||
194                error.code == serverErrors.SessionExpired))
195           {
196             return true;
197           }
198
199           // fall through to next transition, also for "failed"
200           return false;
201         },
202
203       "ontransition" :
204         function(fsm, event)
205         {
206           var caption;
207
208           var error = event.getData(); // retrieve the JSON-RPC error
209           var serverErrors = swat.main.AbstractModuleFsm.JsonRpc_ServerError;
210
211           switch(error.code)
212           {
213           case serverErrors.NotLoggedIn:
214             caption = "Please log in.";
215             break;
216
217           case serverErrors.SessionExpired:
218           default:
219             caption = "Session Expired.  Please log in.";
220             break;
221           }
222
223           // Retrieve the modal authentication window.
224           var loginWin = swat.main.Authenticate.getInstance();
225
226           // Ensure that it's saved in the current finite state machine
227           loginWin.addToFsm(fsm);
228
229           // Set the caption
230           loginWin.setCaption(caption);
231
232           // Set the domain info
233           loginWin.setInfo(error.info);
234
235           // Open the authentication window
236           loginWin.open();
237         }
238     });
239   state.addTransition(trans);
240
241   /*
242    * Transition: AwaitRpcResult to PopStack
243    *
244    * Cause: "failed" (on RPC)
245    */
246   var trans = new qx.util.fsm.Transition(
247     "Transition_AwaitRpcResult_to_PopStack_via_failed",
248     {
249       "nextState" :
250         qx.util.fsm.FiniteStateMachine.StateChange.POP_STATE_STACK,
251
252       "ontransition" :
253         function(fsm, event)
254         {
255           // Get the request object
256           var rpcRequest = _this.getCurrentRpcRequest();
257           
258           // Generate the result for a completed request
259           rpcRequest.setUserData("result",
260                                   {
261                                       type : "failed",
262                                       data : event.getData()
263                                   });
264         }
265     });
266   state.addTransition(trans);
267
268   /*** Remaining transitions are accessed via the jump table ***/
269
270   /*
271    * Transition: AwaitRpcResult to AwaitRpcResult
272    *
273    * Cause: "execute" on swat.main.fsmUtils.abort_rpc
274    */
275   var trans = new qx.util.fsm.Transition(
276     "Transition_AwaitRpcResult_to_AwaitRpcResult_via_button_abort",
277     {
278       "nextState" :
279         "State_AwaitRpcResult",
280
281       "ontransition" :
282         function(fsm, event)
283         {
284           // Get the request object
285           var rpcRequest = _this.getCurrentRpcRequest();
286
287           // Issue an abort for the pending request
288           rpcRequest.request.abort();
289         }
290     });
291   state.addTransition(trans);
292
293   /*
294    * Transition: AwaitRpcResult to PopStack
295    *
296    * Cause: "complete" (on RPC)
297    */
298   var trans = new qx.util.fsm.Transition(
299     "Transition_AwaitRpcResult_to_PopStack_via_complete",
300     {
301       "nextState" :
302         qx.util.fsm.FiniteStateMachine.StateChange.POP_STATE_STACK,
303
304       "ontransition" :
305         function(fsm, event)
306         {
307           // Get the request object
308           var rpcRequest = _this.getCurrentRpcRequest();
309           
310           // Generate the result for a completed request
311           rpcRequest.setUserData("result",
312                                   {
313                                       type : "complete",
314                                       data : event.getData()
315                                   });
316         }
317     });
318   state.addTransition(trans);
319
320   /*
321    * State: Authenticate
322    *
323    * Transition on:
324    *  "execute" on login_button
325    */
326   var state = new qx.util.fsm.State(
327     "State_Authenticate",
328     {
329       "onentry" :
330         function(fsm, event)
331         {
332           // Retrieve the login window object
333           var win = fsm.getObject("login_window");
334
335           // Clear the password field
336           win.password.setValue("");
337
338           // If there's no value selected for domain...
339           if (win.domain.getValue() == null)
340           {
341             // ... then select the first value
342             win.domain.setSelected(win.domain.getList().getFirstChild());
343           }
344
345           // Retrieve the current RPC request
346           var rpcRequest = _this.getCurrentRpcRequest();
347
348           // Did we just return from an RPC request and was it a login request?
349           if (fsm.getPreviousState() == "State_AwaitRpcResult" &&
350               rpcRequest.service == "samba.system" &&
351               rpcRequest.params.length > 1 &&
352               rpcRequest.params[1] == "login")
353           {
354             // Yup.  Display the result.  Pop the old request off the stack
355             var loginRequest = _this.popRpcRequest();
356
357             // Retrieve the result
358             var result = loginRequest.getUserData("result");
359
360             // Did we succeed?
361             if (result.type == "failed")
362             {
363               // Nope.  Just reset the caption, and remain in this state.
364               win.setCaption("Login Failed.  Try again.");
365             }
366             else
367             {
368               // Login was successful.  Generate an event that will transition
369               // us back to the AwaitRpcResult state to again await the result
370               // of the original RPC request.
371               win.dispatchEvent(new qx.event.type.Event("complete"), true);
372
373               // Reissue the original request.  (We already popped the login
374               // request off the stack, so the current request is the original
375               // one.)
376               var origRequest = _this.getCurrentRpcRequest();
377               
378               // Retrieve the RPC object */
379               var rpc = fsm.getObject("swat.main.rpc");
380
381               // Set the service name
382               rpc.setServiceName(origRequest.service);
383
384               // Reissue the request
385               origRequest.request =
386                 qx.io.remote.Rpc.prototype.callAsyncListeners.apply(
387                   rpc,
388                   origRequest.params);
389
390               // Clear the password field, for good measure
391               win.password.setValue("");
392
393               // Close the login window
394               win.close();
395             }
396
397             // Dispose of the login request
398             loginRequest.request.dispose();
399             loginRequest.request = null;
400           }
401         },
402
403       "events" :
404       {
405         "execute"  :
406         {
407           "login_button" :
408             "Transition_Authenticate_to_AwaitRpcResult_via_button_login"
409         },
410
411         "complete"  :
412         {
413           "login_window" :
414             "Transition_Authenticate_to_AwaitRpcResult_via_complete"
415         }
416       }
417     });
418   fsm.addState(state);
419
420   /*
421    * Transition: Authenticate to AwaitRpcResult
422    *
423    * Cause: "execute" on login_button
424    */
425   var trans = new qx.util.fsm.Transition(
426     "Transition_Authenticate_to_AwaitRpcResult_via_button_login",
427     {
428       "nextState" :
429         "State_AwaitRpcResult",
430
431       "ontransition" :
432         function(fsm, event)
433         {
434           // Retrieve the login window object
435           var win = fsm.getObject("login_window");
436
437           // Issue a Login call
438           _this.callRpc(fsm,
439                         "samba.system",
440                         "login",
441                         [
442                           win.userName.getValue(),
443                           win.password.getValue(),
444                           win.domain.getValue()
445                         ]);
446         }
447     });
448   state.addTransition(trans);
449
450   /*
451    * Transition: Authenticate to AwaitRpcResult
452    *
453    * Cause: "complete" on login_window
454    *
455    * We've already re-issued the original request, so we have nothing to do
456    * here but transition back to the AwaitRpcResult state to again await the
457    * result of the original request.
458    */
459   var trans = new qx.util.fsm.Transition(
460     "Transition_Authenticate_to_AwaitRpcResult_via_complete",
461     {
462       "nextState" :
463         "State_AwaitRpcResult"
464     });
465   state.addTransition(trans);
466 };
467
468
469 /**
470  * Issue a remote procedure call.
471  *
472  * @param fsm {qx.util.fsm.FiniteStateMachine}
473  *   The finite state machine issuing this remote procedure call.
474  *
475  * @param service {String}
476  *   The name of the remote service which provides the specified method.
477  *
478  * @param method {String}
479  *   The name of the method within the specified service.
480  *
481  * @param params {Array}
482  *   The parameters to be passed to the specified method.
483  *
484  * @return {Object}
485  *   The request object for the just-issued RPC request.
486  */
487 qx.Proto.callRpc = function(fsm, service, method, params)
488 {
489   // Create an object to hold a copy of the parameters.  (We need a
490   // qx.core.Object() to be able to store this in the finite state machine.)
491   var rpcRequest = new qx.core.Object();
492
493   // Save the service name
494   rpcRequest.service = service;
495
496   // Copy the parameters; we'll prefix our copy with additional params
497   rpcRequest.params = params.slice(0);
498
499   // Prepend the method
500   rpcRequest.params.unshift(method);
501
502   // Prepend the flag indicating to coalesce failure events
503   rpcRequest.params.unshift(true);
504
505   // Retrieve the RPC object */
506   var rpc = fsm.getObject("swat.main.rpc");
507
508   // Set the service name
509   rpc.setServiceName(rpcRequest.service);
510
511   // Issue the request
512   rpcRequest.request =
513     qx.io.remote.Rpc.prototype.callAsyncListeners.apply(rpc,
514                                                         rpcRequest.params);
515
516   // Make the rpc request object available to the AwaitRpcResult state
517   this.pushRpcRequest(rpcRequest);
518
519   // Give 'em what they came for
520   return rpcRequest;
521 };
522
523
524 /**
525  * Push an RPC request onto the request stack.
526  *
527  * @param request {Object}
528  *   The just-issued rpc request object
529  */
530 qx.Proto.pushRpcRequest = function(rpcRequest)
531 {
532   this._requests.push(rpcRequest);
533 };
534
535
536 /**
537  * Retrieve the most recent RPC request from the request stack and pop the
538  * stack.
539  *
540  * @return {Object}
541  *   The rpc request object from the top of the request stack
542  */
543 qx.Proto.popRpcRequest = function()
544 {
545   if (this._requests.length == 0)
546   {
547     throw new Error("Attempt to pop an RPC request when list is empty.");
548   }
549
550   var rpcRequest = this._requests.pop();
551   return rpcRequest;
552 };
553
554
555 /**
556  * Retrieve the most recent RPC request.
557  *
558  * @return {Object}
559  *   The rpc request object at the top of the request stack
560  */
561 qx.Proto.getCurrentRpcRequest = function()
562 {
563   if (this._requests.length == 0)
564   {
565     throw new Error("Attempt to retrieve an RPC request when list is empty.");
566   }
567
568   return this._requests[this._requests.length - 1];
569 };
570
571
572 /**
573  * JSON-RPC error origins
574  */
575 qx.Class.JsonRpc_Origin =
576 {
577   Server              : 1,
578   Application         : 2,
579   Transport           : 3,
580   Client              : 4
581 };
582
583
584 /**
585  * JSON-RPC Errors for origin == Server
586  */
587 qx.Class.JsonRpc_ServerError =
588 {
589   /**
590    * Error code, value 0: Unknown Error
591    *
592    * The default error code, used only when no specific error code is passed
593    * to the JsonRpcError constructor.  This code should generally not be used.
594    */
595   Unknown               : 0,
596
597   /**
598    * Error code, value 1: Illegal Service
599    *
600    * The service name contains illegal characters or is otherwise deemed
601    * unacceptable to the JSON-RPC server.
602    */
603   IllegalService        : 1,
604
605   /**
606    * Error code, value 2: Service Not Found
607    *
608    * The requested service does not exist at the JSON-RPC server.
609    */
610   ServiceNotFound       : 2,
611
612   /**
613    * Error code, value 3: Class Not Found
614    *
615    * If the JSON-RPC server divides service methods into subsets (classes),
616    * this indicates that the specified class was not found.  This is slightly
617    * more detailed than "Method Not Found", but that error would always also
618    * be legal (and true) whenever this one is returned. (Not used in this
619    * implementation)
620    */
621   ClassNotFound         : 3, // not used in this implementation
622
623   /**
624    * Error code, value 4: Method Not Found
625    *
626    * The method specified in the request is not found in the requested
627    * service.
628    */
629   MethodNotFound        : 4,
630
631   /*
632    * Error code, value 5: Parameter Mismatch
633    *
634    * If a method discovers that the parameters (arguments) provided to it do
635    * not match the requisite types for the method's parameters, it should
636    * return this error code to indicate so to the caller.
637    *
638    * This error is also used to indicate an illegal parameter value, in server
639    * scripts.
640    */
641   ParameterMismatch     : 5,
642
643   /**
644    * Error code, value 6: Permission Denied
645    *
646    * A JSON-RPC service provider can require authentication, and that
647    * authentication can be implemented such the method takes authentication
648    * parameters, or such that a method or class of methods requires prior
649    * authentication.  If the caller has not properly authenticated to use the
650    * requested method, this error code is returned.
651    */
652   PermissionDenied      : 6,
653
654   /*** Errors generated by this server which are not qooxdoo-standard ***/
655
656   /*
657    * Error code, value 1000: Unexpected Output
658    *
659    * The called method illegally generated output to the browser, which would
660    * have preceeded the JSON-RPC data.
661    */
662   UnexpectedOutput      : 1000,
663
664   /*
665    * Error code, value 1001: Resource Error
666    *
667    * Too many resources were requested, a system limitation on the total number
668    * of resources has been reached, or a resource or resource id was misused.
669    */
670   ResourceError         : 1001,
671
672   /*
673    * Error code, value 1002: Not Logged In
674    *
675    * The user has logged out and must re-authenticate, or this is a brand new
676    * session and the user must log in.
677    *
678    */
679   NotLoggedIn           : 1002,
680
681   /*
682    * Error code, value 1003: Session Expired
683    *
684    * The session has expired and the user must re-authenticate.
685    *
686    */
687   SessionExpired        : 1003,
688
689   /*
690    * Error code, value 1004: Login Failed
691    *
692    * An attempt to log in failed.
693    *
694    */
695   LoginFailed           : 1004
696 };