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;
45 msg->sender_tz = NULL;
46 msg->sender_ingress = NULL;
53 @brief Return the previous locale.
54 @return A pointer to the locale specified by the last message to have been deserialized,
57 A JSON message may specify a locale string, which is saved as the current locale. If
58 the message does not specify a locale string, then the current locale becomes NULL.
60 This function returns a pointer to an internal buffer. Since both the address and the
61 contents of this buffer may change from one call to the next, the calling code should
62 either use the result immediately or make a copy of the string for later use.
64 const char* osrf_message_get_last_locale() {
65 return current_locale;
69 @brief Set the locale for a specified osrfMessage.
70 @param msg Pointer to the osrfMessage.
71 @param locale Pointer to the locale string to be installed in the osrfMessage.
72 @return Pointer to the new locale string for the osrfMessage, or NULL if either
75 If no locale is specified for an osrfMessage, we use the default locale.
77 Used for a REQUEST message.
79 const char* osrf_message_set_locale( osrfMessage* msg, const char* locale ) {
80 if( msg == NULL || locale == NULL )
82 if( msg->sender_locale )
83 free( msg->sender_locale );
84 return msg->sender_locale = strdup( locale );
88 @brief Set the TZ for a specified osrfMessage.
89 @param msg Pointer to the osrfMessage.
90 @param TZ Pointer to the TZ string to be installed in the osrfMessage.
91 @return Pointer to the new TZ string for the osrfMessage, or NULL if either
94 If no TZ is specified for an osrfMessage, we use the system TZ.
96 Used for a REQUEST message.
98 const char* osrf_message_set_tz( osrfMessage* msg, const char* tz ) {
99 if( msg == NULL || tz == NULL )
102 free( msg->sender_tz );
103 return msg->sender_tz = strdup( tz );
107 @brief Set the ingress for a specified osrfMessage.
108 @param msg Pointer to the osrfMessage.
109 @param ingress Pointer to the ingress string to be installed in the osrfMessage.
110 @return Pointer to the new ingress string for the osrfMessage, or NULL if either
113 If no ingress is specified for an osrfMessage, we use the default ingress.
115 Used for a REQUEST message.
117 const char* osrfMessageSetIngress( osrfMessage* msg, const char* ingress ) {
118 if( msg == NULL || ingress == NULL )
120 if( msg->sender_ingress )
121 free( msg->sender_ingress );
122 return msg->sender_ingress = strdup( ingress );
126 @brief Change the default locale.
127 @param locale The new default locale.
128 @return A pointer to the new default locale if successful, or NULL if not.
130 The function returns NULL if the parameter is NULL, or if the proposed new locale is
131 longer than 16 characters.
133 At this writing, nothing calls this function.
135 const char* osrf_message_set_default_locale( const char* locale ) {
136 if( locale == NULL ) return NULL;
137 if( strlen(locale) > sizeof(default_locale) - 1 ) return NULL;
139 strcpy( default_locale, locale );
140 return default_locale;
144 @brief Populate the method_name member of an osrfMessage.
145 @param msg Pointer to the osrfMessage.
146 @param method_name The method name.
148 Used for a REQUEST message to specify what method to invoke.
150 void osrf_message_set_method( osrfMessage* msg, const char* method_name ) {
151 if( msg == NULL || method_name == NULL )
153 if( msg->method_name )
154 free( msg->method_name );
155 msg->method_name = strdup( method_name );
160 @brief Add a copy of a jsonObject to an osrfMessage as a parameter for a method call.
161 @param msg Pointer to the osrfMessage.
162 @param o Pointer to the jsonObject of which a copy is to be stored.
164 Make a copy of the input jsonObject, with all classnames encoded with JSON_CLASS_KEY and
165 JSON_DATA_KEY. Append it to a JSON_ARRAY stored at msg->_params.
167 If there is nothing at msg->_params, create a new JSON_ARRAY for it and add the new object
168 as the first element.
170 If the jsonObject is raw (i.e. the class information has not been decoded into classnames),
171 decode it. If the class information has already been decoded, discard it.
173 See also osrf_message_add_param().
175 At this writing, nothing calls this function.
177 void osrf_message_add_object_param( osrfMessage* msg, const jsonObject* o ) {
178 if(!msg|| !o) return;
180 msg->_params = jsonNewObjectType( JSON_ARRAY );
181 jsonObjectPush(msg->_params, jsonObjectDecodeClass( o ));
185 @brief Populate the params member of an osrfMessage.
186 @param msg Pointer to the osrfMessage.
187 @param o Pointer to a jsonObject representing the parameter(s) to a method.
189 Make a copy of a jsonObject and install it as the parameter list for a method call.
191 If the osrfMessage already has any parameters, discard them.
193 The @a o parameter should point to a jsonObject of type JSON_ARRAY, with each element
194 of the array being a parameter. If @a o points to any other type of jsonObject, create
195 a JSON_ARRAY as a wrapper for it, and install a copy of @a o as its only element.
197 Used for a REQUEST message, to pass parameters to a method. The alternative is to call
198 osrf_message_add_param() or osrf_message_add_object_param() repeatedly as needed to add
199 one parameter at a time.
201 void osrf_message_set_params( osrfMessage* msg, const jsonObject* o ) {
202 if(!msg || !o) return;
205 jsonObjectFree(msg->_params);
207 if(o->type == JSON_ARRAY) {
208 msg->_params = jsonObjectClone(o);
210 osrfLogDebug( OSRF_LOG_MARK, "passing non-array to osrf_message_set_params(), fixing...");
211 jsonObject* clone = jsonObjectClone(o);
212 msg->_params = jsonNewObjectType( JSON_ARRAY );
213 jsonObjectPush(msg->_params, clone);
220 @brief Add a JSON string to an osrfMessage as a parameter for a method call.
221 @param msg Pointer to the osrfMessage.
222 @param param_string A JSON string encoding the parameter to be added.
224 Translate the JSON string into a jsonObject, and append it to the parameter list.
225 If the parameter list doesn't already exist, create an empty one and add the new
228 Decode any class information in the raw JSON into classnames.
230 If the JSON string is not valid JSON, append a new jsonObject of type JSON_NULL.
232 Used for a REQUEST message, to pass a parameter to a method, The alternative is to
233 call osrf_message_set_params() to provide all the parameters at once. See also
234 osrf_message_add_object_param().
236 void osrf_message_add_param( osrfMessage* msg, const char* param_string ) {
237 if(msg == NULL || param_string == NULL) return;
238 if(!msg->_params) msg->_params = jsonNewObjectType( JSON_ARRAY );
239 jsonObjectPush(msg->_params, jsonParse(param_string));
244 @brief Set the status_name, status_text, and status_code members of an osrfMessage.
245 @param msg Pointer to the osrfMessage to be populated.
246 @param status_name Status name (may be NULL).
247 @param status_text Status text (may be NULL).
248 @param status_code Status code.
250 If the @a status_name or @a status_text parameter is NULL, the corresponding member
253 Used for a RESULT or STATUS message.
255 void osrf_message_set_status_info( osrfMessage* msg,
256 const char* status_name, const char* status_text, int status_code ) {
259 if( status_name != NULL ) {
260 if( msg->status_name )
261 free( msg->status_name );
262 msg->status_name = strdup( status_name );
265 if( status_text != NULL ) {
266 if( msg->status_text )
267 free( msg->status_text );
268 msg->status_text = strdup( status_text );
271 msg->status_code = status_code;
276 @brief Populate the _result_content membersof an osrfMessage from a JSON string.
277 @param msg Pointer to the osrfMessage to be populated.
278 @param json_string A JSON string encoding a result.
280 Used for a RESULT message to return the results of a remote procedure call.
282 void osrf_message_set_result_content( osrfMessage* msg, const char* json_string ) {
283 if( msg == NULL || json_string == NULL) return;
284 if( msg->_result_content )
285 jsonObjectFree( msg->_result_content );
287 msg->_result_content = jsonParse(json_string);
292 @brief Populate the _result_content membersof an osrfMessage from a JSON object.
293 @param msg Pointer to the osrfMessage to be populated.
294 @param obj Pointer to a jsonObject encoding a result.
296 Used for a RESULT message to return the results of a remote procedure call.
298 void osrf_message_set_result( osrfMessage* msg, const jsonObject* obj ) {
299 if( msg == NULL || obj == NULL) return;
300 if( msg->_result_content )
301 jsonObjectFree( msg->_result_content );
303 msg->_result_content = jsonObjectDecodeClass( obj );
308 @brief Free an osrfMessage and everything it owns.
309 @param msg Pointer to the osrfMessage to be freed.
311 void osrfMessageFree( osrfMessage* msg ) {
315 if( msg->status_name != NULL )
316 free(msg->status_name);
318 if( msg->status_text != NULL )
319 free(msg->status_text);
321 if( msg->_result_content != NULL )
322 jsonObjectFree( msg->_result_content );
324 if( msg->method_name != NULL )
325 free(msg->method_name);
327 if( msg->sender_locale != NULL )
328 free(msg->sender_locale);
330 if( msg->sender_tz != NULL )
331 free(msg->sender_tz);
333 if( msg->sender_ingress != NULL )
334 free(msg->sender_ingress);
336 if( msg->_params != NULL )
337 jsonObjectFree(msg->_params);
344 @brief Turn a collection of osrfMessages into one big JSON string.
345 @param msgs Pointer to an array of osrfMessages.
346 @param count Maximum number of messages to serialize.
347 @return Pointer to the JSON string.
349 Traverse the array, adding each osrfMessage in turn to a JSON_ARRAY. Stop when you have added
350 the maximum number of messages, or when you encounter a NULL pointer in the array. Then
351 translate the JSON_ARRAY into a JSON string.
353 The calling code is responsible for freeing the returned string.
355 char* osrfMessageSerializeBatch( osrfMessage* msgs [], int count ) {
356 if( !msgs ) return NULL;
358 jsonObject* wrapper = jsonNewObjectType(JSON_ARRAY);
361 while( (i < count) && msgs[i] ) {
362 jsonObjectPush(wrapper, osrfMessageToJSON( msgs[i] ));
366 char* j = jsonObjectToJSON(wrapper);
367 jsonObjectFree(wrapper);
374 @brief Turn a single osrfMessage into a JSON string.
375 @param msg Pointer to the osrfMessage to be serialized.
376 @return Pointer to the resulting JSON string.
378 Translate the osrfMessage into JSON, wrapped in a JSON array.
380 This function is equivalent to osrfMessageSerializeBatch() for an array of one pointer.
382 The calling code is responsible for freeing the returned string.
384 char* osrf_message_serialize(const osrfMessage* msg) {
386 if( msg == NULL ) return NULL;
389 jsonObject* json = osrfMessageToJSON( msg );
392 jsonObject* wrapper = jsonNewObjectType(JSON_ARRAY);
393 jsonObjectPush(wrapper, json);
394 j = jsonObjectToJSON(wrapper);
395 jsonObjectFree(wrapper);
403 @brief Translate an osrfMessage into a jsonObject.
404 @param msg Pointer to the osrfMessage to be translated.
405 @return Pointer to a newly created jsonObject.
407 The resulting jsonObject is a JSON_HASH with a classname of "osrfMessage", and the following keys:
413 - "payload" (only for STATUS, REQUEST, and RESULT messages)
415 The data for "payload" is also a JSON_HASH, whose structure depends on the message type:
417 For a STATUS message, the payload's classname is msg->status_name. The keys are "status"
418 (carrying msg->status_text) and "statusCode" (carrying the status code as a string).
420 For a REQUEST message, the payload's classname is "osrfMethod". The keys are "method"
421 (carrying msg->method_name) and "params" (carrying a jsonObject to pass any parameters
424 For a RESULT message, the payload's classname is "osrfResult". The keys are "status"
425 (carrying msg->status_text), "statusCode" (carrying the status code as a string), and
426 "content" (carrying a jsonObject to return any results from the method call).
428 The calling code is responsible for freeing the returned jsonObject.
430 jsonObject* osrfMessageToJSON( const osrfMessage* msg ) {
432 jsonObject* json = jsonNewObjectType(JSON_HASH);
433 jsonObjectSetClass(json, "osrfMessage");
436 osrf_clearbuf(sc, sizeof(sc));
438 INT_TO_STRING(msg->thread_trace);
439 jsonObjectSetKey(json, "threadTrace", jsonNewObject(INTSTR));
441 if (msg->sender_locale != NULL) {
442 jsonObjectSetKey(json, "locale", jsonNewObject(msg->sender_locale));
443 } else if (current_locale != NULL) {
444 jsonObjectSetKey(json, "locale", jsonNewObject(current_locale));
446 jsonObjectSetKey(json, "locale", jsonNewObject(default_locale));
449 if (msg->sender_tz != NULL) {
450 jsonObjectSetKey(json, "tz", jsonNewObject(msg->sender_tz));
453 if (msg->sender_ingress != NULL)
454 jsonObjectSetKey(json, "ingress", jsonNewObject(msg->sender_ingress));
456 if (msg->protocol > 0)
457 jsonObjectSetKey(json, "api_level", jsonNewNumberObject(msg->protocol));
459 switch(msg->m_type) {
462 jsonObjectSetKey(json, "type", jsonNewObject("CONNECT"));
466 jsonObjectSetKey(json, "type", jsonNewObject("DISCONNECT"));
470 jsonObjectSetKey(json, "type", jsonNewObject("STATUS"));
471 payload = jsonNewObject(NULL);
472 jsonObjectSetClass(payload, msg->status_name);
473 jsonObjectSetKey(payload, "status", jsonNewObject(msg->status_text));
474 snprintf(sc, sizeof(sc), "%d", msg->status_code);
475 jsonObjectSetKey(payload, "statusCode", jsonNewObject(sc));
476 jsonObjectSetKey(json, "payload", payload);
480 jsonObjectSetKey(json, "type", jsonNewObject("REQUEST"));
481 payload = jsonNewObject(NULL);
482 jsonObjectSetClass(payload, "osrfMethod");
483 jsonObjectSetKey(payload, "method", jsonNewObject(msg->method_name));
484 jsonObjectSetKey( payload, "params", jsonObjectDecodeClass( msg->_params ) );
485 jsonObjectSetKey(json, "payload", payload);
490 jsonObjectSetKey(json, "type", jsonNewObject("RESULT"));
491 payload = jsonNewObject(NULL);
492 char* cname = "osrfResult";
493 if (msg->status_code == OSRF_STATUS_PARTIAL) {
494 cname = "osrfResultPartial";
495 } else if (msg->status_code == OSRF_STATUS_NOCONTENT) {
496 cname = "osrfResultPartialComplete";
498 jsonObjectSetClass(payload, cname);
499 jsonObjectSetKey(payload, "status", jsonNewObject(msg->status_text));
500 snprintf(sc, sizeof(sc), "%d", msg->status_code);
501 jsonObjectSetKey(payload, "statusCode", jsonNewObject(sc));
502 jsonObjectSetKey(payload, "content", jsonObjectDecodeClass( msg->_result_content ));
503 jsonObjectSetKey(json, "payload", payload);
511 @brief Translate a JSON array into an osrfList of osrfMessages.
512 @param string The JSON string to be translated.
513 @param list Pointer to an osrfList of osrfMessages (may be NULL)
514 @return Pointer to an osrfList containing pointers to osrfMessages.
516 The JSON string is expected to be a JSON array, with each element encoding an osrfMessage.
518 Translate each element of the JSON array into an osrfMessage, and store a pointer to the
519 osrfMessage in an osrfList.
521 If the @a list parameter is NULL, create a new osrfList (with osrfMessageFree() as the
522 callback function for freeing items), populate it, and return a pointer to it. Otherwise
523 clear the osrfList provided and reuse it.
525 When calling osrfMessageDeserialize repeatedly, a reasonable strategy is to pass a NULL
526 for the @a list parameter on the first call, and pass the value returned from the first
527 call on subsequent calls.
529 The calling code is responsible for eventually freeing the returned osrfList by calling
532 osrfList* osrfMessageDeserialize( const char* string, osrfList* list ) {
535 osrfListClear( list );
537 if( ! string || ! *string ) {
539 list = osrfNewList( 1 );
540 list->freeItem = (void(*)(void*)) osrfMessageFree;
542 return list; // No string? Return empty list.
546 jsonObject* json = jsonParse(string);
548 osrfLogWarning( OSRF_LOG_MARK,
549 "osrfMessageDeserialize() unable to parse data: \n%s\n", string);
551 list = osrfNewList( 1 );
552 list->freeItem = (void(*)(void*)) osrfMessageFree;
554 return list; // Bad JSON? Return empty list.
557 const unsigned int count = (int) json->size;
559 // Create a right-sized osrfList
560 list = osrfNewList( count );
561 list->freeItem = (void(*)(void*)) osrfMessageFree;
564 // Traverse the JSON_ARRAY, turning each element into an osrfMessage
566 for( i = 0; i < count; ++i ) {
568 const jsonObject* message = jsonObjectGetIndex( json, i );
569 if( message && message->type != JSON_NULL &&
570 message->classname && !strcmp(message->classname, "osrfMessage" )) {
571 osrfListPush( list, deserialize_one_message( message ) );
575 jsonObjectFree( json );
580 @brief Translate a JSON array into an array of osrfMessages.
581 @param string The JSON string to be translated.
582 @param msgs Pointer to an array of pointers to osrfMessage, to receive the results.
583 @param count How many slots are available in the @a msgs array.
584 @return The number of osrfMessages created.
586 The JSON string is expected to be a JSON array, with each element encoding an osrfMessage.
588 If there are too many messages in the JSON array to fit into the pointer array, we
589 silently ignore the excess.
591 int osrf_message_deserialize(const char* string, osrfMessage* msgs[], int count) {
593 if(!string || !msgs || count <= 0) return 0;
597 jsonObject* json = jsonParse(string);
600 osrfLogWarning( OSRF_LOG_MARK,
601 "osrf_message_deserialize() unable to parse data: \n%s\n", string);
605 // Traverse the JSON_ARRAY, turning each element into an osrfMessage
607 for( x = 0; x < json->size && x < count; x++ ) {
609 const jsonObject* message = jsonObjectGetIndex( json, x );
611 if( message && message->type != JSON_NULL &&
612 message->classname && !strcmp(message->classname, "osrfMessage" )) {
613 msgs[numparsed++] = deserialize_one_message( message );
617 jsonObjectFree( json );
623 @brief Translate a jsonObject into a single osrfMessage.
624 @param obj Pointer to the jsonObject to be translated.
625 @return Pointer to a newly created osrfMessage.
627 It is assumed that @a obj is non-NULL and points to a valid representation of a message.
628 For a description of the expected structure of this representations, see osrfMessageToJSON().
630 The calling code is responsible for freeing the osrfMessage by calling osrfMessageFree().
632 static osrfMessage* deserialize_one_message( const jsonObject* obj ) {
634 // Get the message type. If it isn't present, default to CONNECT.
635 const jsonObject* tmp = jsonObjectGetKeyConst( obj, "type" );
637 enum M_TYPE type = CONNECT;
638 const char* t = jsonObjectGetString( tmp );
641 if( !strcmp( t, "CONNECT" )) type = CONNECT;
642 else if( !strcmp( t, "DISCONNECT" )) type = DISCONNECT;
643 else if( !strcmp( t, "STATUS" )) type = STATUS;
644 else if( !strcmp( t, "REQUEST" )) type = REQUEST;
645 else if( !strcmp( t, "RESULT" )) type = RESULT;
648 // Get the thread trace, defaulting to zero.
650 tmp = jsonObjectGetKeyConst( obj, "threadTrace" );
652 const char* tt = jsonObjectGetString( tmp );
658 // Get the protocol, defaulting to zero.
660 tmp = jsonObjectGetKeyConst( obj, "api_level" );
662 const char* proto = jsonObjectGetString(tmp);
664 protocol = atoi( proto );
668 // Now that we have the essentials, create an osrfMessage
669 osrfMessage* msg = osrf_message_init( type, trace, protocol );
671 // Update current_locale with the locale of the message
672 // (or set it to NULL if not specified)
673 tmp = jsonObjectGetKeyConst( obj, "locale" );
674 if(tmp && ( msg->sender_locale = jsonObjectToSimpleString(tmp))) {
675 if ( current_locale ) {
676 if( strcmp( current_locale, msg->sender_locale ) ) {
677 free( current_locale );
678 current_locale = strdup( msg->sender_locale );
679 } // else they're the same already, so don't replace one with the other
681 current_locale = strdup( msg->sender_locale );
683 if ( current_locale ) {
684 free( current_locale );
685 current_locale = NULL;
689 tmp = jsonObjectGetKeyConst(obj, "ingress");
691 osrfMessageSetIngress(msg, jsonObjectGetString(tmp));
694 tmp = jsonObjectGetKeyConst(obj, "tz");
696 osrf_message_set_tz(msg, jsonObjectGetString(tmp));
699 tmp = jsonObjectGetKeyConst( obj, "payload" );
701 // Get method name and parameters for a REQUEST
702 const jsonObject* tmp0 = jsonObjectGetKeyConst(tmp,"method");
703 const char* tmp_str = jsonObjectGetString(tmp0);
705 msg->method_name = strdup(tmp_str);
707 tmp0 = jsonObjectGetKeyConst(tmp,"params");
709 // Note that we use jsonObjectDecodeClass() instead of
710 // jsonObjectClone(). The classnames are already decoded,
711 // but jsonObjectDecodeClass removes the decoded classnames.
712 msg->_params = jsonObjectDecodeClass( tmp0 );
713 if(msg->_params && msg->_params->type == JSON_NULL)
714 msg->_params->type = JSON_ARRAY;
717 // Get status fields for a RESULT or STATUS
719 msg->status_name = strdup(tmp->classname);
721 tmp0 = jsonObjectGetKeyConst(tmp,"status");
722 tmp_str = jsonObjectGetString(tmp0);
724 msg->status_text = strdup(tmp_str);
726 tmp0 = jsonObjectGetKeyConst(tmp,"statusCode");
728 tmp_str = jsonObjectGetString(tmp0);
730 msg->status_code = atoi(tmp_str);
731 if(tmp0->type == JSON_NUMBER)
732 msg->status_code = (int) jsonObjectGetNumber(tmp0);
735 // Get the content for a RESULT
736 tmp0 = jsonObjectGetKeyConst(tmp,"content");
738 // Note that we use jsonObjectDecodeClass() instead of
739 // jsonObjectClone(). The classnames are already decoded,
740 // but jsonObjectDecodeClass removes the decoded classnames.
741 msg->_result_content = jsonObjectDecodeClass( tmp0 );
751 @brief Return a pointer to the result content of an osrfMessage.
752 @param msg Pointer to the osrfMessage whose result content is to be returned.
753 @return Pointer to the result content (or NULL if there is no such content, or if @a msg is
756 The returned pointer points into the innards of the osrfMessage. The calling code should
757 @em not call jsonObjectFree() on it, because the osrfMessage still owns it.
759 const jsonObject* osrfMessageGetResult( osrfMessage* msg ) {
760 if(msg) return msg->_result_content;