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