]> git.evergreen-ils.org Git - OpenSRF.git/blob - src/libopensrf/osrf_app_session.c
LP1999823: Bump libtool library version
[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                         // Subtract the amount of time taken during this loop 
389                         // iteration, not the combined time of all iterations.
390                         time_t tmp = time(NULL);
391                         remaining -= (int) (tmp - start);
392                         start = tmp;
393                 }
394         }
395
396         // Timeout exhausted; no messages for the request in question
397         char* paramString = jsonObjectToJSON(req->payload->_params);
398         osrfLogInfo( OSRF_LOG_MARK, "Returning NULL from app_request_recv after timeout: %s %s",
399                 req->payload->method_name, paramString);
400         free(paramString);
401
402         return NULL;
403 }
404
405 // --------------------------------------------------------------------------
406 // Session API
407 // --------------------------------------------------------------------------
408
409 /**
410         @brief Install a copy of a locale string in a specified session.
411         @param session Pointer to the osrfAppSession in which the locale is to be installed.
412         @param locale The locale string to be copied and installed.
413         @return A pointer to the installed copy of the locale string.
414 */
415 char* osrf_app_session_set_locale( osrfAppSession* session, const char* locale ) {
416         if (!session || !locale)
417                 return NULL;
418
419         if(session->session_locale) {
420                 if( strlen(session->session_locale) >= strlen(locale) ) {
421                         /* There's room available; just copy */
422                         strcpy(session->session_locale, locale);
423                 } else {
424                         free(session->session_locale);
425                         session->session_locale = strdup( locale );
426                 }
427         } else {
428                 session->session_locale = strdup( locale );
429         }
430
431         return session->session_locale;
432 }
433
434 /**
435         @brief Install a copy of a TZ string in a specified session.
436         @param session Pointer to the osrfAppSession in which the TZ is to be installed.
437         @param TZ The TZ string to be copied and installed.
438         @return A pointer to the installed copy of the TZ string.
439 */
440 char* osrf_app_session_set_tz( osrfAppSession* session, const char* tz ) {
441         if (!session || !tz)
442                 return NULL;
443
444         if(session->session_tz) {
445                 if( strlen(session->session_tz) >= strlen(tz) ) {
446                         /* There's room available; just copy */
447                         strcpy(session->session_tz, tz);
448                 } else {
449                         free(session->session_tz);
450                         session->session_tz = strdup( tz );
451                 }
452         } else {
453                 session->session_tz = strdup( tz );
454         }
455
456         return session->session_tz;
457 }
458
459 /**
460         @brief Install a copy of a ingress string as the new default.
461         @param session Pointer to the new strdup'ed default_ingress
462         @param ingress The ingress string to be copied and installed.
463 */
464 char* osrfAppSessionSetIngress(const char* ingress) {
465         if (!ingress) return NULL;
466     if(current_ingress) 
467         free(current_ingress);
468     return current_ingress = strdup(ingress);
469 }
470
471 /**
472     @brief Returns the current ingress value
473     @return A pointer to the installed copy of the ingress string 
474 */
475 const char* osrfAppSessionGetIngress() {
476     return current_ingress;
477 }
478
479 /**
480         @brief Find the osrfAppSession for a given session id.
481         @param session_id The session id to look for.
482         @return Pointer to the corresponding osrfAppSession if found, or NULL if not.
483
484         Search the global session cache for the specified session id.
485 */
486 osrfAppSession* osrf_app_session_find_session( const char* session_id ) {
487         if(session_id)
488                 return osrfHashGet( osrfAppSessionCache, session_id );
489         return NULL;
490 }
491
492
493 /**
494         @brief Add a session to the global session cache, keyed by session id.
495         @param session Pointer to the osrfAppSession to be added.
496
497         If a cache doesn't exist yet, create one.  It's an osrfHash using session ids for the
498         key and osrfAppSessions for the data.
499 */
500 static void _osrf_app_session_push_session( osrfAppSession* session ) {
501         if( session ) {
502                 if( osrfAppSessionCache == NULL )
503                         osrfAppSessionCache = osrfNewHash();
504                 if( osrfHashGet( osrfAppSessionCache, session->session_id ) )
505                         return;   // A session with this id is already in the cache.  Shouldn't happen.
506                 osrfHashSet( osrfAppSessionCache, session, session->session_id );
507         }
508 }
509
510 /**
511         @brief Create an osrfAppSession for a client.
512         @param remote_service Name of the service to which to connect
513         @return Pointer to the new osrfAppSession if successful, or NULL upon error.
514
515         Allocate memory for an osrfAppSession, and initialize it as follows:
516
517         - For talking with Jabber, grab an existing transport_client.  It must have been
518         already set up by a prior call to osrfSystemBootstrapClientResc().
519         - Build a Jabber ID for addressing the service.
520         - Build a session ID based on a fine-grained timestamp and a process ID.  This ID is
521         intended to be unique across the system, but uniqueness is not strictly guaranteed.
522         - Initialize various other bits and scraps.
523         - Add the session to the global session cache.
524
525         Do @em not connect to the service at this point.
526 */
527 osrfAppSession* osrfAppSessionClientInit( const char* remote_service ) {
528
529         if (!remote_service) {
530                 osrfLogWarning( OSRF_LOG_MARK, "No remote service specified in osrfAppSessionClientInit");
531                 return NULL;
532         }
533
534         osrfAppSession* session = safe_malloc(sizeof(osrfAppSession));
535
536         // Grab an existing transport_client for talking with Jabber
537         session->transport_handle = osrfSystemGetTransportClient();
538         if( session->transport_handle == NULL ) {
539                 osrfLogWarning( OSRF_LOG_MARK, "No transport client for service 'client'");
540                 free( session );
541                 return NULL;
542         }
543
544         // Get a list of domain names from the config settings;
545         // ignore all but the first one in the list.
546         osrfStringArray* arr = osrfNewStringArray(8);
547         osrfConfigGetValueList(NULL, arr, "/domain");
548         const char* domain = osrfStringArrayGetString(arr, 0);
549         if (!domain) {
550                 osrfLogWarning( OSRF_LOG_MARK, "No domains specified in the OpenSRF config file");
551                 free( session );
552                 osrfStringArrayFree(arr);
553                 return NULL;
554         }
555
556         // Get a router name from the config settings.
557         char* router_name = osrfConfigGetValue(NULL, "/router_name");
558         if (!router_name) {
559                 osrfLogWarning( OSRF_LOG_MARK, "No router name specified in the OpenSRF config file");
560                 free( session );
561                 osrfStringArrayFree(arr);
562                 return NULL;
563         }
564
565         char target_buf[512];
566         target_buf[ 0 ] = '\0';
567
568         // Using the router name, domain, and service name,
569         // build a Jabber ID for addressing the service.
570         int len = snprintf( target_buf, sizeof(target_buf), "%s@%s/%s",
571                         router_name ? router_name : "(null)",
572                         domain ? domain : "(null)",
573                         remote_service ? remote_service : "(null)" );
574         osrfStringArrayFree(arr);
575         free(router_name);
576
577         if( len >= sizeof( target_buf ) ) {
578                 osrfLogWarning( OSRF_LOG_MARK, "Buffer overflow for remote_id");
579                 free( session );
580                 return NULL;
581         }
582
583         session->remote_id = strdup(target_buf);
584         session->orig_remote_id = strdup(session->remote_id);
585         session->remote_service = strdup(remote_service);
586         session->session_locale = NULL;
587         session->session_tz = NULL;
588         session->transport_error = 0;
589         session->panic = 0;
590         session->outbuf = NULL;   // Not used by client
591
592         #ifdef ASSUME_STATELESS
593         session->stateless = 1;
594         osrfLogDebug( OSRF_LOG_MARK, "%s session is stateless", remote_service );
595         #else
596         session->stateless = 0;
597         osrfLogDebug( OSRF_LOG_MARK, "%s session is NOT stateless", remote_service );
598         #endif
599
600         /* build a chunky, random session id */
601         char id[256];
602
603         snprintf(id, sizeof(id), "%f.%d%ld", get_timestamp_millis(), (int)time(NULL), (long) getpid());
604         session->session_id = strdup(id);
605         osrfLogDebug( OSRF_LOG_MARK,  "Building a new client session with id [%s] [%s]",
606                         session->remote_service, session->session_id );
607
608         session->thread_trace = 0;
609         session->state = OSRF_SESSION_DISCONNECTED;
610         session->type = OSRF_SESSION_CLIENT;
611
612         session->userData = NULL;
613         session->userDataFree = NULL;
614
615         // Initialize the hash table
616         int i;
617         for( i = 0; i < OSRF_REQUEST_HASH_SIZE; ++i )
618                 session->request_hash[ i ] = NULL;
619
620         _osrf_app_session_push_session( session );
621         return session;
622 }
623
624 /**
625         @brief Create an osrfAppSession for a server.
626         @param session_id The session ID.  In practice this comes from the thread member of
627         the transport message from the client.
628         @param our_app The name of the service being provided.
629         @param remote_id Jabber ID of the client.
630         @return Pointer to the newly created osrfAppSession if successful, or NULL upon failure.
631
632         If there is already a session with the specified id, report an error.  Otherwise:
633
634         - Allocate memory for an osrfAppSession.
635         - For talking with Jabber, grab an existing transport_client.  It should have been
636         already set up by a prior call to osrfSystemBootstrapClientResc().
637         - Install a copy of the @a our_app string as remote_service.
638         - Install copies of the @a remote_id string as remote_id and orig_remote_id.
639         - Initialize various other bits and scraps.
640         - Add the session to the global session cache.
641
642         Do @em not respond to the client at this point.
643 */
644 osrfAppSession* osrf_app_server_session_init(
645                 const char* session_id, const char* our_app, const char* remote_id ) {
646
647         osrfLogDebug( OSRF_LOG_MARK, "Initing server session with session id %s, service %s,"
648                         " and remote_id %s", session_id, our_app, remote_id );
649
650         osrfAppSession* session = osrf_app_session_find_session( session_id );
651         if(session) {
652                 osrfLogWarning( OSRF_LOG_MARK, "App session already exists for session id %s",
653                                 session_id );
654                 return NULL;
655         }
656
657         session = safe_malloc(sizeof(osrfAppSession));
658
659         // Grab an existing transport_client for talking with Jabber
660         session->transport_handle = osrfSystemGetTransportClient();
661         if( session->transport_handle == NULL ) {
662                 osrfLogWarning( OSRF_LOG_MARK, "No transport client for service '%s'", our_app );
663                 free(session);
664                 return NULL;
665         }
666
667         // Decide from a config setting whether the session is stateless or not.  However
668         // this determination is pointless because it will immediately be overruled according
669         // to the compile-time macro ASSUME_STATELESS.
670         int stateless = 0;
671         char* statel = osrf_settings_host_value("/apps/%s/stateless", our_app );
672         if( statel )
673                 stateless = atoi( statel );
674         free(statel);
675
676         session->remote_id = strdup(remote_id);
677         session->orig_remote_id = strdup(remote_id);
678         session->session_id = strdup(session_id);
679         session->remote_service = strdup(our_app);
680         session->stateless = stateless;
681
682         #ifdef ASSUME_STATELESS
683         session->stateless = 1;
684         #else
685         session->stateless = 0;
686         #endif
687
688         session->thread_trace = 0;
689         session->state = OSRF_SESSION_DISCONNECTED;
690         session->type = OSRF_SESSION_SERVER;
691         session->session_locale = NULL;
692         session->session_tz = NULL;
693
694         session->userData = NULL;
695         session->userDataFree = NULL;
696         session->transport_error = 0;
697
698         // Initialize the hash table
699         int i;
700         for( i = 0; i < OSRF_REQUEST_HASH_SIZE; ++i )
701                 session->request_hash[ i ] = NULL;
702
703         session->panic = 0;
704         session->outbuf = buffer_init( 4096 );
705
706         _osrf_app_session_push_session( session );
707         return session;
708 }
709
710 /**
711         @brief Create a REQUEST message, send it, and save it for future reference.
712         @param session Pointer to the current session, which has the addressing information.
713         @param params One way of specifying the parameters for the method.
714         @param method_name The name of the method to be called.
715         @param protocol Protocol.
716         @param param_strings Another way of specifying the parameters for the method.
717         @return The request ID of the resulting REQUEST message, or -1 upon error.
718
719         DEPRECATED.  Use osrfAppSessionSendRequest() instead.  It is identical except that it
720         doesn't use the param_strings argument, which is redundant, confusing, and unused.
721
722         If @a params is non-NULL, use it to specify the parameters to the method.  Otherwise
723         use @a param_strings.
724
725         If @a params points to a JSON_ARRAY, then pass each element of the array as a separate
726         parameter.  If @a params points to any other kind of jsonObject, pass it as a single
727         parameter.
728
729         If @a params is NULL, and @a param_strings is not NULL, then each pointer in the
730         osrfStringArray must point to a JSON string encoding a parameter.  Pass them.
731
732         At this writing, all calls to this function use @a params to pass parameters, rather than
733         @a param_strings.
734
735         This function is a thin wrapper for osrfAppSessionMakeLocaleRequest().
736 */
737 int osrfAppSessionMakeRequest(
738                 osrfAppSession* session, const jsonObject* params,
739                 const char* method_name, int protocol, osrfStringArray* param_strings ) {
740
741         osrfLogWarning( OSRF_LOG_MARK, "Function osrfAppSessionMakeRequest() is deprecated; "
742                         "call osrfAppSessionSendRequest() instead" );
743         return osrfAppSessionMakeLocaleRequest( session, params,
744                         method_name, protocol, param_strings, NULL );
745 }
746
747 /**
748         @brief Create a REQUEST message, send it, and save it for future reference.
749         @param session Pointer to the current session, which has the addressing information.
750         @param params One way of specifying the parameters for the method.
751         @param method_name The name of the method to be called.
752         @param protocol Protocol.
753         @return The request ID of the resulting REQUEST message, or -1 upon error.
754
755         If @a params points to a JSON_ARRAY, then pass each element of the array as a separate
756         parameter.  If @a params points to any other kind of jsonObject, pass it as a single
757         parameter.
758
759         This function is a thin wrapper for osrfAppSessionMakeLocaleRequest().
760 */
761 int osrfAppSessionSendRequest( osrfAppSession* session, const jsonObject* params,
762                 const char* method_name, int protocol ) {
763
764         return osrfAppSessionMakeLocaleRequest( session, params,
765                  method_name, protocol, NULL, NULL );
766 }
767
768 /**
769         @brief Create a REQUEST message, send it, and save it for future reference.
770         @param session Pointer to the current session, which has the addressing information.
771         @param params One way of specifying the parameters for the method.
772         @param method_name The name of the method to be called.
773         @param protocol Protocol.
774         @param param_strings Another way of specifying the parameters for the method.
775         @param locale Pointer to a locale string.
776         @return The request ID of the resulting REQUEST message, or -1 upon error.
777
778         See the discussion of osrfAppSessionSendRequest(), which at this writing is the only
779         place that calls this function, except for the similar but deprecated function
780         osrfAppSessionMakeRequest().
781
782         At this writing, the @a param_strings and @a locale parameters are always NULL.
783 */
784 static int osrfAppSessionMakeLocaleRequest(
785                 osrfAppSession* session, const jsonObject* params, const char* method_name,
786                 int protocol, osrfStringArray* param_strings, char* locale ) {
787
788         if(session == NULL) return -1;
789
790         osrfLogMkXid();
791
792         osrfMessage* req_msg = osrf_message_init( REQUEST, ++(session->thread_trace), protocol );
793         osrf_message_set_method(req_msg, method_name);
794
795         if (locale) {
796                 osrf_message_set_locale(req_msg, locale);
797         } else if (session->session_locale) {
798                 osrf_message_set_locale(req_msg, session->session_locale);
799         }
800
801         osrf_message_set_tz(req_msg, session->session_tz);
802
803         if (!current_ingress)
804                 osrfAppSessionSetIngress("opensrf");
805         osrfMessageSetIngress(req_msg, current_ingress);
806
807         if(params) {
808                 osrf_message_set_params(req_msg, params);
809
810         } else {
811
812                 if(param_strings) {
813                         int i;
814                         for(i = 0; i!= param_strings->size ; i++ ) {
815                                 osrf_message_add_param(req_msg,
816                                         osrfStringArrayGetString(param_strings,i));
817                         }
818                 }
819         }
820
821         osrfAppRequest* req = _osrf_app_request_init( session, req_msg );
822         if(_osrf_app_session_send( session, req_msg ) ) {
823                 osrfLogWarning( OSRF_LOG_MARK,  "Error sending request message [%d]",
824                                 session->thread_trace );
825                 _osrf_app_request_free(req);
826                 return -1;
827         }
828
829         osrfLogDebug( OSRF_LOG_MARK,  "Pushing [%d] onto request queue for session [%s] [%s]",
830                         req->request_id, session->remote_service, session->session_id );
831         add_app_request( session, req );
832         return req->request_id;
833 }
834
835 /**
836         @brief Mark an osrfAppRequest (identified by session and ID) as complete.
837         @param session Pointer to the osrfAppSession that owns the request.
838         @param request_id Request ID of the osrfAppRequest.
839 */
840 void osrf_app_session_set_complete( osrfAppSession* session, int request_id ) {
841         if(session == NULL)
842                 return;
843
844         osrfAppRequest* req = find_app_request( session, request_id );
845         if(req)
846                 req->complete = 1;
847 }
848
849 /**
850         @brief Determine whether a osrfAppRequest, identified by session and ID, is complete.
851         @param session Pointer to the osrfAppSession that owns the request.
852         @param request_id Request ID of the osrfAppRequest.
853         @return Non-zero if the request is complete; zero if it isn't, or if it can't be found.
854 */
855 int osrf_app_session_request_complete( const osrfAppSession* session, int request_id ) {
856         if(session == NULL)
857                 return 0;
858
859         osrfAppRequest* req = find_app_request( session, request_id );
860         if(req)
861                 return req->complete;
862
863         return 0;
864 }
865
866 /**
867         @brief Reset the remote ID of a session to its original remote ID.
868         @param session Pointer to the osrfAppSession to be reset.
869 */
870 void osrf_app_session_reset_remote( osrfAppSession* session ){
871         if( session==NULL )
872                 return;
873
874         osrfLogDebug( OSRF_LOG_MARK,  "App Session [%s] [%s] resetting remote id to %s",
875                         session->remote_service, session->session_id, session->orig_remote_id );
876
877         osrf_app_session_set_remote( session, session->orig_remote_id );
878 }
879
880 /**
881         @brief Set a session's remote ID to a specified value.
882         @param session Pointer to the osrfAppSession whose remote ID is to be set.
883         @param remote_id Pointer to the new remote id.
884 */
885 void osrf_app_session_set_remote( osrfAppSession* session, const char* remote_id ) {
886         if( session == NULL || remote_id == NULL )
887                 return;
888
889         if( session->remote_id ) {
890                 if( strlen(session->remote_id) >= strlen(remote_id) ) {
891                         // There's enough room; just copy it
892                         strcpy(session->remote_id, remote_id);
893                 } else {
894                         free(session->remote_id );
895                         session->remote_id = strdup( remote_id );
896                 }
897         } else
898                 session->remote_id = strdup( remote_id );
899 }
900
901 /**
902         @brief Append an osrfMessage to the list of responses to an osrfAppRequest.
903         @param session Pointer to the osrfAppSession that owns the request.
904         @param msg Pointer to the osrfMessage to be added.
905
906         The thread_trace member of the osrfMessage is the request_id of the osrfAppRequest.
907         Find the corresponding request in the session and append the osrfMessage to its list.
908 */
909 void osrf_app_session_push_queue( osrfAppSession* session, osrfMessage* msg ) {
910         if( session && msg ) {
911                 osrfAppRequest* req = find_app_request( session, msg->thread_trace );
912                 if( req )
913                         _osrf_app_request_push_queue( req, msg );
914         }
915 }
916
917 /**
918         @brief Connect to the remote service.
919         @param session Pointer to the osrfAppSession for the service.
920         @return 1 if successful, or 0 if not.
921
922         If already connected, exit immediately, reporting success.  Otherwise, build a CONNECT
923         message and send it to the service.  Wait for up to five seconds for an acknowledgement.
924
925         The timeout value is currently hard-coded.  Perhaps it should be configurable.
926 */
927 int osrfAppSessionConnect( osrfAppSession* session ) {
928
929         if(session == NULL)
930                 return 0;
931
932         if(session->state == OSRF_SESSION_CONNECTED) {
933                 return 1;
934         }
935
936         int timeout = 5; /* XXX CONFIG VALUE */
937
938         osrfLogDebug( OSRF_LOG_MARK,  "AppSession connecting to %s", session->remote_id );
939
940         /* defaulting to protocol 1 for now */
941         osrfMessage* con_msg = osrf_message_init( CONNECT, session->thread_trace, 1 );
942
943         // Address this message to the router
944         osrf_app_session_reset_remote( session );
945         session->state = OSRF_SESSION_CONNECTING;
946         int ret = _osrf_app_session_send( session, con_msg );
947         osrfMessageFree(con_msg);
948         if(ret)
949                 return 0;
950
951         time_t start = time(NULL);
952         time_t remaining = (time_t) timeout;
953
954         // Wait for the acknowledgement.  We look for it repeatedly because, under the covers,
955         // we may receive and process messages other than the one we're looking for.
956         while( session->state != OSRF_SESSION_CONNECTED && remaining >= 0 ) {
957                 osrf_app_session_queue_wait( session, remaining, NULL );
958                 if(session->transport_error) {
959                         osrfLogError(OSRF_LOG_MARK, "cannot communicate with %s", session->remote_service);
960                         return 0;
961                 }
962                 remaining -= (int) (time(NULL) - start);
963         }
964
965         if(session->state == OSRF_SESSION_CONNECTED)
966                 osrfLogDebug( OSRF_LOG_MARK, " * Connected Successfully to %s", session->remote_service );
967
968         if(session->state != OSRF_SESSION_CONNECTED)
969                 return 0;
970
971         return 1;
972 }
973
974 /**
975         @brief Disconnect from the remote service.  No response is expected.
976         @param session Pointer to the osrfAppSession to be disconnected.
977         @return 1 in all cases.
978
979         If we're already disconnected, return immediately without doing anything.  Likewise if
980         we have a stateless session and we're in the process of connecting.  Otherwise, send a
981         DISCONNECT message to the service.
982 */
983 int osrf_app_session_disconnect( osrfAppSession* session){
984         if(session == NULL)
985                 return 1;
986
987         if(session->state == OSRF_SESSION_DISCONNECTED)
988                 return 1;
989
990         if(session->stateless && session->state != OSRF_SESSION_CONNECTED) {
991                 osrfLogDebug( OSRF_LOG_MARK,
992                                 "Exiting disconnect on stateless session %s",
993                                 session->session_id);
994                 return 1;
995         }
996
997         osrfLogDebug(OSRF_LOG_MARK,  "AppSession disconnecting from %s", session->remote_id );
998
999         osrfMessage* dis_msg = osrf_message_init( DISCONNECT, session->thread_trace, 1 );
1000         _osrf_app_session_send( session, dis_msg );
1001         session->state = OSRF_SESSION_DISCONNECTED;
1002
1003         osrfMessageFree( dis_msg );
1004         osrf_app_session_reset_remote( session );
1005         return 1;
1006 }
1007
1008 /**
1009         @brief Resend a request message, as specified by session and request id.
1010         @param session Pointer to the osrfAppSession.
1011         @param req_id Request ID for the request to be resent.
1012         @return Zero if successful, or if the specified request cannot be found; 1 if the
1013         request is already complete, or if the attempt to resend the message fails.
1014
1015         The choice of return codes may seem seem capricious, but at this writing nothing
1016         pays any attention to the return code anyway.
1017 */
1018 int osrf_app_session_request_resend( osrfAppSession* session, int req_id ) {
1019         osrfAppRequest* req = find_app_request( session, req_id );
1020
1021         int rc;
1022         if(req == NULL) {
1023                 rc = 0;
1024         } else if(!req->complete) {
1025                 osrfLogDebug( OSRF_LOG_MARK, "Resending request [%d]", req->request_id );
1026                 rc = _osrf_app_session_send( req->session, req->payload );
1027         } else {
1028                 rc = 1;
1029         }
1030
1031         return rc;
1032 }
1033
1034 /**
1035         @brief Send one or more osrfMessages to the remote service or client.
1036         @param session Pointer to the osrfAppSession responsible for sending the message(s).
1037         @param msgs Pointer to an array of pointers to osrfMessages.
1038         @param size How many messages to send.
1039         @return 0 upon success, or -1 upon failure.
1040 */
1041 static int osrfAppSessionSendBatch( osrfAppSession* session, osrfMessage* msgs[], int size ) {
1042
1043         if( !(session && msgs && size > 0) ) return -1;
1044         int retval = 0;
1045
1046         osrfMessage* msg = msgs[0];
1047
1048         if(msg) {
1049
1050                 // First grab and process any input messages, for any app session.  This gives us
1051                 // a chance to see any CONNECT or DISCONNECT messages that may have arrived.  We
1052                 // may also see some unrelated messages, but we have to process those sooner or
1053                 // later anyway, so we might as well do it now.
1054                 osrf_app_session_queue_wait( session, 0, NULL );
1055
1056                 if(session->state != OSRF_SESSION_CONNECTED)  {
1057
1058                         if(session->stateless) { /* stateless session always send to the root listener */
1059                                 osrf_app_session_reset_remote(session);
1060
1061                         } else {
1062
1063                                 /* do an auto-connect if necessary */
1064                                 if( ! session->stateless &&
1065                                         (msg->m_type != CONNECT) &&
1066                                         (msg->m_type != DISCONNECT) &&
1067                                         (session->state != OSRF_SESSION_CONNECTED) ) {
1068
1069                                         if(!osrfAppSessionConnect( session ))
1070                                                 return -1;
1071                                 }
1072                         }
1073                 }
1074         }
1075
1076         // Translate the collection of osrfMessages into a JSON array
1077         char* string = osrfMessageSerializeBatch(msgs, size);
1078
1079         // Send the JSON as the payload of a transport_message
1080         if( string ) {
1081                 retval = osrfSendTransportPayload( session, string );
1082                 free(string);
1083         }
1084
1085         return retval;
1086 }
1087
1088 /**
1089         @brief Split a given string into one or more transport result messages and send it
1090         @param session Pointer to the osrfAppSession responsible for sending the message(s).
1091         @param request_id Request ID of the osrfAppRequest.
1092         @param payload A string to be sent via Jabber.
1093         @param payload_size length of payload
1094         @param chunk_size chunk_size to use
1095
1096         @return 0 upon success, or -1 upon failure.
1097 */
1098 int osrfSendChunkedResult(
1099         osrfAppSession* session, int request_id, const char* payload,
1100         size_t payload_size, size_t chunk_size ) {
1101
1102         // chunking payload
1103         int i;
1104         for (i = 0; i < payload_size; i += chunk_size) {
1105                 osrfMessage* msg = osrf_message_init(RESULT, request_id, 1);
1106                 osrf_message_set_status_info(msg,
1107                         "osrfResultPartial",
1108                         "Partial Response",
1109                         OSRF_STATUS_PARTIAL
1110                 );
1111
1112                 // see how long this chunk is.  If this is the last
1113                 // chunk, it will likely be less than chunk_size
1114                 int partial_size = strlen(&payload[i]);
1115                 if (partial_size > chunk_size)
1116                         partial_size = chunk_size;
1117
1118                 // substr(data, i, partial_size)
1119                 char partial_buf[partial_size + 1];
1120                 memcpy(partial_buf, &payload[i], partial_size);
1121                 partial_buf[partial_size] = '\0';
1122
1123                 // package the partial chunk as a JSON string object
1124                 jsonObject*  partial_obj = jsonNewObject(partial_buf);
1125                 osrf_message_set_result(msg, partial_obj);
1126                 jsonObjectFree(partial_obj);
1127
1128                 // package the osrf message within an array then
1129                 // serialize to json for delivery
1130                 jsonObject* arr = jsonNewObject(NULL);
1131
1132                 // msg json freed when arr is freed
1133                 jsonObjectPush(arr, osrfMessageToJSON(msg));
1134                 char* json = jsonObjectToJSON(arr);
1135
1136                 osrfSendTransportPayload(session, json);
1137                 osrfMessageFree(msg);
1138                 jsonObjectFree(arr);
1139                 free(json);
1140         }
1141
1142         // all chunks sent; send the final partial-complete msg
1143         osrfMessage* msg = osrf_message_init(RESULT, request_id, 1);
1144         osrf_message_set_status_info(msg,
1145                 "osrfResultPartialComplete",
1146                 "Partial Response Finalized",
1147                 OSRF_STATUS_NOCONTENT
1148         );
1149
1150         jsonObject* arr = jsonNewObject(NULL);
1151         jsonObjectPush(arr, osrfMessageToJSON(msg));
1152         char* json = jsonObjectToJSON(arr);
1153         osrfSendTransportPayload(session, json);
1154         osrfMessageFree(msg);
1155         jsonObjectFree(arr);
1156         free(json);
1157
1158         return 0;
1159 }
1160
1161 /**
1162         @brief Wrap a given string in a transport message and send it.
1163         @param session Pointer to the osrfAppSession responsible for sending the message(s).
1164         @param payload A string to be sent via Jabber.
1165         @return 0 upon success, or -1 upon failure.
1166
1167         In practice the payload is normally a JSON string, but this function assumes nothing
1168         about it.
1169 */
1170 int osrfSendTransportPayload( osrfAppSession* session, const char* payload ) {
1171         transport_message* t_msg = message_init(
1172                 payload, "", session->session_id, session->remote_id, NULL );
1173         message_set_osrf_xid( t_msg, osrfLogGetXid() );
1174
1175         int retval = client_send_message( session->transport_handle, t_msg );
1176         if( retval ) {
1177                 osrfLogError( OSRF_LOG_MARK, "client_send_message failed, exit()ing immediately" );
1178                 exit(99);
1179         }
1180
1181         osrfLogInfo(OSRF_LOG_MARK, "[%s] sent %d bytes of data to %s",
1182                 session->remote_service, strlen( payload ), t_msg->recipient );
1183
1184         osrfLogDebug( OSRF_LOG_MARK, "Sent: %s", payload );
1185
1186         message_free( t_msg );
1187         return retval;
1188 }
1189
1190 /**
1191         @brief Send a single osrfMessage to the remote service or client.
1192         @param session Pointer to the osrfAppSession.
1193         @param msg Pointer to the osrfMessage to be sent.
1194         @return zero upon success, or 1 upon failure.
1195
1196         A thin wrapper.  Create an array of one element, and pass it to osrfAppSessionSendBatch().
1197 */
1198 static int _osrf_app_session_send( osrfAppSession* session, osrfMessage* msg ){
1199         if( !(session && msg) )
1200                 return 1;
1201         osrfMessage* a[1];
1202         a[0] = msg;
1203         return  - osrfAppSessionSendBatch( session, a, 1 );
1204 }
1205
1206
1207 /**
1208         @brief Wait for any input messages to arrive, and process them as needed.
1209         @param session Pointer to the osrfAppSession whose transport_session we will use.
1210         @param timeout How many seconds to wait for the first input message.
1211         @param recvd Pointer to an boolean int.  If you receive at least one message, set the boolean
1212         to true; otherwise set it to false.
1213         @return 0 upon success (even if a timeout occurs), or -1 upon failure.
1214
1215         A thin wrapper for osrf_stack_process().  The timeout applies only to the first
1216         message; process subsequent messages if they are available, but don't wait for them.
1217
1218         The first parameter identifies an osrfApp session, but all we really use it for is to
1219         get a pointer to the transport_session.  Typically, a given process opens only a single
1220         transport_session (to talk to the Jabber server), and all app sessions in that process
1221         use the same transport_session.
1222
1223         Hence this function indiscriminately waits for input messages for all osrfAppSessions
1224         tied to the same Jabber session, not just the one specified.
1225
1226         Dispatch each message to the appropriate processing routine, depending on its type
1227         and contents, and on whether we're acting as a client or as a server for that message.
1228         For example, a response to a request may be appended to the input queue of the
1229         relevant request.  A server session receiving a REQUEST message may execute the
1230         requested method.  And so forth.
1231 */
1232 int osrf_app_session_queue_wait( osrfAppSession* session, int timeout, int* recvd ){
1233         if(session == NULL) return 0;
1234         osrfLogDebug(OSRF_LOG_MARK, "AppSession in queue_wait with timeout %d", timeout );
1235         return osrf_stack_process(session->transport_handle, timeout, recvd);
1236 }
1237
1238 /**
1239         @brief Shut down and destroy an osrfAppSession.
1240         @param session Pointer to the osrfAppSession to be destroyed.
1241
1242         If this is a client session, send a DISCONNECT message.
1243
1244         Remove the session from the global session cache.
1245
1246         Free all associated resources, including any pending osrfAppRequests.
1247 */
1248 void osrfAppSessionFree( osrfAppSession* session ){
1249         if(session == NULL) return;
1250
1251         /* Disconnect */
1252
1253         osrfLogDebug(OSRF_LOG_MARK,  "AppSession [%s] [%s] destroying self and deleting requests",
1254                         session->remote_service, session->session_id );
1255         /* disconnect if we're a client */
1256         if(session->type == OSRF_SESSION_CLIENT
1257                         && session->state != OSRF_SESSION_DISCONNECTED ) {
1258                 osrfMessage* dis_msg = osrf_message_init( DISCONNECT, session->thread_trace, 1 );
1259                 _osrf_app_session_send( session, dis_msg );
1260                 osrfMessageFree(dis_msg);
1261         }
1262
1263         /* Remove self from the global session cache */
1264
1265         osrfHashRemove( osrfAppSessionCache, session->session_id );
1266
1267         /* Free the memory */
1268
1269         if( session->userDataFree && session->userData )
1270                 session->userDataFree(session->userData);
1271
1272         if(session->session_locale)
1273                 free(session->session_locale);
1274
1275         if(session->session_tz)
1276                 free(session->session_tz);
1277
1278         free(session->remote_id);
1279         free(session->orig_remote_id);
1280         free(session->session_id);
1281         free(session->remote_service);
1282
1283         // Free the request hash
1284         int i;
1285         for( i = 0; i < OSRF_REQUEST_HASH_SIZE; ++i ) {
1286                 osrfAppRequest* app = session->request_hash[ i ];
1287                 while( app ) {
1288                         osrfAppRequest* next = app->next;
1289                         _osrf_app_request_free( app );
1290                         app = next;
1291                 }
1292         }
1293
1294         if( session->outbuf )
1295                 buffer_free( session->outbuf );
1296
1297         free(session);
1298 }
1299
1300 /**
1301         @brief Wait for a response to a given request, subject to a timeout.
1302         @param session Pointer to the osrfAppSession that owns the request.
1303         @param req_id Request ID for the request.
1304         @param timeout How many seconds to wait.
1305         @return A pointer to the received osrfMessage if one arrives; otherwise NULL.
1306
1307         A thin wrapper.  Given a session and a request ID, look up the corresponding request
1308         and pass it to _osrf_app_request_recv().
1309 */
1310 osrfMessage* osrfAppSessionRequestRecv(
1311                 osrfAppSession* session, int req_id, int timeout ) {
1312         if(req_id < 0 || session == NULL)
1313                 return NULL;
1314         osrfAppRequest* req = find_app_request( session, req_id );
1315         return _osrf_app_request_recv( req, timeout );
1316 }
1317
1318 /**
1319         @brief In response to a specified request, send a payload of data to a client.
1320         @param ses Pointer to the osrfAppSession that owns the request.
1321         @param requestId Request ID of the osrfAppRequest.
1322         @param data Pointer to a jsonObject containing the data payload.
1323         @return 0 upon success, or -1 upon failure.
1324
1325         Translate the jsonObject to a JSON string, and send it wrapped in a RESULT message.
1326
1327         The only failure detected is if either of the two pointer parameters is NULL.
1328 */
1329 int osrfAppRequestRespond( osrfAppSession* ses, int requestId, const jsonObject* data ) {
1330         if( !ses || ! data )
1331                 return -1;
1332
1333         osrfMessage* msg = osrf_message_init( RESULT, requestId, 1 );
1334         osrf_message_set_status_info( msg, NULL, "OK", OSRF_STATUS_OK );
1335         char* json = jsonObjectToJSON( data );
1336
1337         osrf_message_set_result_content( msg, json );
1338         _osrf_app_session_send( ses, msg );
1339
1340         free(json);
1341         osrfMessageFree( msg );
1342
1343         return 0;
1344 }
1345
1346
1347 /**
1348         @brief Send one or two messages to a client in response to a specified request.
1349         @param ses Pointer to the osrfAppSession that owns the request.
1350         @param requestId Request ID of the osrfAppRequest.
1351         @param data Pointer to a jsonObject containing the data payload.
1352         @return  Zero in all cases.
1353
1354         If the @a data parameter is not NULL, translate the jsonObject into a JSON string, and
1355         incorporate that string into a RESULT message as as the payload .  Also build a STATUS
1356         message indicating that the response is complete.  Send both messages bundled together
1357         in the same transport_message.
1358
1359         If the @a data parameter is NULL, send only a STATUS message indicating that the response
1360         is complete.
1361 */
1362 int osrfAppRequestRespondComplete(
1363                 osrfAppSession* ses, int requestId, const jsonObject* data ) {
1364
1365         osrfMessage* status = osrf_message_init( STATUS, requestId, 1);
1366         osrf_message_set_status_info( status, "osrfConnectStatus", "Request Complete",
1367                         OSRF_STATUS_COMPLETE );
1368
1369         if (data) {
1370                 char* json = jsonObjectToJSON(data);
1371                 size_t raw_size = strlen(json);
1372                 size_t extra_size = osrfXmlEscapingLength(json);
1373                 size_t data_size = raw_size + extra_size;
1374                 size_t chunk_size = OSRF_MSG_CHUNK_SIZE;
1375
1376                 if (data_size > chunk_size) // calculate an escape-scaled chunk size
1377                         chunk_size = ((double)raw_size / (double)data_size) * (double)chunk_size;
1378
1379                 if (chunk_size > 0 && chunk_size < raw_size) {
1380                         // chunking -- response message exceeds max message size.
1381                         // break it up into chunks for partial delivery
1382
1383                         osrfSendChunkedResult(ses, requestId, json, raw_size, chunk_size);
1384                         osrfAppSessionSendBatch( ses, &status, 1 );
1385
1386                 } else {
1387                         // message doesn't need to be chunked
1388                         osrfMessage* payload = osrf_message_init( RESULT, requestId, 1 );
1389                         osrf_message_set_status_info( payload, NULL, "OK", OSRF_STATUS_OK );
1390
1391                         osrf_message_set_result_content( payload, json );
1392
1393                         osrfMessage* ms[2];
1394                         ms[0] = payload;
1395                         ms[1] = status;
1396
1397                         osrfAppSessionSendBatch( ses, ms, 2 );
1398
1399                         osrfMessageFree( payload );
1400                 }
1401
1402                 free(json);
1403
1404         } else {
1405                 osrfAppSessionSendBatch( ses, &status, 1 );
1406         }
1407
1408         osrfMessageFree( status );
1409
1410         return 0;
1411 }
1412
1413 /**
1414         @brief Send a STATUS message, for a specified request, back to the client.
1415         @param ses Pointer to the osrfAppSession connected to the client.
1416         @param type A numeric code denoting the status.
1417         @param name A string naming the status.
1418         @param reqId The request ID of the request.
1419         @param message A brief message describing the status.
1420         @return 0 upon success, or -1 upon failure.
1421
1422         The only detected failure is when the @a ses parameter is NULL.
1423 */
1424 int osrfAppSessionStatus( osrfAppSession* ses, int type,
1425                 const char* name, int reqId, const char* message ) {
1426
1427         if(ses) {
1428                 osrfMessage* msg = osrf_message_init( STATUS, reqId, 1);
1429                 osrf_message_set_status_info( msg, name, message, type );
1430                 _osrf_app_session_send( ses, msg );
1431                 osrfMessageFree( msg );
1432                 return 0;
1433         } else
1434                 return -1;
1435 }
1436
1437 /**
1438         @brief Free the global session cache.
1439
1440         Note that the osrfHash that implements the global session cache does @em not have a
1441         callback function installed for freeing its cargo.  As a result, any remaining
1442         osrfAppSessions are leaked, along with all the osrfAppRequests and osrfMessages they
1443         own.
1444 */
1445 void osrfAppSessionCleanup( void ) {
1446         osrfHashFree(osrfAppSessionCache);
1447         osrfAppSessionCache = NULL;
1448 }
1449
1450 /**
1451         @brief Arrange for immediate termination of the process.
1452         @param ses Pointer to the current osrfAppSession.
1453
1454         Typical use case: a server drone loses its database connection, thereby becoming useless.
1455         It terminates so that it will not receive further requests, being unable to service them.
1456 */
1457 void osrfAppSessionPanic( osrfAppSession* ses ) {
1458         if( ses )
1459                 ses->panic = 1;
1460 }