]> git.evergreen-ils.org Git - OpenSRF.git/blob - src/javascript/opensrf_app_session.js
added support for multi-threaded client interactions. much like the java lib, each...
[OpenSRF.git] / src / javascript / opensrf_app_session.js
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.
5   */
6
7 /** The default wait time when a client calls recv. It
8   * may be overrided by passing in a value to the recv method
9   */
10 AppRequest.DEF_RECV_TIMEOUT = 10000;
11
12 /** Provide a pre-built AppSession object and the payload of the REQUEST
13   * message you wish to send
14   */
15 function AppRequest( session, payload ) {
16
17         /** Our controling session */
18         this.session = session;
19
20         /** This requests message thread trace */
21         this.thread_trace = null;
22
23         /** This request REQUEST payload */
24         this.payload = payload;
25
26         /** True if this request has completed is request cycle */
27         this.is_complete = false;
28
29         /** Stores responses received from requests */
30         this.recv_queue = new Array();
31 }
32
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;
38 }
39
40 /** When called on an AppRequest object, its status will be
41   * set to complete regardless of its previous status
42   */
43 AppRequest.prototype.set_complete = function() {
44         this.is_complete = true;
45 }
46
47 /** Returns the current thread trace */
48 AppRequest.prototype.get_thread_trace = function() {
49         return this.thread_trace;
50 }
51
52 /** Pushes some payload onto the recv queue */
53 AppRequest.prototype.push_queue = function( payload ) {
54         this.recv_queue.push( payload );
55 }
56
57 /** Returns the current payload of this request */
58 AppRequest.prototype.get_payload = function() {
59         return this.payload;
60 }
61
62 /** Removes this request from the our AppSession's request bucket 
63   * Call this method when you are finished with a particular request 
64   */
65 AppRequest.prototype.finish = function() {
66         this.session.remove_request( this );
67 }
68
69
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.
73   */
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 );
80 }
81
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.
86
87   * NOTE: timeout is in * milliseconds *
88   */
89
90 AppRequest.prototype.recv = function( /*int*/ timeout ) {
91
92
93         if( this.recv_queue.length > 0 ) {
94                 return this.recv_queue.shift();
95         }
96
97         if( this.is_complete ) { return null; }
98
99         if( timeout == null ) {
100                 timeout = AppRequest.DEF_RECV_TIMEOUT;
101         }
102
103         while( timeout > 0 ) {
104
105                 var start = new Date().getTime();
106                 this.session.queue_wait( timeout );
107
108                 if( this.recv_queue.length > 0 ) {
109                         return this.recv_queue.shift();
110                 }
111
112                 // shortcircuit the call if we're already complete
113                 if( this.complete() ) { return null; }
114
115                 new Logger().debug( "AppRequest looping in recv " 
116                                 + this.get_thread_trace() + " with timeout " + timeout, Logger.DEBUG );
117
118                 var end = new Date().getTime();
119                 timeout -= ( end - start );
120         }
121
122         return null;
123 }
124
125 /** Resend this AppRequest's REQUEST message, useful for recovering
126  * from disconnects, etc.
127  */
128 AppRequest.prototype.resend = function() {
129
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 );
133 }
134
135
136
137         
138 // -----------------------------------------------------------------------------
139 // -----------------------------------------------------------------------------
140 // AppSession code
141 // -----------------------------------------------------------------------------
142
143 /** Global cach of AppSession objects */
144 AppSession.session_cache = new Array();
145
146 // -----------------------------------------------------------------------------
147 // Session states
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;
157
158 /** Our connection with the outside world */
159 AppSession.transport_handle = null;
160
161
162 /** Returns the session with the given session id */
163 AppSession.find_session = function(session_id) {
164         return AppSession.session_cache[session_id];
165 }
166
167 /** Adds the given session to the global cache */
168 AppSession.push_session = function(session) {
169         AppSession.session_cache[session.get_session_id()] = session;
170 }
171
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;
175 }
176
177 /** Builds a new session.
178   * @param remote_service The remote service we want to make REQUEST's of
179   */
180 function AppSession( remote_service ) {
181
182         if (arguments.length == 3) {
183                 // it used to be AppSession( username, password, remote_service )
184                 remote_service = arguments[2];
185         }
186
187         /** Our logger object */
188         this.logger = new Logger();
189
190         random_num = Math.random() + "";
191         random_num.replace( '.', '' );
192
193         /** Our session id */
194         this.session_id = new Date().getTime() + "" + random_num;
195
196         //this.auth = new userAuth( username, password );
197
198         /** Our AppRequest queue */
199         this.request_queue = new Array();
200
201         /** Our connectivity state */
202         this.state = AppSession.DISCONNECTED;
203
204         var config = new Config();
205         
206         /** The remote ID of the service we are communicating with as retrieved 
207           * from the config file
208          */
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 );
212         }
213
214         /** The current remote ID of the service we are communicating with */
215         this.remote_id = this.orig_remote_id;
216
217         /** Our current request threadtrace, which is incremented with each 
218           * newly sent AppRequest */
219         this.thread_trace = 0;
220
221         /** Our queue of AppRequest objects */
222         this.req_queue = new Array();
223
224         /** Our queue of AppRequests that are awaiting a resend of their payload */
225         this.resend_queue = new Array();
226
227         // Build the transport_handle if if doesn't already exist
228         if( AppSession.transport_handle == null ) {
229                 this.build_transport();
230         }
231
232         AppSession.push_session( this );
233
234 }
235
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 
238   * config.
239   * * This should only be called by the AppSession constructor and only when
240   * the transport_handle is null.
241   */
242 AppSession.prototype.build_transport = function() {
243
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" );
248         }
249
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;
258
259         var eval_string = 
260                 "AppSession.transport_handle = new " + transport_impl + "( username, password, resource );";
261
262         eval( eval_string );
263         
264         if( AppSession.transport_handle == null ) {
265                 throw new oils_ex_config( "Transport implementation defined in config file is not valid" );
266         }
267
268         if( !AppSession.transport_handle.connect( server, port, timeout ) ) {
269                 throw new oils_ex_transport( "Connection attempt to remote service timed out" );
270         }
271
272         if( ! AppSession.transport_handle.connected() ) {
273                 throw new oils_ex_transport( "AppSession is unable to connect to the remote service" );
274         }
275 }
276
277
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;
282 }
283
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;
287 }
288
289 /** Returns the AppRequest with the given thread_trace */
290 AppSession.prototype.get_request = function( thread_trace ) {
291         return this.req_queue[thread_trace];
292 }
293
294
295 /** Returns this AppSession's session id */
296 AppSession.prototype.get_session_id = function() { 
297         return this.session_id; 
298 }
299
300 /** Resets the remote_id for the transport to the original remote_id retrieved
301   * from the config file
302   */
303 AppSession.prototype.reset_remote = function() { 
304         this.remote_id = this.orig_remote_id; 
305 }
306
307 /** Returns the current message thread trace */
308 AppSession.prototype.get_thread_trace = function() {
309         return this.thread_trace;
310 }
311
312 /** Sets the current thread trace */
313 AppSession.prototype.set_thread_trace = function( tt ) {
314         this.thread_trace = tt;
315 }
316
317 /** Returns the state that this session is in (i.e. CONNECTED) */
318 AppSession.prototype.get_state = function() {
319         return this.state;
320 }
321
322 /** Sets the session state.  The state should be one of the predefined 
323   * session AppSession session states.
324   */
325 AppSession.prototype.set_state = function(state) {
326         this.state = state;
327 }
328
329 /** Returns the current remote_id for this session */
330 AppSession.prototype.get_remote_id = function() {
331         return this.remote_id;
332 }
333
334 /** Sets the current remote_id for this session */
335 AppSession.prototype.set_remote_id = function( id ) {
336         this.remote_id = id;
337 }
338
339 /** Pushes an AppRequest object onto the resend queue */
340 AppSession.prototype.push_resend = function( app_request ) {
341         this.resend_queue.push( app_request );
342 }
343
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
347   */
348 AppSession.prototype.destroy = function() {
349
350         new Logger().debug( "Destroying AppSession: " + this.get_session_id(), Logger.DEBUG );
351
352         // disconnect from the remote service
353         if( this.get_state() != AppSession.DISCONNECTED ) {
354                 this.disconnect();
355         }
356         // remove us from the global cache
357         AppSession.delete_session( this.get_session_id() );
358
359         // Remove app request references
360         for( var index in this.req_queue ) {
361                 this.req_queue[index] = null;
362         }
363 }
364
365 /** This forces a resend of all AppRequests that are currently 
366   * in the resend queue
367   */
368 AppSession.prototype.flush_resend = function() {
369
370         if( this.resend_queue.length > 0 ) {
371                 new Logger().debug( "Resending " 
372                         + this.resend_queue.length + " messages", Logger.INFO );
373         }
374
375         var req = this.resend_queue.shift();
376
377         while( req != null ) {
378                 req.resend();
379                 req = this.resend_queue.shift();
380         }
381 }
382
383 /** This method tracks down the AppRequest with the given thread_trace and 
384   * pushes the payload into that AppRequest's recieve queue.
385   */
386 AppSession.prototype.push_queue = function( dom_payload, thread_trace ) {
387
388         var req = this.get_request( thread_trace );
389         if( ! req ) {
390                 new Logger().debug( "No AppRequest exists for TT: " + thread_trace, Logger.ERROR );
391                 return;
392         }
393         req.push_queue( dom_payload );
394 }
395
396
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.
400   */
401 AppSession.prototype.connect = function() {
402
403         if( this.get_state() == AppSession.CONNECTED ) { 
404                 return this;
405         }
406
407         var config = new Config();
408         var rem = config.get_value( "transport/connect_timeout" );
409         if( ! rem ) {
410                 throw new oils_ex_config( "Unable to retreive timeout value from config" );
411         }
412
413         var remaining = rem * 1000; // milliseconds
414
415         this.reset_remote();
416         this.set_state( AppSession.CONNECTING );
417         this.send( oilsMessage.CONNECT, 0, "" );
418
419         new Logger().debug( "CONNECTING with timeout: " + remaining, Logger.DEBUG );
420
421         while( this.get_state() != AppSession.CONNECTED && remaining > 0 ) {
422
423                 var starttime = new Date().getTime();
424                 this.queue_wait( remaining );
425                 var endtime = new Date().getTime();
426                 remaining -= (endtime - starttime);
427         }
428
429         if( ! this.get_state() == AppSession.CONNECTED ) {
430                 return null;
431         }
432
433         return this;
434 }
435
436 /** Disconnects from the remote service */
437 AppSession.prototype.disconnect = function() {
438
439         if( this.get_state() == AppSession.DISCONNECTED ) {
440                 return;
441         }
442
443         this.send( oilsMessage.DISCONNECT, this.get_thread_trace(), "" );
444         this.set_state( AppSession.DISCONNECTED );
445         this.reset_remote();
446 }
447
448
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.
453   *
454   * Note: msg_type and thread_trace must be provided.
455   */
456 AppSession.prototype.send = function( msg_type, thread_trace, payload ) {
457
458         if( msg_type == null || thread_trace == null ) {
459                 throw new oils_ex_args( "Not enough arguments provided to AppSession.send method" );
460         }
461
462         // go ahead and make sure there's nothing new to process
463         this.queue_wait(0);
464
465         var msg;
466         msg = new oilsMessage( msg_type, AppSession.PROTOCOL );
467
468         msg.setThreadTrace( thread_trace );
469
470         if( msg_type == oilsMessage.REQUEST ) {
471                 if( ! payload ) {
472                         throw new oils_ex_args( "No payload provided for REQUEST message in AppSession.send" );
473                 }
474                 msg.add( payload );
475         }
476
477
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" );
483                 }
484         }
485
486         this.logger.debug( "AppSession sending tt: " 
487                         + thread_trace + " to " + this.get_remote_id() 
488                         + " " +  msg_type , Logger.INFO );
489
490         AppSession.transport_handle.send( this.get_remote_id(), this.get_session_id(), msg.toString(true) );
491
492 }
493
494
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
500   */
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 );
505         this.flush_resend();
506 }
507
508
509