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 osrfMessage* deserialize_one_message( const jsonObject* message );
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;
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.
28 The calling code is responsible for freeing the osrfMessage by calling osrfMessageFree().
30 osrfMessage* osrf_message_init( enum M_TYPE type, int thread_trace, int protocol ) {
32 osrfMessage* msg = (osrfMessage*) safe_malloc(sizeof(osrfMessage));
34 msg->thread_trace = thread_trace;
35 msg->protocol = protocol;
36 msg->status_name = NULL;
37 msg->status_text = NULL;
40 msg->is_exception = 0;
42 msg->_result_content = NULL;
43 msg->method_name = NULL;
44 msg->sender_locale = NULL;
51 @brief Return the previous locale.
52 @return A pointer to the locale specified by the last message to have been deserialized,
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.
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.
62 const char* osrf_message_get_last_locale() {
63 return current_locale;
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
73 If no locale is specified for an osrfMessage, we use the default locale.
75 Used for a REQUEST message.
77 const char* osrf_message_set_locale( osrfMessage* msg, const char* locale ) {
78 if( msg == NULL || locale == NULL )
80 if( msg->sender_locale )
81 free( msg->sender_locale );
82 return msg->sender_locale = strdup( locale );
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.
90 The function returns NULL if the parameter is NULL, or if the proposed new locale is
91 longer than 16 characters.
93 At this writing, nothing calls this function.
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;
99 strcpy( default_locale, locale );
100 return default_locale;
104 @brief Populate the method_name member of an osrfMessage.
105 @param msg Pointer to the osrfMessage.
106 @param method_name The method name.
108 Used for a REQUEST message to specify what method to invoke.
110 void osrf_message_set_method( osrfMessage* msg, const char* method_name ) {
111 if( msg == NULL || method_name == NULL )
113 if( msg->method_name )
114 free( msg->method_name );
115 msg->method_name = strdup( method_name );
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.
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.
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.
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.
133 See also osrf_message_add_param().
135 At this writing, nothing calls this function.
137 void osrf_message_add_object_param( osrfMessage* msg, const jsonObject* o ) {
138 if(!msg|| !o) return;
140 msg->_params = jsonNewObjectType( JSON_ARRAY );
141 jsonObjectPush(msg->_params, jsonObjectDecodeClass( o ));
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.
149 Make a copy of a jsonObject and install it as the parameter list for a method call.
151 If the osrfMessage already has any parameters, discard them.
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.
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.
161 void osrf_message_set_params( osrfMessage* msg, const jsonObject* o ) {
162 if(!msg || !o) return;
165 jsonObjectFree(msg->_params);
167 if(o->type == JSON_ARRAY) {
168 msg->_params = jsonObjectClone(o);
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);
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.
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
188 Decode any class information in the raw JSON into classnames.
190 If the JSON string is not valid JSON, append a new jsonObject of type JSON_NULL.
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().
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));
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.
210 If the @a status_name or @a status_text parameter is NULL, the corresponding member
213 Used for a RESULT or STATUS message.
215 void osrf_message_set_status_info( osrfMessage* msg,
216 const char* status_name, const char* status_text, int status_code ) {
219 if( status_name != NULL ) {
220 if( msg->status_name )
221 free( msg->status_name );
222 msg->status_name = strdup( status_name );
225 if( status_text != NULL ) {
226 if( msg->status_text )
227 free( msg->status_text );
228 msg->status_text = strdup( status_text );
231 msg->status_code = status_code;
236 @brief Populate the _result_content membersof an osrfMessage from a JSON string.
237 @param msg Pointer to the osrfMessage to be populated.
238 @param json_string A JSON string encoding a result.
240 Used for a RESULT message to return the results of a remote procedure call.
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 );
247 msg->_result_content = jsonParse(json_string);
252 @brief Populate the _result_content membersof an osrfMessage from a JSON object.
253 @param msg Pointer to the osrfMessage to be populated.
254 @param obj Pointer to a jsonObject encoding a result.
256 Used for a RESULT message to return the results of a remote procedure call.
258 void osrf_message_set_result( osrfMessage* msg, const jsonObject* obj ) {
259 if( msg == NULL || obj == NULL) return;
260 if( msg->_result_content )
261 jsonObjectFree( msg->_result_content );
263 msg->_result_content = jsonObjectDecodeClass( obj );
268 @brief Free an osrfMessage and everything it owns.
269 @param msg Pointer to the osrfMessage to be freed.
271 void osrfMessageFree( osrfMessage* msg ) {
275 if( msg->status_name != NULL )
276 free(msg->status_name);
278 if( msg->status_text != NULL )
279 free(msg->status_text);
281 if( msg->_result_content != NULL )
282 jsonObjectFree( msg->_result_content );
284 if( msg->method_name != NULL )
285 free(msg->method_name);
287 if( msg->sender_locale != NULL )
288 free(msg->sender_locale);
290 if( msg->_params != NULL )
291 jsonObjectFree(msg->_params);
298 @brief Turn a collection of osrfMessages into one big JSON string.
299 @param msgs Pointer to an array of osrfMessages.
300 @param count Maximum number of messages to serialize.
301 @return Pointer to the JSON string.
303 Traverse the array, adding each osrfMessage in turn to a JSON_ARRAY. Stop when you have added
304 the maximum number of messages, or when you encounter a NULL pointer in the array. Then
305 translate the JSON_ARRAY into a JSON string.
307 The calling code is responsible for freeing the returned string.
309 char* osrfMessageSerializeBatch( osrfMessage* msgs [], int count ) {
310 if( !msgs ) return NULL;
312 jsonObject* wrapper = jsonNewObjectType(JSON_ARRAY);
315 while( (i < count) && msgs[i] ) {
316 jsonObjectPush(wrapper, osrfMessageToJSON( msgs[i] ));
320 char* j = jsonObjectToJSON(wrapper);
321 jsonObjectFree(wrapper);
328 @brief Turn a single osrfMessage into a JSON string.
329 @param msg Pointer to the osrfMessage to be serialized.
330 @return Pointer to the resulting JSON string.
332 Translate the osrfMessage into JSON, wrapped in a JSON array.
334 This function is equivalent to osrfMessageSerializeBatch() for an array of one pointer.
336 The calling code is responsible for freeing the returned string.
338 char* osrf_message_serialize(const osrfMessage* msg) {
340 if( msg == NULL ) return NULL;
343 jsonObject* json = osrfMessageToJSON( msg );
346 jsonObject* wrapper = jsonNewObjectType(JSON_ARRAY);
347 jsonObjectPush(wrapper, json);
348 j = jsonObjectToJSON(wrapper);
349 jsonObjectFree(wrapper);
357 @brief Translate an osrfMessage into a jsonObject.
358 @param msg Pointer to the osrfMessage to be translated.
359 @return Pointer to a newly created jsonObject.
361 The resulting jsonObject is a JSON_HASH with a classname of "osrfMessage", and the following keys:
365 - "payload" (only for STATUS, REQUEST, and RESULT messages)
367 The data for "payload" is also a JSON_HASH, whose structure depends on the message type:
369 For a STATUS message, the payload's classname is msg->status_name. The keys are "status"
370 (carrying msg->status_text) and "statusCode" (carrying the status code as a string).
372 For a REQUEST message, the payload's classname is "osrfMethod". The keys are "method"
373 (carrying msg->method_name) and "params" (carrying a jsonObject to pass any parameters
376 For a RESULT message, the payload's classname is "osrfResult". The keys are "status"
377 (carrying msg->status_text), "statusCode" (carrying the status code as a string), and
378 "content" (carrying a jsonObject to return any results from the method call).
380 The calling code is responsible for freeing the returned jsonObject.
382 jsonObject* osrfMessageToJSON( const osrfMessage* msg ) {
384 jsonObject* json = jsonNewObjectType(JSON_HASH);
385 jsonObjectSetClass(json, "osrfMessage");
388 osrf_clearbuf(sc, sizeof(sc));
390 INT_TO_STRING(msg->thread_trace);
391 jsonObjectSetKey(json, "threadTrace", jsonNewObject(INTSTR));
393 if (msg->sender_locale != NULL) {
394 jsonObjectSetKey(json, "locale", jsonNewObject(msg->sender_locale));
395 } else if (current_locale != NULL) {
396 jsonObjectSetKey(json, "locale", jsonNewObject(current_locale));
398 jsonObjectSetKey(json, "locale", jsonNewObject(default_locale));
401 switch(msg->m_type) {
404 jsonObjectSetKey(json, "type", jsonNewObject("CONNECT"));
408 jsonObjectSetKey(json, "type", jsonNewObject("DISCONNECT"));
412 jsonObjectSetKey(json, "type", jsonNewObject("STATUS"));
413 payload = jsonNewObject(NULL);
414 jsonObjectSetClass(payload, msg->status_name);
415 jsonObjectSetKey(payload, "status", jsonNewObject(msg->status_text));
416 snprintf(sc, sizeof(sc), "%d", msg->status_code);
417 jsonObjectSetKey(payload, "statusCode", jsonNewObject(sc));
418 jsonObjectSetKey(json, "payload", payload);
422 jsonObjectSetKey(json, "type", jsonNewObject("REQUEST"));
423 payload = jsonNewObject(NULL);
424 jsonObjectSetClass(payload, "osrfMethod");
425 jsonObjectSetKey(payload, "method", jsonNewObject(msg->method_name));
426 jsonObjectSetKey( payload, "params", jsonObjectDecodeClass( msg->_params ) );
427 jsonObjectSetKey(json, "payload", payload);
432 jsonObjectSetKey(json, "type", jsonNewObject("RESULT"));
433 payload = jsonNewObject(NULL);
434 jsonObjectSetClass(payload,"osrfResult");
435 jsonObjectSetKey(payload, "status", jsonNewObject(msg->status_text));
436 snprintf(sc, sizeof(sc), "%d", msg->status_code);
437 jsonObjectSetKey(payload, "statusCode", jsonNewObject(sc));
438 jsonObjectSetKey(payload, "content", jsonObjectDecodeClass( msg->_result_content ));
439 jsonObjectSetKey(json, "payload", payload);
447 @brief Translate a JSON array into an osrfList of osrfMessages.
448 @param string The JSON string to be translated.
449 @param list Pointer to an osrfList of osrfMessages (may be NULL)
450 @return Pointer to an osrfList containing pointers to osrfMessages.
452 The JSON string is expected to be a JSON array, with each element encoding an osrfMessage.
454 Translate each element of the JSON array into an osrfMessage, and store a pointer to the
455 osrfMessage in an osrfList.
457 If the @a list parameter is NULL, create a new osrfList (with osrfMessageFree() as the
458 callback function for freeing items), populate it, and return a pointer to it. Otherwise
459 clear the osrfList provided and reuse it.
461 When calling osrfMessageDeserialize repeatedly, a reasonable strategy is to pass a NULL
462 for the @a list parameter on the first call, and pass the value returned from the first
463 call on subsequent calls.
465 The calling code is responsible for eventually freeing the returned osrfList by calling
468 osrfList* osrfMessageDeserialize( const char* string, osrfList* list ) {
471 osrfListClear( list );
473 if( ! string || ! *string ) {
475 list = osrfNewList( 1 );
476 list->freeItem = (void(*)(void*)) osrfMessageFree;
478 return list; // No string? Return empty list.
482 jsonObject* json = jsonParse(string);
484 osrfLogWarning( OSRF_LOG_MARK,
485 "osrfMessageDeserialize() unable to parse data: \n%s\n", string);
487 list = osrfNewList( 1 );
488 list->freeItem = (void(*)(void*)) osrfMessageFree;
490 return list; // Bad JSON? Return empty list.
493 const unsigned int count = (int) json->size;
495 // Create a right-sized osrfList
496 list = osrfNewList( count );
497 list->freeItem = (void(*)(void*)) osrfMessageFree;
500 // Traverse the JSON_ARRAY, turning each element into an osrfMessage
502 for( i = 0; i < count; ++i ) {
504 const jsonObject* message = jsonObjectGetIndex( json, i );
505 if( message && message->type != JSON_NULL &&
506 message->classname && !strcmp(message->classname, "osrfMessage" )) {
507 osrfListPush( list, deserialize_one_message( message ) );
511 jsonObjectFree( json );
516 @brief Translate a JSON array into an array of osrfMessages.
517 @param string The JSON string to be translated.
518 @param msgs Pointer to an array of pointers to osrfMessage, to receive the results.
519 @param count How many slots are available in the @a msgs array.
520 @return The number of osrfMessages created.
522 The JSON string is expected to be a JSON array, with each element encoding an osrfMessage.
524 If there are too many messages in the JSON array to fit into the pointer array, we
525 silently ignore the excess.
527 int osrf_message_deserialize(const char* string, osrfMessage* msgs[], int count) {
529 if(!string || !msgs || count <= 0) return 0;
533 jsonObject* json = jsonParse(string);
536 osrfLogWarning( OSRF_LOG_MARK,
537 "osrf_message_deserialize() unable to parse data: \n%s\n", string);
541 // Traverse the JSON_ARRAY, turning each element into an osrfMessage
543 for( x = 0; x < json->size && x < count; x++ ) {
545 const jsonObject* message = jsonObjectGetIndex( json, x );
547 if( message && message->type != JSON_NULL &&
548 message->classname && !strcmp(message->classname, "osrfMessage" )) {
549 msgs[numparsed++] = deserialize_one_message( message );
553 jsonObjectFree( json );
559 @brief Translate a jsonObject into a single osrfMessage.
560 @param obj Pointer to the jsonObject to be translated.
561 @return Pointer to a newly created osrfMessage.
563 It is assumed that @a obj is non-NULL and points to a valid representation of a message.
564 For a description of the expected structure of this representations, see osrfMessageToJSON().
566 The calling code is responsible for freeing the osrfMessage by calling osrfMessageFree().
568 static osrfMessage* deserialize_one_message( const jsonObject* obj ) {
570 // Get the message type. If it isn't present, default to CONNECT.
571 const jsonObject* tmp = jsonObjectGetKeyConst( obj, "type" );
573 enum M_TYPE type = CONNECT;
574 const char* t = jsonObjectGetString( tmp );
577 if( !strcmp( t, "CONNECT" )) type = CONNECT;
578 else if( !strcmp( t, "DISCONNECT" )) type = DISCONNECT;
579 else if( !strcmp( t, "STATUS" )) type = STATUS;
580 else if( !strcmp( t, "REQUEST" )) type = REQUEST;
581 else if( !strcmp( t, "RESULT" )) type = RESULT;
584 // Get the thread trace, defaulting to zero.
586 tmp = jsonObjectGetKeyConst( obj, "threadTrace" );
588 const char* tt = jsonObjectGetString( tmp );
594 // Get the protocol, defaulting to zero.
596 tmp = jsonObjectGetKeyConst( obj, "protocol" );
598 const char* proto = jsonObjectGetString(tmp);
600 protocol = atoi( proto );
604 // Now that we have the essentials, create an osrfMessage
605 osrfMessage* msg = osrf_message_init( type, trace, protocol );
607 // Update current_locale with the locale of the message
608 // (or set it to NULL if not specified)
609 tmp = jsonObjectGetKeyConst( obj, "locale" );
610 if(tmp && ( msg->sender_locale = jsonObjectToSimpleString(tmp))) {
611 if ( current_locale ) {
612 if( strcmp( current_locale, msg->sender_locale ) ) {
613 free( current_locale );
614 current_locale = strdup( msg->sender_locale );
615 } // else they're the same already, so don't replace one with the other
617 current_locale = strdup( msg->sender_locale );
619 if ( current_locale ) {
620 free( current_locale );
621 current_locale = NULL;
625 tmp = jsonObjectGetKeyConst( obj, "payload" );
627 // Get method name and parameters for a REQUEST
628 const jsonObject* tmp0 = jsonObjectGetKeyConst(tmp,"method");
629 const char* tmp_str = jsonObjectGetString(tmp0);
631 msg->method_name = strdup(tmp_str);
633 tmp0 = jsonObjectGetKeyConst(tmp,"params");
635 // Note that we use jsonObjectDecodeClass() instead of
636 // jsonObjectClone(). The classnames are already decoded,
637 // but jsonObjectDecodeClass removes the decoded classnames.
638 msg->_params = jsonObjectDecodeClass( tmp0 );
639 if(msg->_params && msg->_params->type == JSON_NULL)
640 msg->_params->type = JSON_ARRAY;
643 // Get status fields for a RESULT or STATUS
645 msg->status_name = strdup(tmp->classname);
647 tmp0 = jsonObjectGetKeyConst(tmp,"status");
648 tmp_str = jsonObjectGetString(tmp0);
650 msg->status_text = strdup(tmp_str);
652 tmp0 = jsonObjectGetKeyConst(tmp,"statusCode");
654 tmp_str = jsonObjectGetString(tmp0);
656 msg->status_code = atoi(tmp_str);
657 if(tmp0->type == JSON_NUMBER)
658 msg->status_code = (int) jsonObjectGetNumber(tmp0);
661 // Get the content for a RESULT
662 tmp0 = jsonObjectGetKeyConst(tmp,"content");
664 // Note that we use jsonObjectDecodeClass() instead of
665 // jsonObjectClone(). The classnames are already decoded,
666 // but jsonObjectDecodeClass removes the decoded classnames.
667 msg->_result_content = jsonObjectDecodeClass( tmp0 );
677 @brief Return a pointer to the result content of an osrfMessage.
678 @param msg Pointer to the osrfMessage whose result content is to be returned.
679 @return Pointer to the result content (or NULL if there is no such content, or if @a msg is
682 The returned pointer points into the innards of the osrfMessage. The calling code should
683 @em not call jsonObjectFree() on it, because the osrfMessage still owns it.
685 const jsonObject* osrfMessageGetResult( osrfMessage* msg ) {
686 if(msg) return msg->_result_content;