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, blockedEvents)
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
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;
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")
128 // ... then push the previous state onto the state stack
129 fsm.pushState(false);
137 "swat.main.fsmUtils.abort_rpc" :
138 "Transition_AwaitRpcResult_to_AwaitRpcResult_via_button_abort"
142 "Transition_AwaitRpcResult_to_PopStack_via_complete",
145 qx.util.fsm.FiniteStateMachine.EventHandling.PREDICATE
149 // If there are blocked events specified...
152 // ... then add them to the state info events object
153 for (var blockedEvent in blockedEvents)
155 // Ensure it's not already there. Avoid programmer headaches.
156 if (stateInfo["events"][blockedEvent])
158 throw new Error("Attempt to add blocked event " +
159 blockedEvent + " but it is already handled");
163 stateInfo["events"][blockedEvent] = blockedEvents[blockedEvent];
167 var state = new qx.util.fsm.State( "State_AwaitRpcResult", stateInfo);
170 /*** Transitions that use a PREDICATE appear first ***/
173 * Transition: AwaitRpcResult to GetAuthInfo
175 * Cause: "failed" (on RPC) where reason is PermissionDenied
177 var trans = new qx.util.fsm.Transition(
178 "Transition_AwaitRpcResult_to_Authenticate",
181 "State_Authenticate",
186 var error = event.getData(); // retrieve the JSON-RPC error
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))
199 // fall through to next transition, also for "failed"
208 var error = event.getData(); // retrieve the JSON-RPC error
209 var serverErrors = swat.main.AbstractModuleFsm.JsonRpc_ServerError;
213 case serverErrors.NotLoggedIn:
214 caption = "Please log in.";
217 case serverErrors.SessionExpired:
219 caption = "Session Expired. Please log in.";
223 // Retrieve the modal authentication window.
224 var loginWin = swat.main.Authenticate.getInstance();
226 // Ensure that it's saved in the current finite state machine
227 loginWin.addToFsm(fsm);
230 loginWin.setCaption(caption);
232 // Set the domain info
233 loginWin.setInfo(error.info);
235 // Open the authentication window
239 state.addTransition(trans);
242 * Transition: AwaitRpcResult to PopStack
244 * Cause: "failed" (on RPC)
246 var trans = new qx.util.fsm.Transition(
247 "Transition_AwaitRpcResult_to_PopStack_via_failed",
250 qx.util.fsm.FiniteStateMachine.StateChange.POP_STATE_STACK,
255 // Get the request object
256 var rpcRequest = _this.getCurrentRpcRequest();
258 // Generate the result for a completed request
259 rpcRequest.setUserData("result",
262 data : event.getData()
266 state.addTransition(trans);
268 /*** Remaining transitions are accessed via the jump table ***/
271 * Transition: AwaitRpcResult to AwaitRpcResult
273 * Cause: "execute" on swat.main.fsmUtils.abort_rpc
275 var trans = new qx.util.fsm.Transition(
276 "Transition_AwaitRpcResult_to_AwaitRpcResult_via_button_abort",
279 "State_AwaitRpcResult",
284 // Get the request object
285 var rpcRequest = _this.getCurrentRpcRequest();
287 // Issue an abort for the pending request
288 rpcRequest.request.abort();
291 state.addTransition(trans);
294 * Transition: AwaitRpcResult to PopStack
296 * Cause: "complete" (on RPC)
298 var trans = new qx.util.fsm.Transition(
299 "Transition_AwaitRpcResult_to_PopStack_via_complete",
302 qx.util.fsm.FiniteStateMachine.StateChange.POP_STATE_STACK,
307 // Get the request object
308 var rpcRequest = _this.getCurrentRpcRequest();
310 // Generate the result for a completed request
311 rpcRequest.setUserData("result",
314 data : event.getData()
318 state.addTransition(trans);
321 * State: Authenticate
324 * "execute" on login_button
326 var state = new qx.util.fsm.State(
327 "State_Authenticate",
332 // Retrieve the login window object
333 var win = fsm.getObject("login_window");
335 // Clear the password field
336 win.password.setValue("");
338 // If there's no value selected for domain...
339 if (win.domain.getValue() == null)
341 // ... then select the first value
342 win.domain.setSelected(win.domain.getList().getFirstChild());
345 // Retrieve the current RPC request
346 var rpcRequest = _this.getCurrentRpcRequest();
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")
354 // Yup. Display the result. Pop the old request off the stack
355 var loginRequest = _this.popRpcRequest();
357 // Retrieve the result
358 var result = loginRequest.getUserData("result");
361 if (result.type == "failed")
363 // Nope. Just reset the caption, and remain in this state.
364 win.setCaption("Login Failed. Try again.");
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);
373 // Reissue the original request. (We already popped the login
374 // request off the stack, so the current request is the original
376 var origRequest = _this.getCurrentRpcRequest();
378 // Retrieve the RPC object */
379 var rpc = fsm.getObject("swat.main.rpc");
381 // Set the service name
382 rpc.setServiceName(origRequest.service);
384 // Reissue the request
385 origRequest.request =
386 qx.io.remote.Rpc.prototype.callAsyncListeners.apply(
390 // Clear the password field, for good measure
391 win.password.setValue("");
393 // Close the login window
397 // Dispose of the login request
398 loginRequest.request.dispose();
399 loginRequest.request = null;
408 "Transition_Authenticate_to_AwaitRpcResult_via_button_login"
414 "Transition_Authenticate_to_AwaitRpcResult_via_complete"
421 * Transition: Authenticate to AwaitRpcResult
423 * Cause: "execute" on login_button
425 var trans = new qx.util.fsm.Transition(
426 "Transition_Authenticate_to_AwaitRpcResult_via_button_login",
429 "State_AwaitRpcResult",
434 // Retrieve the login window object
435 var win = fsm.getObject("login_window");
437 // Issue a Login call
442 win.userName.getValue(),
443 win.password.getValue(),
444 win.domain.getValue()
448 state.addTransition(trans);
451 * Transition: Authenticate to AwaitRpcResult
453 * Cause: "complete" on login_window
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.
459 var trans = new qx.util.fsm.Transition(
460 "Transition_Authenticate_to_AwaitRpcResult_via_complete",
463 "State_AwaitRpcResult"
465 state.addTransition(trans);
470 * Issue a remote procedure call.
472 * @param fsm {qx.util.fsm.FiniteStateMachine}
473 * The finite state machine issuing this remote procedure call.
475 * @param service {String}
476 * The name of the remote service which provides the specified method.
478 * @param method {String}
479 * The name of the method within the specified service.
481 * @param params {Array}
482 * The parameters to be passed to the specified method.
485 * The request object for the just-issued RPC request.
487 qx.Proto.callRpc = function(fsm, service, method, params)
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();
493 // Save the service name
494 rpcRequest.service = service;
496 // Copy the parameters; we'll prefix our copy with additional params
497 rpcRequest.params = params.slice(0);
499 // Prepend the method
500 rpcRequest.params.unshift(method);
502 // Prepend the flag indicating to coalesce failure events
503 rpcRequest.params.unshift(true);
505 // Retrieve the RPC object */
506 var rpc = fsm.getObject("swat.main.rpc");
508 // Set the service name
509 rpc.setServiceName(rpcRequest.service);
513 qx.io.remote.Rpc.prototype.callAsyncListeners.apply(rpc,
516 // Make the rpc request object available to the AwaitRpcResult state
517 this.pushRpcRequest(rpcRequest);
519 // Give 'em what they came for
525 * Push an RPC request onto the request stack.
527 * @param request {Object}
528 * The just-issued rpc request object
530 qx.Proto.pushRpcRequest = function(rpcRequest)
532 this._requests.push(rpcRequest);
537 * Retrieve the most recent RPC request from the request stack and pop the
541 * The rpc request object from the top of the request stack
543 qx.Proto.popRpcRequest = function()
545 if (this._requests.length == 0)
547 throw new Error("Attempt to pop an RPC request when list is empty.");
550 var rpcRequest = this._requests.pop();
556 * Retrieve the most recent RPC request.
559 * The rpc request object at the top of the request stack
561 qx.Proto.getCurrentRpcRequest = function()
563 if (this._requests.length == 0)
565 throw new Error("Attempt to retrieve an RPC request when list is empty.");
568 return this._requests[this._requests.length - 1];
573 * JSON-RPC error origins
575 qx.Class.JsonRpc_Origin =
585 * JSON-RPC Errors for origin == Server
587 qx.Class.JsonRpc_ServerError =
590 * Error code, value 0: Unknown Error
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.
598 * Error code, value 1: Illegal Service
600 * The service name contains illegal characters or is otherwise deemed
601 * unacceptable to the JSON-RPC server.
606 * Error code, value 2: Service Not Found
608 * The requested service does not exist at the JSON-RPC server.
613 * Error code, value 3: Class Not Found
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
621 ClassNotFound : 3, // not used in this implementation
624 * Error code, value 4: Method Not Found
626 * The method specified in the request is not found in the requested
632 * Error code, value 5: Parameter Mismatch
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.
638 * This error is also used to indicate an illegal parameter value, in server
641 ParameterMismatch : 5,
644 * Error code, value 6: Permission Denied
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.
652 PermissionDenied : 6,
654 /*** Errors generated by this server which are not qooxdoo-standard ***/
657 * Error code, value 1000: Unexpected Output
659 * The called method illegally generated output to the browser, which would
660 * have preceeded the JSON-RPC data.
662 UnexpectedOutput : 1000,
665 * Error code, value 1001: Resource Error
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.
670 ResourceError : 1001,
673 * Error code, value 1002: Not Logged In
675 * The user has logged out and must re-authenticate, or this is a brand new
676 * session and the user must log in.
682 * Error code, value 1003: Session Expired
684 * The session has expired and the user must re-authenticate.
687 SessionExpired : 1003,
690 * Error code, value 1004: Login Failed
692 * An attempt to log in failed.