LP#1709710: Make chunk sizing smart about XML quoting
[opensrf-equinox.git] / src / libopensrf / osrf_application.c
index ece61a2..4a5f53e 100644 (file)
 #include <opensrf/osrf_application.h>
 
-static osrfMethod* _osrfAppBuildMethod( const char* methodName, const char* symbolName,
-               const char* notes, int argc, int options, void* );
+/**
+       @file osrf_application.c
+       @brief Load and manage shared object libraries.
+
+       Maintain a registry of applications, using an osrfHash keyed on application name,
+
+       For each application, load a shared object library so that we can call
+       application-specific functions dynamically.  In order to map method names to the
+       corresponding functions (i.e. symbol names in the library), maintain a registry of
+       methods, using an osrfHash keyed on method name.
+*/
+
+// The following macro is commented out because it ia no longer used.
+
+// Used internally to make sure the method description provided is OK
+/*
+#define OSRF_METHOD_VERIFY_DESCRIPTION(app, d) \
+       if(!app) return -1; \
+       if(!d) return -1;\
+       if(!d->name) { \
+               osrfLogError( OSRF_LOG_MARK,  "No method name provided in description" ), \
+               return -1; \
+} \
+       if(!d->symbol) { \
+               osrfLogError( OSRF_LOG_MARK, "No method symbol provided in description" ), \
+               return -1; \
+} \
+       if(!d->notes) \
+               d->notes = ""; \
+       if(!d->paramNotes) \
+               d->paramNotes = "";\
+       if(!d->returnNotes) \
+               d->returnNotes = "";
+*/
+
+/**
+       @name Well known method names
+       @brief These methods are automatically implemented for every application.
+*/
+/*@{*/
+#define OSRF_SYSMETHOD_INTROSPECT               "opensrf.system.method"
+#define OSRF_SYSMETHOD_INTROSPECT_ATOMIC        "opensrf.system.method.atomic"
+#define OSRF_SYSMETHOD_INTROSPECT_ALL           "opensrf.system.method.all"
+#define OSRF_SYSMETHOD_INTROSPECT_ALL_ATOMIC    "opensrf.system.method.all.atomic"
+#define OSRF_SYSMETHOD_ECHO                     "opensrf.system.echo"
+#define OSRF_SYSMETHOD_ECHO_ATOMIC              "opensrf.system.echo.atomic"
+/*@}*/
+
+/**
+       @name Method options
+       @brief Macros that get OR'd together to form method options.
+
+       These options are in addition to the ones stipulated by the caller of
+       osrfRegisterMethod(), and are not externally visible.
+*/
+/*@{*/
+/**
+       @brief  Marks a method as a system method.
+
+       System methods are implemented by generic functions, called via static linkage.  They
+       are not loaded or executed from shared objects.
+*/
+#define OSRF_METHOD_SYSTEM          1
+/**
+       @brief  Combines all responses into a single RESULT message.
+
+       For a @em non-atomic method, the server returns each response to the client in a
+       separate RESULT message.  It sends a STATUS message at the end to signify the end of the
+       message stream.
+
+       For an @em atomic method, the server buffers all responses until the method returns,
+       and then sends them all at once in a single RESULT message (followed by a STATUS message).
+       Each individual response is encoded as an entry in a JSON array.  This buffering is
+       transparent to the function that implements the method.
+
+       Atomic methods incur less networking overhead than non-atomic methods, at the risk of
+       creating excessively large RESULT messages.  The HTTP gateway requires the atomic versions
+       of streaming methods because of the stateless nature of the HTTP protocol.
+
+       If OSRF_METHOD_STREAMING is set for a method, the application generates both an atomic
+       and a non-atomic method, whose names are identical except that the atomic one carries a
+       suffix of ".atomic".
+*/
+#define OSRF_METHOD_ATOMIC          4
+/*@}*/
+
+/**
+       @brief Represent an Application.
+*/
+typedef struct {
+       void* handle;               /**< Handle to the shared object library. */
+       osrfHash* methods;          /**< Registry of method names. */
+       void (*onExit) (void);      /**< Exit handler for the application. */
+} osrfApplication;
+
+static void register_method( osrfApplication* app, const char* methodName,
+       const char* symbolName, const char* notes, int argc, int options, void * user_data );
+static osrfMethod* build_method( const char* methodName, const char* symbolName,
+       const char* notes, int argc, int options, void* );
 static void osrfAppSetOnExit(osrfApplication* app, const char* appName);
-static int _osrfAppRegisterSysMethods( const char* app );
-static osrfApplication* _osrfAppFindApplication( const char* name );
-static osrfMethod* osrfAppFindMethod( osrfApplication* app, const char* methodName );
+static void register_system_methods( osrfApplication* app );
+static inline osrfApplication* _osrfAppFindApplication( const char* name );
+static inline osrfMethod* osrfAppFindMethod( osrfApplication* app, const char* methodName );
 static int _osrfAppRespond( osrfMethodContext* context, const jsonObject* data, int complete );
 static int _osrfAppPostProcess( osrfMethodContext* context, int retcode );
 static int _osrfAppRunSystemMethod(osrfMethodContext* context);
+static void _osrfAppSetIntrospectMethod( osrfMethodContext* ctx, const osrfMethod* method,
+       jsonObject* resp );
 static int osrfAppIntrospect( osrfMethodContext* ctx );
 static int osrfAppIntrospectAll( osrfMethodContext* ctx );
 static int osrfAppEcho( osrfMethodContext* ctx );
+static void osrfMethodFree( char* name, void* p );
+static void osrfAppFree( char* name, void* p );
+
+/**
+       @brief Registry of applications.
 
+       The key of the hash is the application name, and the associated data is an osrfApplication.
+*/
 static osrfHash* _osrfAppHash = NULL;
 
+/**
+       @brief Register an application.
+       @param appName Name of the application.
+       @param soFile Name of the shared object file to be loaded for this application.
+       @return Zero if successful, or -1 upon error.
+
+       Open the shared object file and call its osrfAppInitialize() function, if it has one.
+       Register the standard system methods for it.  Arrange for the application name to
+       appear in subsequent log messages.
+*/
 int osrfAppRegisterApplication( const char* appName, const char* soFile ) {
-       if(!appName || ! soFile) return -1;
+       if( !appName || ! soFile ) return -1;
        char* error;
 
-       if(!_osrfAppHash) _osrfAppHash = osrfNewHash();
+       osrfLogSetAppname( appName );
 
-       osrfLogInfo( OSRF_LOG_MARK, "Registering application %s with file %s", appName, soFile );
+       if( !_osrfAppHash ) {
+               _osrfAppHash = osrfNewHash();
+               osrfHashSetCallback( _osrfAppHash, osrfAppFree );
+       }
 
-       osrfApplication* app = safe_malloc(sizeof(osrfApplication));
-       app->handle = dlopen (soFile, RTLD_NOW);
-       app->onExit = NULL;
+       osrfLogInfo( OSRF_LOG_MARK, "Registering application %s with file %s", appName, soFile );
 
-       if(!app->handle) {
-               osrfLogWarning( OSRF_LOG_MARK, "Failed to dlopen library file %s: %s", soFile, dlerror() );
-               dlerror(); /* clear the error */
-               free(app);
+       // Open the shared object.
+       void* handle = dlopen( soFile, RTLD_NOW );
+       if( ! handle ) {
+               const char* msg = dlerror();
+               osrfLogError( OSRF_LOG_MARK, "Failed to dlopen library file %s: %s", soFile, msg );
                return -1;
        }
 
+       // Construct the osrfApplication.
+       osrfApplication* app = safe_malloc(sizeof(osrfApplication));
+       app->handle = handle;
        app->methods = osrfNewHash();
+       osrfHashSetCallback( app->methods, osrfMethodFree );
+       app->onExit = NULL;
+
+       // Add the newly-constructed app to the list.
        osrfHashSet( _osrfAppHash, app, appName );
 
-       /* see if we can run the initialize method */
+       // Try to run the initialize method.  Typically it will register one or more
+       // methods of the application.
        int (*init) (void);
-       *(void **) (&init) = dlsym(app->handle, "osrfAppInitialize");
+       *(void **) (&init) = dlsym( handle, "osrfAppInitialize" );
 
        if( (error = dlerror()) != NULL ) {
-               osrfLogWarning( OSRF_LOG_MARK, 
-                       "! Unable to locate method symbol [osrfAppInitialize] for app %s: %s", appName, error );
+               osrfLogWarning( OSRF_LOG_MARK,
+                       "! Unable to locate method symbol [osrfAppInitialize] for app %s: %s",
+                       appName, error );
 
        } else {
 
@@ -51,25 +178,28 @@ int osrfAppRegisterApplication( const char* appName, const char* soFile ) {
                int ret;
                if( (ret = (*init)()) ) {
                        osrfLogWarning( OSRF_LOG_MARK, "Application %s returned non-zero value from "
-                                       "'osrfAppInitialize', not registering...", appName );
-                       //free(app->name); /* need a method to remove an application from the list */
-                       //free(app);
+                               "'osrfAppInitialize', not registering...", appName );
+                       osrfHashRemove( _osrfAppHash, appName );
                        return ret;
                }
        }
 
-       _osrfAppRegisterSysMethods(appName);
-
+       register_system_methods( app );
        osrfLogInfo( OSRF_LOG_MARK, "Application %s registered successfully", appName );
-
-       osrfLogSetAppname(appName);
-
-       osrfAppSetOnExit(app, appName);
+       osrfAppSetOnExit( app, appName );
 
        return 0;
 }
 
-
+/**
+       @brief Save a pointer to the application's exit function.
+       @param app Pointer to the osrfApplication.
+       @param appName Application name (used only for log messages).
+
+       Look in the shared object for a symbol named "osrfAppChildExit".  If you find one, save
+       it as a pointer to the application's exit function.  If present, this function will be
+       called when a server's child process (a so-called "drone") is shutting down.
+*/
 static void osrfAppSetOnExit(osrfApplication* app, const char* appName) {
        if(!(app && appName)) return;
 
@@ -87,7 +217,15 @@ static void osrfAppSetOnExit(osrfApplication* app, const char* appName) {
        app->onExit = (*onExit);
 }
 
-
+/**
+       @brief Run the application-specific child initialization function for a given application.
+       @param appname Name of the application.
+       @return Zero if successful, or if the application has no child initialization function; -1
+       if the application is not registered, or if the function returns non-zero.
+
+       The child initialization function must be named "osrfAppChildInit" within the shared
+       object library.  It initializes a drone process of a server.
+*/
 int osrfAppRunChildInit(const char* appname) {
        osrfApplication* app = _osrfAppFindApplication(appname);
        if(!app) return -1;
@@ -112,21 +250,47 @@ int osrfAppRunChildInit(const char* appname) {
        return 0;
 }
 
+/**
+       @brief Call the exit handler for every application that has one.
 
-void osrfAppRunExitCode() { 
+       Normally a server's child process (a so-called "drone") calls this function just before
+       shutting down.
+*/
+void osrfAppRunExitCode( void ) {
        osrfHashIterator* itr = osrfNewHashIterator(_osrfAppHash);
        osrfApplication* app;
        while( (app = osrfHashIteratorNext(itr)) ) {
                if( app->onExit ) {
-                       osrfLogInfo(OSRF_LOG_MARK, "Running onExit handler for app %s", itr->current);
+                       osrfLogInfo(OSRF_LOG_MARK, "Running onExit handler for app %s",
+                               osrfHashIteratorKey(itr) );
                        app->onExit();
                }
        }
        osrfHashIteratorFree(itr);
 }
 
+/**
+       @brief Register a method for a specified application.
+
+       @param appName Name of the application that implements the method.
+       @param methodName The fully qualified name of the method.
+       @param symbolName The symbol name (function name) that implements the method.
+       @param notes Public documentation for this method.
+       @param argc The minimum number of arguments for the function.
+       @param options Bit switches setting various options.
+       @return Zero on success, or -1 on error.
 
-int osrfAppRegisterMethod( const char* appName, const char* methodName, 
+       Registering a method enables us to call the right function when a client requests a
+       method call.
+
+       The @a options parameter is zero or more of the following macros, OR'd together:
+
+       - OSRF_METHOD_STREAMING     method may return more than one response
+       - OSRF_METHOD_CACHABLE      cache results in memcache
+
+       If the OSRF_METHOD_STREAMING bit is set, also register an ".atomic" version of the method.
+*/
+int osrfAppRegisterMethod( const char* appName, const char* methodName,
                const char* symbolName, const char* notes, int argc, int options ) {
 
        return osrfAppRegisterExtendedMethod(
@@ -138,13 +302,28 @@ int osrfAppRegisterMethod( const char* appName, const char* methodName,
                        options,
                        NULL
        );
-
 }
 
-int osrfAppRegisterExtendedMethod( const char* appName, const char* methodName, 
-               const char* symbolName, const char* notes, int argc, int options, void * user_data ) {
-
-       if( !appName || ! methodName  ) return -1;
+/**
+       @brief Register an extended method for a specified application.
+
+       @param appName Name of the application that implements the method.
+       @param methodName The fully qualified name of the method.
+       @param symbolName The symbol name (function name) that implements the method.
+       @param notes Public documentation for this method.
+       @param argc How many arguments this method expects.
+       @param options Bit switches setting various options.
+       @param user_data Opaque pointer to be passed to the dynamically called function.
+       @return Zero if successful, or -1 upon error.
+
+       This function is identical to osrfAppRegisterMethod(), except that it also installs
+       a method-specific opaque pointer.  When we call the corresponding function at
+       run time, this pointer will be available to the function via the method context.
+*/
+int osrfAppRegisterExtendedMethod( const char* appName, const char* methodName,
+       const char* symbolName, const char* notes, int argc, int options, void * user_data ) {
+
+       if( !appName || ! methodName ) return -1;
 
        osrfApplication* app = _osrfAppFindApplication(appName);
        if(!app) {
@@ -154,142 +333,257 @@ int osrfAppRegisterExtendedMethod( const char* appName, const char* methodName,
 
        osrfLogDebug( OSRF_LOG_MARK, "Registering method %s for app %s", methodName, appName );
 
-       osrfMethod* method = _osrfAppBuildMethod(
-               methodName, symbolName, notes, argc, options, user_data );              
-       method->options = options;
+       // Extract the only valid option bits, and ignore the rest.
+       int opts = options & ( OSRF_METHOD_STREAMING | OSRF_METHOD_CACHABLE );
 
-       /* plug the method into the list of methods */
-       osrfHashSet( app->methods, method, method->name );
+       // Build and install a non-atomic method.
+       register_method(
+               app, methodName, symbolName, notes, argc, opts, user_data );
 
-       if( options & OSRF_METHOD_STREAMING ) { /* build the atomic counterpart */
-               int newops = options | OSRF_METHOD_ATOMIC;
-               osrfMethod* atomicMethod = _osrfAppBuildMethod(
-                       methodName, symbolName, notes, argc, newops, NULL );            
-               osrfHashSet( app->methods, atomicMethod, atomicMethod->name );
-               atomicMethod->userData = method->userData;
+       if( opts & OSRF_METHOD_STREAMING ) {
+               // Build and install an atomic version of the same method.
+               register_method(
+                       app, methodName, symbolName, notes, argc, opts | OSRF_METHOD_ATOMIC, user_data );
        }
 
        return 0;
 }
 
+/**
+       @brief Register a single method for a specified application.
+
+       @param appName Pointer to the application that implements the method.
+       @param methodName The fully qualified name of the method.
+       @param symbolName The symbol name (function name) that implements the method.
+       @param notes Public documentation for this method.
+       @param argc How many arguments this method expects.
+       @param options Bit switches setting various options.
+       @param user_data Opaque pointer to be passed to the dynamically called function.
+*/
+static void register_method( osrfApplication* app, const char* methodName,
+       const char* symbolName, const char* notes, int argc, int options, void * user_data ) {
+
+       if( !app || ! methodName ) return;
+
+       // Build a method and add it to the list of methods
+       osrfMethod* method = build_method(
+               methodName, symbolName, notes, argc, options, user_data );
+       osrfHashSet( app->methods, method, method->name );
+}
 
+/**
+       @brief Allocate and populate an osrfMethod.
+       @param methodName Name of the method.
+       @param symbolName Name of the function that implements the method.
+       @param notes Remarks documenting the method.
+       @param argc Minimum number of arguments to the method.
+       @param options Bit switches setting various options.
+       @param user_data An opaque pointer to be passed in the method context.
+       @return Pointer to the newly allocated osrfMethod.
+
+       If OSRF_METHOD_ATOMIC is set, append ".atomic" to the method name.
+*/
+static osrfMethod* build_method( const char* methodName, const char* symbolName,
+       const char* notes, int argc, int options, void* user_data ) {
+
+       osrfMethod* method      = safe_malloc(sizeof(osrfMethod));
+
+       if( !methodName )
+               methodName = "";  // should never happen
+
+       if( options & OSRF_METHOD_ATOMIC ) {
+               // Append ".atomic" to the name.
+               char mb[ strlen( methodName ) + 8 ];
+               sprintf( mb, "%s.atomic", methodName );
+               method->name        = strdup( mb );
+       } else {
+               method->name        = strdup(methodName);
+       }
 
-static osrfMethod* _osrfAppBuildMethod( const char* methodName, const char* symbolName,
-               const char* notes, int argc, int options, void* user_data ) {
-
-       osrfMethod* method                                      = safe_malloc(sizeof(osrfMethod));
+       if(symbolName)
+               method->symbol      = strdup(symbolName);
+       else
+               method->symbol      = NULL;
 
-       if(methodName) method->name             = strdup(methodName);
-       else method->name    = NULL;
-       if(symbolName) method->symbol           = strdup(symbolName);
-       else method->symbol  = NULL;
-       if(notes) method->notes                         = strdup(notes);
-       else method->notes   = NULL;
-       if(user_data) method->userData  = user_data;
+       if(notes)
+               method->notes       = strdup(notes);
+       else
+               method->notes       = NULL;
 
-       method->argc                                                    = argc;
-       method->options                                         = options;
+       method->argc            = argc;
+       method->options         = options;
 
-       if(options & OSRF_METHOD_ATOMIC) { /* add ".atomic" to the end of the name */
-               char mb[strlen(method->name) + 8];
-               sprintf(mb, "%s.atomic", method->name);
-               free(method->name);
-               method->name = strdup(mb);
-               method->options |= OSRF_METHOD_STREAMING;
-       }
+       if(user_data)
+               method->userData    = user_data;
 
+       method->max_bundle_size = OSRF_MSG_BUNDLE_SIZE;
+    method->max_chunk_size  = OSRF_MSG_CHUNK_SIZE;
        return method;
 }
 
-
 /**
-  Registers all of the system methods for this app so that they may be
-  treated the same as other methods */
-static int _osrfAppRegisterSysMethods( const char* app ) {
-
-       osrfAppRegisterMethod( 
-                       app, OSRF_SYSMETHOD_INTROSPECT, NULL, 
-                       "Return a list of methods whose names have the same initial "
-                       "substring as that of the provided method name PARAMS( methodNameSubstring )", 
-                       1, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING );
-
-       osrfAppRegisterMethod( 
-                       app, OSRF_SYSMETHOD_INTROSPECT_ALL, NULL, 
-                       "Returns a complete list of methods. PARAMS()", 0, 
-                       OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING );
-
-       osrfAppRegisterMethod( 
-                       app, OSRF_SYSMETHOD_ECHO, NULL, 
-                       "Echos all data sent to the server back to the client. PARAMS([a, b, ...])", 0, 
-                       OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING );
+       @brief Set the effective output buffer size for a given method.
+       @param appName Name of the application.
+       @param methodName Name of the method.
+       @param max_bundle_size Desired size of the output buffer, in bytes.
+       @return Zero if successful, or -1 if the specified method cannot be found.
+
+       A smaller buffer size may result in a lower latency for the first response, since we don't
+       wait for as many messages to accumulate before flushing the output buffer.  On the other
+       hand a larger buffer size may result in higher throughput due to lower network overhead.
+
+       Since the buffer size is not an absolute limit, it may be set to zero, in which case each
+       output transport message will contain no more than one RESULT message.
+
+       This function has no effect on atomic methods, because all responses are sent in a single
+       message anyway.  Likewise it has no effect on a method that returns only a single response.
+*/
+int osrfMethodSetBundleSize( const char* appName, const char* methodName, size_t max_bundle_size ) {
+       osrfMethod* method = _osrfAppFindMethod( appName, methodName );
+       if( method ) {
+               osrfLogInfo( OSRF_LOG_MARK,
+                       "Setting outbuf buffer size to %lu for method %s of application %s",
+                       (unsigned long) max_bundle_size, methodName, appName );
+               method->max_bundle_size = max_bundle_size;
+               return 0;
+       } else {
+               osrfLogWarning( OSRF_LOG_MARK,
+                       "Unable to set outbuf buffer size to %lu for method %s of application %s",
+                       (unsigned long) max_bundle_size, methodName, appName );
+               return -1;
+       }
+}
 
-       return 0;
+/**
+       @brief Register all of the system methods for this application.
+       @param app Pointer to the application.
+
+       A client can call these methods the same way it calls application-specific methods,
+       but they are implemented by functions here in this module, not by functions in the
+       shared object.
+*/
+static void register_system_methods( osrfApplication* app ) {
+
+       if( !app ) return;
+
+       register_method(
+               app, OSRF_SYSMETHOD_INTROSPECT, NULL,
+               "Return a list of methods whose names have the same initial "
+               "substring as that of the provided method name PARAMS( methodNameSubstring )",
+               1, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING,
+               NULL );
+
+       register_method(
+               app, OSRF_SYSMETHOD_INTROSPECT, NULL,
+               "Return a list of methods whose names have the same initial "
+               "substring as that of the provided method name PARAMS( methodNameSubstring )",
+               1, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING | OSRF_METHOD_ATOMIC,
+               NULL );
+
+       register_method(
+               app, OSRF_SYSMETHOD_INTROSPECT_ALL, NULL,
+               "Returns a complete list of methods. PARAMS()",
+               0, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING,
+               NULL );
+
+       register_method(
+               app, OSRF_SYSMETHOD_INTROSPECT_ALL, NULL,
+               "Returns a complete list of methods. PARAMS()",
+               0, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING | OSRF_METHOD_ATOMIC,
+               NULL );
+
+       register_method(
+               app, OSRF_SYSMETHOD_ECHO, NULL,
+               "Echos all data sent to the server back to the client. PARAMS([a, b, ...])",
+               0, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING,
+               NULL );
+
+       register_method(
+               app, OSRF_SYSMETHOD_ECHO, NULL,
+               "Echos all data sent to the server back to the client. PARAMS([a, b, ...])",
+               0, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING | OSRF_METHOD_ATOMIC,
+               NULL );
 }
 
 /**
-  Finds the given app in the list of apps
-  @param name The name of the application
-  @return The application pointer or NULL if there is no such application
- */
-static osrfApplication* _osrfAppFindApplication( const char* name ) {
-       if(!name) return NULL;
+       @brief Look up an application by name in the application registry.
+       @param name The name of the application.
+       @return Pointer to the corresponding osrfApplication if found, or NULL if not.
+*/
+static inline osrfApplication* _osrfAppFindApplication( const char* name ) {
        return (osrfApplication*) osrfHashGet(_osrfAppHash, name);
 }
 
 /**
-  Finds the given method for the given app
-  @param app The application object
-  @param methodName The method to find
-  @return A method pointer or NULL if no such method 
-  exists for the given application
- */
-static osrfMethod* osrfAppFindMethod( osrfApplication* app, const char* methodName ) {
-       if(!app || ! methodName) return NULL;
+       @brief Look up a method by name for a given application.
+       @param app Pointer to the osrfApplication that owns the method.
+       @param methodName Name of the method to find.
+       @return Pointer to the corresponding osrfMethod if found, or NULL if not.
+*/
+static inline osrfMethod* osrfAppFindMethod( osrfApplication* app, const char* methodName ) {
+       if( !app ) return NULL;
        return (osrfMethod*) osrfHashGet( app->methods, methodName );
 }
 
+/**
+       @brief Look up a method by name for an application with a given name.
+       @param appName Name of the osrfApplication.
+       @param methodName Name of the method to find.
+       @return Pointer to the corresponding osrfMethod if found, or NULL if not.
+*/
 osrfMethod* _osrfAppFindMethod( const char* appName, const char* methodName ) {
-       if(!appName || ! methodName) return NULL;
+       if( !appName ) return NULL;
        return osrfAppFindMethod( _osrfAppFindApplication(appName), methodName );
 }
 
-
-int osrfAppRunMethod( const char* appName, const char* methodName, 
+/**
+       @brief Call the function that implements a specified method.
+       @param appName Name of the application.
+       @param methodName Name of the method.
+       @param ses Pointer to the current application session.
+       @param reqId The request id of the request invoking the method.
+       @param params Pointer to a jsonObject encoding the parameters to the method.
+       @return Zero if successful, or -1 upon failure.
+
+       If we can't find a function corresponding to the method, or if we call it and it returns
+       a negative return code, send a STATUS message to the client to report an exception.
+
+       A return code of -1 means that the @a appName, @a methodName, or @a ses parameter was NULL.
+*/
+int osrfAppRunMethod( const char* appName, const char* methodName,
                osrfAppSession* ses, int reqId, jsonObject* params ) {
 
        if( !(appName && methodName && ses) ) return -1;
 
-       char* error;
-       osrfApplication* app;
-       osrfMethod* method;
-       osrfMethodContext context;
-
-       context.session = ses;
-       context.params = params;
-       context.request = reqId;
-       context.responses = NULL;
-
-       /* this is the method we're gonna run */
-       int (*meth) (osrfMethodContext*);       
-
-       if( !(app = _osrfAppFindApplication(appName)) )
-               return osrfAppRequestRespondException( ses, 
+       // Find the application, and then find the method for it
+       osrfApplication* app = _osrfAppFindApplication(appName);
+       if( !app )
+               return osrfAppRequestRespondException( ses,
                                reqId, "Application not found: %s", appName );
-       
-       if( !(method = osrfAppFindMethod( app, methodName )) )
-               return osrfAppRequestRespondException( ses, reqId, 
-                               "Method [%s] not found for service %s", methodName, appName );
 
-       context.method = method;
+       osrfMethod* method = osrfAppFindMethod( app, methodName );
+       if( !method )
+               return osrfAppRequestRespondException( ses, reqId,
+                               "Method [%s] not found for service %s", methodName, appName );
 
        #ifdef OSRF_STRICT_PARAMS
        if( method->argc > 0 ) {
+               // Make sure that the client has passed at least the minimum number of arguments.
                if(!params || params->type != JSON_ARRAY || params->size < method->argc )
-                       return osrfAppRequestRespondException( ses, reqId, 
+                       return osrfAppRequestRespondException( ses, reqId,
                                "Not enough params for method %s / service %s", methodName, appName );
        }
        #endif
 
+       // Build an osrfMethodContext, which we will pass by pointer to the function.
+       osrfMethodContext context;
+
+       context.session = ses;
+       context.method = method;
+       context.params = params;
+       context.request = reqId;
+       context.responses = NULL;
+
        int retcode = 0;
 
        if( method->options & OSRF_METHOD_SYSTEM ) {
@@ -297,87 +591,263 @@ int osrfAppRunMethod( const char* appName, const char* methodName,
 
        } else {
 
-               /* open and now run the method */
-               *(void **) (&meth) = dlsym(app->handle, method->symbol);
+               // Function pointer through which we will call the function dynamically
+               int (*meth) (osrfMethodContext*);
+
+               // Open the function that implements the method
+               meth = dlsym(app->handle, method->symbol);
 
-               if( (error = dlerror()) != NULL ) {
-                       return osrfAppRequestRespondException( ses, reqId, 
-                               "Unable to execute method [%s]  for service %s", methodName, appName );
+               const char* error = dlerror();
+               if( error != NULL ) {
+                       return osrfAppRequestRespondException( ses, reqId,
+                               "Unable to execute method [%s] for service %s", methodName, appName );
                }
 
-               retcode = (*meth) (&context);
+               // Run it
+               retcode = meth( &context );
        }
 
-       if(retcode < 0) 
-               return osrfAppRequestRespondException( 
+       if(retcode < 0)
+               return osrfAppRequestRespondException(
                                ses, reqId, "An unknown server error occurred" );
 
-       return _osrfAppPostProcess( &context, retcode );
+       retcode = _osrfAppPostProcess( &context, retcode );
 
+       if( context.responses )
+               jsonObjectFree( context.responses );
+       return retcode;
 }
 
-
+/**
+       @brief Either send or enqueue a response to a client.
+       @param ctx Pointer to the current method context.
+       @param data Pointer to the response, in the form of a jsonObject.
+       @return Zero if successful, or -1 upon error.  The only recognized errors are if either
+       the @a context pointer or its method pointer is NULL.
+
+       For an atomic method, add a copy of the response data to a cache within the method
+       context, to be sent later.  Otherwise, send a RESULT message to the client, with the
+       results in @a data.
+
+       Note that, for an atomic method, this function is equivalent to osrfAppRespondComplete():
+       we send the STATUS message after the method returns, and not before.
+*/
 int osrfAppRespond( osrfMethodContext* ctx, const jsonObject* data ) {
        return _osrfAppRespond( ctx, data, 0 );
 }
 
+/**
+       @brief Either send or enqueue a response to a client, with a completion notice.
+       @param context Pointer to the current method context.
+       @param data Pointer to the response, in the form of a jsonObject.
+       @return Zero if successful, or -1 upon error.  The only recognized errors are if either
+       the @a context pointer or its method pointer is NULL.
+
+       For an atomic method, add a copy of the response data to a cache within the method
+       context, to be sent later.  Otherwise, send a RESULT message to the client, with the
+       results in @a data.  Also send a STATUS message to indicate that the response is complete.
+
+       Note that, for an atomic method, this function is equivalent to osrfAppRespond(): we
+       send the STATUS message after the method returns, and not before.
+*/
 int osrfAppRespondComplete( osrfMethodContext* context, const jsonObject* data ) {
        return _osrfAppRespond( context, data, 1 );
 }
 
+/**
+       @brief Send any response messages that have accumulated in the output buffer.
+       @param ses Pointer to the current application session.
+       @param outbuf Pointer to the output buffer.
+       @return Zero if successful, or -1 if not.
+
+       Used only by servers to respond to clients.
+*/
+static int flush_responses( osrfAppSession* ses, growing_buffer* outbuf ) {
+
+       // Collect any inbound traffic on the socket(s).  This doesn't accomplish anything for the
+       // immediate task at hand, but it may help to keep TCP from getting clogged in some cases.
+       osrf_app_session_queue_wait( ses, 0, NULL );
+
+       int rc = 0;
+       if( buffer_length( outbuf ) > 0 ) {    // If there's anything to send...
+               buffer_add_char( outbuf, ']' );    // Close the JSON array
+               if( osrfSendTransportPayload( ses, OSRF_BUFFER_C_STR( ses->outbuf ))) {
+                       osrfLogError( OSRF_LOG_MARK, "Unable to flush response buffer" );
+                       rc = -1;
+               }
+       }
+       buffer_reset( ses->outbuf );
+       return rc;
+}
+
+/**
+       @brief Add a message to an output buffer.
+       @param outbuf Pointer to the output buffer.
+       @param msg Pointer to the message to be added, in the form of a JSON string.
+
+       Since the output buffer is in the form of a JSON array, prepend a left bracket to the
+       first message, and a comma to subsequent ones.
+
+       Used only by servers to respond to clients.
+*/
+static inline void append_msg( growing_buffer* outbuf, const char* msg ) {
+       if( outbuf && msg ) {
+               char prefix = buffer_length( outbuf ) > 0 ? ',' : '[';
+               buffer_add_char( outbuf, prefix );
+               buffer_add( outbuf, msg );
+       }
+}
+
+/**
+       @brief Either send or enqueue a response to a client, optionally with a completion notice.
+       @param ctx Pointer to the method context.
+       @param data Pointer to the response, in the form of a jsonObject.
+       @param complete Boolean: if true, we will accompany the RESULT message with a STATUS
+       message indicating that the response is complete.
+       @return Zero if successful, or -1 upon error.
+
+       For an atomic method, add a copy of the response data to a cache within the method
+       context, to be sent later.  In this case the @a complete parameter has no effect,
+       because we'll send the STATUS message later when we send the cached results.
+
+       If the method is not atomic, translate the message into JSON and append it to a buffer,
+       flushing the buffer as needed to avoid overflow.  If @a complete is true, append
+       a STATUS message (as JSON) to the buffer and flush the buffer.
+*/
 static int _osrfAppRespond( osrfMethodContext* ctx, const jsonObject* data, int complete ) {
        if(!(ctx && ctx->method)) return -1;
 
        if( ctx->method->options & OSRF_METHOD_ATOMIC ) {
-               osrfLogDebug( OSRF_LOG_MARK,   
+               osrfLogDebug( OSRF_LOG_MARK,
                        "Adding responses to stash for atomic method %s", ctx->method->name );
 
-               if( ctx->responses == NULL )                                                                                            
-                       ctx->responses = jsonParseString("[]");                                                 
+               // If we don't already have one, create a JSON_ARRAY to serve as a cache.
+               if( ctx->responses == NULL )
+                       ctx->responses = jsonNewObjectType( JSON_ARRAY );
 
+               // Add a copy of the data object to the cache.
                if ( data != NULL )
-                       jsonObjectPush( ctx->responses, jsonObjectClone(data) );        
-       }
+                       jsonObjectPush( ctx->responses, jsonObjectClone(data) );
+       } else {
+               osrfLogDebug( OSRF_LOG_MARK,
+                       "Adding responses to stash for method %s", ctx->method->name );
 
+               if( data ) {
+            char* data_str = jsonObjectToJSON(data); // free me (below)
+            size_t raw_size = strlen(data_str);
+            size_t extra_size = osrfXmlEscapingLength(data_str);
+            size_t data_size = raw_size + extra_size;
+            size_t chunk_size = ctx->method->max_chunk_size;
 
-       if(     !(ctx->method->options & OSRF_METHOD_ATOMIC) && 
-                       !(ctx->method->options & OSRF_METHOD_CACHABLE) ) {
+            if (data_size > chunk_size) // calculate an escape-scaled chunk size
+                chunk_size = ((double)raw_size / (double)data_size) * (double)chunk_size;
 
-               if(complete) 
-                       osrfAppRequestRespondComplete( ctx->session, ctx->request, data );
-               else
-                       osrfAppRequestRespond( ctx->session, ctx->request, data );
-               return 0;
-       }
+            if (chunk_size > 0 && chunk_size < raw_size) {
+                // chunking -- response message exceeds max message size.
+                // break it up into chunks for partial delivery
 
-       return 0;
-}
+                               osrfSendChunkedResult(ctx->session, ctx->request,
+                                                                         data_str, raw_size, chunk_size);
+
+            } else {
 
+                // bundling -- message body (may be) too small for single
+                // delivery.  prepare message for bundling.
 
+                // Create an OSRF message
+                osrfMessage* msg = osrf_message_init( RESULT, ctx->request, 1 );
+                osrf_message_set_status_info( msg, NULL, "OK", OSRF_STATUS_OK );
+                osrf_message_set_result( msg, data );
 
+                // Serialize the OSRF message into JSON text
+                char* json = jsonObjectToJSON( osrfMessageToJSON( msg ));
+                osrfMessageFree( msg );
 
+                // If the new message would overflow the buffer, flush the output buffer first
+                int len_so_far = buffer_length( ctx->session->outbuf );
+                if( len_so_far && (strlen( json ) + len_so_far + 3 >= ctx->method->max_bundle_size )) {
+                    if( flush_responses( ctx->session, ctx->session->outbuf ))
+                        return -1;
+                }
+
+                // Append the JSON text to the output buffer
+                append_msg( ctx->session->outbuf, json );
+                free( json );
+            }
+
+            free(data_str);
+               }
+
+               if(complete) {
+                       // Create a STATUS message
+                       osrfMessage* status_msg = osrf_message_init( STATUS, ctx->request, 1 );
+                       osrf_message_set_status_info( status_msg, "osrfConnectStatus", "Request Complete",
+                               OSRF_STATUS_COMPLETE );
+
+                       // Serialize the STATUS message into JSON text
+                       char* json = jsonObjectToJSON( osrfMessageToJSON( status_msg ));
+                       osrfMessageFree( status_msg );
+
+                       // Add the STATUS message to the output buffer.
+                       // It's short, so don't worry about avoiding overflow.
+                       append_msg( ctx->session->outbuf, json );
+                       free( json );
+
+                       // Flush the output buffer, sending any accumulated messages.
+                       if( flush_responses( ctx->session, ctx->session->outbuf ))
+                               return -1;
+               }
+       }
+
+       return 0;
+}
+
+/**
+       @brief Finish up the processing of a request.
+       @param ctx Pointer to the method context.
+       @param retcode The return code from the method's function.
+       @return 0 if successfull, or -1 upon error.
+
+       For an atomic method: send whatever responses we have been saving up, together with a
+       STATUS message to say that we're finished.
+
+       For a non-atomic method: if the return code from the method is greater than zero, just
+       send the STATUS message.  If the return code is zero, do nothing; the method presumably
+       sent the STATUS message on its own.
+*/
 static int _osrfAppPostProcess( osrfMethodContext* ctx, int retcode ) {
        if(!(ctx && ctx->method)) return -1;
 
-       osrfLogDebug( OSRF_LOG_MARK,  "Postprocessing method %s with retcode %d",
+       osrfLogDebug( OSRF_LOG_MARK, "Postprocessing method %s with retcode %d",
                        ctx->method->name, retcode );
 
-       if(ctx->responses) { /* we have cached responses to return (no responses have been sent) */
-
+       if(ctx->responses) {
+               // We have cached atomic responses to return, collected in a JSON ARRAY (we
+               // haven't sent any responses yet).  Now send them all at once, followed by
+               // a STATUS message to say that we're finished.
                osrfAppRequestRespondComplete( ctx->session, ctx->request, ctx->responses );
-               jsonObjectFree(ctx->responses);
-               ctx->responses = NULL;
 
        } else {
-
-               if( retcode > 0 ) 
-                       osrfAppSessionStatus( ctx->session, OSRF_STATUS_COMPLETE,  
-                                       "osrfConnectStatus", ctx->request, "Request Complete" );
+               // We have no cached atomic responses to return, but we may have some
+               // non-atomic messages waiting in the buffer.
+               if( retcode > 0 )
+                       // Send a STATUS message to say that we're finished, and to force a
+                       // final flush of the buffer.
+                       osrfAppRespondComplete( ctx, NULL );
        }
 
        return 0;
 }
 
+/**
+       @brief Send a STATUS message to the client, notifying it of an error.
+       @param ses Pointer to the current application session.
+       @param request Request ID of the request.
+       @param msg A printf-style format string defining an explanatory message to be sent to
+       the client.  Subsequent parameters, if any, will be formatted and inserted into the
+       resulting output string.
+       @return -1 if the @a ses parameter is NULL; otherwise zero.
+*/
 int osrfAppRequestRespondException( osrfAppSession* ses, int request, const char* msg, ... ) {
        if(!ses) return -1;
        if(!msg) msg = "";
@@ -387,121 +857,148 @@ int osrfAppRequestRespondException( osrfAppSession* ses, int request, const char
        return 0;
 }
 
-
-static void _osrfAppSetIntrospectMethod( osrfMethodContext* ctx, const osrfMethod* method, jsonObject* resp ) {
+/**
+       @brief Introspect a specified method.
+       @param ctx Pointer to the method context.
+       @param method Pointer to the osrfMethod for the specified method.
+       @param resp Pointer to the jsonObject into which method information will be placed.
+
+       Treating the @a resp object as a JSON_HASH, insert entries for various bits of information
+       about the specified method.
+*/
+static void _osrfAppSetIntrospectMethod( osrfMethodContext* ctx, const osrfMethod* method,
+               jsonObject* resp ) {
        if(!(ctx && resp)) return;
 
-       jsonObjectSetKey(resp, "api_name",      jsonNewObject(method->name));
-       jsonObjectSetKey(resp, "method",        jsonNewObject(method->symbol));
-       jsonObjectSetKey(resp, "service",       jsonNewObject(ctx->session->remote_service));
-       jsonObjectSetKey(resp, "notes",         jsonNewObject(method->notes));
-       jsonObjectSetKey(resp, "argc",          jsonNewNumberObject(method->argc));
+       jsonObjectSetKey(resp, "api_name",  jsonNewObject(method->name));
+       jsonObjectSetKey(resp, "method",    jsonNewObject(method->symbol));
+       jsonObjectSetKey(resp, "service",   jsonNewObject(ctx->session->remote_service));
+       jsonObjectSetKey(resp, "notes",     jsonNewObject(method->notes));
+       jsonObjectSetKey(resp, "argc",      jsonNewNumberObject(method->argc));
 
-       jsonObjectSetKey(resp, "sysmethod", 
+       jsonObjectSetKey(resp, "sysmethod",
                        jsonNewNumberObject( (method->options & OSRF_METHOD_SYSTEM) ? 1 : 0 ));
-       jsonObjectSetKey(resp, "atomic",                
+       jsonObjectSetKey(resp, "atomic",
                        jsonNewNumberObject( (method->options & OSRF_METHOD_ATOMIC) ? 1 : 0 ));
-       jsonObjectSetKey(resp, "cachable",      
+       jsonObjectSetKey(resp, "cachable",
                        jsonNewNumberObject( (method->options & OSRF_METHOD_CACHABLE) ? 1 : 0 ));
-
-       jsonObjectSetClass(resp, "method");
 }
 
 /**
-  Trys to run the requested method as a system method.
-  A system method is a well known method that all
-  servers implement.  
-  @param context The current method context
-  @return 0 if the method is run successfully, return < 0 means
-  the method was not run, return > 0 means the method was run
-  and the application code now needs to send a 'request complete' 
-  message
- */
+       @brief Run the requested system method.
+       @param ctx The method context.
+       @return Zero if the method is run successfully; -1 if the method was not run; 1 if the
+       method was run and the application code now needs to send a 'request complete' message.
+
+       A system method is a well known method implemented here for all servers.  Instead of
+       looking in the shared object, branch on the method name and call the corresponding
+       function.
+*/
 static int _osrfAppRunSystemMethod(osrfMethodContext* ctx) {
-       OSRF_METHOD_VERIFY_CONTEXT(ctx);
+       if( osrfMethodVerifyContext( ctx ) < 0 ) {
+               osrfLogError( OSRF_LOG_MARK,  "_osrfAppRunSystemMethod: Received invalid method context" );
+               return -1;
+       }
 
-       if(     !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ALL ) || 
+       if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ALL ) ||
                        !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ALL_ATOMIC )) {
-
                return osrfAppIntrospectAll(ctx);
        }
 
-
-       if(     !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT ) ||
+       if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT ) ||
                        !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ATOMIC )) {
-
                return osrfAppIntrospect(ctx);
        }
 
-       if(     !strcmp(ctx->method->name, OSRF_SYSMETHOD_ECHO ) ||
+       if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_ECHO ) ||
                        !strcmp(ctx->method->name, OSRF_SYSMETHOD_ECHO_ATOMIC )) {
-
                return osrfAppEcho(ctx);
        }
 
-
-       osrfAppRequestRespondException( ctx->session, 
+       osrfAppRequestRespondException( ctx->session,
                        ctx->request, "System method implementation not found");
 
        return 0;
 }
 
-
+/**
+       @brief Run the introspect method for a specified method or group of methods.
+       @param ctx Pointer to the method context.
+       @return 1 if successful, or if no search target is specified as a parameter; -1 if unable
+       to find a pointer to the application.
+
+       Traverse the list of methods, and report on each one whose name starts with the specified
+       search target.  In effect, the search target ends with an implicit wild card.
+*/
 static int osrfAppIntrospect( osrfMethodContext* ctx ) {
 
-       jsonObject* resp = NULL;
-       char* methodSubstring = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
-       osrfApplication* app = _osrfAppFindApplication( ctx->session->remote_service );
-       int len = 0;
-
-       if(!methodSubstring) return 1; /* respond with no methods */
+       // Get the name of the method to introspect
+       const char* methodSubstring = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
+       if( !methodSubstring )
+               return 1; /* respond with no methods */
 
-       if(app) {
+       // Get a pointer to the application
+       osrfApplication* app = _osrfAppFindApplication( ctx->session->remote_service );
+       if( !app )
+               return -1;   // Oops, no application...
 
-               osrfHashIterator* itr = osrfNewHashIterator(app->methods);
-               osrfMethod* method;
+       int len = 0;
+       osrfHashIterator* itr = osrfNewHashIterator(app->methods);
+       osrfMethod* method;
 
-               while( (method = osrfHashIteratorNext(itr)) ) {
-                       if( (len = strlen(methodSubstring)) <= strlen(method->name) ) {
-                               if( !strncmp( method->name, methodSubstring, len) ) {
-                                       resp = jsonNewObject(NULL);
-                                       _osrfAppSetIntrospectMethod( ctx, method, resp );
-                                       osrfAppRespond(ctx, resp);
-                                       jsonObjectFree(resp);
-                               }
+       while( (method = osrfHashIteratorNext(itr)) ) {
+               if( (len = strlen(methodSubstring)) <= strlen(method->name) ) {
+                       if( !strncmp( method->name, methodSubstring, len) ) {
+                               jsonObject* resp = jsonNewObject(NULL);
+                               _osrfAppSetIntrospectMethod( ctx, method, resp );
+                               osrfAppRespond(ctx, resp);
+                               jsonObjectFree(resp);
                        }
                }
-               osrfHashIteratorFree(itr);
-               return 1;
        }
 
-       return -1;
-
+       osrfHashIteratorFree(itr);
+       return 1;
 }
 
+/**
+       @brief Run the implement_all method.
+       @param ctx Pointer to the method context.
+       @return 1 if successful, or -1 if unable to find a pointer to the application.
 
+       Report on all of the methods of the application.
+*/
 static int osrfAppIntrospectAll( osrfMethodContext* ctx ) {
-       jsonObject* resp = NULL;
        osrfApplication* app = _osrfAppFindApplication( ctx->session->remote_service );
 
        if(app) {
                osrfHashIterator* itr = osrfNewHashIterator(app->methods);
                osrfMethod* method;
                while( (method = osrfHashIteratorNext(itr)) ) {
-                       resp = jsonNewObject(NULL);
+                       jsonObject* resp = jsonNewObject(NULL);
                        _osrfAppSetIntrospectMethod( ctx, method, resp );
                        osrfAppRespond(ctx, resp);
                        jsonObjectFree(resp);
                }
                osrfHashIteratorFree(itr);
                return 1;
-       }
-
-       return -1;
+       } else
+               return -1;
 }
 
+/**
+       @brief Run the echo method.
+       @param ctx Pointer to the method context.
+       @return -1 if the method context is invalid or corrupted; otherwise 1.
+
+       Send the client a copy of each parameter.
+*/
 static int osrfAppEcho( osrfMethodContext* ctx ) {
-       OSRF_METHOD_VERIFY_CONTEXT(ctx);
+       if( osrfMethodVerifyContext( ctx ) < 0 ) {
+               osrfLogError( OSRF_LOG_MARK,  "osrfAppEcho: Received invalid method context" );
+               return -1;
+       }
+
        int i;
        for( i = 0; i < ctx->params->size; i++ ) {
                const jsonObject* str = jsonObjectGetIndex(ctx->params,i);
@@ -510,3 +1007,109 @@ static int osrfAppEcho( osrfMethodContext* ctx ) {
        return 1;
 }
 
+/**
+       @brief Perform a series of sanity tests on an osrfMethodContext.
+       @param ctx Pointer to the osrfMethodContext to be checked.
+       @return Zero if the osrfMethodContext passes all tests, or -1 if it doesn't.
+*/
+int osrfMethodVerifyContext( osrfMethodContext* ctx )
+{
+       if( !ctx ) {
+               osrfLogError( OSRF_LOG_MARK,  "Context is NULL in app request" );
+               return -1;
+       }
+
+       if( !ctx->session ) {
+               osrfLogError( OSRF_LOG_MARK, "Session is NULL in app request" );
+               return -1;
+       }
+
+       if( !ctx->method )
+       {
+               osrfLogError( OSRF_LOG_MARK, "Method is NULL in app request" );
+               return -1;
+       }
+
+       if( ctx->method->argc ) {
+               if( !ctx->params ) {
+                       osrfLogError( OSRF_LOG_MARK,
+                               "Params is NULL in app request %s", ctx->method->name );
+                       return -1;
+               }
+               if( ctx->params->type != JSON_ARRAY ) {
+                       osrfLogError( OSRF_LOG_MARK,
+                               "'params' is not a JSON array for method %s", ctx->method->name );
+                       return -1;
+               }
+       }
+
+       if( !ctx->method->name ) {
+               osrfLogError( OSRF_LOG_MARK, "Method name is NULL" );
+                return -1;
+       }
+
+       // Log the call, with the method and parameters
+       char* params_str = jsonObjectToJSON( ctx->params );
+       if( params_str ) {
+               // params_str will at minimum be "[]"
+               int i = 0;
+               const char* str;
+               char* method = ctx->method->name;
+               int redact_params = 0;
+               while( (str = osrfStringArrayGetString(log_protect_arr, i++)) ) {
+                       //osrfLogInternal(OSRF_LOG_MARK, "Checking for log protection [%s]", str);
+                       if(!strncmp(method, str, strlen(str))) {
+                               redact_params = 1;
+                               break;
+                       }
+               }
+
+               char* params_logged;
+               if(redact_params) {
+                       params_logged = strdup("**PARAMS REDACTED**");
+               } else {
+                       params_str[strlen(params_str) - 1] = '\0'; // drop the trailing ']'
+                       params_logged = strdup(params_str + 1);
+               }
+               free( params_str );
+               osrfLogInfo( OSRF_LOG_MARK, "CALL: %s %s %s",
+                       ctx->session->remote_service, ctx->method->name, params_logged);
+               free( params_logged );
+       }
+       return 0;
+}
+
+/**
+       @brief Free an osrfMethod.
+       @param name Name of the method (not used).
+       @param p Void pointer pointing to the osrfMethod.
+
+       This function is designed to be installed as a callback for an osrfHash (hence the
+       unused @a name parameter and the void pointer).
+*/
+static void osrfMethodFree( char* name, void* p ) {
+       osrfMethod* method = p;
+       if( method ) {
+               free( method->name );
+               free( method->symbol );
+               free( method->notes );
+               free( method );
+       }
+}
+
+/**
+       @brief Free an osrfApplication
+       @param name Name of the application (not used).
+       @param p Void pointer pointing to the osrfApplication.
+
+       This function is designed to be installed as a callback for an osrfHash (hence the
+       unused @a name parameter and the void pointer).
+*/
+static void osrfAppFree( char* name, void* p ) {
+       osrfApplication* app = p;
+       if( app ) {
+               dlclose( app->handle );
+               osrfHashFree( app->methods );
+               free( app );
+       }
+}