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.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( username, password, remote_service ) {
183 /** Our logger object */
184 this.logger = new Logger();
186 random_num = Math.random() + "";
187 random_num.replace( '.', '' );
189 /** Our session id */
190 this.session_id = new Date().getTime() + "" + random_num;
192 this.auth = new userAuth( username, password );
194 /** Our AppRequest queue */
195 this.request_queue = new Array();
197 /** Our connectivity state */
198 this.state = AppSession.DISCONNECTED;
200 var config = new Config();
202 /** The remote ID of the service we are communicating with as retrieved
203 * from the config file
205 this.orig_remote_id = config.get_value( "remote_service/" + remote_service );
206 if( ! this.orig_remote_id ) {
207 throw new oils_ex_config( "No remote service id for: " + remote_service );
210 /** The current remote ID of the service we are communicating with */
211 this.remote_id = this.orig_remote_id;
213 /** Our current request threadtrace, which is incremented with each
214 * newly sent AppRequest */
215 this.thread_trace = 0;
217 /** Our queue of AppRequest objects */
218 this.req_queue = new Array();
220 /** Our queue of AppRequests that are awaiting a resend of their payload */
221 this.resend_queue = new Array();
223 // Build the transport_handle if if doesn't already exist
224 if( AppSession.transport_handle == null ) {
225 this.build_transport();
228 AppSession.push_session( this );
232 /** The transport implementation is loaded from the config file and magically
233 * eval'ed into an object. All connection settings come from the client
235 * * This should only be called by the AppSession constructor and only when
236 * the transport_handle is null.
238 AppSession.prototype.build_transport = function() {
240 var config = new Config();
241 var transport_impl = config.get_value( "transport/transport_impl" );
242 if( ! transport_impl ) {
243 throw new oils_ex_config( "No transport implementation defined in config file" );
246 var username = config.get_value( "transport/username" );
247 var password = config.get_value( "transport/password" );
248 var this_host = config.get_value( "system/hostname" );
249 var resource = this_host + "_" + new Date().getTime();
250 var server = config.get_value( "transport/primary" );
251 var port = config.get_value( "transport/port" );
252 var tim = config.get_value( "transport/connect_timeout" );
253 var timeout = tim * 1000;
256 "AppSession.transport_handle = new " + transport_impl + "( username, password, resource );";
260 if( AppSession.transport_handle == null ) {
261 throw new oils_ex_config( "Transport implementation defined in config file is not valid" );
264 if( !AppSession.transport_handle.connect( server, port, timeout ) ) {
265 throw new oils_ex_transport( "Connection attempt to remote service timed out" );
268 if( ! AppSession.transport_handle.connected() ) {
269 throw new oils_ex_transport( "AppSession is unable to connect to the remote service" );
274 /** Adds the given AppRequest object to this AppSession's request queue */
275 AppSession.prototype.add_request = function( req_obj ) {
276 new Logger().debug( "Adding AppRequest: " + req_obj.get_thread_trace(), Logger.DEBUG );
277 this.req_queue[req_obj.get_thread_trace()] = req_obj;
280 /** Removes the AppRequest object from this AppSession's request queue */
281 AppSession.prototype.remove_request = function( req_obj ) {
282 this.req_queue[req_obj.get_thread_trace()] = null;
285 /** Returns the AppRequest with the given thread_trace */
286 AppSession.prototype.get_request = function( thread_trace ) {
287 return this.req_queue[thread_trace];
291 /** Returns this AppSession's session id */
292 AppSession.prototype.get_session_id = function() {
293 return this.session_id;
296 /** Resets the remote_id for the transport to the original remote_id retrieved
297 * from the config file
299 AppSession.prototype.reset_remote = function() {
300 this.remote_id = this.orig_remote_id;
303 /** Returns the current message thread trace */
304 AppSession.prototype.get_thread_trace = function() {
305 return this.thread_trace;
308 /** Sets the current thread trace */
309 AppSession.prototype.set_thread_trace = function( tt ) {
310 this.thread_trace = tt;
313 /** Returns the state that this session is in (i.e. CONNECTED) */
314 AppSession.prototype.get_state = function() {
318 /** Sets the session state. The state should be one of the predefined
319 * session AppSession session states.
321 AppSession.prototype.set_state = function(state) {
325 /** Returns the current remote_id for this session */
326 AppSession.prototype.get_remote_id = function() {
327 return this.remote_id;
330 /** Sets the current remote_id for this session */
331 AppSession.prototype.set_remote_id = function( id ) {
335 /** Pushes an AppRequest object onto the resend queue */
336 AppSession.prototype.push_resend = function( app_request ) {
337 this.resend_queue.push( app_request );
340 /** Destroys the current session. This will disconnect from the
341 * remote service, remove all AppRequests from the request
342 * queue, and finally remove this session from the global cache
344 AppSession.prototype.destroy = function() {
346 new Logger().debug( "Destroying AppSession: " + this.get_session_id(), Logger.DEBUG );
348 // disconnect from the remote service
349 if( this.get_state() != AppSession.DISCONNECTED ) {
352 // remove us from the global cache
353 AppSession.delete_session( this.get_session_id() );
355 // Remove app request references
356 for( var index in this.req_queue ) {
357 this.req_queue[index] = null;
361 /** This forces a resend of all AppRequests that are currently
362 * in the resend queue
364 AppSession.prototype.flush_resend = function() {
366 if( this.resend_queue.length > 0 ) {
367 new Logger().debug( "Resending "
368 + this.resend_queue.length + " messages", Logger.INFO );
371 var req = this.resend_queue.shift();
373 while( req != null ) {
375 req = this.resend_queue.shift();
379 /** This method tracks down the AppRequest with the given thread_trace and
380 * pushes the payload into that AppRequest's recieve queue.
382 AppSession.prototype.push_queue = function( dom_payload, thread_trace ) {
384 var req = this.get_request( thread_trace );
386 new Logger().debug( "No AppRequest exists for TT: " + thread_trace, Logger.ERROR );
389 req.push_queue( dom_payload );
393 /** Connect to the remote service. The connect timeout is read from the config.
394 * This method returns null if the connection fails. It returns a reference
395 * to this AppSession object otherwise.
397 AppSession.prototype.connect = function() {
399 if( this.get_state() == AppSession.CONNECTED ) {
403 var config = new Config();
404 var rem = config.get_value( "transport/connect_timeout" );
406 throw new oils_ex_config( "Unable to retreive timeout value from config" );
409 var remaining = rem * 1000; // milliseconds
412 this.set_state( AppSession.CONNECTING );
413 this.send( oilsMessage.CONNECT, 0, "" );
415 new Logger().debug( "CONNECTING with timeout: " + remaining, Logger.DEBUG );
417 while( this.get_state() != AppSession.CONNECTED && remaining > 0 ) {
419 var starttime = new Date().getTime();
420 this.queue_wait( remaining );
421 var endtime = new Date().getTime();
422 remaining -= (endtime - starttime);
425 if( ! this.get_state() == AppSession.CONNECTED ) {
432 /** Disconnects from the remote service */
433 AppSession.prototype.disconnect = function() {
435 if( this.get_state() == AppSession.DISCONNECTED ) {
439 this.send( oilsMessage.DISCONNECT, this.get_thread_trace(), "" );
440 this.set_state( AppSession.DISCONNECTED );
445 /** Builds a new message with the given type and thread_trace. If the message
446 * is a REQUEST, then the payload is added as well.
447 * This method will verify that the session is in the CONNECTED state before
448 * sending any REQUEST's by attempting to do a connect.
450 * Note: msg_type and thread_trace must be provided.
452 AppSession.prototype.send = function( msg_type, thread_trace, payload ) {
454 if( msg_type == null || thread_trace == null ) {
455 throw new oils_ex_args( "Not enough arguments provided to AppSession.send method" );
458 // go ahead and make sure there's nothing new to process
462 if( msg_type == oilsMessage.CONNECT ) {
463 msg = new oilsMessage( msg_type, AppSession.PROTOCOL, this.auth );
465 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 );