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