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