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