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