]> git.evergreen-ils.org Git - OpenSRF.git/blob - src/libopensrf/osrf_application.c
141d39f84e113821c5034eed261b65ddac67bebb
[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 #define OSRF_MSG_BUFFER_SIZE     10240
52
53 /**
54         @brief Represent an Application.
55 */
56 typedef struct {
57         void* handle;               /**< Handle to the shared object library. */
58         osrfHash* methods;          /**< Registry of method names. */
59         void (*onExit) (void);      /**< Exit handler for the application. */
60 } osrfApplication;
61
62 static osrfMethod* _osrfAppBuildMethod( const char* methodName, const char* symbolName,
63                 const char* notes, int argc, int options, void* );
64 static void osrfAppSetOnExit(osrfApplication* app, const char* appName);
65 static void _osrfAppRegisterSysMethods( const char* app );
66 static inline osrfApplication* _osrfAppFindApplication( const char* name );
67 static inline osrfMethod* osrfAppFindMethod( osrfApplication* app, const char* methodName );
68 static int _osrfAppRespond( osrfMethodContext* context, const jsonObject* data, int complete );
69 static int _osrfAppPostProcess( osrfMethodContext* context, int retcode );
70 static int _osrfAppRunSystemMethod(osrfMethodContext* context);
71 static void _osrfAppSetIntrospectMethod( osrfMethodContext* ctx, const osrfMethod* method,
72                 jsonObject* resp );
73 static int osrfAppIntrospect( osrfMethodContext* ctx );
74 static int osrfAppIntrospectAll( osrfMethodContext* ctx );
75 static int osrfAppEcho( osrfMethodContext* ctx );
76
77 /**
78         @brief Registry of applications.
79
80         The key of the hash is the application name, and the associated data is an osrfApplication.
81 */
82 static osrfHash* _osrfAppHash = NULL;
83
84 /**
85         @brief Register an application.
86         @param appName Name of the application.
87         @param soFile Name of the shared object file to be loaded for this application.
88         @return Zero if successful, or -1 upon error.
89
90         Open the shared object file and call its osrfAppInitialize() function, if it has one.
91         Register the standard system methods for it.  Arrange for the application name to
92         appear in subsequent log messages.
93 */
94 int osrfAppRegisterApplication( const char* appName, const char* soFile ) {
95         if( !appName || ! soFile ) return -1;
96         char* error;
97
98         osrfLogSetAppname( appName );
99
100         if( !_osrfAppHash )
101                 _osrfAppHash = osrfNewHash();
102
103         osrfLogInfo( OSRF_LOG_MARK, "Registering application %s with file %s", appName, soFile );
104
105         void* handle = dlopen( soFile, RTLD_NOW );
106         if( ! handle ) {
107                 const char* msg = dlerror();
108                 osrfLogWarning( OSRF_LOG_MARK, "Failed to dlopen library file %s: %s", soFile, msg );
109                 return -1;
110         }
111
112         osrfApplication* app = safe_malloc(sizeof(osrfApplication));
113         app->handle = handle;
114         app->onExit = NULL;
115         app->methods = osrfNewHash();
116         osrfHashSet( _osrfAppHash, app, appName );
117
118         /* see if we can run the initialize method */
119         int (*init) (void);
120         *(void **) (&init) = dlsym(app->handle, "osrfAppInitialize");
121
122         if( (error = dlerror()) != NULL ) {
123                 osrfLogWarning( OSRF_LOG_MARK,
124                         "! Unable to locate method symbol [osrfAppInitialize] for app %s: %s",
125                         appName, error );
126
127         } else {
128
129                 /* run the method */
130                 int ret;
131                 if( (ret = (*init)()) ) {
132                         osrfLogWarning( OSRF_LOG_MARK, "Application %s returned non-zero value from "
133                                         "'osrfAppInitialize', not registering...", appName );
134                         //free(app->name); /* need a method to remove an application from the list */
135                         //free(app);
136                         return ret;
137                 }
138         }
139
140         _osrfAppRegisterSysMethods(appName);
141
142         osrfLogInfo( OSRF_LOG_MARK, "Application %s registered successfully", appName );
143
144         osrfAppSetOnExit(app, appName);
145
146         return 0;
147 }
148
149 /**
150         @brief Save a pointer to the application's exit function.
151         @param app Pointer to the osrfApplication.
152         @param appName Application name (used only for log messages).
153
154         Look in the shared object for a symbol named "osrfAppChildExit".  If you find one, save
155         it as a pointer to the application's exit function.  If present, this function will be
156         called when a server's child process (a so-called "drone") is shutting down.
157 */
158 static void osrfAppSetOnExit(osrfApplication* app, const char* appName) {
159         if(!(app && appName)) return;
160
161         /* see if we can run the initialize method */
162         char* error;
163         void (*onExit) (void);
164         *(void **) (&onExit) = dlsym(app->handle, "osrfAppChildExit");
165
166         if( (error = dlerror()) != NULL ) {
167                 osrfLogDebug(OSRF_LOG_MARK, "No exit handler defined for %s", appName);
168                 return;
169         }
170
171         osrfLogInfo(OSRF_LOG_MARK, "registering exit handler for %s", appName);
172         app->onExit = (*onExit);
173 }
174
175 /**
176         @brief Run the application-specific child initialization function for a given application.
177         @param appname Name of the application.
178         @return Zero if successful, or if the application has no child initialization function; -1
179         if the application is not registered, or if the function returns non-zero.
180
181         The child initialization function must be named "osrfAppChildInit" within the shared
182         object library.  It initializes a drone process of a server.
183 */
184 int osrfAppRunChildInit(const char* appname) {
185         osrfApplication* app = _osrfAppFindApplication(appname);
186         if(!app) return -1;
187
188         char* error;
189         int ret;
190         int (*childInit) (void);
191
192         *(void**) (&childInit) = dlsym(app->handle, "osrfAppChildInit");
193
194         if( (error = dlerror()) != NULL ) {
195                 osrfLogInfo( OSRF_LOG_MARK, "No child init defined for app %s : %s", appname, error);
196                 return 0;
197         }
198
199         if( (ret = (*childInit)()) ) {
200                 osrfLogError(OSRF_LOG_MARK, "App %s child init failed", appname);
201                 return -1;
202         }
203
204         osrfLogInfo(OSRF_LOG_MARK, "%s child init succeeded", appname);
205         return 0;
206 }
207
208 /**
209         @brief Call the exit handler for every application that has one.
210
211         Normally a server's child process (a so-called "drone") calls this function just before
212         shutting down.
213 */
214 void osrfAppRunExitCode( void ) {
215         osrfHashIterator* itr = osrfNewHashIterator(_osrfAppHash);
216         osrfApplication* app;
217         while( (app = osrfHashIteratorNext(itr)) ) {
218                 if( app->onExit ) {
219                         osrfLogInfo(OSRF_LOG_MARK, "Running onExit handler for app %s",
220                                 osrfHashIteratorKey(itr) );
221                         app->onExit();
222                 }
223         }
224         osrfHashIteratorFree(itr);
225 }
226
227 /**
228         @brief Register a method for a specified application.
229
230         @param appName Name of the application that implements the method.
231         @param methodName The fully qualified name of the method.
232         @param symbolName The symbol name (function name) that implements the method.
233         @param notes Public documentation for this method.
234         @param argc The minimum number of arguments for the function.
235         @param options Bit switches setting various options.
236         @return Zero on success, or -1 on error.
237
238         Registering a method enables us to call the right function when a client requests a
239         method call.
240
241         The @a options parameter is zero or more of the following macros, OR'd together:
242
243         - OSRF_METHOD_SYSTEM        called by static linkage (shouldn't be used here)
244         - OSRF_METHOD_STREAMING     method may return more than one response
245         - OSRF_METHOD_ATOMIC        return all responses collected in a single RESULT message
246         - OSRF_METHOD_CACHABLE      cache results in memcache
247
248         If the OSRF_METHOD_STREAMING bit is set, also register an ".atomic" version of the method.
249 */
250 int osrfAppRegisterMethod( const char* appName, const char* methodName,
251                 const char* symbolName, const char* notes, int argc, int options ) {
252
253         return osrfAppRegisterExtendedMethod(
254                         appName,
255                         methodName,
256                         symbolName,
257                         notes,
258                         argc,
259                         options,
260                         NULL
261         );
262 }
263
264 /**
265         @brief Register a method for a specified application.
266
267         @param appName Name of the application that implements the method.
268         @param methodName The fully qualified name of the method.
269         @param symbolName The symbol name (function name) that implements the method.
270         @param notes Public documentation for this method.
271         @param argc How many arguments this method expects.
272         @param options Bit switches setting various options.
273         @param user_data Opaque pointer to be passed to the dynamically called function.
274         @return Zero on success, or -1 on error.
275
276         This function is identical to osrfAppRegisterMethod(), except that it also installs
277         a method-specific opaque pointer.  When we call the corresponding function at
278         run time, this pointer will be available to the function via the method context.
279 */
280 int osrfAppRegisterExtendedMethod( const char* appName, const char* methodName,
281                 const char* symbolName, const char* notes, int argc, int options, void * user_data ) {
282
283         if( !appName || ! methodName  ) return -1;
284
285         osrfApplication* app = _osrfAppFindApplication(appName);
286         if(!app) {
287                 osrfLogWarning( OSRF_LOG_MARK, "Unable to locate application %s", appName );
288                 return -1;
289         }
290
291         osrfLogDebug( OSRF_LOG_MARK, "Registering method %s for app %s", methodName, appName );
292
293         osrfMethod* method = _osrfAppBuildMethod(
294                 methodName, symbolName, notes, argc, options, user_data );
295         method->options = options;
296
297         /* plug the method into the list of methods */
298         osrfHashSet( app->methods, method, method->name );
299
300         if( options & OSRF_METHOD_STREAMING ) { /* build the atomic counterpart */
301                 int newops = options | OSRF_METHOD_ATOMIC;
302                 osrfMethod* atomicMethod = _osrfAppBuildMethod(
303                         methodName, symbolName, notes, argc, newops, user_data );
304                 osrfHashSet( app->methods, atomicMethod, atomicMethod->name );
305         }
306
307         return 0;
308 }
309
310 /**
311         @brief Allocate and populate an osrfMethod.
312         @param methodName Name of the method.
313         @param symbolName Name of the function that implements the method.
314         @param notes Remarks documenting the method.
315         @param argc Minimum number of arguments to the method.
316         @param options Bit switches setting various options.
317         @param user_data An opaque pointer to be passed in the method context.
318         @return Pointer to the newly allocated osrfMethod.
319 */
320 static osrfMethod* _osrfAppBuildMethod( const char* methodName, const char* symbolName,
321                 const char* notes, int argc, int options, void* user_data ) {
322
323         osrfMethod* method      = safe_malloc(sizeof(osrfMethod));
324
325         if( !methodName )
326                 methodName = "";  // should never happen
327
328         if( options & OSRF_METHOD_ATOMIC ) {
329                 // Append ".atomic" to the name, and make the method atomic
330                 char mb[ strlen( methodName ) + 8 ];
331                 sprintf( mb, "%s.atomic", methodName );
332                 method->name        = strdup( mb );
333                 options |= OSRF_METHOD_STREAMING;
334         } else {
335                 method->name        = strdup(methodName);
336         }
337
338         if(symbolName)
339                 method->symbol      = strdup(symbolName);
340         else
341                 method->symbol      = NULL;
342
343         if(notes)
344                 method->notes       = strdup(notes);
345         else
346                 method->notes       = NULL;
347
348         method->argc            = argc;
349         method->options         = options;
350
351         if(user_data)
352                 method->userData    = user_data;
353
354         return method;
355 }
356
357 /**
358         @brief Register all of the system methods for this application.
359         @param app Application name.
360
361         A client can call these methods the same way it calls application-specific methods,
362         but they are implemented by functions here in this module, not by functions in the
363         shared object.
364 */
365 static void _osrfAppRegisterSysMethods( const char* app ) {
366
367         osrfAppRegisterMethod(
368                         app, OSRF_SYSMETHOD_INTROSPECT, NULL,
369                         "Return a list of methods whose names have the same initial "
370                         "substring as that of the provided method name PARAMS( methodNameSubstring )",
371                         1, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING );
372
373         osrfAppRegisterMethod(
374                         app, OSRF_SYSMETHOD_INTROSPECT_ALL, NULL,
375                         "Returns a complete list of methods. PARAMS()", 0,
376                         OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING );
377
378         osrfAppRegisterMethod(
379                         app, OSRF_SYSMETHOD_ECHO, NULL,
380                         "Echos all data sent to the server back to the client. PARAMS([a, b, ...])", 0,
381                         OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING );
382 }
383
384 /**
385         @brief Look up an application by name in the application registry.
386         @param name The name of the application.
387         @return Pointer to the corresponding osrfApplication if found, or NULL if not.
388 */
389 static inline osrfApplication* _osrfAppFindApplication( const char* name ) {
390         return (osrfApplication*) osrfHashGet(_osrfAppHash, name);
391 }
392
393 /**
394         @brief Look up a method by name for a given application.
395         @param app Pointer to the osrfApplication that owns the method.
396         @param methodName Name of the method to find.
397         @return Pointer to the corresponding osrfMethod if found, or NULL if not.
398 */
399 static inline osrfMethod* osrfAppFindMethod( osrfApplication* app, const char* methodName ) {
400         if( !app ) return NULL;
401         return (osrfMethod*) osrfHashGet( app->methods, methodName );
402 }
403
404 /**
405         @brief Look up a method by name for an application with a given name.
406         @param appName Name of the osrfApplication.
407         @param methodName Name of the method to find.
408         @return Pointer to the corresponding osrfMethod if found, or NULL if not.
409 */
410 osrfMethod* _osrfAppFindMethod( const char* appName, const char* methodName ) {
411         if( !appName ) return NULL;
412         return osrfAppFindMethod( _osrfAppFindApplication(appName), methodName );
413 }
414
415 /**
416         @brief Call the function that implements a specified method.
417         @param appName Name of the application.
418         @param methodName Name of the method.
419         @param ses Pointer to the current application session.
420         @param reqId The request id of the request invoking the method.
421         @param params Pointer to a jsonObject encoding the parameters to the method.
422         @return Zero if successful, or -1 upon failure.
423
424         If we can't find a function corresponding to the method, or if we call it and it returns
425         a negative return code, send a STATUS message to the client to report an exception.
426
427         A return code of -1 means that the @a appName, @a methodName, or @a ses parameter was NULL.
428 */
429 int osrfAppRunMethod( const char* appName, const char* methodName,
430                 osrfAppSession* ses, int reqId, jsonObject* params ) {
431
432         if( !(appName && methodName && ses) ) return -1;
433
434         // Find the application, and then find the method for it
435         osrfApplication* app = _osrfAppFindApplication(appName);
436         if( !app )
437                 return osrfAppRequestRespondException( ses,
438                                 reqId, "Application not found: %s", appName );
439
440         osrfMethod* method = osrfAppFindMethod( app, methodName );
441         if( !method )
442                 return osrfAppRequestRespondException( ses, reqId,
443                                 "Method [%s] not found for service %s", methodName, appName );
444
445         #ifdef OSRF_STRICT_PARAMS
446         if( method->argc > 0 ) {
447                 // Make sure that the client has passed at least the minimum number of arguments.
448                 if(!params || params->type != JSON_ARRAY || params->size < method->argc )
449                         return osrfAppRequestRespondException( ses, reqId,
450                                 "Not enough params for method %s / service %s", methodName, appName );
451         }
452         #endif
453
454         // Build an osrfMethodContext, which we will pass by pointer to the function.
455         osrfMethodContext context;
456
457         context.session = ses;
458         context.method = method;
459         context.params = params;
460         context.request = reqId;
461         context.responses = NULL;
462
463         int retcode = 0;
464
465         if( method->options & OSRF_METHOD_SYSTEM ) {
466                 retcode = _osrfAppRunSystemMethod(&context);
467
468         } else {
469
470                 // Function pointer through which we will call the function dynamically
471                 int (*meth) (osrfMethodContext*);
472
473                 // Open the function that implements the method
474                 meth = dlsym(app->handle, method->symbol);
475
476                 const char* error = dlerror();
477                 if( error != NULL ) {
478                         return osrfAppRequestRespondException( ses, reqId,
479                                 "Unable to execute method [%s] for service %s", methodName, appName );
480                 }
481
482                 // Run it
483                 retcode = meth( &context );
484         }
485
486         if(retcode < 0)
487                 return osrfAppRequestRespondException(
488                                 ses, reqId, "An unknown server error occurred" );
489
490         retcode = _osrfAppPostProcess( &context, retcode );
491
492         if( context.responses )
493                 jsonObjectFree( context.responses );
494         return retcode;
495 }
496
497 /**
498         @brief Either send or enqueue a response to a client.
499         @param ctx Pointer to the current method context.
500         @param data Pointer to the response, in the form of a jsonObject.
501         @return Zero if successful, or -1 upon error.  The only recognized errors are if either
502         the @a context pointer or its method pointer is NULL.
503
504         For an atomic method, add a copy of the response data to a cache within the method
505         context, to be sent later.  Otherwise, send a RESULT message to the client, with the
506         results in @a data.
507
508         Note that, for an atomic method, this function is equivalent to osrfAppRespondComplete():
509         we send the STATUS message after the method returns, and not before.
510 */
511 int osrfAppRespond( osrfMethodContext* ctx, const jsonObject* data ) {
512         return _osrfAppRespond( ctx, data, 0 );
513 }
514
515 /**
516         @brief Either send or enqueue a response to a client, with a completion notice.
517         @param context Pointer to the current method context.
518         @param data Pointer to the response, in the form of a jsonObject.
519         @return Zero if successful, or -1 upon error.  The only recognized errors are if either
520         the @a context pointer or its method pointer is NULL.
521
522         For an atomic method, add a copy of the response data to a cache within the method
523         context, to be sent later.  Otherwise, send a RESULT message to the client, with the
524         results in @a data.  Also send a STATUS message to indicate that the response is complete.
525
526         Note that, for an atomic method, this function is equivalent to osrfAppRespond(): we
527         send the STATUS message after the method returns, and not before.
528 */
529 int osrfAppRespondComplete( osrfMethodContext* context, const jsonObject* data ) {
530         return _osrfAppRespond( context, data, 1 );
531 }
532
533 /**
534         @brief Send any response messages that have accumulated in the output buffer.
535         @param ses Pointer to the current application session.
536         @param outbuf Pointer to the output buffer.
537         @return Zero if successful, or -1 if not.
538
539         Used only by servers to respond to clients.
540 */
541 static int flush_responses( osrfAppSession* ses, growing_buffer* outbuf ) {
542
543         // Collect any inbound traffic on the socket(s).  This doesn't accomplish anything for the
544         // immediate task at hand, but it may help to keep TCP from getting clogged in some cases.
545         osrf_app_session_queue_wait( ses, 0, NULL );
546
547         int rc = 0;
548         if( buffer_length( outbuf ) > 0 ) {    // If there's anything to send...
549                 buffer_add_char( outbuf, ']' );    // Close the JSON array
550                 if( osrfSendTransportPayload( ses, OSRF_BUFFER_C_STR( ses->outbuf ))) {
551                         osrfLogError( OSRF_LOG_MARK, "Unable to flush response buffer" );
552                         rc = -1;
553                 }
554         }
555         buffer_reset( ses->outbuf );
556         return rc;
557 }
558
559 /**
560         @brief Add a message to an output buffer.
561         @param outbuf Pointer to the output buffer.
562         @param msg Pointer to the message to be added, in the form of a JSON string.
563
564         Since the output buffer is in the form of a JSON array, prepend a left bracket to the
565         first message, and a comma to subsequent ones.
566
567         Used only by servers to respond to clients.
568 */
569 static inline void append_msg( growing_buffer* outbuf, const char* msg ) {
570         if( outbuf && msg ) {
571                 char prefix = buffer_length( outbuf ) > 0 ? ',' : '[';
572                 buffer_add_char( outbuf, prefix );
573                 buffer_add( outbuf, msg );
574         }
575 }
576
577 /**
578         @brief Either send or enqueue a response to a client, optionally with a completion notice.
579         @param ctx Pointer to the method context.
580         @param data Pointer to the response, in the form of a jsonObject.
581         @param complete Boolean: if true, we will accompany the RESULT message with a STATUS
582         message indicating that the response is complete.
583         @return Zero if successful, or -1 upon error.
584
585         For an atomic method, add a copy of the response data to a cache within the method
586         context, to be sent later.  In this case the @a complete parameter has no effect,
587         because we'll send the STATUS message later when we send the cached results.
588
589         If the method is not atomic, translate the message into JSON and append it to a buffer,
590         flushing the buffer as needed to avoid overflow.  If @a complete is true, append
591         a STATUS message (as JSON) to the buffer and flush the buffer.
592 */
593 static int _osrfAppRespond( osrfMethodContext* ctx, const jsonObject* data, int complete ) {
594         if(!(ctx && ctx->method)) return -1;
595
596         if( ctx->method->options & OSRF_METHOD_ATOMIC ) {
597                 osrfLogDebug( OSRF_LOG_MARK,
598                         "Adding responses to stash for atomic method %s", ctx->method->name );
599
600                 // If we don't already have one, create a JSON_ARRAY to serve as a cache.
601                 if( ctx->responses == NULL )
602                         ctx->responses = jsonNewObjectType( JSON_ARRAY );
603
604                 // Add a copy of the data object to the cache.
605                 if ( data != NULL )
606                         jsonObjectPush( ctx->responses, jsonObjectClone(data) );
607         } else {
608                 osrfLogDebug( OSRF_LOG_MARK,
609                         "Adding responses to stash for method %s", ctx->method->name );
610
611                 if( data ) {
612                         // If you want to flush the intput buffers for every output message,
613                         // this is the place to do it.
614                         //osrf_app_session_queue_wait( ctx->session, 0, NULL );
615
616                         // Create an OSRF message
617                         osrfMessage* msg = osrf_message_init( RESULT, ctx->request, 1 );
618                         osrf_message_set_status_info( msg, NULL, "OK", OSRF_STATUS_OK );
619                         osrf_message_set_result( msg, data );
620
621                         // Serialize the OSRF message into JSON text
622                         char* json = jsonObjectToJSON( osrfMessageToJSON( msg ));
623                         osrfMessageFree( msg );
624
625                         // If the new message would overflow the buffer, flush the output buffer first
626                         int len_so_far = buffer_length( ctx->session->outbuf );
627                         if( len_so_far && (strlen( json ) + len_so_far >= OSRF_MSG_BUFFER_SIZE - 3) ) {
628                                 if( flush_responses( ctx->session, ctx->session->outbuf ))
629                                         return -1;
630                         }
631
632                         // Append the JSON text to the output buffer
633                         append_msg( ctx->session->outbuf, json );
634                         free( json );
635                 }
636
637                 if(complete) {
638                         // Create a STATUS message
639                         osrfMessage* status_msg = osrf_message_init( STATUS, ctx->request, 1 );
640                         osrf_message_set_status_info( status_msg, "osrfConnectStatus", "Request Complete",
641                                 OSRF_STATUS_COMPLETE );
642
643                         // Serialize the STATUS message into JSON text
644                         char* json = jsonObjectToJSON( osrfMessageToJSON( status_msg ));
645                         osrfMessageFree( status_msg );
646
647                         // Add the STATUS message to the output buffer.
648                         // It's short, so don't worry about avoiding overflow.
649                         append_msg( ctx->session->outbuf, json );
650                         free( json );
651
652                         // Flush the output buffer, sending any accumulated messages.
653                         if( flush_responses( ctx->session, ctx->session->outbuf ))
654                                 return -1;
655                 }
656         }
657
658         return 0;
659 }
660
661 /**
662         @brief Finish up the processing of a request.
663         @param ctx Pointer to the method context.
664         @param retcode The return code from the method's function.
665         @return 0 if successfull, or -1 upon error.
666
667         For an atomic method: send whatever responses we have been saving up, together with a
668         STATUS message to say that we're finished.
669
670         For a non-atomic method: if the return code from the method is greater than zero, just
671         send the STATUS message.  If the return code is zero, do nothing; the method presumably
672         sent the STATUS message on its own.
673 */
674 static int _osrfAppPostProcess( osrfMethodContext* ctx, int retcode ) {
675         if(!(ctx && ctx->method)) return -1;
676
677         osrfLogDebug( OSRF_LOG_MARK, "Postprocessing method %s with retcode %d",
678                         ctx->method->name, retcode );
679
680         if(ctx->responses) {
681                 // We have cached responses to return, collected in a JSON ARRAY (we haven't sent
682                 // any responses yet).  Now send them all at once, followed by a STATUS message
683                 // to say that we're finished.
684                 osrfAppRequestRespondComplete( ctx->session, ctx->request, ctx->responses );
685
686         } else {
687                 // We have no cached responses to return.
688                 if( retcode > 0 )
689                         // Send a STATUS message to say that we're finished.
690                         osrfAppSessionStatus( ctx->session, OSRF_STATUS_COMPLETE,
691                                         "osrfConnectStatus", ctx->request, "Request Complete" );
692         }
693
694         return 0;
695 }
696
697 /**
698         @brief Send a STATUS message to the client, notifying it of an error.
699         @param ses Pointer to the current application session.
700         @param request Request ID of the request.
701         @param msg A printf-style format string defining an explanatory message to be sent to
702         the client.  Subsequent parameters, if any, will be formatted and inserted into the
703         resulting output string.
704         @return -1 if the @a ses parameter is NULL; otherwise zero.
705 */
706 int osrfAppRequestRespondException( osrfAppSession* ses, int request, const char* msg, ... ) {
707         if(!ses) return -1;
708         if(!msg) msg = "";
709         VA_LIST_TO_STRING(msg);
710         osrfLogWarning( OSRF_LOG_MARK,  "Returning method exception with message: %s", VA_BUF );
711         osrfAppSessionStatus( ses, OSRF_STATUS_NOTFOUND, "osrfMethodException", request,  VA_BUF );
712         return 0;
713 }
714
715 /**
716         @brief Introspect a specified method.
717         @param ctx Pointer to the method context.
718         @param method Pointer to the osrfMethod for the specified method.
719         @param resp Pointer to the jsonObject into which method information will be placed.
720
721         Treating the @a resp object as a JSON_HASH, insert entries for various bits of information
722         about the specified method.
723 */
724 static void _osrfAppSetIntrospectMethod( osrfMethodContext* ctx, const osrfMethod* method,
725                 jsonObject* resp ) {
726         if(!(ctx && resp)) return;
727
728         jsonObjectSetKey(resp, "api_name",  jsonNewObject(method->name));
729         jsonObjectSetKey(resp, "method",    jsonNewObject(method->symbol));
730         jsonObjectSetKey(resp, "service",   jsonNewObject(ctx->session->remote_service));
731         jsonObjectSetKey(resp, "notes",     jsonNewObject(method->notes));
732         jsonObjectSetKey(resp, "argc",      jsonNewNumberObject(method->argc));
733
734         jsonObjectSetKey(resp, "sysmethod",
735                         jsonNewNumberObject( (method->options & OSRF_METHOD_SYSTEM) ? 1 : 0 ));
736         jsonObjectSetKey(resp, "atomic",
737                         jsonNewNumberObject( (method->options & OSRF_METHOD_ATOMIC) ? 1 : 0 ));
738         jsonObjectSetKey(resp, "cachable",
739                         jsonNewNumberObject( (method->options & OSRF_METHOD_CACHABLE) ? 1 : 0 ));
740 }
741
742 /**
743         @brief Run the requested system method.
744         @param ctx The method context.
745         @return Zero if the method is run successfully; -1 if the method was not run; 1 if the
746         method was run and the application code now needs to send a 'request complete' message.
747
748         A system method is a well known method implemented here for all servers.  Instead of
749         looking in the shared object, branch on the method name and call the corresponding
750         function.
751 */
752 static int _osrfAppRunSystemMethod(osrfMethodContext* ctx) {
753         if( osrfMethodVerifyContext( ctx ) < 0 ) {
754                 osrfLogError( OSRF_LOG_MARK,  "_osrfAppRunSystemMethod: Received invalid method context" );
755                 return -1;
756         }
757
758         if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ALL ) ||
759                         !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ALL_ATOMIC )) {
760                 return osrfAppIntrospectAll(ctx);
761         }
762
763         if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT ) ||
764                         !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ATOMIC )) {
765                 return osrfAppIntrospect(ctx);
766         }
767
768         if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_ECHO ) ||
769                         !strcmp(ctx->method->name, OSRF_SYSMETHOD_ECHO_ATOMIC )) {
770                 return osrfAppEcho(ctx);
771         }
772
773         osrfAppRequestRespondException( ctx->session,
774                         ctx->request, "System method implementation not found");
775
776         return 0;
777 }
778
779 /**
780         @brief Run the introspect method for a specified method or group of methods.
781         @param ctx Pointer to the method context.
782         @return 1 if successful, or if no search target is specified as a parameter; -1 if unable
783         to find a pointer to the application.
784
785         Traverse the list of methods, and report on each one whose name starts with the specified
786         search target.  In effect, the search target ends with an implicit wild card.
787 */
788 static int osrfAppIntrospect( osrfMethodContext* ctx ) {
789
790         // Get the name of the method to introspect
791         const char* methodSubstring = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
792         if( !methodSubstring )
793                 return 1; /* respond with no methods */
794
795         // Get a pointer to the application
796         osrfApplication* app = _osrfAppFindApplication( ctx->session->remote_service );
797         if( !app )
798                 return -1;   // Oops, no application...
799
800         int len = 0;
801         osrfHashIterator* itr = osrfNewHashIterator(app->methods);
802         osrfMethod* method;
803
804         while( (method = osrfHashIteratorNext(itr)) ) {
805                 if( (len = strlen(methodSubstring)) <= strlen(method->name) ) {
806                         if( !strncmp( method->name, methodSubstring, len) ) {
807                                 jsonObject* resp = jsonNewObject(NULL);
808                                 _osrfAppSetIntrospectMethod( ctx, method, resp );
809                                 osrfAppRespond(ctx, resp);
810                                 jsonObjectFree(resp);
811                         }
812                 }
813         }
814
815         osrfHashIteratorFree(itr);
816         return 1;
817 }
818
819 /**
820         @brief Run the implement_all method.
821         @param ctx Pointer to the method context.
822         @return 1 if successful, or -1 if unable to find a pointer to the application.
823
824         Report on all of the methods of the application.
825 */
826 static int osrfAppIntrospectAll( osrfMethodContext* ctx ) {
827         osrfApplication* app = _osrfAppFindApplication( ctx->session->remote_service );
828
829         if(app) {
830                 osrfHashIterator* itr = osrfNewHashIterator(app->methods);
831                 osrfMethod* method;
832                 while( (method = osrfHashIteratorNext(itr)) ) {
833                         jsonObject* resp = jsonNewObject(NULL);
834                         _osrfAppSetIntrospectMethod( ctx, method, resp );
835                         osrfAppRespond(ctx, resp);
836                         jsonObjectFree(resp);
837                 }
838                 osrfHashIteratorFree(itr);
839                 return 1;
840         } else
841                 return -1;
842 }
843
844 /**
845         @brief Run the echo method.
846         @param ctx Pointer to the method context.
847         @return -1 if the method context is invalid or corrupted; otherwise 1.
848
849         Send the client a copy of each parameter.
850 */
851 static int osrfAppEcho( osrfMethodContext* ctx ) {
852         if( osrfMethodVerifyContext( ctx ) < 0 ) {
853                 osrfLogError( OSRF_LOG_MARK,  "osrfAppEcho: Received invalid method context" );
854                 return -1;
855         }
856
857         int i;
858         for( i = 0; i < ctx->params->size; i++ ) {
859                 const jsonObject* str = jsonObjectGetIndex(ctx->params,i);
860                 osrfAppRespond(ctx, str);
861         }
862         return 1;
863 }
864
865 /**
866         @brief Perform a series of sanity tests on an osrfMethodContext.
867         @param ctx Pointer to the osrfMethodContext to be checked.
868         @return Zero if the osrfMethodContext passes all tests, or -1 if it doesn't.
869 */
870 int osrfMethodVerifyContext( osrfMethodContext* ctx )
871 {
872         if( !ctx ) {
873                 osrfLogError( OSRF_LOG_MARK,  "Context is NULL in app request" );
874                 return -1;
875         }
876
877         if( !ctx->session ) {
878                 osrfLogError( OSRF_LOG_MARK, "Session is NULL in app request" );
879                 return -1;
880         }
881
882         if( !ctx->method )
883         {
884                 osrfLogError( OSRF_LOG_MARK, "Method is NULL in app request" );
885                 return -1;
886         }
887
888         if( ctx->method->argc ) {
889                 if( !ctx->params ) {
890                         osrfLogError( OSRF_LOG_MARK,
891                                 "Params is NULL in app request %s", ctx->method->name );
892                         return -1;
893                 }
894                 if( ctx->params->type != JSON_ARRAY ) {
895                         osrfLogError( OSRF_LOG_MARK,
896                                 "'params' is not a JSON array for method %s", ctx->method->name );
897                         return -1;
898                 }
899         }
900
901         if( !ctx->method->name ) {
902                 osrfLogError( OSRF_LOG_MARK, "Method name is NULL" );
903                  return -1;
904         }
905
906         // Log the call, with the method and parameters
907         char* params_str = jsonObjectToJSON( ctx->params );
908         if( params_str ) {
909                 osrfLogInfo( OSRF_LOG_MARK, "CALL:\t%s %s - %s",
910                          ctx->session->remote_service, ctx->method->name, params_str );
911                 free( params_str );
912         }
913         return 0;
914 }