1 #include <opensrf/osrf_application.h>
4 @file osrf_application.c
5 @brief Load and manage shared object libraries.
7 Maintain a registry of applications, using an osrfHash keyed on application name,
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.
15 // The following macro is commented out because it appears to be unused.
17 // Used internally to make sure the method description provided is OK
19 #define OSRF_METHOD_VERIFY_DESCRIPTION(app, d) \
23 osrfLogError( OSRF_LOG_MARK, "No method name provided in description" ), \
27 osrfLogError( OSRF_LOG_MARK, "No method symbol provided in description" ), \
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"
47 @brief Represent an Application.
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. */
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 );
69 Registry of applications. The key of the hash is the application name, and the associated
70 data is an osrfApplication.
72 static osrfHash* _osrfAppHash = NULL;
74 int osrfAppRegisterApplication( const char* appName, const char* soFile ) {
75 if(!appName || ! soFile) return -1;
79 _osrfAppHash = osrfNewHash();
81 osrfLogInfo( OSRF_LOG_MARK, "Registering application %s with file %s", appName, soFile );
83 osrfApplication* app = safe_malloc(sizeof(osrfApplication));
84 app->handle = dlopen (soFile, RTLD_NOW);
88 osrfLogWarning( OSRF_LOG_MARK, "Failed to dlopen library file %s: %s", soFile, dlerror() );
89 dlerror(); /* clear the error */
94 app->methods = osrfNewHash();
95 osrfHashSet( _osrfAppHash, app, appName );
97 /* see if we can run the initialize method */
99 *(void **) (&init) = dlsym(app->handle, "osrfAppInitialize");
101 if( (error = dlerror()) != NULL ) {
102 osrfLogWarning( OSRF_LOG_MARK,
103 "! Unable to locate method symbol [osrfAppInitialize] for app %s: %s",
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 */
119 _osrfAppRegisterSysMethods(appName);
121 osrfLogInfo( OSRF_LOG_MARK, "Application %s registered successfully", appName );
123 osrfLogSetAppname(appName);
125 osrfAppSetOnExit(app, appName);
131 static void osrfAppSetOnExit(osrfApplication* app, const char* appName) {
132 if(!(app && appName)) return;
134 /* see if we can run the initialize method */
136 void (*onExit) (void);
137 *(void **) (&onExit) = dlsym(app->handle, "osrfAppChildExit");
139 if( (error = dlerror()) != NULL ) {
140 osrfLogDebug(OSRF_LOG_MARK, "No exit handler defined for %s", appName);
144 osrfLogInfo(OSRF_LOG_MARK, "registering exit handler for %s", appName);
145 app->onExit = (*onExit);
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.
155 The child initialization function must be named "osrfAppChildInit" within the shared
156 object library. It initializes a drone process of a server.
158 int osrfAppRunChildInit(const char* appname) {
159 osrfApplication* app = _osrfAppFindApplication(appname);
164 int (*childInit) (void);
166 *(void**) (&childInit) = dlsym(app->handle, "osrfAppChildInit");
168 if( (error = dlerror()) != NULL ) {
169 osrfLogInfo( OSRF_LOG_MARK, "No child init defined for app %s : %s", appname, error);
173 if( (ret = (*childInit)()) ) {
174 osrfLogError(OSRF_LOG_MARK, "App %s child init failed", appname);
178 osrfLogInfo(OSRF_LOG_MARK, "%s child init succeeded", appname);
184 @brief Call the exit handler for every application that has one.
186 void osrfAppRunExitCode( void ) {
187 osrfHashIterator* itr = osrfNewHashIterator(_osrfAppHash);
188 osrfApplication* app;
189 while( (app = osrfHashIteratorNext(itr)) ) {
191 osrfLogInfo(OSRF_LOG_MARK, "Running onExit handler for app %s",
192 osrfHashIteratorKey(itr) );
196 osrfHashIteratorFree(itr);
200 int osrfAppRegisterMethod( const char* appName, const char* methodName,
201 const char* symbolName, const char* notes, int argc, int options ) {
203 return osrfAppRegisterExtendedMethod(
214 int osrfAppRegisterExtendedMethod( const char* appName, const char* methodName,
215 const char* symbolName, const char* notes, int argc, int options, void * user_data ) {
217 if( !appName || ! methodName ) return -1;
219 osrfApplication* app = _osrfAppFindApplication(appName);
221 osrfLogWarning( OSRF_LOG_MARK, "Unable to locate application %s", appName );
225 osrfLogDebug( OSRF_LOG_MARK, "Registering method %s for app %s", methodName, appName );
227 osrfMethod* method = _osrfAppBuildMethod(
228 methodName, symbolName, notes, argc, options, user_data );
229 method->options = options;
231 /* plug the method into the list of methods */
232 osrfHashSet( app->methods, method, method->name );
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;
247 static osrfMethod* _osrfAppBuildMethod( const char* methodName, const char* symbolName,
248 const char* notes, int argc, int options, void* user_data ) {
250 osrfMethod* method = safe_malloc(sizeof(osrfMethod));
253 method->name = strdup(methodName);
258 method->symbol = strdup(symbolName);
260 method->symbol = NULL;
263 method->notes = strdup(notes);
265 method->notes = NULL;
268 method->userData = user_data;
271 method->options = options;
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);
277 method->name = strdup(mb);
278 method->options |= OSRF_METHOD_STREAMING;
286 Register all of the system methods for this app so that they may be
287 treated the same as other methods.
289 static int _osrfAppRegisterSysMethods( const char* app ) {
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 );
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 );
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 );
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.
315 static inline osrfApplication* _osrfAppFindApplication( const char* name ) {
316 return (osrfApplication*) osrfHashGet(_osrfAppHash, name);
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.
325 static inline osrfMethod* osrfAppFindMethod( osrfApplication* app, const char* methodName ) {
326 if( !app ) return NULL;
327 return (osrfMethod*) osrfHashGet( app->methods, methodName );
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.
336 osrfMethod* _osrfAppFindMethod( const char* appName, const char* methodName ) {
337 if( !appName ) return NULL;
338 return osrfAppFindMethod( _osrfAppFindApplication(appName), methodName );
342 int osrfAppRunMethod( const char* appName, const char* methodName,
343 osrfAppSession* ses, int reqId, jsonObject* params ) {
345 if( !(appName && methodName && ses) ) return -1;
348 osrfApplication* app;
350 osrfMethodContext context;
352 context.session = ses;
353 context.params = params;
354 context.request = reqId;
355 context.responses = NULL;
357 /* this is the method we're gonna run */
358 int (*meth) (osrfMethodContext*);
360 if( !(app = _osrfAppFindApplication(appName)) )
361 return osrfAppRequestRespondException( ses,
362 reqId, "Application not found: %s", appName );
364 if( !(method = osrfAppFindMethod( app, methodName )) )
365 return osrfAppRequestRespondException( ses, reqId,
366 "Method [%s] not found for service %s", methodName, appName );
368 context.method = method;
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 );
380 if( method->options & OSRF_METHOD_SYSTEM ) {
381 retcode = _osrfAppRunSystemMethod(&context);
385 /* open and now run the method */
386 *(void **) (&meth) = dlsym(app->handle, method->symbol);
388 if( (error = dlerror()) != NULL ) {
389 return osrfAppRequestRespondException( ses, reqId,
390 "Unable to execute method [%s] for service %s", methodName, appName );
393 retcode = (*meth) (&context);
397 return osrfAppRequestRespondException(
398 ses, reqId, "An unknown server error occurred" );
400 return _osrfAppPostProcess( &context, retcode );
405 int osrfAppRespond( osrfMethodContext* ctx, const jsonObject* data ) {
406 return _osrfAppRespond( ctx, data, 0 );
409 int osrfAppRespondComplete( osrfMethodContext* context, const jsonObject* data ) {
410 return _osrfAppRespond( context, data, 1 );
413 static int _osrfAppRespond( osrfMethodContext* ctx, const jsonObject* data, int complete ) {
414 if(!(ctx && ctx->method)) return -1;
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 );
420 if( ctx->responses == NULL )
421 ctx->responses = jsonNewObjectType( JSON_ARRAY );
424 jsonObjectPush( ctx->responses, jsonObjectClone(data) );
428 if( !(ctx->method->options & OSRF_METHOD_ATOMIC ) &&
429 !(ctx->method->options & OSRF_METHOD_CACHABLE) ) {
432 osrfAppRequestRespondComplete( ctx->session, ctx->request, data );
434 osrfAppRequestRespond( ctx->session, ctx->request, data );
442 static int _osrfAppPostProcess( osrfMethodContext* ctx, int retcode ) {
443 if(!(ctx && ctx->method)) return -1;
445 osrfLogDebug( OSRF_LOG_MARK, "Postprocessing method %s with retcode %d",
446 ctx->method->name, retcode );
448 if(ctx->responses) { /* we have cached responses to return (no responses have been sent) */
450 osrfAppRequestRespondComplete( ctx->session, ctx->request, ctx->responses );
451 jsonObjectFree(ctx->responses);
452 ctx->responses = NULL;
457 osrfAppSessionStatus( ctx->session, OSRF_STATUS_COMPLETE,
458 "osrfConnectStatus", ctx->request, "Request Complete" );
464 int osrfAppRequestRespondException( osrfAppSession* ses, int request, const char* 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 );
474 static void _osrfAppSetIntrospectMethod( osrfMethodContext* ctx, const osrfMethod* method, jsonObject* resp ) {
475 if(!(ctx && resp)) return;
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));
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 ));
492 Tries to run the requested method as a system method.
493 A system method is a well known method that all
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'
501 static int _osrfAppRunSystemMethod(osrfMethodContext* ctx) {
502 if( osrfMethodVerifyContext( ctx ) < 0 ) {
503 osrfLogError( OSRF_LOG_MARK, "_osrfAppRunSystemMethod: Received invalid method context" );
507 if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ALL ) ||
508 !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ALL_ATOMIC )) {
510 return osrfAppIntrospectAll(ctx);
514 if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT ) ||
515 !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ATOMIC )) {
517 return osrfAppIntrospect(ctx);
520 if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_ECHO ) ||
521 !strcmp(ctx->method->name, OSRF_SYSMETHOD_ECHO_ATOMIC )) {
523 return osrfAppEcho(ctx);
527 osrfAppRequestRespondException( ctx->session,
528 ctx->request, "System method implementation not found");
534 static int osrfAppIntrospect( osrfMethodContext* ctx ) {
536 jsonObject* resp = NULL;
537 const char* methodSubstring = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
538 osrfApplication* app = _osrfAppFindApplication( ctx->session->remote_service );
541 if(!methodSubstring) return 1; /* respond with no methods */
545 osrfHashIterator* itr = osrfNewHashIterator(app->methods);
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);
558 osrfHashIteratorFree(itr);
567 static int osrfAppIntrospectAll( osrfMethodContext* ctx ) {
568 jsonObject* resp = NULL;
569 osrfApplication* app = _osrfAppFindApplication( ctx->session->remote_service );
572 osrfHashIterator* itr = osrfNewHashIterator(app->methods);
574 while( (method = osrfHashIteratorNext(itr)) ) {
575 resp = jsonNewObject(NULL);
576 _osrfAppSetIntrospectMethod( ctx, method, resp );
577 osrfAppRespond(ctx, resp);
578 jsonObjectFree(resp);
580 osrfHashIteratorFree(itr);
587 static int osrfAppEcho( osrfMethodContext* ctx ) {
588 if( osrfMethodVerifyContext( ctx ) < 0 ) {
589 osrfLogError( OSRF_LOG_MARK, "osrfAppEcho: Received invalid method context" );
594 for( i = 0; i < ctx->params->size; i++ ) {
595 const jsonObject* str = jsonObjectGetIndex(ctx->params,i);
596 osrfAppRespond(ctx, str);
602 Determine whether the context looks healthy.
603 Return 0 if it does, or -1 if it doesn't.
605 int osrfMethodVerifyContext( osrfMethodContext* ctx )
608 osrfLogError( OSRF_LOG_MARK, "Context is NULL in app request" );
612 if( !ctx->session ) {
613 osrfLogError( OSRF_LOG_MARK, "Session is NULL in app request" );
619 osrfLogError( OSRF_LOG_MARK, "Method is NULL in app request" );
623 if( ctx->method->argc ) {
625 osrfLogError( OSRF_LOG_MARK,
626 "Params is NULL in app request %s", ctx->method->name );
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 );
636 if( !ctx->method->name ) {
637 osrfLogError( OSRF_LOG_MARK, "Method name is NULL" );
641 // Log the call, with the method and parameters
642 char* params_str = jsonObjectToJSON( ctx->params );
644 osrfLogInfo( OSRF_LOG_MARK, "CALL:\t%s %s - %s",
645 ctx->session->remote_service, ctx->method->name, params_str );