3 * (C) 2006 by Derrell Lipman
7 * LGPL 2.1: http://creativecommons.org/licenses/LGPL/2.1/
11 * Common facilities for modules' finite state machines. Each module's FSM
12 * should extend this class.
14 qx.OO.defineClass("swat.main.AbstractModuleFsm", qx.core.Object, function()
16 qx.core.Object.call(this);
18 // Create an array for pushing request objects
23 qx.Proto.buildFsm = function(module)
25 throw new Error("Module must overload buildFsm() " +
26 "to build its custom finite state machine.");
29 qx.Proto.addAwaitRpcResultState = function(module)
35 * State: AwaitRpcResult
38 * - enable any objects in group "swat.main.fsmUtils.enable_during_rpc"
39 * - disable any objects in group "swat.main.fsmUtils.disable_during_rpc"
42 * - disable any objects in group "swat.main.fsmUtils.enable_during_rpc"
43 * - enable any objects in group "swat.main.fsmUtils.disable_during_rpc"
46 * "completed" (on RPC)
48 * "execute" on swat.main.fsmUtils.abort_rpc
50 var state = new qx.util.fsm.State(
51 "State_AwaitRpcResult",
53 "autoActionsBeforeOnentry" :
55 // The name of a function.
59 // We want to enable objects in the group
60 // swat.main.fsmUtils.enable_during_rpc
61 "parameters" : [ true ],
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" ]
70 // We want to disable objects in the group
71 // swat.main.fsmUtils.disable_during_rpc
72 "parameters" : [ false ],
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" ]
82 "autoActionsBeforeOnexit" :
84 // The name of a function.
88 // We want to re-disable objects we had enabled, in the group
89 // swat.main.fsmUtils.enable_during_rpc
90 "parameters" : [ false ],
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" ]
99 // We want to re-enable objects we had disabled, in the group
100 // swat.main.fsmUtils.disable_during_rpc
101 "parameters" : [ true ],
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" ]
114 var bAuthCompleted = false;
116 // See if we just completed an authentication
117 if (fsm.getPreviousState() == "State_Authenticate" &&
118 event.getType() == "complete")
120 bAuthCompleted = true;
122 _this.debug("bAuthCompleted=" + bAuthCompleted);
124 // If we didn't just complete an authentication and we're coming
125 // from some other state...
126 if (! bAuthCompleted &&
127 fsm.getPreviousState() != "State_AwaitRpcResult")
129 // ... then push the previous state onto the state stack
130 _this.warn("PUSHING STATE");
131 fsm.pushState(false);
139 "swat.main.fsmUtils.abort_rpc" :
140 "Transition_AwaitRpcResult_to_AwaitRpcResult_via_button_abort"
144 "Transition_AwaitRpcResult_to_PopStack_via_complete",
147 qx.util.fsm.FiniteStateMachine.EventHandling.PREDICATE
152 /*** Transitions that use a PREDICATE appear first ***/
155 * Transition: AwaitRpcResult to GetAuthInfo
157 * Cause: "failed" (on RPC) where reason is PermissionDenied
159 var trans = new qx.util.fsm.Transition(
160 "Transition_AwaitRpcResult_to_Authenticate",
163 "State_Authenticate",
168 var error = event.getData(); // retrieve the JSON-RPC error
170 // Did we get get origin=Server, and either
171 // code=NotLoggedIn or code=SessionExpired ?
172 var origins = swat.main.AbstractModuleFsm.JsonRpc_Origin;
173 var serverErrors = swat.main.AbstractModuleFsm.JsonRpc_ServerError;
174 if (error.origin == origins.Server &&
175 (error.code == serverErrors.NotLoggedIn ||
176 error.code == serverErrors.SessionExpired))
181 // fall through to next transition, also for "failed"
190 var error = event.getData(); // retrieve the JSON-RPC error
191 var serverErrors = swat.main.AbstractModuleFsm.JsonRpc_ServerError;
195 case serverErrors.NotLoggedIn:
196 caption = "Please log in.";
199 case serverErrors.SessionExpired:
201 caption = "Session Expired. Please log in.";
205 // Retrieve the modal authentication window.
207 var loginWin = swat.main.Authenticate.getInstance(module);
210 loginWin.setCaption(caption);
212 // Set the domain info
213 loginWin.setInfo(error.info);
215 // Open the authentication window
219 state.addTransition(trans);
222 * Transition: AwaitRpcResult to PopStack
224 * Cause: "failed" (on RPC)
226 var trans = new qx.util.fsm.Transition(
227 "Transition_AwaitRpcResult_to_PopStack_via_failed",
230 qx.util.fsm.FiniteStateMachine.StateChange.POP_STATE_STACK,
235 // Get the request object
236 var rpcRequest = _this.getCurrentRpcRequest();
238 // Generate the result for a completed request
239 rpcRequest.setUserData("result",
242 data : event.getData()
246 state.addTransition(trans);
248 /*** Remaining transitions are accessed via the jump table ***/
251 * Transition: AwaitRpcResult to AwaitRpcResult
253 * Cause: "execute" on swat.main.fsmUtils.abort_rpc
255 var trans = new qx.util.fsm.Transition(
256 "Transition_AwaitRpcResult_to_AwaitRpcResult_via_button_abort",
259 "State_AwaitRpcResult",
264 // Get the request object
265 var rpcRequest = _this.getCurrentRpcRequest();
267 // Issue an abort for the pending request
268 rpcRequest.request.abort();
271 state.addTransition(trans);
274 * Transition: AwaitRpcResult to PopStack
276 * Cause: "complete" (on RPC)
278 var trans = new qx.util.fsm.Transition(
279 "Transition_AwaitRpcResult_to_PopStack_via_complete",
282 qx.util.fsm.FiniteStateMachine.StateChange.POP_STATE_STACK,
287 // Get the request object
288 var rpcRequest = _this.getCurrentRpcRequest();
290 // Generate the result for a completed request
291 rpcRequest.setUserData("result",
294 data : event.getData()
298 state.addTransition(trans);
301 * State: Authenticate
304 * "execute" on login_button
306 var state = new qx.util.fsm.State(
307 "State_Authenticate",
312 // Retrieve the login window object
313 var win = module.fsm.getObject("login_window");
315 // Clear the password field
316 win.password.setValue("");
318 // If there's no value selected for domain...
319 if (win.domain.getValue() == null)
321 // ... then select the first value
322 win.domain.setSelected(win.domain.getList().getFirstChild());
325 // Retrieve the current RPC request
326 var rpcRequest = _this.getCurrentRpcRequest();
328 // Did we just return from an RPC request and was it a login request?
329 if (fsm.getPreviousState() == "State_AwaitRpcResult" &&
330 rpcRequest.service == "samba.system" &&
331 rpcRequest.params.length > 1 &&
332 rpcRequest.params[1] == "login")
334 // Yup. Display the result. Pop the old request off the stack
335 var loginRequest = _this.popRpcRequest();
337 // Retrieve the result
338 var result = loginRequest.getUserData("result");
341 if (result.type == "failed")
343 // Nope. Just reset the caption, and remain in this state.
344 win.setCaption("Login Failed. Try again.");
348 // Login was successful. Generate an event that will transition
349 // us back to the AwaitRpcResult state to again await the result
350 // of the original RPC request.
351 win.dispatchEvent(new qx.event.type.Event("complete"), true);
353 // Reissue the original request. (We already popped the login
354 // request off the stack, so the current request is the original
356 var origRequest = _this.getCurrentRpcRequest();
358 // Retrieve the RPC object */
359 var rpc = fsm.getObject("swat.main.rpc");
361 // Set the service name
362 rpc.setServiceName(origRequest.service);
364 // Reissue the request
365 origRequest.request =
366 qx.io.remote.Rpc.prototype.callAsyncListeners.apply(
370 // Clear the password field, for good measure
371 win.password.setValue("");
373 // Close the login window
377 // Dispose of the login request
378 loginRequest.request.dispose();
379 loginRequest.request = null;
388 "Transition_Authenticate_to_AwaitRpcResult_via_button_login"
394 "Transition_Authenticate_to_AwaitRpcResult_via_complete"
401 * Transition: Authenticate to AwaitRpcResult
403 * Cause: "execute" on login_button
405 var trans = new qx.util.fsm.Transition(
406 "Transition_Authenticate_to_AwaitRpcResult_via_button_login",
409 "State_AwaitRpcResult",
414 // Retrieve the login window object
415 var win = fsm.getObject("login_window");
417 // Issue a Login call
422 win.userName.getValue(),
423 win.password.getValue(),
424 win.domain.getValue()
428 state.addTransition(trans);
431 * Transition: Authenticate to AwaitRpcResult
433 * Cause: "complete" on login_window
435 * We've already re-issued the original request, so we have nothing to do
436 * here but transition back to the AwaitRpcResult state to again await the
437 * result of the original request.
439 var trans = new qx.util.fsm.Transition(
440 "Transition_Authenticate_to_AwaitRpcResult_via_complete",
443 "State_AwaitRpcResult"
445 state.addTransition(trans);
450 * Issue a remote procedure call.
452 * @param fsm {qx.util.fsm.FiniteStateMachine}
453 * The finite state machine issuing this remote procedure call.
455 * @param service {string}
456 * The name of the remote service which provides the specified method.
458 * @param method {string}
459 * The name of the method within the specified service.
461 * @param params {Array}
462 * The parameters to be passed to the specified method.
465 * The request object for the just-issued RPC request.
467 qx.Proto.callRpc = function(fsm, service, method, params)
469 // Create an object to hold a copy of the parameters. (We need a
470 // qx.core.Object() to be able to store this in the finite state machine.)
471 var rpcRequest = new qx.core.Object();
473 // Save the service name
474 rpcRequest.service = service;
476 // Copy the parameters; we'll prefix our copy with additional params
477 rpcRequest.params = params.slice(0);
479 // Prepend the method
480 rpcRequest.params.unshift(method);
482 // Prepend the flag indicating to coalesce failure events
483 rpcRequest.params.unshift(true);
485 // Retrieve the RPC object */
486 var rpc = fsm.getObject("swat.main.rpc");
488 // Set the service name
489 rpc.setServiceName(rpcRequest.service);
493 qx.io.remote.Rpc.prototype.callAsyncListeners.apply(rpc,
496 // Make the rpc request object available to the AwaitRpcResult state
497 this.pushRpcRequest(rpcRequest);
499 // Give 'em what they came for
505 * Push an RPC request onto the request stack.
507 * @param request {Object}
508 * The just-issued rpc request object
510 qx.Proto.pushRpcRequest = function(rpcRequest)
512 this._requests.push(rpcRequest);
517 * Retrieve the most recent RPC request from the request stack and pop the
521 * The rpc request object from the top of the request stack
523 qx.Proto.popRpcRequest = function()
525 if (this._requests.length == 0)
527 throw new Error("Attempt to pop an RPC request when list is empty.");
530 var rpcRequest = this._requests.pop();
536 * Retrieve the most recent RPC request.
539 * The rpc request object at the top of the request stack
541 qx.Proto.getCurrentRpcRequest = function()
543 if (this._requests.length == 0)
545 throw new Error("Attempt to retrieve an RPC request when list is empty.");
548 return this._requests[this._requests.length - 1];
553 * JSON-RPC error origins
555 qx.Class.JsonRpc_Origin =
565 * JSON-RPC Errors for origin == Server
567 qx.Class.JsonRpc_ServerError =
570 * Error code, value 0: Unknown Error
572 * The default error code, used only when no specific error code is passed
573 * to the JsonRpcError constructor. This code should generally not be used.
578 * Error code, value 1: Illegal Service
580 * The service name contains illegal characters or is otherwise deemed
581 * unacceptable to the JSON-RPC server.
586 * Error code, value 2: Service Not Found
588 * The requested service does not exist at the JSON-RPC server.
593 * Error code, value 3: Class Not Found
595 * If the JSON-RPC server divides service methods into subsets (classes),
596 * this indicates that the specified class was not found. This is slightly
597 * more detailed than "Method Not Found", but that error would always also
598 * be legal (and true) whenever this one is returned. (Not used in this
601 ClassNotFound : 3, // not used in this implementation
604 * Error code, value 4: Method Not Found
606 * The method specified in the request is not found in the requested
612 * Error code, value 5: Parameter Mismatch
614 * If a method discovers that the parameters (arguments) provided to it do
615 * not match the requisite types for the method's parameters, it should
616 * return this error code to indicate so to the caller.
618 * This error is also used to indicate an illegal parameter value, in server
621 ParameterMismatch : 5,
624 * Error code, value 6: Permission Denied
626 * A JSON-RPC service provider can require authentication, and that
627 * authentication can be implemented such the method takes authentication
628 * parameters, or such that a method or class of methods requires prior
629 * authentication. If the caller has not properly authenticated to use the
630 * requested method, this error code is returned.
632 PermissionDenied : 6,
634 /*** Errors generated by this server which are not qooxdoo-standard ***/
637 * Error code, value 1000: Unexpected Output
639 * The called method illegally generated output to the browser, which would
640 * have preceeded the JSON-RPC data.
642 UnexpectedOutput : 1000,
645 * Error code, value 1001: Resource Error
647 * Too many resources were requested, a system limitation on the total number
648 * of resources has been reached, or a resource or resource id was misused.
650 ResourceError : 1001,
653 * Error code, value 1002: Not Logged In
655 * The user has logged out and must re-authenticate, or this is a brand new
656 * session and the user must log in.
662 * Error code, value 1003: Session Expired
664 * The session has expired and the user must re-authenticate.
667 SessionExpired : 1003,
670 * Error code, value 1004: Login Failed
672 * An attempt to log in failed.