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_ingress = 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 Set the ingress for a specified osrfMessage.
88 @param msg Pointer to the osrfMessage.
89 @param ingress Pointer to the ingress string to be installed in the osrfMessage.
90 @return Pointer to the new ingress string for the osrfMessage, or NULL if either
93 If no ingress is specified for an osrfMessage, we use the default ingress.
95 Used for a REQUEST message.
97 const char* osrfMessageSetIngress( osrfMessage* msg, const char* ingress ) {
98 if( msg == NULL || ingress == NULL )
100 if( msg->sender_ingress )
101 free( msg->sender_ingress );
102 return msg->sender_ingress = strdup( ingress );
106 @brief Change the default locale.
107 @param locale The new default locale.
108 @return A pointer to the new default locale if successful, or NULL if not.
110 The function returns NULL if the parameter is NULL, or if the proposed new locale is
111 longer than 16 characters.
113 At this writing, nothing calls this function.
115 const char* osrf_message_set_default_locale( const char* locale ) {
116 if( locale == NULL ) return NULL;
117 if( strlen(locale) > sizeof(default_locale) - 1 ) return NULL;
119 strcpy( default_locale, locale );
120 return default_locale;
124 @brief Populate the method_name member of an osrfMessage.
125 @param msg Pointer to the osrfMessage.
126 @param method_name The method name.
128 Used for a REQUEST message to specify what method to invoke.
130 void osrf_message_set_method( osrfMessage* msg, const char* method_name ) {
131 if( msg == NULL || method_name == NULL )
133 if( msg->method_name )
134 free( msg->method_name );
135 msg->method_name = strdup( method_name );
140 @brief Add a copy of a jsonObject to an osrfMessage as a parameter for a method call.
141 @param msg Pointer to the osrfMessage.
142 @param o Pointer to the jsonObject of which a copy is to be stored.
144 Make a copy of the input jsonObject, with all classnames encoded with JSON_CLASS_KEY and
145 JSON_DATA_KEY. Append it to a JSON_ARRAY stored at msg->_params.
147 If there is nothing at msg->_params, create a new JSON_ARRAY for it and add the new object
148 as the first element.
150 If the jsonObject is raw (i.e. the class information has not been decoded into classnames),
151 decode it. If the class information has already been decoded, discard it.
153 See also osrf_message_add_param().
155 At this writing, nothing calls this function.
157 void osrf_message_add_object_param( osrfMessage* msg, const jsonObject* o ) {
158 if(!msg|| !o) return;
160 msg->_params = jsonNewObjectType( JSON_ARRAY );
161 jsonObjectPush(msg->_params, jsonObjectDecodeClass( o ));
165 @brief Populate the params member of an osrfMessage.
166 @param msg Pointer to the osrfMessage.
167 @param o Pointer to a jsonObject representing the parameter(s) to a method.
169 Make a copy of a jsonObject and install it as the parameter list for a method call.
171 If the osrfMessage already has any parameters, discard them.
173 The @a o parameter should point to a jsonObject of type JSON_ARRAY, with each element
174 of the array being a parameter. If @a o points to any other type of jsonObject, create
175 a JSON_ARRAY as a wrapper for it, and install a copy of @a o as its only element.
177 Used for a REQUEST message, to pass parameters to a method. The alternative is to call
178 osrf_message_add_param() or osrf_message_add_object_param() repeatedly as needed to add
179 one parameter at a time.
181 void osrf_message_set_params( osrfMessage* msg, const jsonObject* o ) {
182 if(!msg || !o) return;
185 jsonObjectFree(msg->_params);
187 if(o->type == JSON_ARRAY) {
188 msg->_params = jsonObjectClone(o);
190 osrfLogDebug( OSRF_LOG_MARK, "passing non-array to osrf_message_set_params(), fixing...");
191 jsonObject* clone = jsonObjectClone(o);
192 msg->_params = jsonNewObjectType( JSON_ARRAY );
193 jsonObjectPush(msg->_params, clone);
200 @brief Add a JSON string to an osrfMessage as a parameter for a method call.
201 @param msg Pointer to the osrfMessage.
202 @param param_string A JSON string encoding the parameter to be added.
204 Translate the JSON string into a jsonObject, and append it to the parameter list.
205 If the parameter list doesn't already exist, create an empty one and add the new
208 Decode any class information in the raw JSON into classnames.
210 If the JSON string is not valid JSON, append a new jsonObject of type JSON_NULL.
212 Used for a REQUEST message, to pass a parameter to a method, The alternative is to
213 call osrf_message_set_params() to provide all the parameters at once. See also
214 osrf_message_add_object_param().
216 void osrf_message_add_param( osrfMessage* msg, const char* param_string ) {
217 if(msg == NULL || param_string == NULL) return;
218 if(!msg->_params) msg->_params = jsonNewObjectType( JSON_ARRAY );
219 jsonObjectPush(msg->_params, jsonParse(param_string));
224 @brief Set the status_name, status_text, and status_code members of an osrfMessage.
225 @param msg Pointer to the osrfMessage to be populated.
226 @param status_name Status name (may be NULL).
227 @param status_text Status text (may be NULL).
228 @param status_code Status code.
230 If the @a status_name or @a status_text parameter is NULL, the corresponding member
233 Used for a RESULT or STATUS message.
235 void osrf_message_set_status_info( osrfMessage* msg,
236 const char* status_name, const char* status_text, int status_code ) {
239 if( status_name != NULL ) {
240 if( msg->status_name )
241 free( msg->status_name );
242 msg->status_name = strdup( status_name );
245 if( status_text != NULL ) {
246 if( msg->status_text )
247 free( msg->status_text );
248 msg->status_text = strdup( status_text );
251 msg->status_code = status_code;
256 @brief Populate the _result_content membersof an osrfMessage from a JSON string.
257 @param msg Pointer to the osrfMessage to be populated.
258 @param json_string A JSON string encoding a result.
260 Used for a RESULT message to return the results of a remote procedure call.
262 void osrf_message_set_result_content( osrfMessage* msg, const char* json_string ) {
263 if( msg == NULL || json_string == NULL) return;
264 if( msg->_result_content )
265 jsonObjectFree( msg->_result_content );
267 msg->_result_content = jsonParse(json_string);
272 @brief Populate the _result_content membersof an osrfMessage from a JSON object.
273 @param msg Pointer to the osrfMessage to be populated.
274 @param obj Pointer to a jsonObject encoding a result.
276 Used for a RESULT message to return the results of a remote procedure call.
278 void osrf_message_set_result( osrfMessage* msg, const jsonObject* obj ) {
279 if( msg == NULL || obj == NULL) return;
280 if( msg->_result_content )
281 jsonObjectFree( msg->_result_content );
283 msg->_result_content = jsonObjectDecodeClass( obj );
288 @brief Free an osrfMessage and everything it owns.
289 @param msg Pointer to the osrfMessage to be freed.
291 void osrfMessageFree( osrfMessage* msg ) {
295 if( msg->status_name != NULL )
296 free(msg->status_name);
298 if( msg->status_text != NULL )
299 free(msg->status_text);
301 if( msg->_result_content != NULL )
302 jsonObjectFree( msg->_result_content );
304 if( msg->method_name != NULL )
305 free(msg->method_name);
307 if( msg->sender_locale != NULL )
308 free(msg->sender_locale);
310 if( msg->sender_ingress != NULL )
311 free(msg->sender_ingress);
313 if( msg->_params != NULL )
314 jsonObjectFree(msg->_params);
321 @brief Turn a collection of osrfMessages into one big JSON string.
322 @param msgs Pointer to an array of osrfMessages.
323 @param count Maximum number of messages to serialize.
324 @return Pointer to the JSON string.
326 Traverse the array, adding each osrfMessage in turn to a JSON_ARRAY. Stop when you have added
327 the maximum number of messages, or when you encounter a NULL pointer in the array. Then
328 translate the JSON_ARRAY into a JSON string.
330 The calling code is responsible for freeing the returned string.
332 char* osrfMessageSerializeBatch( osrfMessage* msgs [], int count ) {
333 if( !msgs ) return NULL;
335 jsonObject* wrapper = jsonNewObjectType(JSON_ARRAY);
338 while( (i < count) && msgs[i] ) {
339 jsonObjectPush(wrapper, osrfMessageToJSON( msgs[i] ));
343 char* j = jsonObjectToJSON(wrapper);
344 jsonObjectFree(wrapper);
351 @brief Turn a single osrfMessage into a JSON string.
352 @param msg Pointer to the osrfMessage to be serialized.
353 @return Pointer to the resulting JSON string.
355 Translate the osrfMessage into JSON, wrapped in a JSON array.
357 This function is equivalent to osrfMessageSerializeBatch() for an array of one pointer.
359 The calling code is responsible for freeing the returned string.
361 char* osrf_message_serialize(const osrfMessage* msg) {
363 if( msg == NULL ) return NULL;
366 jsonObject* json = osrfMessageToJSON( msg );
369 jsonObject* wrapper = jsonNewObjectType(JSON_ARRAY);
370 jsonObjectPush(wrapper, json);
371 j = jsonObjectToJSON(wrapper);
372 jsonObjectFree(wrapper);
380 @brief Translate an osrfMessage into a jsonObject.
381 @param msg Pointer to the osrfMessage to be translated.
382 @return Pointer to a newly created jsonObject.
384 The resulting jsonObject is a JSON_HASH with a classname of "osrfMessage", and the following keys:
389 - "payload" (only for STATUS, REQUEST, and RESULT messages)
391 The data for "payload" is also a JSON_HASH, whose structure depends on the message type:
393 For a STATUS message, the payload's classname is msg->status_name. The keys are "status"
394 (carrying msg->status_text) and "statusCode" (carrying the status code as a string).
396 For a REQUEST message, the payload's classname is "osrfMethod". The keys are "method"
397 (carrying msg->method_name) and "params" (carrying a jsonObject to pass any parameters
400 For a RESULT message, the payload's classname is "osrfResult". The keys are "status"
401 (carrying msg->status_text), "statusCode" (carrying the status code as a string), and
402 "content" (carrying a jsonObject to return any results from the method call).
404 The calling code is responsible for freeing the returned jsonObject.
406 jsonObject* osrfMessageToJSON( const osrfMessage* msg ) {
408 jsonObject* json = jsonNewObjectType(JSON_HASH);
409 jsonObjectSetClass(json, "osrfMessage");
412 osrf_clearbuf(sc, sizeof(sc));
414 INT_TO_STRING(msg->thread_trace);
415 jsonObjectSetKey(json, "threadTrace", jsonNewObject(INTSTR));
417 if (msg->sender_locale != NULL) {
418 jsonObjectSetKey(json, "locale", jsonNewObject(msg->sender_locale));
419 } else if (current_locale != NULL) {
420 jsonObjectSetKey(json, "locale", jsonNewObject(current_locale));
422 jsonObjectSetKey(json, "locale", jsonNewObject(default_locale));
425 if (msg->sender_ingress != NULL)
426 jsonObjectSetKey(json, "ingress", jsonNewObject(msg->sender_ingress));
428 if (msg->protocol > 0)
429 jsonObjectSetKey(json, "api_level", jsonNewNumberObject(msg->protocol));
431 switch(msg->m_type) {
434 jsonObjectSetKey(json, "type", jsonNewObject("CONNECT"));
438 jsonObjectSetKey(json, "type", jsonNewObject("DISCONNECT"));
442 jsonObjectSetKey(json, "type", jsonNewObject("STATUS"));
443 payload = jsonNewObject(NULL);
444 jsonObjectSetClass(payload, msg->status_name);
445 jsonObjectSetKey(payload, "status", jsonNewObject(msg->status_text));
446 snprintf(sc, sizeof(sc), "%d", msg->status_code);
447 jsonObjectSetKey(payload, "statusCode", jsonNewObject(sc));
448 jsonObjectSetKey(json, "payload", payload);
452 jsonObjectSetKey(json, "type", jsonNewObject("REQUEST"));
453 payload = jsonNewObject(NULL);
454 jsonObjectSetClass(payload, "osrfMethod");
455 jsonObjectSetKey(payload, "method", jsonNewObject(msg->method_name));
456 jsonObjectSetKey( payload, "params", jsonObjectDecodeClass( msg->_params ) );
457 jsonObjectSetKey(json, "payload", payload);
462 jsonObjectSetKey(json, "type", jsonNewObject("RESULT"));
463 payload = jsonNewObject(NULL);
464 jsonObjectSetClass(payload,"osrfResult");
465 jsonObjectSetKey(payload, "status", jsonNewObject(msg->status_text));
466 snprintf(sc, sizeof(sc), "%d", msg->status_code);
467 jsonObjectSetKey(payload, "statusCode", jsonNewObject(sc));
468 jsonObjectSetKey(payload, "content", jsonObjectDecodeClass( msg->_result_content ));
469 jsonObjectSetKey(json, "payload", payload);
477 @brief Translate a JSON array into an osrfList of osrfMessages.
478 @param string The JSON string to be translated.
479 @param list Pointer to an osrfList of osrfMessages (may be NULL)
480 @return Pointer to an osrfList containing pointers to osrfMessages.
482 The JSON string is expected to be a JSON array, with each element encoding an osrfMessage.
484 Translate each element of the JSON array into an osrfMessage, and store a pointer to the
485 osrfMessage in an osrfList.
487 If the @a list parameter is NULL, create a new osrfList (with osrfMessageFree() as the
488 callback function for freeing items), populate it, and return a pointer to it. Otherwise
489 clear the osrfList provided and reuse it.
491 When calling osrfMessageDeserialize repeatedly, a reasonable strategy is to pass a NULL
492 for the @a list parameter on the first call, and pass the value returned from the first
493 call on subsequent calls.
495 The calling code is responsible for eventually freeing the returned osrfList by calling
498 osrfList* osrfMessageDeserialize( const char* string, osrfList* list ) {
501 osrfListClear( list );
503 if( ! string || ! *string ) {
505 list = osrfNewList( 1 );
506 list->freeItem = (void(*)(void*)) osrfMessageFree;
508 return list; // No string? Return empty list.
512 jsonObject* json = jsonParse(string);
514 osrfLogWarning( OSRF_LOG_MARK,
515 "osrfMessageDeserialize() unable to parse data: \n%s\n", string);
517 list = osrfNewList( 1 );
518 list->freeItem = (void(*)(void*)) osrfMessageFree;
520 return list; // Bad JSON? Return empty list.
523 const unsigned int count = (int) json->size;
525 // Create a right-sized osrfList
526 list = osrfNewList( count );
527 list->freeItem = (void(*)(void*)) osrfMessageFree;
530 // Traverse the JSON_ARRAY, turning each element into an osrfMessage
532 for( i = 0; i < count; ++i ) {
534 const jsonObject* message = jsonObjectGetIndex( json, i );
535 if( message && message->type != JSON_NULL &&
536 message->classname && !strcmp(message->classname, "osrfMessage" )) {
537 osrfListPush( list, deserialize_one_message( message ) );
541 jsonObjectFree( json );
546 @brief Translate a JSON array into an array of osrfMessages.
547 @param string The JSON string to be translated.
548 @param msgs Pointer to an array of pointers to osrfMessage, to receive the results.
549 @param count How many slots are available in the @a msgs array.
550 @return The number of osrfMessages created.
552 The JSON string is expected to be a JSON array, with each element encoding an osrfMessage.
554 If there are too many messages in the JSON array to fit into the pointer array, we
555 silently ignore the excess.
557 int osrf_message_deserialize(const char* string, osrfMessage* msgs[], int count) {
559 if(!string || !msgs || count <= 0) return 0;
563 jsonObject* json = jsonParse(string);
566 osrfLogWarning( OSRF_LOG_MARK,
567 "osrf_message_deserialize() unable to parse data: \n%s\n", string);
571 // Traverse the JSON_ARRAY, turning each element into an osrfMessage
573 for( x = 0; x < json->size && x < count; x++ ) {
575 const jsonObject* message = jsonObjectGetIndex( json, x );
577 if( message && message->type != JSON_NULL &&
578 message->classname && !strcmp(message->classname, "osrfMessage" )) {
579 msgs[numparsed++] = deserialize_one_message( message );
583 jsonObjectFree( json );
589 @brief Translate a jsonObject into a single osrfMessage.
590 @param obj Pointer to the jsonObject to be translated.
591 @return Pointer to a newly created osrfMessage.
593 It is assumed that @a obj is non-NULL and points to a valid representation of a message.
594 For a description of the expected structure of this representations, see osrfMessageToJSON().
596 The calling code is responsible for freeing the osrfMessage by calling osrfMessageFree().
598 static osrfMessage* deserialize_one_message( const jsonObject* obj ) {
600 // Get the message type. If it isn't present, default to CONNECT.
601 const jsonObject* tmp = jsonObjectGetKeyConst( obj, "type" );
603 enum M_TYPE type = CONNECT;
604 const char* t = jsonObjectGetString( tmp );
607 if( !strcmp( t, "CONNECT" )) type = CONNECT;
608 else if( !strcmp( t, "DISCONNECT" )) type = DISCONNECT;
609 else if( !strcmp( t, "STATUS" )) type = STATUS;
610 else if( !strcmp( t, "REQUEST" )) type = REQUEST;
611 else if( !strcmp( t, "RESULT" )) type = RESULT;
614 // Get the thread trace, defaulting to zero.
616 tmp = jsonObjectGetKeyConst( obj, "threadTrace" );
618 const char* tt = jsonObjectGetString( tmp );
624 // Get the protocol, defaulting to zero.
626 tmp = jsonObjectGetKeyConst( obj, "api_level" );
628 const char* proto = jsonObjectGetString(tmp);
630 protocol = atoi( proto );
634 // Now that we have the essentials, create an osrfMessage
635 osrfMessage* msg = osrf_message_init( type, trace, protocol );
637 // Update current_locale with the locale of the message
638 // (or set it to NULL if not specified)
639 tmp = jsonObjectGetKeyConst( obj, "locale" );
640 if(tmp && ( msg->sender_locale = jsonObjectToSimpleString(tmp))) {
641 if ( current_locale ) {
642 if( strcmp( current_locale, msg->sender_locale ) ) {
643 free( current_locale );
644 current_locale = strdup( msg->sender_locale );
645 } // else they're the same already, so don't replace one with the other
647 current_locale = strdup( msg->sender_locale );
649 if ( current_locale ) {
650 free( current_locale );
651 current_locale = NULL;
655 tmp = jsonObjectGetKeyConst(obj, "ingress");
657 osrfMessageSetIngress(msg, jsonObjectGetString(tmp));
660 tmp = jsonObjectGetKeyConst( obj, "payload" );
662 // Get method name and parameters for a REQUEST
663 const jsonObject* tmp0 = jsonObjectGetKeyConst(tmp,"method");
664 const char* tmp_str = jsonObjectGetString(tmp0);
666 msg->method_name = strdup(tmp_str);
668 tmp0 = jsonObjectGetKeyConst(tmp,"params");
670 // Note that we use jsonObjectDecodeClass() instead of
671 // jsonObjectClone(). The classnames are already decoded,
672 // but jsonObjectDecodeClass removes the decoded classnames.
673 msg->_params = jsonObjectDecodeClass( tmp0 );
674 if(msg->_params && msg->_params->type == JSON_NULL)
675 msg->_params->type = JSON_ARRAY;
678 // Get status fields for a RESULT or STATUS
680 msg->status_name = strdup(tmp->classname);
682 tmp0 = jsonObjectGetKeyConst(tmp,"status");
683 tmp_str = jsonObjectGetString(tmp0);
685 msg->status_text = strdup(tmp_str);
687 tmp0 = jsonObjectGetKeyConst(tmp,"statusCode");
689 tmp_str = jsonObjectGetString(tmp0);
691 msg->status_code = atoi(tmp_str);
692 if(tmp0->type == JSON_NUMBER)
693 msg->status_code = (int) jsonObjectGetNumber(tmp0);
696 // Get the content for a RESULT
697 tmp0 = jsonObjectGetKeyConst(tmp,"content");
699 // Note that we use jsonObjectDecodeClass() instead of
700 // jsonObjectClone(). The classnames are already decoded,
701 // but jsonObjectDecodeClass removes the decoded classnames.
702 msg->_result_content = jsonObjectDecodeClass( tmp0 );
712 @brief Return a pointer to the result content of an osrfMessage.
713 @param msg Pointer to the osrfMessage whose result content is to be returned.
714 @return Pointer to the result content (or NULL if there is no such content, or if @a msg is
717 The returned pointer points into the innards of the osrfMessage. The calling code should
718 @em not call jsonObjectFree() on it, because the osrfMessage still owns it.
720 const jsonObject* osrfMessageGetResult( osrfMessage* msg ) {
721 if(msg) return msg->_result_content;