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