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