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