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