]> git.evergreen-ils.org Git - OpenSRF.git/blob - src/libopensrf/osrf_application.c
LP#1612771: C support for receiving chunked responses
[OpenSRF.git] / src / libopensrf / osrf_application.c
1 #include <opensrf/osrf_application.h>
2
3 /**
4         @file osrf_application.c
5         @brief Load and manage shared object libraries.
6
7         Maintain a registry of applications, using an osrfHash keyed on application name,
8
9         For each application, load a shared object library so that we can call
10         application-specific functions dynamically.  In order to map method names to the
11         corresponding functions (i.e. symbol names in the library), maintain a registry of
12         methods, using an osrfHash keyed on method name.
13 */
14
15 // The following macro is commented out because it ia no longer used.
16
17 // Used internally to make sure the method description provided is OK
18 /*
19 #define OSRF_METHOD_VERIFY_DESCRIPTION(app, d) \
20         if(!app) return -1; \
21         if(!d) return -1;\
22         if(!d->name) { \
23                 osrfLogError( OSRF_LOG_MARK,  "No method name provided in description" ), \
24                 return -1; \
25 } \
26         if(!d->symbol) { \
27                 osrfLogError( OSRF_LOG_MARK, "No method symbol provided in description" ), \
28                 return -1; \
29 } \
30         if(!d->notes) \
31                 d->notes = ""; \
32         if(!d->paramNotes) \
33                 d->paramNotes = "";\
34         if(!d->returnNotes) \
35                 d->returnNotes = "";
36 */
37
38 /**
39         @name Well known method names
40         @brief These methods are automatically implemented for every application.
41 */
42 /*@{*/
43 #define OSRF_SYSMETHOD_INTROSPECT               "opensrf.system.method"
44 #define OSRF_SYSMETHOD_INTROSPECT_ATOMIC        "opensrf.system.method.atomic"
45 #define OSRF_SYSMETHOD_INTROSPECT_ALL           "opensrf.system.method.all"
46 #define OSRF_SYSMETHOD_INTROSPECT_ALL_ATOMIC    "opensrf.system.method.all.atomic"
47 #define OSRF_SYSMETHOD_ECHO                     "opensrf.system.echo"
48 #define OSRF_SYSMETHOD_ECHO_ATOMIC              "opensrf.system.echo.atomic"
49 /*@}*/
50
51 /**
52         @name Method options
53         @brief Macros that get OR'd together to form method options.
54
55         These options are in addition to the ones stipulated by the caller of
56         osrfRegisterMethod(), and are not externally visible.
57 */
58 /*@{*/
59 /**
60         @brief  Marks a method as a system method.
61
62         System methods are implemented by generic functions, called via static linkage.  They
63         are not loaded or executed from shared objects.
64 */
65 #define OSRF_METHOD_SYSTEM          1
66 /**
67         @brief  Combines all responses into a single RESULT message.
68
69         For a @em non-atomic method, the server returns each response to the client in a
70         separate RESULT message.  It sends a STATUS message at the end to signify the end of the
71         message stream.
72
73         For an @em atomic method, the server buffers all responses until the method returns,
74         and then sends them all at once in a single RESULT message (followed by a STATUS message).
75         Each individual response is encoded as an entry in a JSON array.  This buffering is
76         transparent to the function that implements the method.
77
78         Atomic methods incur less networking overhead than non-atomic methods, at the risk of
79         creating excessively large RESULT messages.  The HTTP gateway requires the atomic versions
80         of streaming methods because of the stateless nature of the HTTP protocol.
81
82         If OSRF_METHOD_STREAMING is set for a method, the application generates both an atomic
83         and a non-atomic method, whose names are identical except that the atomic one carries a
84         suffix of ".atomic".
85 */
86 #define OSRF_METHOD_ATOMIC          4
87 /*@}*/
88
89 /**
90         @brief Default size of output buffer.
91 */
92 #define OSRF_MSG_BUNDLE_SIZE     10240
93
94 /**
95         @brief Represent an Application.
96 */
97 typedef struct {
98         void* handle;               /**< Handle to the shared object library. */
99         osrfHash* methods;          /**< Registry of method names. */
100         void (*onExit) (void);      /**< Exit handler for the application. */
101 } osrfApplication;
102
103 static void register_method( osrfApplication* app, const char* methodName,
104         const char* symbolName, const char* notes, int argc, int options, void * user_data );
105 static osrfMethod* build_method( const char* methodName, const char* symbolName,
106         const char* notes, int argc, int options, void* );
107 static void osrfAppSetOnExit(osrfApplication* app, const char* appName);
108 static void register_system_methods( osrfApplication* app );
109 static inline osrfApplication* _osrfAppFindApplication( const char* name );
110 static inline osrfMethod* osrfAppFindMethod( osrfApplication* app, const char* methodName );
111 static int _osrfAppRespond( osrfMethodContext* context, const jsonObject* data, int complete );
112 static int _osrfAppPostProcess( osrfMethodContext* context, int retcode );
113 static int _osrfAppRunSystemMethod(osrfMethodContext* context);
114 static void _osrfAppSetIntrospectMethod( osrfMethodContext* ctx, const osrfMethod* method,
115         jsonObject* resp );
116 static int osrfAppIntrospect( osrfMethodContext* ctx );
117 static int osrfAppIntrospectAll( osrfMethodContext* ctx );
118 static int osrfAppEcho( osrfMethodContext* ctx );
119 static void osrfMethodFree( char* name, void* p );
120 static void osrfAppFree( char* name, void* p );
121
122 /**
123         @brief Registry of applications.
124
125         The key of the hash is the application name, and the associated data is an osrfApplication.
126 */
127 static osrfHash* _osrfAppHash = NULL;
128
129 /**
130         @brief Register an application.
131         @param appName Name of the application.
132         @param soFile Name of the shared object file to be loaded for this application.
133         @return Zero if successful, or -1 upon error.
134
135         Open the shared object file and call its osrfAppInitialize() function, if it has one.
136         Register the standard system methods for it.  Arrange for the application name to
137         appear in subsequent log messages.
138 */
139 int osrfAppRegisterApplication( const char* appName, const char* soFile ) {
140         if( !appName || ! soFile ) return -1;
141         char* error;
142
143         osrfLogSetAppname( appName );
144
145         if( !_osrfAppHash ) {
146                 _osrfAppHash = osrfNewHash();
147                 osrfHashSetCallback( _osrfAppHash, osrfAppFree );
148         }
149
150         osrfLogInfo( OSRF_LOG_MARK, "Registering application %s with file %s", appName, soFile );
151
152         // Open the shared object.
153         void* handle = dlopen( soFile, RTLD_NOW );
154         if( ! handle ) {
155                 const char* msg = dlerror();
156                 osrfLogError( OSRF_LOG_MARK, "Failed to dlopen library file %s: %s", soFile, msg );
157                 return -1;
158         }
159
160         // Construct the osrfApplication.
161         osrfApplication* app = safe_malloc(sizeof(osrfApplication));
162         app->handle = handle;
163         app->methods = osrfNewHash();
164         osrfHashSetCallback( app->methods, osrfMethodFree );
165         app->onExit = NULL;
166
167         // Add the newly-constructed app to the list.
168         osrfHashSet( _osrfAppHash, app, appName );
169
170         // Try to run the initialize method.  Typically it will register one or more
171         // methods of the application.
172         int (*init) (void);
173         *(void **) (&init) = dlsym( handle, "osrfAppInitialize" );
174
175         if( (error = dlerror()) != NULL ) {
176                 osrfLogWarning( OSRF_LOG_MARK,
177                         "! Unable to locate method symbol [osrfAppInitialize] for app %s: %s",
178                         appName, error );
179
180         } else {
181
182                 /* run the method */
183                 int ret;
184                 if( (ret = (*init)()) ) {
185                         osrfLogWarning( OSRF_LOG_MARK, "Application %s returned non-zero value from "
186                                 "'osrfAppInitialize', not registering...", appName );
187                         osrfHashRemove( _osrfAppHash, appName );
188                         return ret;
189                 }
190         }
191
192         register_system_methods( app );
193         osrfLogInfo( OSRF_LOG_MARK, "Application %s registered successfully", appName );
194         osrfAppSetOnExit( app, appName );
195
196         return 0;
197 }
198
199 /**
200         @brief Save a pointer to the application's exit function.
201         @param app Pointer to the osrfApplication.
202         @param appName Application name (used only for log messages).
203
204         Look in the shared object for a symbol named "osrfAppChildExit".  If you find one, save
205         it as a pointer to the application's exit function.  If present, this function will be
206         called when a server's child process (a so-called "drone") is shutting down.
207 */
208 static void osrfAppSetOnExit(osrfApplication* app, const char* appName) {
209         if(!(app && appName)) return;
210
211         /* see if we can run the initialize method */
212         char* error;
213         void (*onExit) (void);
214         *(void **) (&onExit) = dlsym(app->handle, "osrfAppChildExit");
215
216         if( (error = dlerror()) != NULL ) {
217                 osrfLogDebug(OSRF_LOG_MARK, "No exit handler defined for %s", appName);
218                 return;
219         }
220
221         osrfLogInfo(OSRF_LOG_MARK, "registering exit handler for %s", appName);
222         app->onExit = (*onExit);
223 }
224
225 /**
226         @brief Run the application-specific child initialization function for a given application.
227         @param appname Name of the application.
228         @return Zero if successful, or if the application has no child initialization function; -1
229         if the application is not registered, or if the function returns non-zero.
230
231         The child initialization function must be named "osrfAppChildInit" within the shared
232         object library.  It initializes a drone process of a server.
233 */
234 int osrfAppRunChildInit(const char* appname) {
235         osrfApplication* app = _osrfAppFindApplication(appname);
236         if(!app) return -1;
237
238         char* error;
239         int ret;
240         int (*childInit) (void);
241
242         *(void**) (&childInit) = dlsym(app->handle, "osrfAppChildInit");
243
244         if( (error = dlerror()) != NULL ) {
245                 osrfLogInfo( OSRF_LOG_MARK, "No child init defined for app %s : %s", appname, error);
246                 return 0;
247         }
248
249         if( (ret = (*childInit)()) ) {
250                 osrfLogError(OSRF_LOG_MARK, "App %s child init failed", appname);
251                 return -1;
252         }
253
254         osrfLogInfo(OSRF_LOG_MARK, "%s child init succeeded", appname);
255         return 0;
256 }
257
258 /**
259         @brief Call the exit handler for every application that has one.
260
261         Normally a server's child process (a so-called "drone") calls this function just before
262         shutting down.
263 */
264 void osrfAppRunExitCode( void ) {
265         osrfHashIterator* itr = osrfNewHashIterator(_osrfAppHash);
266         osrfApplication* app;
267         while( (app = osrfHashIteratorNext(itr)) ) {
268                 if( app->onExit ) {
269                         osrfLogInfo(OSRF_LOG_MARK, "Running onExit handler for app %s",
270                                 osrfHashIteratorKey(itr) );
271                         app->onExit();
272                 }
273         }
274         osrfHashIteratorFree(itr);
275 }
276
277 /**
278         @brief Register a method for a specified application.
279
280         @param appName Name of the application that implements the method.
281         @param methodName The fully qualified name of the method.
282         @param symbolName The symbol name (function name) that implements the method.
283         @param notes Public documentation for this method.
284         @param argc The minimum number of arguments for the function.
285         @param options Bit switches setting various options.
286         @return Zero on success, or -1 on error.
287
288         Registering a method enables us to call the right function when a client requests a
289         method call.
290
291         The @a options parameter is zero or more of the following macros, OR'd together:
292
293         - OSRF_METHOD_STREAMING     method may return more than one response
294         - OSRF_METHOD_CACHABLE      cache results in memcache
295
296         If the OSRF_METHOD_STREAMING bit is set, also register an ".atomic" version of the method.
297 */
298 int osrfAppRegisterMethod( const char* appName, const char* methodName,
299                 const char* symbolName, const char* notes, int argc, int options ) {
300
301         return osrfAppRegisterExtendedMethod(
302                         appName,
303                         methodName,
304                         symbolName,
305                         notes,
306                         argc,
307                         options,
308                         NULL
309         );
310 }
311
312 /**
313         @brief Register an extended method for a specified application.
314
315         @param appName Name of the application that implements the method.
316         @param methodName The fully qualified name of the method.
317         @param symbolName The symbol name (function name) that implements the method.
318         @param notes Public documentation for this method.
319         @param argc How many arguments this method expects.
320         @param options Bit switches setting various options.
321         @param user_data Opaque pointer to be passed to the dynamically called function.
322         @return Zero if successful, or -1 upon error.
323
324         This function is identical to osrfAppRegisterMethod(), except that it also installs
325         a method-specific opaque pointer.  When we call the corresponding function at
326         run time, this pointer will be available to the function via the method context.
327 */
328 int osrfAppRegisterExtendedMethod( const char* appName, const char* methodName,
329         const char* symbolName, const char* notes, int argc, int options, void * user_data ) {
330
331         if( !appName || ! methodName ) return -1;
332
333         osrfApplication* app = _osrfAppFindApplication(appName);
334         if(!app) {
335                 osrfLogWarning( OSRF_LOG_MARK, "Unable to locate application %s", appName );
336                 return -1;
337         }
338
339         osrfLogDebug( OSRF_LOG_MARK, "Registering method %s for app %s", methodName, appName );
340
341         // Extract the only valid option bits, and ignore the rest.
342         int opts = options & ( OSRF_METHOD_STREAMING | OSRF_METHOD_CACHABLE );
343
344         // Build and install a non-atomic method.
345         register_method(
346                 app, methodName, symbolName, notes, argc, opts, user_data );
347
348         if( opts & OSRF_METHOD_STREAMING ) {
349                 // Build and install an atomic version of the same method.
350                 register_method(
351                         app, methodName, symbolName, notes, argc, opts | OSRF_METHOD_ATOMIC, user_data );
352         }
353
354         return 0;
355 }
356
357 /**
358         @brief Register a single method for a specified application.
359
360         @param appName Pointer to the application that implements the method.
361         @param methodName The fully qualified name of the method.
362         @param symbolName The symbol name (function name) that implements the method.
363         @param notes Public documentation for this method.
364         @param argc How many arguments this method expects.
365         @param options Bit switches setting various options.
366         @param user_data Opaque pointer to be passed to the dynamically called function.
367 */
368 static void register_method( osrfApplication* app, const char* methodName,
369         const char* symbolName, const char* notes, int argc, int options, void * user_data ) {
370
371         if( !app || ! methodName ) return;
372
373         // Build a method and add it to the list of methods
374         osrfMethod* method = build_method(
375                 methodName, symbolName, notes, argc, options, user_data );
376         osrfHashSet( app->methods, method, method->name );
377 }
378
379 /**
380         @brief Allocate and populate an osrfMethod.
381         @param methodName Name of the method.
382         @param symbolName Name of the function that implements the method.
383         @param notes Remarks documenting the method.
384         @param argc Minimum number of arguments to the method.
385         @param options Bit switches setting various options.
386         @param user_data An opaque pointer to be passed in the method context.
387         @return Pointer to the newly allocated osrfMethod.
388
389         If OSRF_METHOD_ATOMIC is set, append ".atomic" to the method name.
390 */
391 static osrfMethod* build_method( const char* methodName, const char* symbolName,
392         const char* notes, int argc, int options, void* user_data ) {
393
394         osrfMethod* method      = safe_malloc(sizeof(osrfMethod));
395
396         if( !methodName )
397                 methodName = "";  // should never happen
398
399         if( options & OSRF_METHOD_ATOMIC ) {
400                 // Append ".atomic" to the name.
401                 char mb[ strlen( methodName ) + 8 ];
402                 sprintf( mb, "%s.atomic", methodName );
403                 method->name        = strdup( mb );
404         } else {
405                 method->name        = strdup(methodName);
406         }
407
408         if(symbolName)
409                 method->symbol      = strdup(symbolName);
410         else
411                 method->symbol      = NULL;
412
413         if(notes)
414                 method->notes       = strdup(notes);
415         else
416                 method->notes       = NULL;
417
418         method->argc            = argc;
419         method->options         = options;
420
421         if(user_data)
422                 method->userData    = user_data;
423
424         method->max_bundle_size = OSRF_MSG_BUNDLE_SIZE;
425     method->max_chunk_size  = OSRF_MSG_CHUNK_SIZE;
426         return method;
427 }
428
429 /**
430         @brief Set the effective output buffer size for a given method.
431         @param appName Name of the application.
432         @param methodName Name of the method.
433         @param max_bundle_size Desired size of the output buffer, in bytes.
434         @return Zero if successful, or -1 if the specified method cannot be found.
435
436         A smaller buffer size may result in a lower latency for the first response, since we don't
437         wait for as many messages to accumulate before flushing the output buffer.  On the other
438         hand a larger buffer size may result in higher throughput due to lower network overhead.
439
440         Since the buffer size is not an absolute limit, it may be set to zero, in which case each
441         output transport message will contain no more than one RESULT message.
442
443         This function has no effect on atomic methods, because all responses are sent in a single
444         message anyway.  Likewise it has no effect on a method that returns only a single response.
445 */
446 int osrfMethodSetBundleSize( const char* appName, const char* methodName, size_t max_bundle_size ) {
447         osrfMethod* method = _osrfAppFindMethod( appName, methodName );
448         if( method ) {
449                 osrfLogInfo( OSRF_LOG_MARK,
450                         "Setting outbuf buffer size to %lu for method %s of application %s",
451                         (unsigned long) max_bundle_size, methodName, appName );
452                 method->max_bundle_size = max_bundle_size;
453                 return 0;
454         } else {
455                 osrfLogWarning( OSRF_LOG_MARK,
456                         "Unable to set outbuf buffer size to %lu for method %s of application %s",
457                         (unsigned long) max_bundle_size, methodName, appName );
458                 return -1;
459         }
460 }
461
462 /**
463         @brief Register all of the system methods for this application.
464         @param app Pointer to the application.
465
466         A client can call these methods the same way it calls application-specific methods,
467         but they are implemented by functions here in this module, not by functions in the
468         shared object.
469 */
470 static void register_system_methods( osrfApplication* app ) {
471
472         if( !app ) return;
473
474         register_method(
475                 app, OSRF_SYSMETHOD_INTROSPECT, NULL,
476                 "Return a list of methods whose names have the same initial "
477                 "substring as that of the provided method name PARAMS( methodNameSubstring )",
478                 1, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING,
479                 NULL );
480
481         register_method(
482                 app, OSRF_SYSMETHOD_INTROSPECT, NULL,
483                 "Return a list of methods whose names have the same initial "
484                 "substring as that of the provided method name PARAMS( methodNameSubstring )",
485                 1, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING | OSRF_METHOD_ATOMIC,
486                 NULL );
487
488         register_method(
489                 app, OSRF_SYSMETHOD_INTROSPECT_ALL, NULL,
490                 "Returns a complete list of methods. PARAMS()",
491                 0, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING,
492                 NULL );
493
494         register_method(
495                 app, OSRF_SYSMETHOD_INTROSPECT_ALL, NULL,
496                 "Returns a complete list of methods. PARAMS()",
497                 0, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING | OSRF_METHOD_ATOMIC,
498                 NULL );
499
500         register_method(
501                 app, OSRF_SYSMETHOD_ECHO, NULL,
502                 "Echos all data sent to the server back to the client. PARAMS([a, b, ...])",
503                 0, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING,
504                 NULL );
505
506         register_method(
507                 app, OSRF_SYSMETHOD_ECHO, NULL,
508                 "Echos all data sent to the server back to the client. PARAMS([a, b, ...])",
509                 0, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING | OSRF_METHOD_ATOMIC,
510                 NULL );
511 }
512
513 /**
514         @brief Look up an application by name in the application registry.
515         @param name The name of the application.
516         @return Pointer to the corresponding osrfApplication if found, or NULL if not.
517 */
518 static inline osrfApplication* _osrfAppFindApplication( const char* name ) {
519         return (osrfApplication*) osrfHashGet(_osrfAppHash, name);
520 }
521
522 /**
523         @brief Look up a method by name for a given application.
524         @param app Pointer to the osrfApplication that owns the method.
525         @param methodName Name of the method to find.
526         @return Pointer to the corresponding osrfMethod if found, or NULL if not.
527 */
528 static inline osrfMethod* osrfAppFindMethod( osrfApplication* app, const char* methodName ) {
529         if( !app ) return NULL;
530         return (osrfMethod*) osrfHashGet( app->methods, methodName );
531 }
532
533 /**
534         @brief Look up a method by name for an application with a given name.
535         @param appName Name of the osrfApplication.
536         @param methodName Name of the method to find.
537         @return Pointer to the corresponding osrfMethod if found, or NULL if not.
538 */
539 osrfMethod* _osrfAppFindMethod( const char* appName, const char* methodName ) {
540         if( !appName ) return NULL;
541         return osrfAppFindMethod( _osrfAppFindApplication(appName), methodName );
542 }
543
544 /**
545         @brief Call the function that implements a specified method.
546         @param appName Name of the application.
547         @param methodName Name of the method.
548         @param ses Pointer to the current application session.
549         @param reqId The request id of the request invoking the method.
550         @param params Pointer to a jsonObject encoding the parameters to the method.
551         @return Zero if successful, or -1 upon failure.
552
553         If we can't find a function corresponding to the method, or if we call it and it returns
554         a negative return code, send a STATUS message to the client to report an exception.
555
556         A return code of -1 means that the @a appName, @a methodName, or @a ses parameter was NULL.
557 */
558 int osrfAppRunMethod( const char* appName, const char* methodName,
559                 osrfAppSession* ses, int reqId, jsonObject* params ) {
560
561         if( !(appName && methodName && ses) ) return -1;
562
563         // Find the application, and then find the method for it
564         osrfApplication* app = _osrfAppFindApplication(appName);
565         if( !app )
566                 return osrfAppRequestRespondException( ses,
567                                 reqId, "Application not found: %s", appName );
568
569         osrfMethod* method = osrfAppFindMethod( app, methodName );
570         if( !method )
571                 return osrfAppRequestRespondException( ses, reqId,
572                                 "Method [%s] not found for service %s", methodName, appName );
573
574         #ifdef OSRF_STRICT_PARAMS
575         if( method->argc > 0 ) {
576                 // Make sure that the client has passed at least the minimum number of arguments.
577                 if(!params || params->type != JSON_ARRAY || params->size < method->argc )
578                         return osrfAppRequestRespondException( ses, reqId,
579                                 "Not enough params for method %s / service %s", methodName, appName );
580         }
581         #endif
582
583         // Build an osrfMethodContext, which we will pass by pointer to the function.
584         osrfMethodContext context;
585
586         context.session = ses;
587         context.method = method;
588         context.params = params;
589         context.request = reqId;
590         context.responses = NULL;
591
592         int retcode = 0;
593
594         if( method->options & OSRF_METHOD_SYSTEM ) {
595                 retcode = _osrfAppRunSystemMethod(&context);
596
597         } else {
598
599                 // Function pointer through which we will call the function dynamically
600                 int (*meth) (osrfMethodContext*);
601
602                 // Open the function that implements the method
603                 meth = dlsym(app->handle, method->symbol);
604
605                 const char* error = dlerror();
606                 if( error != NULL ) {
607                         return osrfAppRequestRespondException( ses, reqId,
608                                 "Unable to execute method [%s] for service %s", methodName, appName );
609                 }
610
611                 // Run it
612                 retcode = meth( &context );
613         }
614
615         if(retcode < 0)
616                 return osrfAppRequestRespondException(
617                                 ses, reqId, "An unknown server error occurred" );
618
619         retcode = _osrfAppPostProcess( &context, retcode );
620
621         if( context.responses )
622                 jsonObjectFree( context.responses );
623         return retcode;
624 }
625
626 /**
627         @brief Either send or enqueue a response to a client.
628         @param ctx Pointer to the current method context.
629         @param data Pointer to the response, in the form of a jsonObject.
630         @return Zero if successful, or -1 upon error.  The only recognized errors are if either
631         the @a context pointer or its method pointer is NULL.
632
633         For an atomic method, add a copy of the response data to a cache within the method
634         context, to be sent later.  Otherwise, send a RESULT message to the client, with the
635         results in @a data.
636
637         Note that, for an atomic method, this function is equivalent to osrfAppRespondComplete():
638         we send the STATUS message after the method returns, and not before.
639 */
640 int osrfAppRespond( osrfMethodContext* ctx, const jsonObject* data ) {
641         return _osrfAppRespond( ctx, data, 0 );
642 }
643
644 /**
645         @brief Either send or enqueue a response to a client, with a completion notice.
646         @param context Pointer to the current method context.
647         @param data Pointer to the response, in the form of a jsonObject.
648         @return Zero if successful, or -1 upon error.  The only recognized errors are if either
649         the @a context pointer or its method pointer is NULL.
650
651         For an atomic method, add a copy of the response data to a cache within the method
652         context, to be sent later.  Otherwise, send a RESULT message to the client, with the
653         results in @a data.  Also send a STATUS message to indicate that the response is complete.
654
655         Note that, for an atomic method, this function is equivalent to osrfAppRespond(): we
656         send the STATUS message after the method returns, and not before.
657 */
658 int osrfAppRespondComplete( osrfMethodContext* context, const jsonObject* data ) {
659         return _osrfAppRespond( context, data, 1 );
660 }
661
662 /**
663         @brief Send any response messages that have accumulated in the output buffer.
664         @param ses Pointer to the current application session.
665         @param outbuf Pointer to the output buffer.
666         @return Zero if successful, or -1 if not.
667
668         Used only by servers to respond to clients.
669 */
670 static int flush_responses( osrfAppSession* ses, growing_buffer* outbuf ) {
671
672         // Collect any inbound traffic on the socket(s).  This doesn't accomplish anything for the
673         // immediate task at hand, but it may help to keep TCP from getting clogged in some cases.
674         osrf_app_session_queue_wait( ses, 0, NULL );
675
676         int rc = 0;
677         if( buffer_length( outbuf ) > 0 ) {    // If there's anything to send...
678                 buffer_add_char( outbuf, ']' );    // Close the JSON array
679                 if( osrfSendTransportPayload( ses, OSRF_BUFFER_C_STR( ses->outbuf ))) {
680                         osrfLogError( OSRF_LOG_MARK, "Unable to flush response buffer" );
681                         rc = -1;
682                 }
683         }
684         buffer_reset( ses->outbuf );
685         return rc;
686 }
687
688 /**
689         @brief Add a message to an output buffer.
690         @param outbuf Pointer to the output buffer.
691         @param msg Pointer to the message to be added, in the form of a JSON string.
692
693         Since the output buffer is in the form of a JSON array, prepend a left bracket to the
694         first message, and a comma to subsequent ones.
695
696         Used only by servers to respond to clients.
697 */
698 static inline void append_msg( growing_buffer* outbuf, const char* msg ) {
699         if( outbuf && msg ) {
700                 char prefix = buffer_length( outbuf ) > 0 ? ',' : '[';
701                 buffer_add_char( outbuf, prefix );
702                 buffer_add( outbuf, msg );
703         }
704 }
705
706 /**
707         @brief Either send or enqueue a response to a client, optionally with a completion notice.
708         @param ctx Pointer to the method context.
709         @param data Pointer to the response, in the form of a jsonObject.
710         @param complete Boolean: if true, we will accompany the RESULT message with a STATUS
711         message indicating that the response is complete.
712         @return Zero if successful, or -1 upon error.
713
714         For an atomic method, add a copy of the response data to a cache within the method
715         context, to be sent later.  In this case the @a complete parameter has no effect,
716         because we'll send the STATUS message later when we send the cached results.
717
718         If the method is not atomic, translate the message into JSON and append it to a buffer,
719         flushing the buffer as needed to avoid overflow.  If @a complete is true, append
720         a STATUS message (as JSON) to the buffer and flush the buffer.
721 */
722 static int _osrfAppRespond( osrfMethodContext* ctx, const jsonObject* data, int complete ) {
723         if(!(ctx && ctx->method)) return -1;
724
725         if( ctx->method->options & OSRF_METHOD_ATOMIC ) {
726                 osrfLogDebug( OSRF_LOG_MARK,
727                         "Adding responses to stash for atomic method %s", ctx->method->name );
728
729                 // If we don't already have one, create a JSON_ARRAY to serve as a cache.
730                 if( ctx->responses == NULL )
731                         ctx->responses = jsonNewObjectType( JSON_ARRAY );
732
733                 // Add a copy of the data object to the cache.
734                 if ( data != NULL )
735                         jsonObjectPush( ctx->responses, jsonObjectClone(data) );
736         } else {
737                 osrfLogDebug( OSRF_LOG_MARK,
738                         "Adding responses to stash for method %s", ctx->method->name );
739
740                 if( data ) {
741                         // If you want to flush the intput buffers for every output message,
742                         // this is the place to do it.
743                         //osrf_app_session_queue_wait( ctx->session, 0, NULL );
744
745                         // Create an OSRF message
746                         osrfMessage* msg = osrf_message_init( RESULT, ctx->request, 1 );
747                         osrf_message_set_status_info( msg, NULL, "OK", OSRF_STATUS_OK );
748                         osrf_message_set_result( msg, data );
749
750                         // Serialize the OSRF message into JSON text
751                         char* json = jsonObjectToJSON( osrfMessageToJSON( msg ));
752                         osrfMessageFree( msg );
753
754                         // If the new message would overflow the buffer, flush the output buffer first
755                         int len_so_far = buffer_length( ctx->session->outbuf );
756                         if( len_so_far && (strlen( json ) + len_so_far + 3 >= ctx->method->max_bundle_size )) {
757                                 if( flush_responses( ctx->session, ctx->session->outbuf ))
758                                         return -1;
759                         }
760
761                         // Append the JSON text to the output buffer
762                         append_msg( ctx->session->outbuf, json );
763                         free( json );
764                 }
765
766                 if(complete) {
767                         // Create a STATUS message
768                         osrfMessage* status_msg = osrf_message_init( STATUS, ctx->request, 1 );
769                         osrf_message_set_status_info( status_msg, "osrfConnectStatus", "Request Complete",
770                                 OSRF_STATUS_COMPLETE );
771
772                         // Serialize the STATUS message into JSON text
773                         char* json = jsonObjectToJSON( osrfMessageToJSON( status_msg ));
774                         osrfMessageFree( status_msg );
775
776                         // Add the STATUS message to the output buffer.
777                         // It's short, so don't worry about avoiding overflow.
778                         append_msg( ctx->session->outbuf, json );
779                         free( json );
780
781                         // Flush the output buffer, sending any accumulated messages.
782                         if( flush_responses( ctx->session, ctx->session->outbuf ))
783                                 return -1;
784                 }
785         }
786
787         return 0;
788 }
789
790 /**
791         @brief Finish up the processing of a request.
792         @param ctx Pointer to the method context.
793         @param retcode The return code from the method's function.
794         @return 0 if successfull, or -1 upon error.
795
796         For an atomic method: send whatever responses we have been saving up, together with a
797         STATUS message to say that we're finished.
798
799         For a non-atomic method: if the return code from the method is greater than zero, just
800         send the STATUS message.  If the return code is zero, do nothing; the method presumably
801         sent the STATUS message on its own.
802 */
803 static int _osrfAppPostProcess( osrfMethodContext* ctx, int retcode ) {
804         if(!(ctx && ctx->method)) return -1;
805
806         osrfLogDebug( OSRF_LOG_MARK, "Postprocessing method %s with retcode %d",
807                         ctx->method->name, retcode );
808
809         if(ctx->responses) {
810                 // We have cached atomic responses to return, collected in a JSON ARRAY (we
811                 // haven't sent any responses yet).  Now send them all at once, followed by
812                 // a STATUS message to say that we're finished.
813                 osrfAppRequestRespondComplete( ctx->session, ctx->request, ctx->responses );
814
815         } else {
816                 // We have no cached atomic responses to return, but we may have some
817                 // non-atomic messages waiting in the buffer.
818                 if( retcode > 0 )
819                         // Send a STATUS message to say that we're finished, and to force a
820                         // final flush of the buffer.
821                         osrfAppRespondComplete( ctx, NULL );
822         }
823
824         return 0;
825 }
826
827 /**
828         @brief Send a STATUS message to the client, notifying it of an error.
829         @param ses Pointer to the current application session.
830         @param request Request ID of the request.
831         @param msg A printf-style format string defining an explanatory message to be sent to
832         the client.  Subsequent parameters, if any, will be formatted and inserted into the
833         resulting output string.
834         @return -1 if the @a ses parameter is NULL; otherwise zero.
835 */
836 int osrfAppRequestRespondException( osrfAppSession* ses, int request, const char* msg, ... ) {
837         if(!ses) return -1;
838         if(!msg) msg = "";
839         VA_LIST_TO_STRING(msg);
840         osrfLogWarning( OSRF_LOG_MARK,  "Returning method exception with message: %s", VA_BUF );
841         osrfAppSessionStatus( ses, OSRF_STATUS_NOTFOUND, "osrfMethodException", request,  VA_BUF );
842         return 0;
843 }
844
845 /**
846         @brief Introspect a specified method.
847         @param ctx Pointer to the method context.
848         @param method Pointer to the osrfMethod for the specified method.
849         @param resp Pointer to the jsonObject into which method information will be placed.
850
851         Treating the @a resp object as a JSON_HASH, insert entries for various bits of information
852         about the specified method.
853 */
854 static void _osrfAppSetIntrospectMethod( osrfMethodContext* ctx, const osrfMethod* method,
855                 jsonObject* resp ) {
856         if(!(ctx && resp)) return;
857
858         jsonObjectSetKey(resp, "api_name",  jsonNewObject(method->name));
859         jsonObjectSetKey(resp, "method",    jsonNewObject(method->symbol));
860         jsonObjectSetKey(resp, "service",   jsonNewObject(ctx->session->remote_service));
861         jsonObjectSetKey(resp, "notes",     jsonNewObject(method->notes));
862         jsonObjectSetKey(resp, "argc",      jsonNewNumberObject(method->argc));
863
864         jsonObjectSetKey(resp, "sysmethod",
865                         jsonNewNumberObject( (method->options & OSRF_METHOD_SYSTEM) ? 1 : 0 ));
866         jsonObjectSetKey(resp, "atomic",
867                         jsonNewNumberObject( (method->options & OSRF_METHOD_ATOMIC) ? 1 : 0 ));
868         jsonObjectSetKey(resp, "cachable",
869                         jsonNewNumberObject( (method->options & OSRF_METHOD_CACHABLE) ? 1 : 0 ));
870 }
871
872 /**
873         @brief Run the requested system method.
874         @param ctx The method context.
875         @return Zero if the method is run successfully; -1 if the method was not run; 1 if the
876         method was run and the application code now needs to send a 'request complete' message.
877
878         A system method is a well known method implemented here for all servers.  Instead of
879         looking in the shared object, branch on the method name and call the corresponding
880         function.
881 */
882 static int _osrfAppRunSystemMethod(osrfMethodContext* ctx) {
883         if( osrfMethodVerifyContext( ctx ) < 0 ) {
884                 osrfLogError( OSRF_LOG_MARK,  "_osrfAppRunSystemMethod: Received invalid method context" );
885                 return -1;
886         }
887
888         if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ALL ) ||
889                         !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ALL_ATOMIC )) {
890                 return osrfAppIntrospectAll(ctx);
891         }
892
893         if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT ) ||
894                         !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ATOMIC )) {
895                 return osrfAppIntrospect(ctx);
896         }
897
898         if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_ECHO ) ||
899                         !strcmp(ctx->method->name, OSRF_SYSMETHOD_ECHO_ATOMIC )) {
900                 return osrfAppEcho(ctx);
901         }
902
903         osrfAppRequestRespondException( ctx->session,
904                         ctx->request, "System method implementation not found");
905
906         return 0;
907 }
908
909 /**
910         @brief Run the introspect method for a specified method or group of methods.
911         @param ctx Pointer to the method context.
912         @return 1 if successful, or if no search target is specified as a parameter; -1 if unable
913         to find a pointer to the application.
914
915         Traverse the list of methods, and report on each one whose name starts with the specified
916         search target.  In effect, the search target ends with an implicit wild card.
917 */
918 static int osrfAppIntrospect( osrfMethodContext* ctx ) {
919
920         // Get the name of the method to introspect
921         const char* methodSubstring = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
922         if( !methodSubstring )
923                 return 1; /* respond with no methods */
924
925         // Get a pointer to the application
926         osrfApplication* app = _osrfAppFindApplication( ctx->session->remote_service );
927         if( !app )
928                 return -1;   // Oops, no application...
929
930         int len = 0;
931         osrfHashIterator* itr = osrfNewHashIterator(app->methods);
932         osrfMethod* method;
933
934         while( (method = osrfHashIteratorNext(itr)) ) {
935                 if( (len = strlen(methodSubstring)) <= strlen(method->name) ) {
936                         if( !strncmp( method->name, methodSubstring, len) ) {
937                                 jsonObject* resp = jsonNewObject(NULL);
938                                 _osrfAppSetIntrospectMethod( ctx, method, resp );
939                                 osrfAppRespond(ctx, resp);
940                                 jsonObjectFree(resp);
941                         }
942                 }
943         }
944
945         osrfHashIteratorFree(itr);
946         return 1;
947 }
948
949 /**
950         @brief Run the implement_all method.
951         @param ctx Pointer to the method context.
952         @return 1 if successful, or -1 if unable to find a pointer to the application.
953
954         Report on all of the methods of the application.
955 */
956 static int osrfAppIntrospectAll( osrfMethodContext* ctx ) {
957         osrfApplication* app = _osrfAppFindApplication( ctx->session->remote_service );
958
959         if(app) {
960                 osrfHashIterator* itr = osrfNewHashIterator(app->methods);
961                 osrfMethod* method;
962                 while( (method = osrfHashIteratorNext(itr)) ) {
963                         jsonObject* resp = jsonNewObject(NULL);
964                         _osrfAppSetIntrospectMethod( ctx, method, resp );
965                         osrfAppRespond(ctx, resp);
966                         jsonObjectFree(resp);
967                 }
968                 osrfHashIteratorFree(itr);
969                 return 1;
970         } else
971                 return -1;
972 }
973
974 /**
975         @brief Run the echo method.
976         @param ctx Pointer to the method context.
977         @return -1 if the method context is invalid or corrupted; otherwise 1.
978
979         Send the client a copy of each parameter.
980 */
981 static int osrfAppEcho( osrfMethodContext* ctx ) {
982         if( osrfMethodVerifyContext( ctx ) < 0 ) {
983                 osrfLogError( OSRF_LOG_MARK,  "osrfAppEcho: Received invalid method context" );
984                 return -1;
985         }
986
987         int i;
988         for( i = 0; i < ctx->params->size; i++ ) {
989                 const jsonObject* str = jsonObjectGetIndex(ctx->params,i);
990                 osrfAppRespond(ctx, str);
991         }
992         return 1;
993 }
994
995 /**
996         @brief Perform a series of sanity tests on an osrfMethodContext.
997         @param ctx Pointer to the osrfMethodContext to be checked.
998         @return Zero if the osrfMethodContext passes all tests, or -1 if it doesn't.
999 */
1000 int osrfMethodVerifyContext( osrfMethodContext* ctx )
1001 {
1002         if( !ctx ) {
1003                 osrfLogError( OSRF_LOG_MARK,  "Context is NULL in app request" );
1004                 return -1;
1005         }
1006
1007         if( !ctx->session ) {
1008                 osrfLogError( OSRF_LOG_MARK, "Session is NULL in app request" );
1009                 return -1;
1010         }
1011
1012         if( !ctx->method )
1013         {
1014                 osrfLogError( OSRF_LOG_MARK, "Method is NULL in app request" );
1015                 return -1;
1016         }
1017
1018         if( ctx->method->argc ) {
1019                 if( !ctx->params ) {
1020                         osrfLogError( OSRF_LOG_MARK,
1021                                 "Params is NULL in app request %s", ctx->method->name );
1022                         return -1;
1023                 }
1024                 if( ctx->params->type != JSON_ARRAY ) {
1025                         osrfLogError( OSRF_LOG_MARK,
1026                                 "'params' is not a JSON array for method %s", ctx->method->name );
1027                         return -1;
1028                 }
1029         }
1030
1031         if( !ctx->method->name ) {
1032                 osrfLogError( OSRF_LOG_MARK, "Method name is NULL" );
1033                  return -1;
1034         }
1035
1036         // Log the call, with the method and parameters
1037         char* params_str = jsonObjectToJSON( ctx->params );
1038         if( params_str ) {
1039                 // params_str will at minimum be "[]"
1040                 int i = 0;
1041                 const char* str;
1042                 char* method = ctx->method->name;
1043                 int redact_params = 0;
1044                 while( (str = osrfStringArrayGetString(log_protect_arr, i++)) ) {
1045                         //osrfLogInternal(OSRF_LOG_MARK, "Checking for log protection [%s]", str);
1046                         if(!strncmp(method, str, strlen(str))) {
1047                                 redact_params = 1;
1048                                 break;
1049                         }
1050                 }
1051
1052                 char* params_logged;
1053                 if(redact_params) {
1054                         params_logged = strdup("**PARAMS REDACTED**");
1055                 } else {
1056                         params_str[strlen(params_str) - 1] = '\0'; // drop the trailing ']'
1057                         params_logged = strdup(params_str + 1);
1058                 }
1059                 free( params_str );
1060                 osrfLogInfo( OSRF_LOG_MARK, "CALL: %s %s %s",
1061                         ctx->session->remote_service, ctx->method->name, params_logged);
1062                 free( params_logged );
1063         }
1064         return 0;
1065 }
1066
1067 /**
1068         @brief Free an osrfMethod.
1069         @param name Name of the method (not used).
1070         @param p Void pointer pointing to the osrfMethod.
1071
1072         This function is designed to be installed as a callback for an osrfHash (hence the
1073         unused @a name parameter and the void pointer).
1074 */
1075 static void osrfMethodFree( char* name, void* p ) {
1076         osrfMethod* method = p;
1077         if( method ) {
1078                 free( method->name );
1079                 free( method->symbol );
1080                 free( method->notes );
1081                 free( method );
1082         }
1083 }
1084
1085 /**
1086         @brief Free an osrfApplication
1087         @param name Name of the application (not used).
1088         @param p Void pointer pointing to the osrfApplication.
1089
1090         This function is designed to be installed as a callback for an osrfHash (hence the
1091         unused @a name parameter and the void pointer).
1092 */
1093 static void osrfAppFree( char* name, void* p ) {
1094         osrfApplication* app = p;
1095         if( app ) {
1096                 dlclose( app->handle );
1097                 osrfHashFree( app->methods );
1098                 free( app );
1099         }
1100 }