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