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