4 @brief Implementation of osrfMessage.
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>
14 #include <opensrf/osrf_message.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->result_string = NULL;
45 msg->method_name = NULL;
46 msg->sender_locale = NULL;
47 msg->sender_tz_offset = 0;
54 @brief Return the previous locale.
55 @return A pointer to the locale specified by the last message to have been deserialized,
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.
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.
65 const char* osrf_message_get_last_locale() {
66 return current_locale;
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
76 If no locale is specified for an osrfMessage, we use the default locale.
78 Used for a REQUEST message.
80 const char* osrf_message_set_locale( osrfMessage* msg, const char* locale ) {
81 if( msg == NULL || locale == NULL )
83 if( msg->sender_locale )
84 free( msg->sender_locale );
85 return msg->sender_locale = strdup( locale );
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.
93 The function returns NULL if the parameter is NULL, or if the proposed new locale is
94 longer than 16 characters.
96 At this writing, nothing calls this function.
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;
102 strcpy( default_locale, locale );
103 return default_locale;
107 @brief Populate the method_name member of an osrfMessage.
108 @param msg Pointer to the osrfMessage.
109 @param method_name The method name.
111 Used for a REQUEST message to specify what method to invoke.
113 void osrf_message_set_method( osrfMessage* msg, const char* method_name ) {
114 if( msg == NULL || method_name == NULL )
116 if( msg->method_name )
117 free( msg->method_name );
118 msg->method_name = strdup( method_name );
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.
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.
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.
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.
136 See also osrf_message_add_param().
138 At this writing, nothing calls this function.
140 void osrf_message_add_object_param( osrfMessage* msg, const jsonObject* o ) {
141 if(!msg|| !o) return;
143 msg->_params = jsonNewObjectType( JSON_ARRAY );
144 jsonObjectPush(msg->_params, jsonObjectDecodeClass( o ));
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.
152 Make a copy of a jsonObject and install it as the parameter list for a method call.
154 If the osrfMessage already has any parameters, discard them.
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.
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.
164 void osrf_message_set_params( osrfMessage* msg, const jsonObject* o ) {
165 if(!msg || !o) return;
168 jsonObjectFree(msg->_params);
170 if(o->type == JSON_ARRAY) {
171 msg->_params = jsonObjectClone(o);
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);
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.
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
191 Decode any class information in the raw JSON into classnames.
193 If the JSON string is not valid JSON, append a new jsonObject of type JSON_NULL.
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().
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));
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.
213 If the @a status_name or @a status_text parameter is NULL, the corresponding member
216 Used for a RESULT or STATUS message.
218 void osrf_message_set_status_info( osrfMessage* msg,
219 const char* status_name, const char* status_text, int status_code ) {
222 if( status_name != NULL ) {
223 if( msg->status_name )
224 free( msg->status_name );
225 msg->status_name = strdup( status_name );
228 if( status_text != NULL ) {
229 if( msg->status_text )
230 free( msg->status_text );
231 msg->status_text = strdup( status_text );
234 msg->status_code = status_code;
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.
243 Used for a RESULT message to return the results of a request, such as a database lookup.
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 );
252 msg->result_string = strdup(json_string);
253 msg->_result_content = jsonParseString(json_string);
258 @brief Free an osrfMessage and everything it owns.
259 @param msg Pointer to the osrfMessage to be freed.
261 void osrfMessageFree( osrfMessage* msg ) {
265 if( msg->status_name != NULL )
266 free(msg->status_name);
268 if( msg->status_text != NULL )
269 free(msg->status_text);
271 if( msg->_result_content != NULL )
272 jsonObjectFree( msg->_result_content );
274 if( msg->result_string != NULL )
275 free( msg->result_string);
277 if( msg->method_name != NULL )
278 free(msg->method_name);
280 if( msg->sender_locale != NULL )
281 free(msg->sender_locale);
283 if( msg->_params != NULL )
284 jsonObjectFree(msg->_params);
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.
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.
300 The calling code is responsible for freeing the returned string.
302 char* osrfMessageSerializeBatch( osrfMessage* msgs [], int count ) {
303 if( !msgs ) return NULL;
305 jsonObject* wrapper = jsonNewObjectType(JSON_ARRAY);
308 while( (i < count) && msgs[i] ) {
309 jsonObjectPush(wrapper, osrfMessageToJSON( msgs[i] ));
313 char* j = jsonObjectToJSON(wrapper);
314 jsonObjectFree(wrapper);
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.
325 Translate the osrfMessage into JSON, wrapped in a JSON array.
327 This function is equivalent to osrfMessageSerializeBatch() for an array of one pointer.
329 The calling code is responsible for freeing the returned string.
331 char* osrf_message_serialize(const osrfMessage* msg) {
333 if( msg == NULL ) return NULL;
336 jsonObject* json = osrfMessageToJSON( msg );
339 jsonObject* wrapper = jsonNewObjectType(JSON_ARRAY);
340 jsonObjectPush(wrapper, json);
341 j = jsonObjectToJSON(wrapper);
342 jsonObjectFree(wrapper);
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.
354 The resulting jsonObject is a JSON_HASH with a classname of "osrfMessage", and the following keys:
358 - "payload" (only for STATUS, REQUEST, and RESULT messages)
360 The data for "payload" is also a JSON_HASH, whose structure depends on the message type:
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).
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
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).
373 The calling code is responsible for freeing the returned jsonObject.
375 static jsonObject* osrfMessageToJSON( const osrfMessage* msg ) {
377 jsonObject* json = jsonNewObjectType(JSON_HASH);
378 jsonObjectSetClass(json, "osrfMessage");
381 osrf_clearbuf(sc, sizeof(sc));
383 INT_TO_STRING(msg->thread_trace);
384 jsonObjectSetKey(json, "threadTrace", jsonNewObject(INTSTR));
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));
391 jsonObjectSetKey(json, "locale", jsonNewObject(default_locale));
394 switch(msg->m_type) {
397 jsonObjectSetKey(json, "type", jsonNewObject("CONNECT"));
401 jsonObjectSetKey(json, "type", jsonNewObject("DISCONNECT"));
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);
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);
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);
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.
445 The JSON string is expected to be a JSON array, with each element encoding an osrfMessage.
447 Translate each element of the JSON array into an osrfMessage, and store a pointer to the
448 osrfMessage in an osrfList.
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.
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.
458 The calling code is responsible for eventually freeing the returned osrfList by calling
461 osrfList* osrfMessageDeserialize( const char* string, osrfList* list ) {
464 osrfListClear( list );
466 if( ! string || ! *string ) {
468 list = osrfNewList( 1 );
469 list->freeItem = (void(*)(void*)) osrfMessageFree;
471 return list; // No string? Return empty list.
475 jsonObject* json = jsonParseString(string);
477 osrfLogWarning( OSRF_LOG_MARK,
478 "osrfMessageDeserialize() unable to parse data: \n%s\n", string);
480 list = osrfNewList( 1 );
481 list->freeItem = (void(*)(void*)) osrfMessageFree;
483 return list; // Bad JSON? Return empty list.
486 const unsigned int count = (int) json->size;
488 // Create a right-sized osrfList
489 list = osrfNewList( count );
490 list->freeItem = (void(*)(void*)) osrfMessageFree;
493 // Traverse the JSON_ARRAY, turning each element into an osrfMessage
495 for( i = 0; i < count; ++i ) {
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 ) );
504 jsonObjectFree( json );
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.
515 The JSON string is expected to be a JSON array, with each element encoding an osrfMessage.
517 If there are too many messages in the JSON array to fit into the pointer array, we
518 silently ignore the excess.
520 int osrf_message_deserialize(const char* string, osrfMessage* msgs[], int count) {
522 if(!string || !msgs || count <= 0) return 0;
526 jsonObject* json = jsonParseString(string);
529 osrfLogWarning( OSRF_LOG_MARK,
530 "osrf_message_deserialize() unable to parse data: \n%s\n", string);
534 // Traverse the JSON_ARRAY, turning each element into an osrfMessage
536 for( x = 0; x < json->size && x < count; x++ ) {
538 const jsonObject* message = jsonObjectGetIndex( json, x );
540 if( message && message->type != JSON_NULL &&
541 message->classname && !strcmp(message->classname, "osrfMessage" )) {
542 msgs[numparsed++] = deserialize_one_message( message );
546 jsonObjectFree( json );
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.
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().
559 The calling code is responsible for freeing the osrfMessage by calling osrfMessageFree().
561 static osrfMessage* deserialize_one_message( const jsonObject* obj ) {
563 // Get the message type. If it isn't present, default to CONNECT.
564 const jsonObject* tmp = jsonObjectGetKeyConst( obj, "type" );
566 enum M_TYPE type = CONNECT;
567 const char* t = jsonObjectGetString( tmp );
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;
577 // Get the thread trace, defaulting to zero.
579 tmp = jsonObjectGetKeyConst( obj, "threadTrace" );
581 const char* tt = jsonObjectGetString( tmp );
587 // Get the protocol, defaulting to zero.
589 tmp = jsonObjectGetKeyConst( obj, "protocol" );
591 const char* proto = jsonObjectGetString(tmp);
593 protocol = atoi( proto );
597 // Now that we have the essentials, create an osrfMessage
598 osrfMessage* msg = osrf_message_init( type, trace, protocol );
600 // Get the sender's locale, or leave it NULL if not specified.
601 if ( current_locale )
602 free( current_locale );
604 tmp = jsonObjectGetKeyConst( obj, "locale" );
605 if(tmp && ( msg->sender_locale = jsonObjectToSimpleString(tmp))) {
606 current_locale = strdup( msg->sender_locale );
608 current_locale = NULL;
611 tmp = jsonObjectGetKeyConst( obj, "payload" );
613 // Get method name and parameters for a REQUEST
614 const jsonObject* tmp0 = jsonObjectGetKeyConst(tmp,"method");
615 const char* tmp_str = jsonObjectGetString(tmp0);
617 msg->method_name = strdup(tmp_str);
619 tmp0 = jsonObjectGetKeyConst(tmp,"params");
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;
629 // Get status fields for a RESULT or STATUS
631 msg->status_name = strdup(tmp->classname);
633 tmp0 = jsonObjectGetKeyConst(tmp,"status");
634 tmp_str = jsonObjectGetString(tmp0);
636 msg->status_text = strdup(tmp_str);
638 tmp0 = jsonObjectGetKeyConst(tmp,"statusCode");
640 tmp_str = jsonObjectGetString(tmp0);
642 msg->status_code = atoi(tmp_str);
643 if(tmp0->type == JSON_NUMBER)
644 msg->status_code = (int) jsonObjectGetNumber(tmp0);
647 // Get the content for a RESULT
648 tmp0 = jsonObjectGetKeyConst(tmp,"content");
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 );
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
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.
671 jsonObject* osrfMessageGetResult( osrfMessage* msg ) {
672 if(msg) return msg->_result_content;