]> git.evergreen-ils.org Git - OpenSRF.git/blob - src/libopensrf/osrf_app_session.c
c7b80d30a83dfa1f5ae4fc4e0a382b29752df82c
[OpenSRF.git] / src / libopensrf / osrf_app_session.c
1 /**
2         @file osrf_app_session.c
3         @brief Implementation of osrfAppSession.
4 */
5
6 #include <time.h>
7 #include "opensrf/osrf_app_session.h"
8 #include "opensrf/osrf_stack.h"
9
10 static char* current_ingress = NULL;
11
12 struct osrf_app_request_struct {
13         /** The controlling session. */
14         struct osrf_app_session_struct* session;
15
16         /** Request id.  It is the same as the thread_trace of the REQUEST message
17                 for which it was created.
18         */
19         int request_id;
20         /** True if we have received a 'request complete' message from our request. */
21         int complete;
22         /** The original REQUEST message payload. */
23         osrfMessage* payload;
24         /** Linked list of responses to the request. */
25         osrfMessage* result;
26
27     /** Buffer used to collect partial response messages */
28     growing_buffer* part_response_buffer;
29
30         /** Boolean; if true, then a call that is waiting on a response will reset the
31         timeout and set this variable back to false. */
32         int reset_timeout;
33         /** Linkage pointers for a linked list.  We maintain a hash table of pending requests,
34             and each slot of the hash table is a doubly linked list. */
35         osrfAppRequest* next;
36         osrfAppRequest* prev;
37 };
38
39 static inline unsigned int request_id_hash( int req_id );
40 static osrfAppRequest* find_app_request( const osrfAppSession* session, int req_id );
41 static void add_app_request( osrfAppSession* session, osrfAppRequest* req );
42
43 /* Send the given message */
44 static int _osrf_app_session_send( osrfAppSession*, osrfMessage* msg );
45
46 static int osrfAppSessionMakeLocaleRequest(
47                 osrfAppSession* session, const jsonObject* params, const char* method_name,
48                 int protocol, osrfStringArray* param_strings, char* locale );
49
50 /** @brief The global session cache.
51
52         Key: session_id.  Data: osrfAppSession.
53 */
54 static osrfHash* osrfAppSessionCache = NULL;
55
56 // --------------------------------------------------------------------------
57 // Request API
58 // --------------------------------------------------------------------------
59
60 /**
61         @brief Create a new osrfAppRequest.
62         @param session Pointer to the osrfAppSession that will own the new osrfAppRequest.
63         @param msg Pointer to the osrfMessage representing the request.
64         @return Pointer to the new osrfAppRequest.
65
66         The calling code is responsible for freeing the osrfAppRequest by calling
67         _osrf_app_request_free().
68 */
69 static osrfAppRequest* _osrf_app_request_init(
70                 osrfAppSession* session, osrfMessage* msg ) {
71
72         osrfAppRequest* req = safe_malloc(sizeof(osrfAppRequest));
73
74         req->session        = session;
75         req->request_id     = msg->thread_trace;
76         req->complete       = 0;
77         req->payload        = msg;
78         req->result         = NULL;
79         req->reset_timeout  = 0;
80         req->next           = NULL;
81         req->prev           = NULL;
82         req->part_response_buffer = NULL;
83
84         return req;
85 }
86
87
88 /**
89         @brief Free an osrfAppRequest and everything it owns.
90         @param req Pointer to an osrfAppRequest.
91 */
92 static void _osrf_app_request_free( osrfAppRequest * req ) {
93         if( req ) {
94                 if( req->payload )
95                         osrfMessageFree( req->payload );
96
97                 /* Free the messages in the result queue */
98                 osrfMessage* next_msg;
99                 while( req->result ) {
100                         next_msg = req->result->next;
101                         osrfMessageFree( req->result );
102                         req->result = next_msg;
103                 }
104
105         if (req->part_response_buffer)
106             buffer_free(req->part_response_buffer);
107
108                 free( req );
109         }
110 }
111
112 /**
113         @brief Append a new message to the list of responses to a request.
114         @param req Pointer to the osrfAppRequest for the original REQUEST message.
115         @param result Pointer to an osrfMessage received in response to the request.
116
117         For each osrfAppRequest we maintain a linked list of response messages, and traverse
118         it to find the end.
119 */
120 static void _osrf_app_request_push_queue( osrfAppRequest* req, osrfMessage* result ){
121         if(req == NULL || result == NULL)
122                 return;
123
124     if (result->status_code == OSRF_STATUS_PARTIAL) {
125         osrfLogDebug(OSRF_LOG_MARK, "received partial message response");
126
127         if (!req->part_response_buffer) {
128             // assume the max_chunk_size of the server matches ours for
129             // buffer initialization,  since the setting will usually be 
130             // a site-wide value.
131                 req->part_response_buffer = buffer_init(OSRF_MSG_CHUNK_SIZE + 1);
132         }
133
134         const char* partial = jsonObjectGetString(result->_result_content);
135
136         if (partial != NULL) {
137             osrfLogDebug(OSRF_LOG_MARK, 
138                 "adding %d bytes to response buffer", strlen(partial));
139         
140             // add the partial contents of the message to the buffer
141             buffer_add(req->part_response_buffer, partial);
142         }
143
144         // all done.  req and result are freed by the caller
145         return;
146
147     } else if (result->status_code == OSRF_STATUS_NOCONTENT) {
148         if (req->part_response_buffer && req->part_response_buffer->n_used) {
149
150             // part_response_buffer contains a stitched-together JSON string
151             osrfLogDebug(OSRF_LOG_MARK, 
152                 "partial response complete, parsing %d bytes", 
153                 req->part_response_buffer->n_used);
154
155             // coerce the partial-complete response into a standard RESULT.
156             osrf_message_set_status_info(result, NULL, "OK", OSRF_STATUS_OK);
157
158             // use the stitched-together JSON string as the result conten
159             osrf_message_set_result_content(
160                 result, req->part_response_buffer->buf);
161
162             // free string, keep the buffer
163             buffer_reset(req->part_response_buffer); 
164
165         } else {
166             osrfLogDebug(OSRF_LOG_MARK, 
167                 "Received OSRF_STATUS_NOCONTENT with no preceeding content");
168             return;
169         }
170     }
171
172         osrfLogDebug( OSRF_LOG_MARK, "App Session pushing request [%d] onto request queue",
173                         result->thread_trace );
174
175         if(req->result == NULL) {
176                 req->result = result;   // Add the first node
177
178         } else {
179
180                 // Find the last node in the list, and append the new node to it
181                 osrfMessage* ptr = req->result;
182                 osrfMessage* ptr2 = req->result->next;
183                 while( ptr2 ) {
184                         ptr = ptr2;
185                         ptr2 = ptr2->next;
186                 }
187                 ptr->next = result;
188         }
189 }
190
191 /**
192         @brief Remove an osrfAppRequest (identified by request_id) from an osrfAppSession.
193         @param session Pointer to the osrfAppSession that owns the osrfAppRequest.
194         @param req_id request_id of the osrfAppRequest to be removed.
195 */
196 void osrf_app_session_request_finish( osrfAppSession* session, int req_id ) {
197
198         if( session ) {
199                 // Search the hash table for the request in question
200                 unsigned int index = request_id_hash( req_id );
201                 osrfAppRequest* old_req = session->request_hash[ index ];
202                 while( old_req ) {
203                         if( old_req->request_id == req_id )
204                                 break;
205                         else
206                                 old_req = old_req->next;
207                 }
208
209                 if( old_req ) {
210                         // Remove the request from the doubly linked list
211                         if( old_req->prev )
212                                 old_req->prev->next = old_req->next;
213                         else
214                                 session->request_hash[ index ] = old_req->next;
215
216                         if( old_req->next )
217                                 old_req->next->prev = old_req->prev;
218
219                         _osrf_app_request_free( old_req );
220                 }
221         }
222 }
223
224 /**
225         @brief Derive a hash key from a request id.
226         @param req_id The request id.
227         @return The corresponding hash key; an index into request_hash[].
228
229         If OSRF_REQUEST_HASH_SIZE is a power of two, then this calculation should
230         reduce to a binary AND.
231 */
232 static inline unsigned int request_id_hash( int req_id ) {
233         return ((unsigned int) req_id ) % OSRF_REQUEST_HASH_SIZE;
234 }
235
236 /**
237         @brief Search for an osrfAppRequest in the hash table, given a request id.
238         @param session Pointer to the relevant osrfAppSession.
239         @param req_id The request_id of the osrfAppRequest being sought.
240         @return A pointer to the osrfAppRequest if found, or NULL if not.
241 */
242 static osrfAppRequest* find_app_request( const osrfAppSession* session, int req_id ) {
243
244         osrfAppRequest* req = session->request_hash[ request_id_hash( req_id) ];
245         while( req ) {
246                 if( req->request_id == req_id )
247                         break;
248                 else
249                         req = req->next;
250         }
251
252         return req;
253 }
254
255 /**
256         @brief Add an osrfAppRequest to the hash table of a given osrfAppSession.
257         @param session Pointer to the session to which the request belongs.
258         @param req Pointer to the osrfAppRequest to be stored.
259
260         Find the right spot in the hash table; then add the request to the linked list at that
261         spot.  We just add it to the head of the list, without trying to maintain any particular
262         ordering.
263 */
264 static void add_app_request( osrfAppSession* session, osrfAppRequest* req ) {
265         if( session && req ) {
266                 unsigned int index = request_id_hash( req->request_id );
267                 req->next = session->request_hash[ index ];
268                 req->prev = NULL;
269                 session->request_hash[ index ] = req;
270         }
271 }
272
273 /**
274         @brief Request a reset of the timeout period for a request.
275         @param session Pointer to the relevant osrfAppSession.
276         @param req_id Request ID of the request whose timeout is to be reset.
277
278         This happens when a client receives a STATUS message with a status code
279         OSRF_STATUS_CONTINUE; in effect the server is asking for more time.
280
281         The request to be reset is identified by the combination of session and request id.
282 */
283 void osrf_app_session_request_reset_timeout( osrfAppSession* session, int req_id ) {
284         if(session == NULL)
285                 return;
286         osrfLogDebug( OSRF_LOG_MARK, "Resetting request timeout %d", req_id );
287         osrfAppRequest* req = find_app_request( session, req_id );
288         if( req )
289                 req->reset_timeout = 1;
290 }
291
292 /**
293         @brief Fetch the next response message to a given previous request, subject to a timeout.
294         @param req Pointer to the osrfAppRequest representing the request.
295         @param timeout Maxmimum time to wait, in seconds.
296
297         @return Pointer to the next osrfMessage for this request, if one is available, or if it
298         becomes available before the end of the timeout; otherwise NULL;
299
300         If there is already a message available in the input queue for this request, dequeue and
301         return it immediately.  Otherwise wait up to timeout seconds until you either get an
302         input message for the specified request, run out of time, or encounter an error.
303
304         If the only message we receive for this request is a STATUS message with a status code
305         OSRF_STATUS_COMPLETE, then return NULL.  That means that the server has nothing further
306         to send in response to this request.
307
308         You may also receive other messages for other requests, and other sessions.  These other
309         messages will be wholly or partially processed behind the scenes while you wait for the
310         one you want.
311 */
312 static osrfMessage* _osrf_app_request_recv( osrfAppRequest* req, int timeout ) {
313
314         if(req == NULL) return NULL;
315
316         if( req->result != NULL ) {
317                 /* Dequeue the next message in the list */
318                 osrfMessage* tmp_msg = req->result;
319                 req->result = req->result->next;
320                 return tmp_msg;
321         }
322
323         time_t start = time(NULL);
324         time_t remaining = (time_t) timeout;
325
326         // Wait repeatedly for input messages until you either receive one for the request
327         // you're interested in, run out of time, or encounter an error.
328         // Wait repeatedly because you may also receive messages for other requests, or for
329         // other sessions, and process them behind the scenes. These are not the messages
330         // you're looking for.
331         while( remaining >= 0 ) {
332                 /* tell the session to wait for stuff */
333                 osrfLogDebug( OSRF_LOG_MARK,  "In app_request receive with remaining time [%d]",
334                                 (int) remaining );
335
336
337                 osrf_app_session_queue_wait( req->session, 0, NULL );
338                 if(req->session->transport_error) {
339                         osrfLogError(OSRF_LOG_MARK, "Transport error in recv()");
340                         return NULL;
341                 }
342
343                 if( req->result != NULL ) { /* if we received any results for this request */
344                         /* dequeue the first message in the list */
345                         osrfLogDebug( OSRF_LOG_MARK, "app_request_recv received a message, returning it" );
346                         osrfMessage* ret_msg = req->result;
347                         req->result = ret_msg->next;
348                         if (ret_msg->sender_locale)
349                                 osrf_app_session_set_locale(req->session, ret_msg->sender_locale);
350
351                         return ret_msg;
352                 }
353
354                 if( req->complete )
355                         return NULL;
356
357                 osrf_app_session_queue_wait( req->session, (int) remaining, NULL );
358
359                 if(req->session->transport_error) {
360                         osrfLogError(OSRF_LOG_MARK, "Transport error in recv()");
361                         return NULL;
362                 }
363
364                 if( req->result != NULL ) { /* if we received any results for this request */
365                         /* dequeue the first message in the list */
366                         osrfLogDebug( OSRF_LOG_MARK,  "app_request_recv received a message, returning it");
367                         osrfMessage* ret_msg = req->result;
368                         req->result = ret_msg->next;
369                         if (ret_msg->sender_locale)
370                                 osrf_app_session_set_locale(req->session, ret_msg->sender_locale);
371
372                         return ret_msg;
373                 }
374
375                 if( req->complete )
376                         return NULL;
377
378                 // Determine how much time is left
379                 if(req->reset_timeout) {
380                         // We got a reprieve.  This happens when a client receives a STATUS message
381                         // with a status code OSRF_STATUS_CONTINUE.  We restart the timer from the
382                         // beginning -- but only once.  We reset reset_timeout to zero. so that a
383                         // second attempted reprieve will allow, at most, only one more second.
384                         remaining = (time_t) timeout;
385                         req->reset_timeout = 0;
386                         osrfLogDebug( OSRF_LOG_MARK, "Received a timeout reset");
387                 } else {
388                         remaining -= (int) (time(NULL) - start);
389                 }
390         }
391
392         // Timeout exhausted; no messages for the request in question
393         char* paramString = jsonObjectToJSON(req->payload->_params);
394         osrfLogInfo( OSRF_LOG_MARK, "Returning NULL from app_request_recv after timeout: %s %s",
395                 req->payload->method_name, paramString);
396         free(paramString);
397
398         return NULL;
399 }
400
401 // --------------------------------------------------------------------------
402 // Session API
403 // --------------------------------------------------------------------------
404
405 /**
406         @brief Install a copy of a locale string in a specified session.
407         @param session Pointer to the osrfAppSession in which the locale is to be installed.
408         @param locale The locale string to be copied and installed.
409         @return A pointer to the installed copy of the locale string.
410 */
411 char* osrf_app_session_set_locale( osrfAppSession* session, const char* locale ) {
412         if (!session || !locale)
413                 return NULL;
414
415         if(session->session_locale) {
416                 if( strlen(session->session_locale) >= strlen(locale) ) {
417                         /* There's room available; just copy */
418                         strcpy(session->session_locale, locale);
419                 } else {
420                         free(session->session_locale);
421                         session->session_locale = strdup( locale );
422                 }
423         } else {
424                 session->session_locale = strdup( locale );
425         }
426
427         return session->session_locale;
428 }
429
430 /**
431         @brief Install a copy of a TZ string in a specified session.
432         @param session Pointer to the osrfAppSession in which the TZ is to be installed.
433         @param TZ The TZ string to be copied and installed.
434         @return A pointer to the installed copy of the TZ string.
435 */
436 char* osrf_app_session_set_tz( osrfAppSession* session, const char* tz ) {
437         if (!session || !tz)
438                 return NULL;
439
440         if(session->session_tz) {
441                 if( strlen(session->session_tz) >= strlen(tz) ) {
442                         /* There's room available; just copy */
443                         strcpy(session->session_tz, tz);
444                 } else {
445                         free(session->session_tz);
446                         session->session_tz = strdup( tz );
447                 }
448         } else {
449                 session->session_tz = strdup( tz );
450         }
451
452         return session->session_tz;
453 }
454
455 /**
456         @brief Install a copy of a ingress string as the new default.
457         @param session Pointer to the new strdup'ed default_ingress
458         @param ingress The ingress string to be copied and installed.
459 */
460 char* osrfAppSessionSetIngress(const char* ingress) {
461         if (!ingress) return NULL;
462     if(current_ingress) 
463         free(current_ingress);
464     return current_ingress = strdup(ingress);
465 }
466
467 /**
468     @brief Returns the current ingress value
469     @return A pointer to the installed copy of the ingress string 
470 */
471 const char* osrfAppSessionGetIngress() {
472     return current_ingress;
473 }
474
475 /**
476         @brief Find the osrfAppSession for a given session id.
477         @param session_id The session id to look for.
478         @return Pointer to the corresponding osrfAppSession if found, or NULL if not.
479
480         Search the global session cache for the specified session id.
481 */
482 osrfAppSession* osrf_app_session_find_session( const char* session_id ) {
483         if(session_id)
484                 return osrfHashGet( osrfAppSessionCache, session_id );
485         return NULL;
486 }
487
488
489 /**
490         @brief Add a session to the global session cache, keyed by session id.
491         @param session Pointer to the osrfAppSession to be added.
492
493         If a cache doesn't exist yet, create one.  It's an osrfHash using session ids for the
494         key and osrfAppSessions for the data.
495 */
496 static void _osrf_app_session_push_session( osrfAppSession* session ) {
497         if( session ) {
498                 if( osrfAppSessionCache == NULL )
499                         osrfAppSessionCache = osrfNewHash();
500                 if( osrfHashGet( osrfAppSessionCache, session->session_id ) )
501                         return;   // A session with this id is already in the cache.  Shouldn't happen.
502                 osrfHashSet( osrfAppSessionCache, session, session->session_id );
503         }
504 }
505
506 /**
507         @brief Create an osrfAppSession for a client.
508         @param remote_service Name of the service to which to connect
509         @return Pointer to the new osrfAppSession if successful, or NULL upon error.
510
511         Allocate memory for an osrfAppSession, and initialize it as follows:
512
513         - For talking with Jabber, grab an existing transport_client.  It must have been
514         already set up by a prior call to osrfSystemBootstrapClientResc().
515         - Build a Jabber ID for addressing the service.
516         - Build a session ID based on a fine-grained timestamp and a process ID.  This ID is
517         intended to be unique across the system, but uniqueness is not strictly guaranteed.
518         - Initialize various other bits and scraps.
519         - Add the session to the global session cache.
520
521         Do @em not connect to the service at this point.
522 */
523 osrfAppSession* osrfAppSessionClientInit( const char* remote_service ) {
524
525         if (!remote_service) {
526                 osrfLogWarning( OSRF_LOG_MARK, "No remote service specified in osrfAppSessionClientInit");
527                 return NULL;
528         }
529
530         osrfAppSession* session = safe_malloc(sizeof(osrfAppSession));
531
532         // Grab an existing transport_client for talking with Jabber
533         session->transport_handle = osrfSystemGetTransportClient();
534         if( session->transport_handle == NULL ) {
535                 osrfLogWarning( OSRF_LOG_MARK, "No transport client for service 'client'");
536                 free( session );
537                 return NULL;
538         }
539
540         // Get a list of domain names from the config settings;
541         // ignore all but the first one in the list.
542         osrfStringArray* arr = osrfNewStringArray(8);
543         osrfConfigGetValueList(NULL, arr, "/domain");
544         const char* domain = osrfStringArrayGetString(arr, 0);
545         if (!domain) {
546                 osrfLogWarning( OSRF_LOG_MARK, "No domains specified in the OpenSRF config file");
547                 free( session );
548                 osrfStringArrayFree(arr);
549                 return NULL;
550         }
551
552         // Get a router name from the config settings.
553         char* router_name = osrfConfigGetValue(NULL, "/router_name");
554         if (!router_name) {
555                 osrfLogWarning( OSRF_LOG_MARK, "No router name specified in the OpenSRF config file");
556                 free( session );
557                 osrfStringArrayFree(arr);
558                 return NULL;
559         }
560
561         char target_buf[512];
562         target_buf[ 0 ] = '\0';
563
564         // Using the router name, domain, and service name,
565         // build a Jabber ID for addressing the service.
566         int len = snprintf( target_buf, sizeof(target_buf), "%s@%s/%s",
567                         router_name ? router_name : "(null)",
568                         domain ? domain : "(null)",
569                         remote_service ? remote_service : "(null)" );
570         osrfStringArrayFree(arr);
571         free(router_name);
572
573         if( len >= sizeof( target_buf ) ) {
574                 osrfLogWarning( OSRF_LOG_MARK, "Buffer overflow for remote_id");
575                 free( session );
576                 return NULL;
577         }
578
579         session->remote_id = strdup(target_buf);
580         session->orig_remote_id = strdup(session->remote_id);
581         session->remote_service = strdup(remote_service);
582         session->session_locale = NULL;
583         session->session_tz = NULL;
584         session->transport_error = 0;
585         session->panic = 0;
586         session->outbuf = NULL;   // Not used by client
587
588         #ifdef ASSUME_STATELESS
589         session->stateless = 1;
590         osrfLogDebug( OSRF_LOG_MARK, "%s session is stateless", remote_service );
591         #else
592         session->stateless = 0;
593         osrfLogDebug( OSRF_LOG_MARK, "%s session is NOT stateless", remote_service );
594         #endif
595
596         /* build a chunky, random session id */
597         char id[256];
598
599         snprintf(id, sizeof(id), "%f.%d%ld", get_timestamp_millis(), (int)time(NULL), (long) getpid());
600         session->session_id = strdup(id);
601         osrfLogDebug( OSRF_LOG_MARK,  "Building a new client session with id [%s] [%s]",
602                         session->remote_service, session->session_id );
603
604         session->thread_trace = 0;
605         session->state = OSRF_SESSION_DISCONNECTED;
606         session->type = OSRF_SESSION_CLIENT;
607
608         session->userData = NULL;
609         session->userDataFree = NULL;
610
611         // Initialize the hash table
612         int i;
613         for( i = 0; i < OSRF_REQUEST_HASH_SIZE; ++i )
614                 session->request_hash[ i ] = NULL;
615
616         _osrf_app_session_push_session( session );
617         return session;
618 }
619
620 /**
621         @brief Create an osrfAppSession for a server.
622         @param session_id The session ID.  In practice this comes from the thread member of
623         the transport message from the client.
624         @param our_app The name of the service being provided.
625         @param remote_id Jabber ID of the client.
626         @return Pointer to the newly created osrfAppSession if successful, or NULL upon failure.
627
628         If there is already a session with the specified id, report an error.  Otherwise:
629
630         - Allocate memory for an osrfAppSession.
631         - For talking with Jabber, grab an existing transport_client.  It should have been
632         already set up by a prior call to osrfSystemBootstrapClientResc().
633         - Install a copy of the @a our_app string as remote_service.
634         - Install copies of the @a remote_id string as remote_id and orig_remote_id.
635         - Initialize various other bits and scraps.
636         - Add the session to the global session cache.
637
638         Do @em not respond to the client at this point.
639 */
640 osrfAppSession* osrf_app_server_session_init(
641                 const char* session_id, const char* our_app, const char* remote_id ) {
642
643         osrfLogDebug( OSRF_LOG_MARK, "Initing server session with session id %s, service %s,"
644                         " and remote_id %s", session_id, our_app, remote_id );
645
646         osrfAppSession* session = osrf_app_session_find_session( session_id );
647         if(session) {
648                 osrfLogWarning( OSRF_LOG_MARK, "App session already exists for session id %s",
649                                 session_id );
650                 return NULL;
651         }
652
653         session = safe_malloc(sizeof(osrfAppSession));
654
655         // Grab an existing transport_client for talking with Jabber
656         session->transport_handle = osrfSystemGetTransportClient();
657         if( session->transport_handle == NULL ) {
658                 osrfLogWarning( OSRF_LOG_MARK, "No transport client for service '%s'", our_app );
659                 free(session);
660                 return NULL;
661         }
662
663         // Decide from a config setting whether the session is stateless or not.  However
664         // this determination is pointless because it will immediately be overruled according
665         // to the compile-time macro ASSUME_STATELESS.
666         int stateless = 0;
667         char* statel = osrf_settings_host_value("/apps/%s/stateless", our_app );
668         if( statel )
669                 stateless = atoi( statel );
670         free(statel);
671
672         session->remote_id = strdup(remote_id);
673         session->orig_remote_id = strdup(remote_id);
674         session->session_id = strdup(session_id);
675         session->remote_service = strdup(our_app);
676         session->stateless = stateless;
677
678         #ifdef ASSUME_STATELESS
679         session->stateless = 1;
680         #else
681         session->stateless = 0;
682         #endif
683
684         session->thread_trace = 0;
685         session->state = OSRF_SESSION_DISCONNECTED;
686         session->type = OSRF_SESSION_SERVER;
687         session->session_locale = NULL;
688         session->session_tz = NULL;
689
690         session->userData = NULL;
691         session->userDataFree = NULL;
692         session->transport_error = 0;
693
694         // Initialize the hash table
695         int i;
696         for( i = 0; i < OSRF_REQUEST_HASH_SIZE; ++i )
697                 session->request_hash[ i ] = NULL;
698
699         session->panic = 0;
700         session->outbuf = buffer_init( 4096 );
701
702         _osrf_app_session_push_session( session );
703         return session;
704 }
705
706 /**
707         @brief Create a REQUEST message, send it, and save it for future reference.
708         @param session Pointer to the current session, which has the addressing information.
709         @param params One way of specifying the parameters for the method.
710         @param method_name The name of the method to be called.
711         @param protocol Protocol.
712         @param param_strings Another way of specifying the parameters for the method.
713         @return The request ID of the resulting REQUEST message, or -1 upon error.
714
715         DEPRECATED.  Use osrfAppSessionSendRequest() instead.  It is identical except that it
716         doesn't use the param_strings argument, which is redundant, confusing, and unused.
717
718         If @a params is non-NULL, use it to specify the parameters to the method.  Otherwise
719         use @a param_strings.
720
721         If @a params points to a JSON_ARRAY, then pass each element of the array as a separate
722         parameter.  If @a params points to any other kind of jsonObject, pass it as a single
723         parameter.
724
725         If @a params is NULL, and @a param_strings is not NULL, then each pointer in the
726         osrfStringArray must point to a JSON string encoding a parameter.  Pass them.
727
728         At this writing, all calls to this function use @a params to pass parameters, rather than
729         @a param_strings.
730
731         This function is a thin wrapper for osrfAppSessionMakeLocaleRequest().
732 */
733 int osrfAppSessionMakeRequest(
734                 osrfAppSession* session, const jsonObject* params,
735                 const char* method_name, int protocol, osrfStringArray* param_strings ) {
736
737         osrfLogWarning( OSRF_LOG_MARK, "Function osrfAppSessionMakeRequest() is deprecated; "
738                         "call osrfAppSessionSendRequest() instead" );
739         return osrfAppSessionMakeLocaleRequest( session, params,
740                         method_name, protocol, param_strings, NULL );
741 }
742
743 /**
744         @brief Create a REQUEST message, send it, and save it for future reference.
745         @param session Pointer to the current session, which has the addressing information.
746         @param params One way of specifying the parameters for the method.
747         @param method_name The name of the method to be called.
748         @param protocol Protocol.
749         @return The request ID of the resulting REQUEST message, or -1 upon error.
750
751         If @a params points to a JSON_ARRAY, then pass each element of the array as a separate
752         parameter.  If @a params points to any other kind of jsonObject, pass it as a single
753         parameter.
754
755         This function is a thin wrapper for osrfAppSessionMakeLocaleRequest().
756 */
757 int osrfAppSessionSendRequest( osrfAppSession* session, const jsonObject* params,
758                 const char* method_name, int protocol ) {
759
760         return osrfAppSessionMakeLocaleRequest( session, params,
761                  method_name, protocol, NULL, NULL );
762 }
763
764 /**
765         @brief Create a REQUEST message, send it, and save it for future reference.
766         @param session Pointer to the current session, which has the addressing information.
767         @param params One way of specifying the parameters for the method.
768         @param method_name The name of the method to be called.
769         @param protocol Protocol.
770         @param param_strings Another way of specifying the parameters for the method.
771         @param locale Pointer to a locale string.
772         @return The request ID of the resulting REQUEST message, or -1 upon error.
773
774         See the discussion of osrfAppSessionSendRequest(), which at this writing is the only
775         place that calls this function, except for the similar but deprecated function
776         osrfAppSessionMakeRequest().
777
778         At this writing, the @a param_strings and @a locale parameters are always NULL.
779 */
780 static int osrfAppSessionMakeLocaleRequest(
781                 osrfAppSession* session, const jsonObject* params, const char* method_name,
782                 int protocol, osrfStringArray* param_strings, char* locale ) {
783
784         if(session == NULL) return -1;
785
786         osrfLogMkXid();
787
788         osrfMessage* req_msg = osrf_message_init( REQUEST, ++(session->thread_trace), protocol );
789         osrf_message_set_method(req_msg, method_name);
790
791         if (locale) {
792                 osrf_message_set_locale(req_msg, locale);
793         } else if (session->session_locale) {
794                 osrf_message_set_locale(req_msg, session->session_locale);
795         }
796
797         osrf_message_set_tz(req_msg, session->session_tz);
798
799         if (!current_ingress)
800                 osrfAppSessionSetIngress("opensrf");
801         osrfMessageSetIngress(req_msg, current_ingress);
802
803         if(params) {
804                 osrf_message_set_params(req_msg, params);
805
806         } else {
807
808                 if(param_strings) {
809                         int i;
810                         for(i = 0; i!= param_strings->size ; i++ ) {
811                                 osrf_message_add_param(req_msg,
812                                         osrfStringArrayGetString(param_strings,i));
813                         }
814                 }
815         }
816
817         osrfAppRequest* req = _osrf_app_request_init( session, req_msg );
818         if(_osrf_app_session_send( session, req_msg ) ) {
819                 osrfLogWarning( OSRF_LOG_MARK,  "Error sending request message [%d]",
820                                 session->thread_trace );
821                 _osrf_app_request_free(req);
822                 return -1;
823         }
824
825         osrfLogDebug( OSRF_LOG_MARK,  "Pushing [%d] onto request queue for session [%s] [%s]",
826                         req->request_id, session->remote_service, session->session_id );
827         add_app_request( session, req );
828         return req->request_id;
829 }
830
831 /**
832         @brief Mark an osrfAppRequest (identified by session and ID) as complete.
833         @param session Pointer to the osrfAppSession that owns the request.
834         @param request_id Request ID of the osrfAppRequest.
835 */
836 void osrf_app_session_set_complete( osrfAppSession* session, int request_id ) {
837         if(session == NULL)
838                 return;
839
840         osrfAppRequest* req = find_app_request( session, request_id );
841         if(req)
842                 req->complete = 1;
843 }
844
845 /**
846         @brief Determine whether a osrfAppRequest, identified by session and ID, is complete.
847         @param session Pointer to the osrfAppSession that owns the request.
848         @param request_id Request ID of the osrfAppRequest.
849         @return Non-zero if the request is complete; zero if it isn't, or if it can't be found.
850 */
851 int osrf_app_session_request_complete( const osrfAppSession* session, int request_id ) {
852         if(session == NULL)
853                 return 0;
854
855         osrfAppRequest* req = find_app_request( session, request_id );
856         if(req)
857                 return req->complete;
858
859         return 0;
860 }
861
862 /**
863         @brief Reset the remote ID of a session to its original remote ID.
864         @param session Pointer to the osrfAppSession to be reset.
865 */
866 void osrf_app_session_reset_remote( osrfAppSession* session ){
867         if( session==NULL )
868                 return;
869
870         osrfLogDebug( OSRF_LOG_MARK,  "App Session [%s] [%s] resetting remote id to %s",
871                         session->remote_service, session->session_id, session->orig_remote_id );
872
873         osrf_app_session_set_remote( session, session->orig_remote_id );
874 }
875
876 /**
877         @brief Set a session's remote ID to a specified value.
878         @param session Pointer to the osrfAppSession whose remote ID is to be set.
879         @param remote_id Pointer to the new remote id.
880 */
881 void osrf_app_session_set_remote( osrfAppSession* session, const char* remote_id ) {
882         if( session == NULL || remote_id == NULL )
883                 return;
884
885         if( session->remote_id ) {
886                 if( strlen(session->remote_id) >= strlen(remote_id) ) {
887                         // There's enough room; just copy it
888                         strcpy(session->remote_id, remote_id);
889                 } else {
890                         free(session->remote_id );
891                         session->remote_id = strdup( remote_id );
892                 }
893         } else
894                 session->remote_id = strdup( remote_id );
895 }
896
897 /**
898         @brief Append an osrfMessage to the list of responses to an osrfAppRequest.
899         @param session Pointer to the osrfAppSession that owns the request.
900         @param msg Pointer to the osrfMessage to be added.
901
902         The thread_trace member of the osrfMessage is the request_id of the osrfAppRequest.
903         Find the corresponding request in the session and append the osrfMessage to its list.
904 */
905 void osrf_app_session_push_queue( osrfAppSession* session, osrfMessage* msg ) {
906         if( session && msg ) {
907                 osrfAppRequest* req = find_app_request( session, msg->thread_trace );
908                 if( req )
909                         _osrf_app_request_push_queue( req, msg );
910         }
911 }
912
913 /**
914         @brief Connect to the remote service.
915         @param session Pointer to the osrfAppSession for the service.
916         @return 1 if successful, or 0 if not.
917
918         If already connected, exit immediately, reporting success.  Otherwise, build a CONNECT
919         message and send it to the service.  Wait for up to five seconds for an acknowledgement.
920
921         The timeout value is currently hard-coded.  Perhaps it should be configurable.
922 */
923 int osrfAppSessionConnect( osrfAppSession* session ) {
924
925         if(session == NULL)
926                 return 0;
927
928         if(session->state == OSRF_SESSION_CONNECTED) {
929                 return 1;
930         }
931
932         int timeout = 5; /* XXX CONFIG VALUE */
933
934         osrfLogDebug( OSRF_LOG_MARK,  "AppSession connecting to %s", session->remote_id );
935
936         /* defaulting to protocol 1 for now */
937         osrfMessage* con_msg = osrf_message_init( CONNECT, session->thread_trace, 1 );
938
939         // Address this message to the router
940         osrf_app_session_reset_remote( session );
941         session->state = OSRF_SESSION_CONNECTING;
942         int ret = _osrf_app_session_send( session, con_msg );
943         osrfMessageFree(con_msg);
944         if(ret)
945                 return 0;
946
947         time_t start = time(NULL);
948         time_t remaining = (time_t) timeout;
949
950         // Wait for the acknowledgement.  We look for it repeatedly because, under the covers,
951         // we may receive and process messages other than the one we're looking for.
952         while( session->state != OSRF_SESSION_CONNECTED && remaining >= 0 ) {
953                 osrf_app_session_queue_wait( session, remaining, NULL );
954                 if(session->transport_error) {
955                         osrfLogError(OSRF_LOG_MARK, "cannot communicate with %s", session->remote_service);
956                         return 0;
957                 }
958                 remaining -= (int) (time(NULL) - start);
959         }
960
961         if(session->state == OSRF_SESSION_CONNECTED)
962                 osrfLogDebug( OSRF_LOG_MARK, " * Connected Successfully to %s", session->remote_service );
963
964         if(session->state != OSRF_SESSION_CONNECTED)
965                 return 0;
966
967         return 1;
968 }
969
970 /**
971         @brief Disconnect from the remote service.  No response is expected.
972         @param session Pointer to the osrfAppSession to be disconnected.
973         @return 1 in all cases.
974
975         If we're already disconnected, return immediately without doing anything.  Likewise if
976         we have a stateless session and we're in the process of connecting.  Otherwise, send a
977         DISCONNECT message to the service.
978 */
979 int osrf_app_session_disconnect( osrfAppSession* session){
980         if(session == NULL)
981                 return 1;
982
983         if(session->state == OSRF_SESSION_DISCONNECTED)
984                 return 1;
985
986         if(session->stateless && session->state != OSRF_SESSION_CONNECTED) {
987                 osrfLogDebug( OSRF_LOG_MARK,
988                                 "Exiting disconnect on stateless session %s",
989                                 session->session_id);
990                 return 1;
991         }
992
993         osrfLogDebug(OSRF_LOG_MARK,  "AppSession disconnecting from %s", session->remote_id );
994
995         osrfMessage* dis_msg = osrf_message_init( DISCONNECT, session->thread_trace, 1 );
996         _osrf_app_session_send( session, dis_msg );
997         session->state = OSRF_SESSION_DISCONNECTED;
998
999         osrfMessageFree( dis_msg );
1000         osrf_app_session_reset_remote( session );
1001         return 1;
1002 }
1003
1004 /**
1005         @brief Resend a request message, as specified by session and request id.
1006         @param session Pointer to the osrfAppSession.
1007         @param req_id Request ID for the request to be resent.
1008         @return Zero if successful, or if the specified request cannot be found; 1 if the
1009         request is already complete, or if the attempt to resend the message fails.
1010
1011         The choice of return codes may seem seem capricious, but at this writing nothing
1012         pays any attention to the return code anyway.
1013 */
1014 int osrf_app_session_request_resend( osrfAppSession* session, int req_id ) {
1015         osrfAppRequest* req = find_app_request( session, req_id );
1016
1017         int rc;
1018         if(req == NULL) {
1019                 rc = 0;
1020         } else if(!req->complete) {
1021                 osrfLogDebug( OSRF_LOG_MARK, "Resending request [%d]", req->request_id );
1022                 rc = _osrf_app_session_send( req->session, req->payload );
1023         } else {
1024                 rc = 1;
1025         }
1026
1027         return rc;
1028 }
1029
1030 /**
1031         @brief Send one or more osrfMessages to the remote service or client.
1032         @param session Pointer to the osrfAppSession responsible for sending the message(s).
1033         @param msgs Pointer to an array of pointers to osrfMessages.
1034         @param size How many messages to send.
1035         @return 0 upon success, or -1 upon failure.
1036 */
1037 static int osrfAppSessionSendBatch( osrfAppSession* session, osrfMessage* msgs[], int size ) {
1038
1039         if( !(session && msgs && size > 0) ) return -1;
1040         int retval = 0;
1041
1042         osrfMessage* msg = msgs[0];
1043
1044         if(msg) {
1045
1046                 // First grab and process any input messages, for any app session.  This gives us
1047                 // a chance to see any CONNECT or DISCONNECT messages that may have arrived.  We
1048                 // may also see some unrelated messages, but we have to process those sooner or
1049                 // later anyway, so we might as well do it now.
1050                 osrf_app_session_queue_wait( session, 0, NULL );
1051
1052                 if(session->state != OSRF_SESSION_CONNECTED)  {
1053
1054                         if(session->stateless) { /* stateless session always send to the root listener */
1055                                 osrf_app_session_reset_remote(session);
1056
1057                         } else {
1058
1059                                 /* do an auto-connect if necessary */
1060                                 if( ! session->stateless &&
1061                                         (msg->m_type != CONNECT) &&
1062                                         (msg->m_type != DISCONNECT) &&
1063                                         (session->state != OSRF_SESSION_CONNECTED) ) {
1064
1065                                         if(!osrfAppSessionConnect( session ))
1066                                                 return -1;
1067                                 }
1068                         }
1069                 }
1070         }
1071
1072         // Translate the collection of osrfMessages into a JSON array
1073         char* string = osrfMessageSerializeBatch(msgs, size);
1074
1075         // Send the JSON as the payload of a transport_message
1076         if( string ) {
1077                 retval = osrfSendTransportPayload( session, string );
1078                 free(string);
1079         }
1080
1081         return retval;
1082 }
1083
1084 /**
1085         @brief Wrap a given string in a transport message and send it.
1086         @param session Pointer to the osrfAppSession responsible for sending the message(s).
1087         @param payload A string to be sent via Jabber.
1088         @return 0 upon success, or -1 upon failure.
1089
1090         In practice the payload is normally a JSON string, but this function assumes nothing
1091         about it.
1092 */
1093 int osrfSendTransportPayload( osrfAppSession* session, const char* payload ) {
1094         transport_message* t_msg = message_init(
1095                 payload, "", session->session_id, session->remote_id, NULL );
1096         message_set_osrf_xid( t_msg, osrfLogGetXid() );
1097
1098         int retval = client_send_message( session->transport_handle, t_msg );
1099         if( retval ) {
1100                 osrfLogError( OSRF_LOG_MARK, "client_send_message failed, exit()ing immediately" );
1101                 exit(99);
1102         }
1103
1104         osrfLogInfo(OSRF_LOG_MARK, "[%s] sent %d bytes of data to %s",
1105                 session->remote_service, strlen( payload ), t_msg->recipient );
1106
1107         osrfLogDebug( OSRF_LOG_MARK, "Sent: %s", payload );
1108
1109         message_free( t_msg );
1110         return retval;
1111 }
1112
1113 /**
1114         @brief Send a single osrfMessage to the remote service or client.
1115         @param session Pointer to the osrfAppSession.
1116         @param msg Pointer to the osrfMessage to be sent.
1117         @return zero upon success, or 1 upon failure.
1118
1119         A thin wrapper.  Create an array of one element, and pass it to osrfAppSessionSendBatch().
1120 */
1121 static int _osrf_app_session_send( osrfAppSession* session, osrfMessage* msg ){
1122         if( !(session && msg) )
1123                 return 1;
1124         osrfMessage* a[1];
1125         a[0] = msg;
1126         return  - osrfAppSessionSendBatch( session, a, 1 );
1127 }
1128
1129
1130 /**
1131         @brief Wait for any input messages to arrive, and process them as needed.
1132         @param session Pointer to the osrfAppSession whose transport_session we will use.
1133         @param timeout How many seconds to wait for the first input message.
1134         @param recvd Pointer to an boolean int.  If you receive at least one message, set the boolean
1135         to true; otherwise set it to false.
1136         @return 0 upon success (even if a timeout occurs), or -1 upon failure.
1137
1138         A thin wrapper for osrf_stack_process().  The timeout applies only to the first
1139         message; process subsequent messages if they are available, but don't wait for them.
1140
1141         The first parameter identifies an osrfApp session, but all we really use it for is to
1142         get a pointer to the transport_session.  Typically, a given process opens only a single
1143         transport_session (to talk to the Jabber server), and all app sessions in that process
1144         use the same transport_session.
1145
1146         Hence this function indiscriminately waits for input messages for all osrfAppSessions
1147         tied to the same Jabber session, not just the one specified.
1148
1149         Dispatch each message to the appropriate processing routine, depending on its type
1150         and contents, and on whether we're acting as a client or as a server for that message.
1151         For example, a response to a request may be appended to the input queue of the
1152         relevant request.  A server session receiving a REQUEST message may execute the
1153         requested method.  And so forth.
1154 */
1155 int osrf_app_session_queue_wait( osrfAppSession* session, int timeout, int* recvd ){
1156         if(session == NULL) return 0;
1157         osrfLogDebug(OSRF_LOG_MARK, "AppSession in queue_wait with timeout %d", timeout );
1158         return osrf_stack_process(session->transport_handle, timeout, recvd);
1159 }
1160
1161 /**
1162         @brief Shut down and destroy an osrfAppSession.
1163         @param session Pointer to the osrfAppSession to be destroyed.
1164
1165         If this is a client session, send a DISCONNECT message.
1166
1167         Remove the session from the global session cache.
1168
1169         Free all associated resources, including any pending osrfAppRequests.
1170 */
1171 void osrfAppSessionFree( osrfAppSession* session ){
1172         if(session == NULL) return;
1173
1174         /* Disconnect */
1175
1176         osrfLogDebug(OSRF_LOG_MARK,  "AppSession [%s] [%s] destroying self and deleting requests",
1177                         session->remote_service, session->session_id );
1178         /* disconnect if we're a client */
1179         if(session->type == OSRF_SESSION_CLIENT
1180                         && session->state != OSRF_SESSION_DISCONNECTED ) {
1181                 osrfMessage* dis_msg = osrf_message_init( DISCONNECT, session->thread_trace, 1 );
1182                 _osrf_app_session_send( session, dis_msg );
1183                 osrfMessageFree(dis_msg);
1184         }
1185
1186         /* Remove self from the global session cache */
1187
1188         osrfHashRemove( osrfAppSessionCache, session->session_id );
1189
1190         /* Free the memory */
1191
1192         if( session->userDataFree && session->userData )
1193                 session->userDataFree(session->userData);
1194
1195         if(session->session_locale)
1196                 free(session->session_locale);
1197
1198         if(session->session_tz)
1199                 free(session->session_tz);
1200
1201         free(session->remote_id);
1202         free(session->orig_remote_id);
1203         free(session->session_id);
1204         free(session->remote_service);
1205
1206         // Free the request hash
1207         int i;
1208         for( i = 0; i < OSRF_REQUEST_HASH_SIZE; ++i ) {
1209                 osrfAppRequest* app = session->request_hash[ i ];
1210                 while( app ) {
1211                         osrfAppRequest* next = app->next;
1212                         _osrf_app_request_free( app );
1213                         app = next;
1214                 }
1215         }
1216
1217         if( session->outbuf )
1218                 buffer_free( session->outbuf );
1219
1220         free(session);
1221 }
1222
1223 /**
1224         @brief Wait for a response to a given request, subject to a timeout.
1225         @param session Pointer to the osrfAppSession that owns the request.
1226         @param req_id Request ID for the request.
1227         @param timeout How many seconds to wait.
1228         @return A pointer to the received osrfMessage if one arrives; otherwise NULL.
1229
1230         A thin wrapper.  Given a session and a request ID, look up the corresponding request
1231         and pass it to _osrf_app_request_recv().
1232 */
1233 osrfMessage* osrfAppSessionRequestRecv(
1234                 osrfAppSession* session, int req_id, int timeout ) {
1235         if(req_id < 0 || session == NULL)
1236                 return NULL;
1237         osrfAppRequest* req = find_app_request( session, req_id );
1238         return _osrf_app_request_recv( req, timeout );
1239 }
1240
1241 /**
1242         @brief In response to a specified request, send a payload of data to a client.
1243         @param ses Pointer to the osrfAppSession that owns the request.
1244         @param requestId Request ID of the osrfAppRequest.
1245         @param data Pointer to a jsonObject containing the data payload.
1246         @return 0 upon success, or -1 upon failure.
1247
1248         Translate the jsonObject to a JSON string, and send it wrapped in a RESULT message.
1249
1250         The only failure detected is if either of the two pointer parameters is NULL.
1251 */
1252 int osrfAppRequestRespond( osrfAppSession* ses, int requestId, const jsonObject* data ) {
1253         if( !ses || ! data )
1254                 return -1;
1255
1256         osrfMessage* msg = osrf_message_init( RESULT, requestId, 1 );
1257         osrf_message_set_status_info( msg, NULL, "OK", OSRF_STATUS_OK );
1258         char* json = jsonObjectToJSON( data );
1259
1260         osrf_message_set_result_content( msg, json );
1261         _osrf_app_session_send( ses, msg );
1262
1263         free(json);
1264         osrfMessageFree( msg );
1265
1266         return 0;
1267 }
1268
1269
1270 /**
1271         @brief Send one or two messages to a client in response to a specified request.
1272         @param ses Pointer to the osrfAppSession that owns the request.
1273         @param requestId Request ID of the osrfAppRequest.
1274         @param data Pointer to a jsonObject containing the data payload.
1275         @return  Zero in all cases.
1276
1277         If the @a data parameter is not NULL, translate the jsonObject into a JSON string, and
1278         incorporate that string into a RESULT message as as the payload .  Also build a STATUS
1279         message indicating that the response is complete.  Send both messages bundled together
1280         in the same transport_message.
1281
1282         If the @a data parameter is NULL, send only a STATUS message indicating that the response
1283         is complete.
1284 */
1285 int osrfAppRequestRespondComplete(
1286                 osrfAppSession* ses, int requestId, const jsonObject* data ) {
1287
1288         osrfMessage* status = osrf_message_init( STATUS, requestId, 1);
1289         osrf_message_set_status_info( status, "osrfConnectStatus", "Request Complete",
1290                         OSRF_STATUS_COMPLETE );
1291
1292         if (data) {
1293                 osrfMessage* payload = osrf_message_init( RESULT, requestId, 1 );
1294                 osrf_message_set_status_info( payload, NULL, "OK", OSRF_STATUS_OK );
1295
1296                 char* json = jsonObjectToJSON( data );
1297                 osrf_message_set_result_content( payload, json );
1298                 free(json);
1299
1300                 osrfMessage* ms[2];
1301                 ms[0] = payload;
1302                 ms[1] = status;
1303
1304                 osrfAppSessionSendBatch( ses, ms, 2 );
1305
1306                 osrfMessageFree( payload );
1307         } else {
1308                 osrfAppSessionSendBatch( ses, &status, 1 );
1309         }
1310
1311         osrfMessageFree( status );
1312
1313         return 0;
1314 }
1315
1316 /**
1317         @brief Send a STATUS message, for a specified request, back to the client.
1318         @param ses Pointer to the osrfAppSession connected to the client.
1319         @param type A numeric code denoting the status.
1320         @param name A string naming the status.
1321         @param reqId The request ID of the request.
1322         @param message A brief message describing the status.
1323         @return 0 upon success, or -1 upon failure.
1324
1325         The only detected failure is when the @a ses parameter is NULL.
1326 */
1327 int osrfAppSessionStatus( osrfAppSession* ses, int type,
1328                 const char* name, int reqId, const char* message ) {
1329
1330         if(ses) {
1331                 osrfMessage* msg = osrf_message_init( STATUS, reqId, 1);
1332                 osrf_message_set_status_info( msg, name, message, type );
1333                 _osrf_app_session_send( ses, msg );
1334                 osrfMessageFree( msg );
1335                 return 0;
1336         } else
1337                 return -1;
1338 }
1339
1340 /**
1341         @brief Free the global session cache.
1342
1343         Note that the osrfHash that implements the global session cache does @em not have a
1344         callback function installed for freeing its cargo.  As a result, any remaining
1345         osrfAppSessions are leaked, along with all the osrfAppRequests and osrfMessages they
1346         own.
1347 */
1348 void osrfAppSessionCleanup( void ) {
1349         osrfHashFree(osrfAppSessionCache);
1350         osrfAppSessionCache = NULL;
1351 }
1352
1353 /**
1354         @brief Arrange for immediate termination of the process.
1355         @param ses Pointer to the current osrfAppSession.
1356
1357         Typical use case: a server drone loses its database connection, thereby becoming useless.
1358         It terminates so that it will not receive further requests, being unable to service them.
1359 */
1360 void osrfAppSessionPanic( osrfAppSession* ses ) {
1361         if( ses )
1362                 ses->panic = 1;
1363 }