1 /** @file oils_app_session.js
2 * @brief AppRequest and AppSession.
3 * The AppSession class does most of the communication work and the AppRequest
4 * contains the top level client API.
7 /** The default wait time when a client calls recv. It
8 * may be overrided by passing in a value to the recv method
10 AppRequest.DEF_RECV_TIMEOUT = 10000;
12 /** Provide a pre-built AppSession object and the payload of the REQUEST
13 * message you wish to send
15 function AppRequest( session, payload ) {
17 /** Our controling session */
18 this.session = session;
20 /** This requests message thread trace */
21 this.thread_trace = null;
23 /** This request REQUEST payload */
24 this.payload = payload;
26 /** True if this request has completed is request cycle */
27 this.is_complete = false;
29 /** Stores responses received from requests */
30 this.recv_queue = new Array();
33 /** returns true if this AppRequest has completed its request cycle. */
34 AppRequest.prototype.complete = function() {
35 if( this.is_complete ) { return true; }
36 this.session.queue_wait(0);
37 return this.is_complete;
40 /** When called on an AppRequest object, its status will be
41 * set to complete regardless of its previous status
43 AppRequest.prototype.set_complete = function() {
44 this.is_complete = true;
47 /** Returns the current thread trace */
48 AppRequest.prototype.get_thread_trace = function() {
49 return this.thread_trace;
52 /** Pushes some payload onto the recv queue */
53 AppRequest.prototype.push_queue = function( payload ) {
54 this.recv_queue.push( payload );
57 /** Returns the current payload of this request */
58 AppRequest.prototype.get_payload = function() {
62 /** Removes this request from the our AppSession's request bucket
63 * Call this method when you are finished with a particular request
65 AppRequest.prototype.finish = function() {
66 this.session.remove_request( this );
70 /** Retrieves the current thread trace from the associated AppSession object,
71 * increments that session's thread trace, sets this AppRequest's thread trace
72 * to the new value. The request is then sent.
74 AppRequest.prototype.make_request = function() {
75 var tt = this.session.get_thread_trace();
76 this.session.set_thread_trace( ++tt );
77 this.thread_trace = tt;
78 this.session.add_request( this );
79 this.session.send( oilsMessage.REQUEST, tt, this.payload );
82 /** Checks the receive queue for message payloads. If any are found, the first
83 * is returned. Otherwise, this method will wait at most timeout seconds for
84 * a message to appear in the receive queue. Once it arrives it is returned.
85 * If no messages arrive in the timeout provided, null is returned.
87 * NOTE: timeout is in * milliseconds *
90 AppRequest.prototype.recv = function( /*int*/ timeout ) {
93 if( this.recv_queue.length > 0 ) {
94 return this.recv_queue.shift();
97 if( this.is_complete ) { return null; }
99 if( timeout == null ) {
100 timeout = AppRequest.DEF_RECV_TIMEOUT;
103 while( timeout > 0 ) {
105 var start = new Date().getTime();
106 this.session.queue_wait( timeout );
108 if( this.recv_queue.length > 0 ) {
109 return this.recv_queue.shift();
112 // shortcircuit the call if we're already complete
113 if( this.complete() ) { return null; }
115 new Logger().debug( "AppRequest looping in recv "
116 + this.get_thread_trace() + " with timeout " + timeout, Logger.DEBUG );
118 var end = new Date().getTime();
119 timeout -= ( end - start );
125 /** Resend this AppRequest's REQUEST message, useful for recovering
126 * from disconnects, etc.
128 AppRequest.prototype.resend = function() {
130 new Logger().debug( "Resending msg with thread trace: "
131 + this.get_thread_trace(), Logger.DEBUG );
132 this.session.send( oilsMessage.REQUEST, this.get_thread_trace(), this.payload );
138 // -----------------------------------------------------------------------------
139 // -----------------------------------------------------------------------------
141 // -----------------------------------------------------------------------------
143 /** Global cach of AppSession objects */
144 AppSession.session_cache = new Array();
146 // -----------------------------------------------------------------------------
148 // -----------------------------------------------------------------------------
149 /** True when we're attempting to connect to a remte service */
150 AppSession.CONNECTING = 0;
151 /** True when we have successfully connected to a remote service */
152 AppSession.CONNECTED = 1;
153 /** True when we have been disconnected from a remote service */
154 AppSession.DISCONNECTED = 2;
155 /** The current default method protocol */
156 AppSession.PROTOCOL = 1;
158 /** Our connection with the outside world */
159 AppSession.transport_handle = null;
162 /** Returns the session with the given session id */
163 AppSession.find_session = function(session_id) {
164 return AppSession.session_cache[session_id];
167 /** Adds the given session to the global cache */
168 AppSession.push_session = function(session) {
169 AppSession.session_cache[session.get_session_id()] = session;
172 /** Deletes the session with the given session id from the global cache */
173 AppSession.delete_session = function(session_id) {
174 AppSession.session_cache[session_id] = null;
177 /** Builds a new session.
178 * @param remote_service The remote service we want to make REQUEST's of
180 function AppSession( remote_service ) {
182 if (arguments.length == 3) {
183 // it used to be AppSession( username, password, remote_service )
184 remote_service = arguments[2];
187 /** Our logger object */
188 this.logger = new Logger();
190 random_num = Math.random() + "";
191 random_num.replace( '.', '' );
193 /** Our session id */
194 this.session_id = new Date().getTime() + "" + random_num;
196 //this.auth = new userAuth( username, password );
198 /** Our AppRequest queue */
199 this.request_queue = new Array();
201 /** Our connectivity state */
202 this.state = AppSession.DISCONNECTED;
204 var config = new Config();
206 /** The remote ID of the service we are communicating with as retrieved
207 * from the config file
209 this.orig_remote_id = config.get_value( "remote_service/" + remote_service );
210 if( ! this.orig_remote_id ) {
211 throw new oils_ex_config( "No remote service id for: " + remote_service );
214 /** The current remote ID of the service we are communicating with */
215 this.remote_id = this.orig_remote_id;
217 /** Our current request threadtrace, which is incremented with each
218 * newly sent AppRequest */
219 this.thread_trace = 0;
221 /** Our queue of AppRequest objects */
222 this.req_queue = new Array();
224 /** Our queue of AppRequests that are awaiting a resend of their payload */
225 this.resend_queue = new Array();
227 // Build the transport_handle if if doesn't already exist
228 if( AppSession.transport_handle == null ) {
229 this.build_transport();
232 AppSession.push_session( this );
236 /** The transport implementation is loaded from the config file and magically
237 * eval'ed into an object. All connection settings come from the client
239 * * This should only be called by the AppSession constructor and only when
240 * the transport_handle is null.
242 AppSession.prototype.build_transport = function() {
244 var config = new Config();
245 var transport_impl = config.get_value( "transport/transport_impl" );
246 if( ! transport_impl ) {
247 throw new oils_ex_config( "No transport implementation defined in config file" );
250 var username = config.get_value( "transport/username" );
251 var password = config.get_value( "transport/password" );
252 var this_host = config.get_value( "system/hostname" );
253 var resource = this_host + "_" + new Date().getTime();
254 var server = config.get_value( "transport/primary" );
255 var port = config.get_value( "transport/port" );
256 var tim = config.get_value( "transport/connect_timeout" );
257 var timeout = tim * 1000;
260 "AppSession.transport_handle = new " + transport_impl + "( username, password, resource );";
264 if( AppSession.transport_handle == null ) {
265 throw new oils_ex_config( "Transport implementation defined in config file is not valid" );
268 if( !AppSession.transport_handle.connect( server, port, timeout ) ) {
269 throw new oils_ex_transport( "Connection attempt to remote service timed out" );
272 if( ! AppSession.transport_handle.connected() ) {
273 throw new oils_ex_transport( "AppSession is unable to connect to the remote service" );
278 /** Adds the given AppRequest object to this AppSession's request queue */
279 AppSession.prototype.add_request = function( req_obj ) {
280 new Logger().debug( "Adding AppRequest: " + req_obj.get_thread_trace(), Logger.DEBUG );
281 this.req_queue[req_obj.get_thread_trace()] = req_obj;
284 /** Removes the AppRequest object from this AppSession's request queue */
285 AppSession.prototype.remove_request = function( req_obj ) {
286 this.req_queue[req_obj.get_thread_trace()] = null;
289 /** Returns the AppRequest with the given thread_trace */
290 AppSession.prototype.get_request = function( thread_trace ) {
291 return this.req_queue[thread_trace];
295 /** Returns this AppSession's session id */
296 AppSession.prototype.get_session_id = function() {
297 return this.session_id;
300 /** Resets the remote_id for the transport to the original remote_id retrieved
301 * from the config file
303 AppSession.prototype.reset_remote = function() {
304 this.remote_id = this.orig_remote_id;
307 /** Returns the current message thread trace */
308 AppSession.prototype.get_thread_trace = function() {
309 return this.thread_trace;
312 /** Sets the current thread trace */
313 AppSession.prototype.set_thread_trace = function( tt ) {
314 this.thread_trace = tt;
317 /** Returns the state that this session is in (i.e. CONNECTED) */
318 AppSession.prototype.get_state = function() {
322 /** Sets the session state. The state should be one of the predefined
323 * session AppSession session states.
325 AppSession.prototype.set_state = function(state) {
329 /** Returns the current remote_id for this session */
330 AppSession.prototype.get_remote_id = function() {
331 return this.remote_id;
334 /** Sets the current remote_id for this session */
335 AppSession.prototype.set_remote_id = function( id ) {
339 /** Pushes an AppRequest object onto the resend queue */
340 AppSession.prototype.push_resend = function( app_request ) {
341 this.resend_queue.push( app_request );
344 /** Destroys the current session. This will disconnect from the
345 * remote service, remove all AppRequests from the request
346 * queue, and finally remove this session from the global cache
348 AppSession.prototype.destroy = function() {
350 new Logger().debug( "Destroying AppSession: " + this.get_session_id(), Logger.DEBUG );
352 // disconnect from the remote service
353 if( this.get_state() != AppSession.DISCONNECTED ) {
356 // remove us from the global cache
357 AppSession.delete_session( this.get_session_id() );
359 // Remove app request references
360 for( var index in this.req_queue ) {
361 this.req_queue[index] = null;
365 /** This forces a resend of all AppRequests that are currently
366 * in the resend queue
368 AppSession.prototype.flush_resend = function() {
370 if( this.resend_queue.length > 0 ) {
371 new Logger().debug( "Resending "
372 + this.resend_queue.length + " messages", Logger.INFO );
375 var req = this.resend_queue.shift();
377 while( req != null ) {
379 req = this.resend_queue.shift();
383 /** This method tracks down the AppRequest with the given thread_trace and
384 * pushes the payload into that AppRequest's recieve queue.
386 AppSession.prototype.push_queue = function( dom_payload, thread_trace ) {
388 var req = this.get_request( thread_trace );
390 new Logger().debug( "No AppRequest exists for TT: " + thread_trace, Logger.ERROR );
393 req.push_queue( dom_payload );
397 /** Connect to the remote service. The connect timeout is read from the config.
398 * This method returns null if the connection fails. It returns a reference
399 * to this AppSession object otherwise.
401 AppSession.prototype.connect = function() {
403 if( this.get_state() == AppSession.CONNECTED ) {
407 var config = new Config();
408 var rem = config.get_value( "transport/connect_timeout" );
410 throw new oils_ex_config( "Unable to retreive timeout value from config" );
413 var remaining = rem * 1000; // milliseconds
416 this.set_state( AppSession.CONNECTING );
417 this.send( oilsMessage.CONNECT, 0, "" );
419 new Logger().debug( "CONNECTING with timeout: " + remaining, Logger.DEBUG );
421 while( this.get_state() != AppSession.CONNECTED && remaining > 0 ) {
423 var starttime = new Date().getTime();
424 this.queue_wait( remaining );
425 var endtime = new Date().getTime();
426 remaining -= (endtime - starttime);
429 if( ! this.get_state() == AppSession.CONNECTED ) {
436 /** Disconnects from the remote service */
437 AppSession.prototype.disconnect = function() {
439 if( this.get_state() == AppSession.DISCONNECTED ) {
443 this.send( oilsMessage.DISCONNECT, this.get_thread_trace(), "" );
444 this.set_state( AppSession.DISCONNECTED );
449 /** Builds a new message with the given type and thread_trace. If the message
450 * is a REQUEST, then the payload is added as well.
451 * This method will verify that the session is in the CONNECTED state before
452 * sending any REQUEST's by attempting to do a connect.
454 * Note: msg_type and thread_trace must be provided.
456 AppSession.prototype.send = function( msg_type, thread_trace, payload ) {
458 if( msg_type == null || thread_trace == null ) {
459 throw new oils_ex_args( "Not enough arguments provided to AppSession.send method" );
462 // go ahead and make sure there's nothing new to process
466 msg = new oilsMessage( msg_type, AppSession.PROTOCOL );
468 msg.setThreadTrace( thread_trace );
470 if( msg_type == oilsMessage.REQUEST ) {
472 throw new oils_ex_args( "No payload provided for REQUEST message in AppSession.send" );
478 // Make sure we're connected
479 if( (msg_type != oilsMessage.DISCONNECT) && (msg_type != oilsMessage.CONNECT) &&
480 (this.get_state() != AppSession.CONNECTED) ) {
481 if( ! this.connect() ) {
482 throw new oils_ex_session( this.get_session_id() + " | Unable to connect to remote service after redirect" );
486 this.logger.debug( "AppSession sending tt: "
487 + thread_trace + " to " + this.get_remote_id()
488 + " " + msg_type , Logger.INFO );
490 AppSession.transport_handle.send( this.get_remote_id(), this.get_session_id(), msg.toString(true) );
495 /** Waits up to 'timeout' milliseconds for some data to arrive.
496 * Any data that arrives will be process according to its
497 * payload and message type. This method will return after
498 * any data has arrived.
499 * @param timeout How many milliseconds to wait or data to arrive
501 AppSession.prototype.queue_wait = function( timeout ) {
502 this.flush_resend(); // necessary if running parallel sessions
503 new Logger().debug( "In queue_wait " + timeout, Logger.DEBUG );
504 var tran_msg = AppSession.transport_handle.process_msg( timeout );