3 @brief Implementation of osrfMessage.
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>
13 #include <opensrf/osrf_message.h>
14 #include "opensrf/osrf_stack.h"
16 static jsonObject* osrfMessageToJSON( const osrfMessage* msg );
17 static osrfMessage* deserialize_one_message( const jsonObject* message );
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;
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.
29 The calling code is responsible for freeing the osrfMessage by calling osrfMessageFree().
31 osrfMessage* osrf_message_init( enum M_TYPE type, int thread_trace, int protocol ) {
33 osrfMessage* msg = (osrfMessage*) safe_malloc(sizeof(osrfMessage));
35 msg->thread_trace = thread_trace;
36 msg->protocol = protocol;
37 msg->status_name = NULL;
38 msg->status_text = NULL;
41 msg->is_exception = 0;
43 msg->_result_content = NULL;
44 msg->method_name = NULL;
45 msg->sender_locale = NULL;
52 @brief Return the previous locale.
53 @return A pointer to the locale specified by the last message to have been deserialized,
56 A JSON message may specify a locale string, which is saved as the current locale. If
57 the message does not specify a locale string, then the current locale becomes NULL.
59 This function returns a pointer to an internal buffer. Since both the address and the
60 contents of this buffer may change from one call to the next, the calling code should
61 either use the result immediately or make a copy of the string for later use.
63 const char* osrf_message_get_last_locale() {
64 return current_locale;
68 @brief Set the locale for a specified osrfMessage.
69 @param msg Pointer to the osrfMessage.
70 @param locale Pointer to the locale string to be installed in the osrfMessage.
71 @return Pointer to the new locale string for the osrfMessage, or NULL if either
74 If no locale is specified for an osrfMessage, we use the default locale.
76 Used for a REQUEST message.
78 const char* osrf_message_set_locale( osrfMessage* msg, const char* locale ) {
79 if( msg == NULL || locale == NULL )
81 if( msg->sender_locale )
82 free( msg->sender_locale );
83 return msg->sender_locale = strdup( locale );
87 @brief Change the default locale.
88 @param locale The new default locale.
89 @return A pointer to the new default locale if successful, or NULL if not.
91 The function returns NULL if the parameter is NULL, or if the proposed new locale is
92 longer than 16 characters.
94 At this writing, nothing calls this function.
96 const char* osrf_message_set_default_locale( const char* locale ) {
97 if( locale == NULL ) return NULL;
98 if( strlen(locale) > sizeof(default_locale) - 1 ) return NULL;
100 strcpy( default_locale, locale );
101 return default_locale;
105 @brief Populate the method_name member of an osrfMessage.
106 @param msg Pointer to the osrfMessage.
107 @param method_name The method name.
109 Used for a REQUEST message to specify what method to invoke.
111 void osrf_message_set_method( osrfMessage* msg, const char* method_name ) {
112 if( msg == NULL || method_name == NULL )
114 if( msg->method_name )
115 free( msg->method_name );
116 msg->method_name = strdup( method_name );
121 @brief Add a copy of a jsonObject to an osrfMessage as a parameter for a method call.
122 @param msg Pointer to the osrfMessage.
123 @param o Pointer to the jsonObject of which a copy is to be stored.
125 Make a copy of the input jsonObject, with all classnames encoded with JSON_CLASS_KEY and
126 JSON_DATA_KEY. Append it to a JSON_ARRAY stored at msg->_params.
128 If there is nothing at msg->_params, create a new JSON_ARRAY for it and add the new object
129 as the first element.
131 If the jsonObject is raw (i.e. the class information has not been decoded into classnames),
132 decode it. If the class information has already been decoded, discard it.
134 See also osrf_message_add_param().
136 At this writing, nothing calls this function.
138 void osrf_message_add_object_param( osrfMessage* msg, const jsonObject* o ) {
139 if(!msg|| !o) return;
141 msg->_params = jsonNewObjectType( JSON_ARRAY );
142 jsonObjectPush(msg->_params, jsonObjectDecodeClass( o ));
146 @brief Populate the params member of an osrfMessage.
147 @param msg Pointer to the osrfMessage.
148 @param o Pointer to a jsonObject representing the parameter(s) to a method.
150 Make a copy of a jsonObject and install it as the parameter list for a method call.
152 If the osrfMessage already has any parameters, discard them.
154 The @a o parameter should point to a jsonObject of type JSON_ARRAY, with each element
155 of the array being a parameter. If @a o points to any other type of jsonObject, create
156 a JSON_ARRAY as a wrapper for it, and install a copy of @a o as its only element.
158 Used for a REQUEST message, to pass parameters to a method. The alternative is to call
159 osrf_message_add_param() or osrf_message_add_object_param() repeatedly as needed to add
160 one parameter at a time.
162 void osrf_message_set_params( osrfMessage* msg, const jsonObject* o ) {
163 if(!msg || !o) return;
166 jsonObjectFree(msg->_params);
168 if(o->type == JSON_ARRAY) {
169 msg->_params = jsonObjectClone(o);
171 osrfLogDebug( OSRF_LOG_MARK, "passing non-array to osrf_message_set_params(), fixing...");
172 jsonObject* clone = jsonObjectClone(o);
173 msg->_params = jsonNewObjectType( JSON_ARRAY );
174 jsonObjectPush(msg->_params, clone);
181 @brief Add a JSON string to an osrfMessage as a parameter for a method call.
182 @param msg Pointer to the osrfMessage.
183 @param param_string A JSON string encoding the parameter to be added.
185 Translate the JSON string into a jsonObject, and append it to the parameter list.
186 If the parameter list doesn't already exist, create an empty one and add the new
189 Decode any class information in the raw JSON into classnames.
191 If the JSON string is not valid JSON, append a new jsonObject of type JSON_NULL.
193 Used for a REQUEST message, to pass a parameter to a method, The alternative is to
194 call osrf_message_set_params() to provide all the parameters at once. See also
195 osrf_message_add_object_param().
197 void osrf_message_add_param( osrfMessage* msg, const char* param_string ) {
198 if(msg == NULL || param_string == NULL) return;
199 if(!msg->_params) msg->_params = jsonNewObjectType( JSON_ARRAY );
200 jsonObjectPush(msg->_params, jsonParse(param_string));
205 @brief Set the status_name, status_text, and status_code members of an osrfMessage.
206 @param msg Pointer to the osrfMessage to be populated.
207 @param status_name Status name (may be NULL).
208 @param status_text Status text (may be NULL).
209 @param status_code Status code.
211 If the @a status_name or @a status_text parameter is NULL, the corresponding member
214 Used for a RESULT or STATUS message.
216 void osrf_message_set_status_info( osrfMessage* msg,
217 const char* status_name, const char* status_text, int status_code ) {
220 if( status_name != NULL ) {
221 if( msg->status_name )
222 free( msg->status_name );
223 msg->status_name = strdup( status_name );
226 if( status_text != NULL ) {
227 if( msg->status_text )
228 free( msg->status_text );
229 msg->status_text = strdup( status_text );
232 msg->status_code = status_code;
237 @brief Populate the _result_content membersof an osrfMessage.
238 @param msg Pointer to the osrfMessage to be populated.
239 @param json_string A JSON string encoding a result.
241 Used for a RESULT message to return the results of a remote procedure call.
243 void osrf_message_set_result_content( osrfMessage* msg, const char* json_string ) {
244 if( msg == NULL || json_string == NULL) return;
245 if( msg->_result_content )
246 jsonObjectFree( msg->_result_content );
248 msg->_result_content = jsonParse(json_string);
253 @brief Free an osrfMessage and everything it owns.
254 @param msg Pointer to the osrfMessage to be freed.
256 void osrfMessageFree( osrfMessage* msg ) {
260 if( msg->status_name != NULL )
261 free(msg->status_name);
263 if( msg->status_text != NULL )
264 free(msg->status_text);
266 if( msg->_result_content != NULL )
267 jsonObjectFree( msg->_result_content );
269 if( msg->method_name != NULL )
270 free(msg->method_name);
272 if( msg->sender_locale != NULL )
273 free(msg->sender_locale);
275 if( msg->_params != NULL )
276 jsonObjectFree(msg->_params);
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.
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.
292 The calling code is responsible for freeing the returned string.
294 char* osrfMessageSerializeBatch( osrfMessage* msgs [], int count ) {
295 if( !msgs ) return NULL;
297 jsonObject* wrapper = jsonNewObjectType(JSON_ARRAY);
300 while( (i < count) && msgs[i] ) {
301 jsonObjectPush(wrapper, osrfMessageToJSON( msgs[i] ));
305 char* j = jsonObjectToJSON(wrapper);
306 jsonObjectFree(wrapper);
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.
317 Translate the osrfMessage into JSON, wrapped in a JSON array.
319 This function is equivalent to osrfMessageSerializeBatch() for an array of one pointer.
321 The calling code is responsible for freeing the returned string.
323 char* osrf_message_serialize(const osrfMessage* msg) {
325 if( msg == NULL ) return NULL;
328 jsonObject* json = osrfMessageToJSON( msg );
331 jsonObject* wrapper = jsonNewObjectType(JSON_ARRAY);
332 jsonObjectPush(wrapper, json);
333 j = jsonObjectToJSON(wrapper);
334 jsonObjectFree(wrapper);
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.
346 The resulting jsonObject is a JSON_HASH with a classname of "osrfMessage", and the following keys:
350 - "payload" (only for STATUS, REQUEST, and RESULT messages)
352 The data for "payload" is also a JSON_HASH, whose structure depends on the message type:
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).
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
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).
365 The calling code is responsible for freeing the returned jsonObject.
367 static jsonObject* osrfMessageToJSON( const osrfMessage* msg ) {
369 jsonObject* json = jsonNewObjectType(JSON_HASH);
370 jsonObjectSetClass(json, "osrfMessage");
373 osrf_clearbuf(sc, sizeof(sc));
375 INT_TO_STRING(msg->thread_trace);
376 jsonObjectSetKey(json, "threadTrace", jsonNewObject(INTSTR));
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));
383 jsonObjectSetKey(json, "locale", jsonNewObject(default_locale));
386 switch(msg->m_type) {
389 jsonObjectSetKey(json, "type", jsonNewObject("CONNECT"));
393 jsonObjectSetKey(json, "type", jsonNewObject("DISCONNECT"));
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);
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);
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);
432 @brief Translate a JSON array into an osrfList of osrfMessages.
433 @param string The JSON string to be translated.
434 @param list Pointer to an osrfList of osrfMessages (may be NULL)
435 @return Pointer to an osrfList containing pointers to osrfMessages.
437 The JSON string is expected to be a JSON array, with each element encoding an osrfMessage.
439 Translate each element of the JSON array into an osrfMessage, and store a pointer to the
440 osrfMessage in an osrfList.
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.
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.
450 The calling code is responsible for eventually freeing the returned osrfList by calling
453 osrfList* osrfMessageDeserialize( const char* string, osrfList* list ) {
456 osrfListClear( list );
458 if( ! string || ! *string ) {
460 list = osrfNewList( 1 );
461 list->freeItem = (void(*)(void*)) osrfMessageFree;
463 return list; // No string? Return empty list.
467 jsonObject* json = jsonParse(string);
469 osrfLogWarning( OSRF_LOG_MARK,
470 "osrfMessageDeserialize() unable to parse data: \n%s\n", string);
472 list = osrfNewList( 1 );
473 list->freeItem = (void(*)(void*)) osrfMessageFree;
475 return list; // Bad JSON? Return empty list.
478 const unsigned int count = (int) json->size;
480 // Create a right-sized osrfList
481 list = osrfNewList( count );
482 list->freeItem = (void(*)(void*)) osrfMessageFree;
485 // Traverse the JSON_ARRAY, turning each element into an osrfMessage
487 for( i = 0; i < count; ++i ) {
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 ) );
496 jsonObjectFree( json );
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.
507 The JSON string is expected to be a JSON array, with each element encoding an osrfMessage.
509 If there are too many messages in the JSON array to fit into the pointer array, we
510 silently ignore the excess.
512 int osrf_message_deserialize(const char* string, osrfMessage* msgs[], int count) {
514 if(!string || !msgs || count <= 0) return 0;
518 jsonObject* json = jsonParse(string);
521 osrfLogWarning( OSRF_LOG_MARK,
522 "osrf_message_deserialize() unable to parse data: \n%s\n", string);
526 // Traverse the JSON_ARRAY, turning each element into an osrfMessage
528 for( x = 0; x < json->size && x < count; x++ ) {
530 const jsonObject* message = jsonObjectGetIndex( json, x );
532 if( message && message->type != JSON_NULL &&
533 message->classname && !strcmp(message->classname, "osrfMessage" )) {
534 msgs[numparsed++] = deserialize_one_message( message );
538 jsonObjectFree( json );
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.
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().
551 The calling code is responsible for freeing the osrfMessage by calling osrfMessageFree().
553 static osrfMessage* deserialize_one_message( const jsonObject* obj ) {
555 // Get the message type. If it isn't present, default to CONNECT.
556 const jsonObject* tmp = jsonObjectGetKeyConst( obj, "type" );
558 enum M_TYPE type = CONNECT;
559 const char* t = jsonObjectGetString( tmp );
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;
569 // Get the thread trace, defaulting to zero.
571 tmp = jsonObjectGetKeyConst( obj, "threadTrace" );
573 const char* tt = jsonObjectGetString( tmp );
579 // Get the protocol, defaulting to zero.
581 tmp = jsonObjectGetKeyConst( obj, "protocol" );
583 const char* proto = jsonObjectGetString(tmp);
585 protocol = atoi( proto );
589 // Now that we have the essentials, create an osrfMessage
590 osrfMessage* msg = osrf_message_init( type, trace, protocol );
592 // Update current_locale with the locale of the message
593 // (or set it to NULL if not specified)
594 tmp = jsonObjectGetKeyConst( obj, "locale" );
595 if(tmp && ( msg->sender_locale = jsonObjectToSimpleString(tmp))) {
596 if ( current_locale ) {
597 if( strcmp( current_locale, msg->sender_locale ) ) {
598 free( current_locale );
599 current_locale = strdup( msg->sender_locale );
600 } // else they're the same already, so don't replace one with the other
602 current_locale = strdup( msg->sender_locale );
604 if ( current_locale ) {
605 free( current_locale );
606 current_locale = NULL;
610 tmp = jsonObjectGetKeyConst( obj, "payload" );
612 // Get method name and parameters for a REQUEST
613 const jsonObject* tmp0 = jsonObjectGetKeyConst(tmp,"method");
614 const char* tmp_str = jsonObjectGetString(tmp0);
616 msg->method_name = strdup(tmp_str);
618 tmp0 = jsonObjectGetKeyConst(tmp,"params");
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;
628 // Get status fields for a RESULT or STATUS
630 msg->status_name = strdup(tmp->classname);
632 tmp0 = jsonObjectGetKeyConst(tmp,"status");
633 tmp_str = jsonObjectGetString(tmp0);
635 msg->status_text = strdup(tmp_str);
637 tmp0 = jsonObjectGetKeyConst(tmp,"statusCode");
639 tmp_str = jsonObjectGetString(tmp0);
641 msg->status_code = atoi(tmp_str);
642 if(tmp0->type == JSON_NUMBER)
643 msg->status_code = (int) jsonObjectGetNumber(tmp0);
646 // Get the content for a RESULT
647 tmp0 = jsonObjectGetKeyConst(tmp,"content");
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 );
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
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.
670 jsonObject* osrfMessageGetResult( osrfMessage* msg ) {
671 if(msg) return msg->_result_content;