]> git.evergreen-ils.org Git - OpenSRF.git/blob - src/libopensrf/osrf_application.c
Tidying up various things; nothing very substantial.
[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 appears to be unused.
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 /* Some well known parameters */
39 #define OSRF_SYSMETHOD_INTROSPECT               "opensrf.system.method"
40 #define OSRF_SYSMETHOD_INTROSPECT_ATOMIC        "opensrf.system.method.atomic"
41 #define OSRF_SYSMETHOD_INTROSPECT_ALL           "opensrf.system.method.all"
42 #define OSRF_SYSMETHOD_INTROSPECT_ALL_ATOMIC    "opensrf.system.method.all.atomic"
43 #define OSRF_SYSMETHOD_ECHO                     "opensrf.system.echo"
44 #define OSRF_SYSMETHOD_ECHO_ATOMIC              "opensrf.system.echo.atomic"
45
46 /**
47         @brief Represent an Application.
48 */
49 typedef struct {
50         void* handle;               /**< Handle to the shared object library. */
51         osrfHash* methods;          /**< Registry of method names. */
52         void (*onExit) (void);      /**< Exit handler for the application. */
53 } osrfApplication;
54
55 static osrfMethod* _osrfAppBuildMethod( const char* methodName, const char* symbolName,
56                 const char* notes, int argc, int options, void* );
57 static void osrfAppSetOnExit(osrfApplication* app, const char* appName);
58 static int _osrfAppRegisterSysMethods( const char* app );
59 static inline osrfApplication* _osrfAppFindApplication( const char* name );
60 static inline osrfMethod* osrfAppFindMethod( osrfApplication* app, const char* methodName );
61 static int _osrfAppRespond( osrfMethodContext* context, const jsonObject* data, int complete );
62 static int _osrfAppPostProcess( osrfMethodContext* context, int retcode );
63 static int _osrfAppRunSystemMethod(osrfMethodContext* context);
64 static int osrfAppIntrospect( osrfMethodContext* ctx );
65 static int osrfAppIntrospectAll( osrfMethodContext* ctx );
66 static int osrfAppEcho( osrfMethodContext* ctx );
67
68 /**
69         Registry of applications.  The key of the hash is the application name, and the associated
70         data is an osrfApplication.
71 */
72 static osrfHash* _osrfAppHash = NULL;
73
74 int osrfAppRegisterApplication( const char* appName, const char* soFile ) {
75         if(!appName || ! soFile) return -1;
76         char* error;
77
78         if(!_osrfAppHash)
79                 _osrfAppHash = osrfNewHash();
80
81         osrfLogInfo( OSRF_LOG_MARK, "Registering application %s with file %s", appName, soFile );
82
83         osrfApplication* app = safe_malloc(sizeof(osrfApplication));
84         app->handle = dlopen (soFile, RTLD_NOW);
85         app->onExit = NULL;
86
87         if(!app->handle) {
88                 osrfLogWarning( OSRF_LOG_MARK, "Failed to dlopen library file %s: %s", soFile, dlerror() );
89                 dlerror(); /* clear the error */
90                 free(app);
91                 return -1;
92         }
93
94         app->methods = osrfNewHash();
95         osrfHashSet( _osrfAppHash, app, appName );
96
97         /* see if we can run the initialize method */
98         int (*init) (void);
99         *(void **) (&init) = dlsym(app->handle, "osrfAppInitialize");
100
101         if( (error = dlerror()) != NULL ) {
102                 osrfLogWarning( OSRF_LOG_MARK,
103                         "! Unable to locate method symbol [osrfAppInitialize] for app %s: %s",
104                 appName, error );
105
106         } else {
107
108                 /* run the method */
109                 int ret;
110                 if( (ret = (*init)()) ) {
111                         osrfLogWarning( OSRF_LOG_MARK, "Application %s returned non-zero value from "
112                                         "'osrfAppInitialize', not registering...", appName );
113                         //free(app->name); /* need a method to remove an application from the list */
114                         //free(app);
115                         return ret;
116                 }
117         }
118
119         _osrfAppRegisterSysMethods(appName);
120
121         osrfLogInfo( OSRF_LOG_MARK, "Application %s registered successfully", appName );
122
123         osrfLogSetAppname(appName);
124
125         osrfAppSetOnExit(app, appName);
126
127         return 0;
128 }
129
130
131 static void osrfAppSetOnExit(osrfApplication* app, const char* appName) {
132         if(!(app && appName)) return;
133
134         /* see if we can run the initialize method */
135         char* error;
136         void (*onExit) (void);
137         *(void **) (&onExit) = dlsym(app->handle, "osrfAppChildExit");
138
139         if( (error = dlerror()) != NULL ) {
140                 osrfLogDebug(OSRF_LOG_MARK, "No exit handler defined for %s", appName);
141                 return;
142         }
143
144         osrfLogInfo(OSRF_LOG_MARK, "registering exit handler for %s", appName);
145         app->onExit = (*onExit);
146 }
147
148
149 /**
150         @brief Run the application-specific child initialization function for a given application.
151         @param appname Name of the application.
152         @return Zero if successful, or if the application has no child initialization function; -1
153         if the application is not registered, or if the function returns non-zero.
154
155         The child initialization function must be named "osrfAppChildInit" within the shared
156         object library.  It initializes a drone process of a server.
157 */
158 int osrfAppRunChildInit(const char* appname) {
159         osrfApplication* app = _osrfAppFindApplication(appname);
160         if(!app) return -1;
161
162         char* error;
163         int ret;
164         int (*childInit) (void);
165
166         *(void**) (&childInit) = dlsym(app->handle, "osrfAppChildInit");
167
168         if( (error = dlerror()) != NULL ) {
169                 osrfLogInfo( OSRF_LOG_MARK, "No child init defined for app %s : %s", appname, error);
170                 return 0;
171         }
172
173         if( (ret = (*childInit)()) ) {
174                 osrfLogError(OSRF_LOG_MARK, "App %s child init failed", appname);
175                 return -1;
176         }
177
178         osrfLogInfo(OSRF_LOG_MARK, "%s child init succeeded", appname);
179         return 0;
180 }
181
182
183 /**
184         @brief Call the exit handler for every application that has one.
185 */
186 void osrfAppRunExitCode( void ) {
187         osrfHashIterator* itr = osrfNewHashIterator(_osrfAppHash);
188         osrfApplication* app;
189         while( (app = osrfHashIteratorNext(itr)) ) {
190                 if( app->onExit ) {
191                         osrfLogInfo(OSRF_LOG_MARK, "Running onExit handler for app %s",
192                                 osrfHashIteratorKey(itr) );
193                         app->onExit();
194                 }
195         }
196         osrfHashIteratorFree(itr);
197 }
198
199
200 int osrfAppRegisterMethod( const char* appName, const char* methodName,
201                 const char* symbolName, const char* notes, int argc, int options ) {
202
203         return osrfAppRegisterExtendedMethod(
204                         appName,
205                         methodName,
206                         symbolName,
207                         notes,
208                         argc,
209                         options,
210                         NULL
211         );
212 }
213
214 int osrfAppRegisterExtendedMethod( const char* appName, const char* methodName,
215                 const char* symbolName, const char* notes, int argc, int options, void * user_data ) {
216
217         if( !appName || ! methodName  ) return -1;
218
219         osrfApplication* app = _osrfAppFindApplication(appName);
220         if(!app) {
221                 osrfLogWarning( OSRF_LOG_MARK, "Unable to locate application %s", appName );
222                 return -1;
223         }
224
225         osrfLogDebug( OSRF_LOG_MARK, "Registering method %s for app %s", methodName, appName );
226
227         osrfMethod* method = _osrfAppBuildMethod(
228                 methodName, symbolName, notes, argc, options, user_data );
229         method->options = options;
230
231         /* plug the method into the list of methods */
232         osrfHashSet( app->methods, method, method->name );
233
234         if( options & OSRF_METHOD_STREAMING ) { /* build the atomic counterpart */
235                 int newops = options | OSRF_METHOD_ATOMIC;
236                 osrfMethod* atomicMethod = _osrfAppBuildMethod(
237                         methodName, symbolName, notes, argc, newops, NULL );
238                 osrfHashSet( app->methods, atomicMethod, atomicMethod->name );
239                 atomicMethod->userData = method->userData;
240         }
241
242         return 0;
243 }
244
245
246
247 static osrfMethod* _osrfAppBuildMethod( const char* methodName, const char* symbolName,
248                 const char* notes, int argc, int options, void* user_data ) {
249
250         osrfMethod* method      = safe_malloc(sizeof(osrfMethod));
251
252         if(methodName)
253                 method->name        = strdup(methodName);
254         else
255                 method->name        = NULL;
256
257         if(symbolName)
258                 method->symbol      = strdup(symbolName);
259         else
260                 method->symbol      = NULL;
261
262         if(notes)
263                 method->notes       = strdup(notes);
264         else
265                 method->notes       = NULL;
266
267         if(user_data)
268                 method->userData    = user_data;
269
270         method->argc            = argc;
271         method->options         = options;
272
273         if(options & OSRF_METHOD_ATOMIC) { /* add ".atomic" to the end of the name */
274                 char mb[strlen(method->name) + 8];
275                 sprintf(mb, "%s.atomic", method->name);
276                 free(method->name);
277                 method->name = strdup(mb);
278                 method->options |= OSRF_METHOD_STREAMING;
279         }
280
281         return method;
282 }
283
284
285 /**
286         Register all of the system methods for this app so that they may be
287         treated the same as other methods.
288 */
289 static int _osrfAppRegisterSysMethods( const char* app ) {
290
291         osrfAppRegisterMethod(
292                         app, OSRF_SYSMETHOD_INTROSPECT, NULL,
293                         "Return a list of methods whose names have the same initial "
294                         "substring as that of the provided method name PARAMS( methodNameSubstring )",
295                         1, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING );
296
297         osrfAppRegisterMethod(
298                         app, OSRF_SYSMETHOD_INTROSPECT_ALL, NULL,
299                         "Returns a complete list of methods. PARAMS()", 0,
300                         OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING );
301
302         osrfAppRegisterMethod(
303                         app, OSRF_SYSMETHOD_ECHO, NULL,
304                         "Echos all data sent to the server back to the client. PARAMS([a, b, ...])", 0,
305                         OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING );
306
307         return 0;
308 }
309
310 /**
311         @brief Look up an application by name in the application registry.
312         @param name The name of the application.
313         @return Pointer to the corresponding osrfApplication if found, or NULL if not.
314 */
315 static inline osrfApplication* _osrfAppFindApplication( const char* name ) {
316         return (osrfApplication*) osrfHashGet(_osrfAppHash, name);
317 }
318
319 /**
320         @brief Look up a method by name for a given application.
321         @param app Pointer to the osrfApplication that owns the method.
322         @param methodName Name of the method to find.
323         @return Pointer to the corresponding osrfMethod if found, or NULL if not.
324 */
325 static inline osrfMethod* osrfAppFindMethod( osrfApplication* app, const char* methodName ) {
326         if( !app ) return NULL;
327         return (osrfMethod*) osrfHashGet( app->methods, methodName );
328 }
329
330 /**
331         @brief Look up a method by name for an application with a given name.
332         @param appName Name of the osrfApplication.
333         @param methodName Name of the method to find.
334         @return Pointer to the corresponding osrfMethod if found, or NULL if not.
335  */
336 osrfMethod* _osrfAppFindMethod( const char* appName, const char* methodName ) {
337         if( !appName ) return NULL;
338         return osrfAppFindMethod( _osrfAppFindApplication(appName), methodName );
339 }
340
341
342 int osrfAppRunMethod( const char* appName, const char* methodName,
343                 osrfAppSession* ses, int reqId, jsonObject* params ) {
344
345         if( !(appName && methodName && ses) ) return -1;
346
347         char* error;
348         osrfApplication* app;
349         osrfMethod* method;
350         osrfMethodContext context;
351
352         context.session = ses;
353         context.params = params;
354         context.request = reqId;
355         context.responses = NULL;
356
357         /* this is the method we're gonna run */
358         int (*meth) (osrfMethodContext*);
359
360         if( !(app = _osrfAppFindApplication(appName)) )
361                 return osrfAppRequestRespondException( ses,
362                                 reqId, "Application not found: %s", appName );
363
364         if( !(method = osrfAppFindMethod( app, methodName )) )
365                 return osrfAppRequestRespondException( ses, reqId,
366                                 "Method [%s] not found for service %s", methodName, appName );
367
368         context.method = method;
369
370         #ifdef OSRF_STRICT_PARAMS
371         if( method->argc > 0 ) {
372                 if(!params || params->type != JSON_ARRAY || params->size < method->argc )
373                         return osrfAppRequestRespondException( ses, reqId,
374                                 "Not enough params for method %s / service %s", methodName, appName );
375         }
376         #endif
377
378         int retcode = 0;
379
380         if( method->options & OSRF_METHOD_SYSTEM ) {
381                 retcode = _osrfAppRunSystemMethod(&context);
382
383         } else {
384
385                 /* open and now run the method */
386                 *(void **) (&meth) = dlsym(app->handle, method->symbol);
387
388                 if( (error = dlerror()) != NULL ) {
389                         return osrfAppRequestRespondException( ses, reqId,
390                                 "Unable to execute method [%s]  for service %s", methodName, appName );
391                 }
392
393                 retcode = (*meth) (&context);
394         }
395
396         if(retcode < 0)
397                 return osrfAppRequestRespondException(
398                                 ses, reqId, "An unknown server error occurred" );
399
400         return _osrfAppPostProcess( &context, retcode );
401
402 }
403
404
405 int osrfAppRespond( osrfMethodContext* ctx, const jsonObject* data ) {
406         return _osrfAppRespond( ctx, data, 0 );
407 }
408
409 int osrfAppRespondComplete( osrfMethodContext* context, const jsonObject* data ) {
410         return _osrfAppRespond( context, data, 1 );
411 }
412
413 static int _osrfAppRespond( osrfMethodContext* ctx, const jsonObject* data, int complete ) {
414         if(!(ctx && ctx->method)) return -1;
415
416         if( ctx->method->options & OSRF_METHOD_ATOMIC ) {
417                 osrfLogDebug( OSRF_LOG_MARK,
418                         "Adding responses to stash for atomic method %s", ctx->method->name );
419
420                 if( ctx->responses == NULL )
421                         ctx->responses = jsonNewObjectType( JSON_ARRAY );
422
423                 if ( data != NULL )
424                         jsonObjectPush( ctx->responses, jsonObjectClone(data) );
425         }
426
427
428         if( !(ctx->method->options & OSRF_METHOD_ATOMIC ) &&
429                         !(ctx->method->options & OSRF_METHOD_CACHABLE) ) {
430
431                 if(complete)
432                         osrfAppRequestRespondComplete( ctx->session, ctx->request, data );
433                 else
434                         osrfAppRequestRespond( ctx->session, ctx->request, data );
435                 return 0;
436         }
437
438         return 0;
439 }
440
441
442 static int _osrfAppPostProcess( osrfMethodContext* ctx, int retcode ) {
443         if(!(ctx && ctx->method)) return -1;
444
445         osrfLogDebug( OSRF_LOG_MARK,  "Postprocessing method %s with retcode %d",
446                         ctx->method->name, retcode );
447
448         if(ctx->responses) { /* we have cached responses to return (no responses have been sent) */
449
450                 osrfAppRequestRespondComplete( ctx->session, ctx->request, ctx->responses );
451                 jsonObjectFree(ctx->responses);
452                 ctx->responses = NULL;
453
454         } else {
455
456                 if( retcode > 0 )
457                         osrfAppSessionStatus( ctx->session, OSRF_STATUS_COMPLETE,
458                                         "osrfConnectStatus", ctx->request, "Request Complete" );
459         }
460
461         return 0;
462 }
463
464 int osrfAppRequestRespondException( osrfAppSession* ses, int request, const char* msg, ... ) {
465         if(!ses) return -1;
466         if(!msg) msg = "";
467         VA_LIST_TO_STRING(msg);
468         osrfLogWarning( OSRF_LOG_MARK,  "Returning method exception with message: %s", VA_BUF );
469         osrfAppSessionStatus( ses, OSRF_STATUS_NOTFOUND, "osrfMethodException", request,  VA_BUF );
470         return 0;
471 }
472
473
474 static void _osrfAppSetIntrospectMethod( osrfMethodContext* ctx, const osrfMethod* method, jsonObject* resp ) {
475         if(!(ctx && resp)) return;
476
477         jsonObjectSetKey(resp, "api_name",  jsonNewObject(method->name));
478         jsonObjectSetKey(resp, "method",    jsonNewObject(method->symbol));
479         jsonObjectSetKey(resp, "service",   jsonNewObject(ctx->session->remote_service));
480         jsonObjectSetKey(resp, "notes",     jsonNewObject(method->notes));
481         jsonObjectSetKey(resp, "argc",      jsonNewNumberObject(method->argc));
482
483         jsonObjectSetKey(resp, "sysmethod",
484                         jsonNewNumberObject( (method->options & OSRF_METHOD_SYSTEM) ? 1 : 0 ));
485         jsonObjectSetKey(resp, "atomic",
486                         jsonNewNumberObject( (method->options & OSRF_METHOD_ATOMIC) ? 1 : 0 ));
487         jsonObjectSetKey(resp, "cachable",
488                         jsonNewNumberObject( (method->options & OSRF_METHOD_CACHABLE) ? 1 : 0 ));
489 }
490
491 /**
492         Tries to run the requested method as a system method.
493         A system method is a well known method that all
494         servers implement.
495         @param context The current method context
496         @return 0 if the method is run successfully, return < 0 means
497         the method was not run, return > 0 means the method was run
498         and the application code now needs to send a 'request complete'
499         message
500 */
501 static int _osrfAppRunSystemMethod(osrfMethodContext* ctx) {
502         if( osrfMethodVerifyContext( ctx ) < 0 ) {
503                 osrfLogError( OSRF_LOG_MARK,  "_osrfAppRunSystemMethod: Received invalid method context" );
504                 return -1;
505         }
506
507         if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ALL ) ||
508                         !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ALL_ATOMIC )) {
509
510                 return osrfAppIntrospectAll(ctx);
511         }
512
513
514         if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT ) ||
515                         !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ATOMIC )) {
516
517                 return osrfAppIntrospect(ctx);
518         }
519
520         if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_ECHO ) ||
521                         !strcmp(ctx->method->name, OSRF_SYSMETHOD_ECHO_ATOMIC )) {
522
523                 return osrfAppEcho(ctx);
524         }
525
526
527         osrfAppRequestRespondException( ctx->session,
528                         ctx->request, "System method implementation not found");
529
530         return 0;
531 }
532
533
534 static int osrfAppIntrospect( osrfMethodContext* ctx ) {
535
536         jsonObject* resp = NULL;
537         const char* methodSubstring = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
538         osrfApplication* app = _osrfAppFindApplication( ctx->session->remote_service );
539         int len = 0;
540
541         if(!methodSubstring) return 1; /* respond with no methods */
542
543         if(app) {
544
545                 osrfHashIterator* itr = osrfNewHashIterator(app->methods);
546                 osrfMethod* method;
547
548                 while( (method = osrfHashIteratorNext(itr)) ) {
549                         if( (len = strlen(methodSubstring)) <= strlen(method->name) ) {
550                                 if( !strncmp( method->name, methodSubstring, len) ) {
551                                         resp = jsonNewObject(NULL);
552                                         _osrfAppSetIntrospectMethod( ctx, method, resp );
553                                         osrfAppRespond(ctx, resp);
554                                         jsonObjectFree(resp);
555                                 }
556                         }
557                 }
558                 osrfHashIteratorFree(itr);
559                 return 1;
560         }
561
562         return -1;
563
564 }
565
566
567 static int osrfAppIntrospectAll( osrfMethodContext* ctx ) {
568         jsonObject* resp = NULL;
569         osrfApplication* app = _osrfAppFindApplication( ctx->session->remote_service );
570
571         if(app) {
572                 osrfHashIterator* itr = osrfNewHashIterator(app->methods);
573                 osrfMethod* method;
574                 while( (method = osrfHashIteratorNext(itr)) ) {
575                         resp = jsonNewObject(NULL);
576                         _osrfAppSetIntrospectMethod( ctx, method, resp );
577                         osrfAppRespond(ctx, resp);
578                         jsonObjectFree(resp);
579                 }
580                 osrfHashIteratorFree(itr);
581                 return 1;
582         }
583
584         return -1;
585 }
586
587 static int osrfAppEcho( osrfMethodContext* ctx ) {
588         if( osrfMethodVerifyContext( ctx ) < 0 ) {
589                 osrfLogError( OSRF_LOG_MARK,  "osrfAppEcho: Received invalid method context" );
590                 return -1;
591         }
592
593         int i;
594         for( i = 0; i < ctx->params->size; i++ ) {
595                 const jsonObject* str = jsonObjectGetIndex(ctx->params,i);
596                 osrfAppRespond(ctx, str);
597         }
598         return 1;
599 }
600
601 /**
602         Determine whether the context looks healthy.
603         Return 0 if it does, or -1 if it doesn't.
604 */
605 int osrfMethodVerifyContext( osrfMethodContext* ctx )
606 {
607         if( !ctx ) {
608                 osrfLogError( OSRF_LOG_MARK,  "Context is NULL in app request" );
609                 return -1;
610         }
611
612         if( !ctx->session ) {
613                 osrfLogError( OSRF_LOG_MARK, "Session is NULL in app request" );
614                 return -1;
615         }
616
617         if( !ctx->method )
618         {
619                 osrfLogError( OSRF_LOG_MARK, "Method is NULL in app request" );
620                 return -1;
621         }
622
623         if( ctx->method->argc ) {
624                 if( !ctx->params ) {
625                         osrfLogError( OSRF_LOG_MARK,
626                                 "Params is NULL in app request %s", ctx->method->name );
627                         return -1;
628                 }
629                 if( ctx->params->type != JSON_ARRAY ) {
630                         osrfLogError( OSRF_LOG_MARK,
631                                 "'params' is not a JSON array for method %s", ctx->method->name );
632                         return -1;
633                 }
634         }
635
636         if( !ctx->method->name ) {
637                 osrfLogError( OSRF_LOG_MARK, "Method name is NULL" );
638                  return -1;
639         }
640
641         // Log the call, with the method and parameters
642         char* params_str = jsonObjectToJSON( ctx->params );
643         if( params_str ) {
644                 osrfLogInfo( OSRF_LOG_MARK, "CALL:\t%s %s - %s",
645                          ctx->session->remote_service, ctx->method->name, params_str );
646                 free( params_str );
647         }
648         return 0;
649 }