1. Changes to comments and white space.
[OpenSRF.git] / src / libopensrf / osrf_message.c
1 /**
2         @file osrf_message.c
3         @brief Implementation of osrfMessage.
4 */
5
6 /* libxml stuff for the config reader */
7 #include <libxml/xmlmemory.h>
8 #include <libxml/parser.h>
9 #include <libxml/xpath.h>
10 #include <libxml/xpathInternals.h>
11 #include <libxml/tree.h>
12
13 #include <opensrf/osrf_message.h>
14
15 static jsonObject* osrfMessageToJSON( const osrfMessage* msg );
16 static osrfMessage* deserialize_one_message( const jsonObject* message );
17
18 static char default_locale[17] = "en-US\0\0\0\0\0\0\0\0\0\0\0\0";
19 static char* current_locale = NULL;
20
21 /**
22         @brief Allocate and initialize an osrfMessage.
23         @param type One of CONNECT, REQUEST, RESULT, STATUS, or DISCONNECT.
24         @param thread_trace Thread trace.
25         @param protocol Protocol.
26         @return Pointer to the newly allocated osrfMessage.
27
28         The calling code is responsible for freeing the osrfMessage by calling osrfMessageFree().
29 */
30 osrfMessage* osrf_message_init( enum M_TYPE type, int thread_trace, int protocol ) {
31
32         osrfMessage* msg            = (osrfMessage*) safe_malloc(sizeof(osrfMessage));
33         msg->m_type                 = type;
34         msg->thread_trace           = thread_trace;
35         msg->protocol               = protocol;
36         msg->status_name            = NULL;
37         msg->status_text            = NULL;
38         msg->status_code            = 0;
39         msg->next                   = NULL;
40         msg->is_exception           = 0;
41         msg->_params                = NULL;
42         msg->_result_content        = NULL;
43         msg->result_string          = NULL;
44         msg->method_name            = NULL;
45         msg->sender_locale          = NULL;
46         msg->sender_tz_offset       = 0;
47
48         return msg;
49 }
50
51
52 /**
53         @brief Return the previous locale.
54         @return A pointer to the locale specified by the last message to have been deserialized,
55                 or NULL.
56
57         A JSON message may specify a locale string, which is saved as the current locale.  If
58         the message does not specify a locale string, then the current locale becomes NULL.
59
60         This function returns a pointer to an internal buffer.  Since both the address and the
61         contents of this buffer may change from one call to the next, the calling code should
62         either use the result immediately or make a copy of the string for later use.
63 */
64 const char* osrf_message_get_last_locale() {
65         return current_locale;
66 }
67
68 /**
69         @brief Set the locale for a specified osrfMessage.
70         @param msg Pointer to the osrfMessage.
71         @param locale Pointer to the locale string to be installed in the osrfMessage.
72         @return Pointer to the new locale string for the osrfMessage, or NULL if either
73                 parameter is NULL.
74
75         If no locale is specified for an osrfMessage, we use the default locale.
76
77         Used for a REQUEST message.
78 */
79 const char* osrf_message_set_locale( osrfMessage* msg, const char* locale ) {
80         if( msg == NULL || locale == NULL )
81                 return NULL;
82         if( msg->sender_locale )
83                 free( msg->sender_locale );
84         return msg->sender_locale = strdup( locale );
85 }
86
87 /**
88         @brief Change the default locale.
89         @param locale The new default locale.
90         @return A pointer to the new default locale if successful, or NULL if not.
91
92         The function returns NULL if the parameter is NULL, or if the proposed new locale is
93         longer than 16 characters.
94
95         At this writing, nothing calls this function.
96 */
97 const char* osrf_message_set_default_locale( const char* locale ) {
98         if( locale == NULL ) return NULL;
99         if( strlen(locale) > sizeof(default_locale) - 1 ) return NULL;
100
101         strcpy( default_locale, locale );
102         return default_locale;
103 }
104
105 /**
106         @brief Populate the method_name member of an osrfMessage.
107         @param msg Pointer to the osrfMessage.
108         @param method_name The method name.
109
110         Used for a REQUEST message to specify what method to invoke.
111 */
112 void osrf_message_set_method( osrfMessage* msg, const char* method_name ) {
113         if( msg == NULL || method_name == NULL )
114                 return;
115         if( msg->method_name )
116                 free( msg->method_name );
117         msg->method_name = strdup( method_name );
118 }
119
120
121 /**
122         @brief Add a copy of a jsonObject to an osrfMessage as a parameter for a method call.
123         @param msg Pointer to the osrfMessage.
124         @param o Pointer to the jsonObject of which a copy is to be stored.
125
126         Make a copy of the input jsonObject, with all classnames encoded with JSON_CLASS_KEY and
127         JSON_DATA_KEY.  Append it to a JSON_ARRAY stored at msg->_params.
128
129         If there is nothing at msg->_params, create a new JSON_ARRAY for it and add the new object
130         as the first element.
131
132         If the jsonObject is raw (i.e. the class information has not been decoded into classnames),
133         decode it.  If the class information has already been decoded, discard it.
134
135         See also osrf_message_add_param().
136
137         At this writing, nothing calls this function.
138 */
139 void osrf_message_add_object_param( osrfMessage* msg, const jsonObject* o ) {
140         if(!msg|| !o) return;
141         if(!msg->_params)
142                 msg->_params = jsonNewObjectType( JSON_ARRAY );
143         jsonObjectPush(msg->_params, jsonObjectDecodeClass( o ));
144 }
145
146 /**
147         @brief Populate the params member of an osrfMessage.
148         @param msg Pointer to the osrfMessage.
149         @param o Pointer to a jsonObject representing the parameter(s) to a method.
150
151         Make a copy of a jsonObject and install it as the parameter list for a method call.
152
153         If the osrfMessage already has any parameters, discard them.
154
155         The @a o parameter should point to a jsonObject of type JSON_ARRAY, with each element
156         of the array being a parameter.  If @a o points to any other type of jsonObject, create
157         a JSON_ARRAY as a wrapper for it, and install a copy of @a o as its only element.
158
159         Used for a REQUEST message, to pass parameters to a method.  The alternative is to call
160         osrf_message_add_param() or osrf_message_add_object_param() repeatedly as needed to add
161         one parameter at a time.
162 */
163 void osrf_message_set_params( osrfMessage* msg, const jsonObject* o ) {
164         if(!msg || !o) return;
165
166         if(msg->_params)
167                 jsonObjectFree(msg->_params);
168
169         if(o->type == JSON_ARRAY) {
170                 msg->_params = jsonObjectClone(o);
171         } else {
172                 osrfLogDebug( OSRF_LOG_MARK, "passing non-array to osrf_message_set_params(), fixing...");
173                 jsonObject* clone = jsonObjectClone(o);
174                 msg->_params = jsonNewObjectType( JSON_ARRAY );
175                 jsonObjectPush(msg->_params, clone);
176                 return;
177         }
178 }
179
180
181 /**
182         @brief Add a JSON string to an osrfMessage as a parameter for a method call.
183         @param msg Pointer to the osrfMessage.
184         @param param_string A JSON string encoding the parameter to be added.
185
186         Translate the JSON string into a jsonObject, and append it to the parameter list.
187         If the parameter list doesn't already exist, create an empty one and add the new
188         parameter to it.
189
190         Decode any class information in the raw JSON into classnames.
191
192         If the JSON string is not valid JSON, append a new jsonObject of type JSON_NULL.
193
194         Used for a REQUEST message, to pass a parameter to a method,  The alternative is to
195         call osrf_message_set_params() to provide all the parameters at once.  See also
196         osrf_message_add_object_param().
197 */
198 void osrf_message_add_param( osrfMessage* msg, const char* param_string ) {
199         if(msg == NULL || param_string == NULL) return;
200         if(!msg->_params) msg->_params = jsonNewObjectType( JSON_ARRAY );
201         jsonObjectPush(msg->_params, jsonParseString(param_string));
202 }
203
204
205 /**
206         @brief Set the status_name, status_text, and status_code members of an osrfMessage.
207         @param msg Pointer to the osrfMessage to be populated.
208         @param status_name Status name (may be NULL).
209         @param status_text Status text (may be NULL).
210         @param status_code Status code.
211
212         If the @a status_name or @a status_text parameter is NULL, the corresponding member
213         is left unchanged.
214
215         Used for a RESULT or STATUS message.
216 */
217 void osrf_message_set_status_info( osrfMessage* msg,
218                 const char* status_name, const char* status_text, int status_code ) {
219         if(!msg) return;
220
221         if( status_name != NULL ) {
222                 if( msg->status_name )
223                         free( msg->status_name );
224                 msg->status_name = strdup( status_name );
225         }
226
227         if( status_text != NULL ) {
228                 if( msg->status_text )
229                         free( msg->status_text );
230                 msg->status_text = strdup( status_text );
231         }
232
233         msg->status_code = status_code;
234 }
235
236
237 /**
238         @brief Populate the result_string and _result_content members of an osrfMessage.
239         @param msg Pointer to the osrfMessage to be populated.
240         @param json_string A JSON string encoding a result set.
241
242         Used for a RESULT message to return the results of a request, such as a database lookup.
243 */
244 void osrf_message_set_result_content( osrfMessage* msg, const char* json_string ) {
245         if( msg == NULL || json_string == NULL) return;
246         if( msg->result_string )
247                 free( msg->result_string );
248         if( msg->_result_content )
249                 jsonObjectFree( msg->_result_content );
250
251         msg->result_string   = strdup(json_string);
252         msg->_result_content = jsonParseString(json_string);
253 }
254
255
256 /**
257         @brief Free an osrfMessage and everything it owns.
258         @param msg Pointer to the osrfMessage to be freed.
259 */
260 void osrfMessageFree( osrfMessage* msg ) {
261         if( msg == NULL )
262                 return;
263
264         if( msg->status_name != NULL )
265                 free(msg->status_name);
266
267         if( msg->status_text != NULL )
268                 free(msg->status_text);
269
270         if( msg->_result_content != NULL )
271                 jsonObjectFree( msg->_result_content );
272
273         if( msg->result_string != NULL )
274                 free( msg->result_string);
275
276         if( msg->method_name != NULL )
277                 free(msg->method_name);
278
279         if( msg->sender_locale != NULL )
280                 free(msg->sender_locale);
281
282         if( msg->_params != NULL )
283                 jsonObjectFree(msg->_params);
284
285         free(msg);
286 }
287
288
289 /**
290         @brief Turn a collection of osrfMessages into one big JSON string.
291         @param msgs Pointer to an array of osrfMessages.
292         @param count Maximum number of messages to serialize.
293         @return Pointer to the JSON string.
294
295         Traverse the array, adding each osrfMessage in turn to a JSON_ARRAY.  Stop when you have added
296         the maximum number of messages, or when you encounter a NULL pointer in the array.  Then
297         translate the JSON_ARRAY into a JSON string.
298
299         The calling code is responsible for freeing the returned string.
300 */
301 char* osrfMessageSerializeBatch( osrfMessage* msgs [], int count ) {
302         if( !msgs ) return NULL;
303
304         jsonObject* wrapper = jsonNewObjectType(JSON_ARRAY);
305
306         int i = 0;
307         while( (i < count) && msgs[i] ) {
308                 jsonObjectPush(wrapper, osrfMessageToJSON( msgs[i] ));
309                 ++i;
310         }
311
312         char* j = jsonObjectToJSON(wrapper);
313         jsonObjectFree(wrapper);
314
315         return j;
316 }
317
318
319 /**
320         @brief Turn a single osrfMessage into a JSON string.
321         @param msg Pointer to the osrfMessage to be serialized.
322         @return Pointer to the resulting JSON string.
323
324         Translate the osrfMessage into JSON, wrapped in a JSON array.
325
326         This function is equivalent to osrfMessageSerializeBatch() for an array of one pointer.
327
328         The calling code is responsible for freeing the returned string.
329 */
330 char* osrf_message_serialize(const osrfMessage* msg) {
331
332         if( msg == NULL ) return NULL;
333         char* j = NULL;
334
335         jsonObject* json = osrfMessageToJSON( msg );
336
337         if(json) {
338                 jsonObject* wrapper = jsonNewObjectType(JSON_ARRAY);
339                 jsonObjectPush(wrapper, json);
340                 j = jsonObjectToJSON(wrapper);
341                 jsonObjectFree(wrapper);
342         }
343
344         return j;
345 }
346
347
348 /**
349         @brief Translate an osrfMessage into a jsonObject.
350         @param msg Pointer to the osrfMessage to be translated.
351         @return Pointer to a newly created jsonObject.
352
353         The resulting jsonObject is a JSON_HASH with a classname of "osrfMessage", and the following keys:
354         - "threadTrace"
355         - "locale"
356         - "type"
357         - "payload" (only for STATUS, REQUEST, and RESULT messages)
358
359         The data for "payload" is also a JSON_HASH, whose structure depends on the message type:
360
361         For a STATUS message, the payload's classname is msg->status_name.  The keys are "status"
362         (carrying msg->status_text) and "statusCode" (carrying the status code as a string).
363
364         For a REQUEST message, the payload's classname is "osrfMethod".  The keys are "method"
365         (carrying msg->method_name) and "params" (carrying a jsonObject to pass any parameters
366         to the method call).
367
368         For a RESULT message, the payload's classname is "osrfResult".  The keys are "status"
369         (carrying msg->status_text), "statusCode" (carrying the status code as a string), and
370         "content" (carrying a jsonObject to return any results from the method call).
371
372         The calling code is responsible for freeing the returned jsonObject.
373 */
374 static jsonObject* osrfMessageToJSON( const osrfMessage* msg ) {
375
376         jsonObject* json = jsonNewObjectType(JSON_HASH);
377         jsonObjectSetClass(json, "osrfMessage");
378         jsonObject* payload;
379         char sc[64];
380         osrf_clearbuf(sc, sizeof(sc));
381
382         INT_TO_STRING(msg->thread_trace);
383         jsonObjectSetKey(json, "threadTrace", jsonNewObject(INTSTR));
384
385         if (msg->sender_locale != NULL) {
386                 jsonObjectSetKey(json, "locale", jsonNewObject(msg->sender_locale));
387         } else if (current_locale != NULL) {
388                 jsonObjectSetKey(json, "locale", jsonNewObject(current_locale));
389         } else {
390                 jsonObjectSetKey(json, "locale", jsonNewObject(default_locale));
391         }
392
393         switch(msg->m_type) {
394
395                 case CONNECT:
396                         jsonObjectSetKey(json, "type", jsonNewObject("CONNECT"));
397                         break;
398
399                 case DISCONNECT:
400                         jsonObjectSetKey(json, "type", jsonNewObject("DISCONNECT"));
401                         break;
402
403                 case STATUS:
404                         jsonObjectSetKey(json, "type", jsonNewObject("STATUS"));
405                         payload = jsonNewObject(NULL);
406                         jsonObjectSetClass(payload, msg->status_name);
407                         jsonObjectSetKey(payload, "status", jsonNewObject(msg->status_text));
408                         snprintf(sc, sizeof(sc), "%d", msg->status_code);
409                         jsonObjectSetKey(payload, "statusCode", jsonNewObject(sc));
410                         jsonObjectSetKey(json, "payload", payload);
411                         break;
412
413                 case REQUEST:
414                         jsonObjectSetKey(json, "type", jsonNewObject("REQUEST"));
415                         payload = jsonNewObject(NULL);
416                         jsonObjectSetClass(payload, "osrfMethod");
417                         jsonObjectSetKey(payload, "method", jsonNewObject(msg->method_name));
418                         jsonObjectSetKey( payload, "params", jsonObjectDecodeClass( msg->_params ) );
419                         jsonObjectSetKey(json, "payload", payload);
420
421                         break;
422
423                 case RESULT:
424                         jsonObjectSetKey(json, "type", jsonNewObject("RESULT"));
425                         payload = jsonNewObject(NULL);
426                         jsonObjectSetClass(payload,"osrfResult");
427                         jsonObjectSetKey(payload, "status", jsonNewObject(msg->status_text));
428                         snprintf(sc, sizeof(sc), "%d", msg->status_code);
429                         jsonObjectSetKey(payload, "statusCode", jsonNewObject(sc));
430                         jsonObjectSetKey(payload, "content", jsonObjectDecodeClass( msg->_result_content ));
431                         jsonObjectSetKey(json, "payload", payload);
432                         break;
433         }
434
435         return json;
436 }
437
438 /**
439         @brief Translate a JSON array into an osrfList of osrfMessages.
440         @param string The JSON string to be translated.
441         @param list Pointer to an osrfList of osrfMessages (may be NULL)
442         @return Pointer to an osrfList containing pointers to osrfMessages.
443
444         The JSON string is expected to be a JSON array, with each element encoding an osrfMessage.
445
446         Translate each element of the JSON array into an osrfMessage, and store a pointer to the
447         osrfMessage in an osrfList.
448
449         If the @a list parameter is NULL, create a new osrfList (with osrfMessageFree() as the
450         callback function for freeing items), populate it, and return a pointer to it.  Otherwise
451         clear the osrfList provided and reuse it.
452
453         When calling osrfMessageDeserialize repeatedly, a reasonable strategy is to pass a NULL
454         for the @a list parameter on the first call, and pass the value returned from the first
455         call on subsequent calls.
456
457         The calling code is responsible for eventually freeing the returned osrfList by calling
458         osrfListFree().
459  */
460 osrfList* osrfMessageDeserialize( const char* string, osrfList* list ) {
461
462         if( list )
463                 osrfListClear( list );
464
465         if( ! string  || ! *string ) {
466                 if( ! list ) {
467                         list = osrfNewList( 1 );
468                         list->freeItem = (void(*)(void*)) osrfMessageFree;
469                 }
470                 return list;                   // No string?  Return empty list.
471         }
472         
473         // Parse the JSON
474         jsonObject* json = jsonParseString(string);
475         if(!json) {
476                 osrfLogWarning( OSRF_LOG_MARK,
477                                 "osrfMessageDeserialize() unable to parse data: \n%s\n", string);
478                 if( ! list ) {
479                         list = osrfNewList( 1 );
480                         list->freeItem = (void(*)(void*)) osrfMessageFree;
481                 }
482                 return list;                   // Bad JSON?  Return empty list.
483         }
484
485         const unsigned int count = (int) json->size;
486         if( ! list ) {
487                 // Create a right-sized osrfList
488                 list = osrfNewList( count );
489                 list->freeItem = (void(*)(void*)) osrfMessageFree;
490         }
491
492         // Traverse the JSON_ARRAY, turning each element into an osrfMessage
493         int i;
494         for( i = 0; i < count; ++i ) {
495
496                 const jsonObject* message = jsonObjectGetIndex( json, i );
497                 if( message && message->type != JSON_NULL &&
498                                   message->classname && !strcmp(message->classname, "osrfMessage" )) {
499                         osrfListPush( list, deserialize_one_message( message ) );
500                 }
501         }
502
503         jsonObjectFree( json );
504         return list;
505 }
506
507 /**
508         @brief Translate a JSON array into an array of osrfMessages.
509         @param string The JSON string to be translated.
510         @param msgs Pointer to an array of pointers to osrfMessage, to receive the results.
511         @param count How many slots are available in the @a msgs array.
512         @return The number of osrfMessages created.
513
514         The JSON string is expected to be a JSON array, with each element encoding an osrfMessage.
515
516         If there are too many messages in the JSON array to fit into the pointer array, we
517         silently ignore the excess.
518 */
519 int osrf_message_deserialize(const char* string, osrfMessage* msgs[], int count) {
520
521         if(!string || !msgs || count <= 0) return 0;
522         int numparsed = 0;
523
524         // Parse the JSON
525         jsonObject* json = jsonParseString(string);
526
527         if(!json) {
528                 osrfLogWarning( OSRF_LOG_MARK,
529                         "osrf_message_deserialize() unable to parse data: \n%s\n", string);
530                 return 0;
531         }
532
533         // Traverse the JSON_ARRAY, turning each element into an osrfMessage
534         int x;
535         for( x = 0; x < json->size && x < count; x++ ) {
536
537                 const jsonObject* message = jsonObjectGetIndex( json, x );
538
539                 if( message && message->type != JSON_NULL &&
540                         message->classname && !strcmp(message->classname, "osrfMessage" )) {
541                         msgs[numparsed++] = deserialize_one_message( message );
542                 }
543         }
544
545         jsonObjectFree( json );
546         return numparsed;
547 }
548
549
550 /**
551         @brief Translate a jsonObject into a single osrfMessage.
552         @param obj Pointer to the jsonObject to be translated.
553         @return Pointer to a newly created osrfMessage.
554
555         It is assumed that @a obj is non-NULL and points to a valid representation of a message.
556         For a description of the expected structure of this representations, see osrfMessageToJSON().
557
558         The calling code is responsible for freeing the osrfMessage by calling osrfMessageFree().
559 */
560 static osrfMessage* deserialize_one_message( const jsonObject* obj ) {
561
562         // Get the message type.  If it isn't present, default to CONNECT.
563         const jsonObject* tmp = jsonObjectGetKeyConst( obj, "type" );
564
565         enum M_TYPE type = CONNECT;
566         const char* t = jsonObjectGetString( tmp );
567         if( t ) {
568
569                 if(      !strcmp( t, "CONNECT"    ))   type = CONNECT;
570                 else if( !strcmp( t, "DISCONNECT" ))   type = DISCONNECT;
571                 else if( !strcmp( t, "STATUS"     ))   type = STATUS;
572                 else if( !strcmp( t, "REQUEST"    ))   type = REQUEST;
573                 else if( !strcmp( t, "RESULT"     ))   type = RESULT;
574         }
575
576         // Get the thread trace, defaulting to zero.
577         int trace = 0;
578         tmp = jsonObjectGetKeyConst( obj, "threadTrace" );
579         if( tmp ) {
580                 const char* tt = jsonObjectGetString( tmp );
581                 if( tt ) {
582                         trace = atoi( tt );
583                 }
584         }
585
586         // Get the protocol, defaulting to zero.
587         int protocol = 0;
588         tmp = jsonObjectGetKeyConst( obj, "protocol" );
589         if(tmp) {
590                 const char* proto = jsonObjectGetString(tmp);
591                 if( proto ) {
592                         protocol = atoi( proto );
593                 }
594         }
595
596         // Now that we have the essentials, create an osrfMessage
597         osrfMessage* msg = osrf_message_init( type, trace, protocol );
598
599         // Get the sender's locale, or leave it NULL if not specified.
600         if ( current_locale )
601                 free( current_locale );
602
603         tmp = jsonObjectGetKeyConst( obj, "locale" );
604         if(tmp && ( msg->sender_locale = jsonObjectToSimpleString(tmp))) {
605                 current_locale = strdup( msg->sender_locale );
606         } else {
607                 current_locale = NULL;
608         }
609
610         tmp = jsonObjectGetKeyConst( obj, "payload" );
611         if(tmp) {
612                 // Get method name and parameters for a REQUEST
613                 const jsonObject* tmp0 = jsonObjectGetKeyConst(tmp,"method");
614                 const char* tmp_str = jsonObjectGetString(tmp0);
615                 if(tmp_str)
616                         msg->method_name = strdup(tmp_str);
617
618                 tmp0 = jsonObjectGetKeyConst(tmp,"params");
619                 if(tmp0) {
620                         // Note that we use jsonObjectDecodeClass() instead of
621                         // jsonObjectClone().  The classnames are already decoded,
622                         // but jsonObjectDecodeClass removes the decoded classnames.
623                         msg->_params = jsonObjectDecodeClass( tmp0 );
624                         if(msg->_params && msg->_params->type == JSON_NULL)
625                                 msg->_params->type = JSON_ARRAY;
626                 }
627
628                 // Get status fields for a RESULT or STATUS
629                 if(tmp->classname)
630                         msg->status_name = strdup(tmp->classname);
631
632                 tmp0 = jsonObjectGetKeyConst(tmp,"status");
633                 tmp_str = jsonObjectGetString(tmp0);
634                 if(tmp_str)
635                         msg->status_text = strdup(tmp_str);
636
637                 tmp0 = jsonObjectGetKeyConst(tmp,"statusCode");
638                 if(tmp0) {
639                         tmp_str = jsonObjectGetString(tmp0);
640                         if(tmp_str)
641                                 msg->status_code = atoi(tmp_str);
642                         if(tmp0->type == JSON_NUMBER)
643                                 msg->status_code = (int) jsonObjectGetNumber(tmp0);
644                 }
645
646                 // Get the content for a RESULT
647                 tmp0 = jsonObjectGetKeyConst(tmp,"content");
648                 if(tmp0) {
649                         // Note that we use jsonObjectDecodeClass() instead of
650                         // jsonObjectClone().  The classnames are already decoded,
651                         // but jsonObjectDecodeClass removes the decoded classnames.
652                         msg->_result_content = jsonObjectDecodeClass( tmp0 );
653                 }
654
655         }
656
657         return msg;
658 }
659
660
661 /**
662         @brief Return a pointer to the result content of an osrfMessage.
663         @param msg Pointer to the osrfMessage whose result content is to be returned.
664         @return Pointer to the result content (or NULL if there is no such content, or if @a msg is
665         NULL).
666
667         The returned pointer points into the innards of the osrfMessage.  The calling code should
668         @em not call jsonObjectFree() on it, because the osrfMessage still owns it.
669 */
670 jsonObject* osrfMessageGetResult( osrfMessage* msg ) {
671         if(msg) return msg->_result_content;
672         return NULL;
673 }