1 #include <opensrf/osrf_message.h>
5 @brief Implementation of osrfMessage.
8 static jsonObject* osrfMessageToJSON( const osrfMessage* msg );
9 static osrfMessage* deserialize_one_message( const jsonObject* message );
11 static char default_locale[17] = "en-US\0\0\0\0\0\0\0\0\0\0\0\0";
12 static char* current_locale = NULL;
15 @brief Allocate and initialize an osrfMessage.
16 @param type One of CONNECT, REQUEST, RESULT, STATUS, or DISCONNECT.
17 @param thread_trace Thread trace.
18 @param protocol Protocol.
19 @return Pointer to the newly allocated osrfMessage.
21 The calling code is responsible for freeing the osrfMessage by calling osrfMessageFree().
23 osrfMessage* osrf_message_init( enum M_TYPE type, int thread_trace, int protocol ) {
25 osrfMessage* msg = (osrfMessage*) safe_malloc(sizeof(osrfMessage));
27 msg->thread_trace = thread_trace;
28 msg->protocol = protocol;
29 msg->status_name = NULL;
30 msg->status_text = NULL;
33 msg->is_exception = 0;
35 msg->_result_content = NULL;
36 msg->result_string = NULL;
37 msg->method_name = NULL;
38 msg->sender_locale = NULL;
39 msg->sender_tz_offset = 0;
46 @brief Return the previous locale.
47 @return A pointer to the locale specified by the last message to have been deserialized,
50 A JSON message may specify a locale string, which is saved as the current locale. If
51 the message does not specify a locale string, then the current locale becomes NULL.
53 This function returns a pointer to an internal buffer. Since both the address and the
54 contents of this buffer may change from one call to the next, the calling code should
55 either use the result immediately or make a copy of the string for later use.
57 const char* osrf_message_get_last_locale() {
58 return current_locale;
62 @brief Set the locale for a specified osrfMessage.
63 @param msg Pointer to the osrfMessage.
64 @param locale Pointer to the locale string to be installed in the osrfMessage.
65 @return Pointer to the new locale string for the osrfMessage, or NULL if either
68 If no locale is specified for an osrfMessage, we use the default locale.
70 Used for a REQUEST message.
72 const char* osrf_message_set_locale( osrfMessage* msg, const char* locale ) {
73 if( msg == NULL || locale == NULL )
75 if( msg->sender_locale )
76 free( msg->sender_locale );
77 return msg->sender_locale = strdup( locale );
81 @brief Change the default locale.
82 @param locale The new default locale.
83 @return A pointer to the new default locale if successful, or NULL if not.
85 The function returns NULL if the parameter is NULL, or if the proposed new locale is
86 longer than 16 characters.
88 At this writing, nothing calls this function.
90 const char* osrf_message_set_default_locale( const char* locale ) {
91 if( locale == NULL ) return NULL;
92 if( strlen(locale) > sizeof(default_locale) - 1 ) return NULL;
94 strcpy( default_locale, locale );
95 return default_locale;
99 @brief Populate the method_name member of an osrfMessage.
100 @param msg Pointer to the osrfMessage.
101 @param method_name The method name.
103 Used for a REQUEST message to specify what method to invoke.
105 void osrf_message_set_method( osrfMessage* msg, const char* method_name ) {
106 if( msg == NULL || method_name == NULL )
108 if( msg->method_name )
109 free( msg->method_name );
110 msg->method_name = strdup( method_name );
115 @brief Add a copy of a jsonObject to an osrfMessage as a parameter for a method call.
116 @param msg Pointer to the osrfMessage.
117 @param o Pointer to the jsonObject of which a copy is to be stored.
119 Make a copy of the input jsonObject, with all classnames encoded with JSON_CLASS_KEY and
120 JSON_DATA_KEY. Append it to a JSON_ARRAY stored at msg->_params.
122 If there is nothing at msg->_params, create a new JSON_ARRAY for it and add the new object
123 as the first element.
125 If the jsonObject is raw (i.e. the class information has not been decoded into classnames),
126 decode it. If the class information has already been decoded, discard it.
128 See also osrf_message_add_param().
130 At this writing, nothing calls this function.
132 void osrf_message_add_object_param( osrfMessage* msg, const jsonObject* o ) {
133 if(!msg|| !o) return;
135 msg->_params = jsonNewObjectType( JSON_ARRAY );
136 jsonObjectPush(msg->_params, jsonObjectDecodeClass( o ));
140 @brief Populate the params member of an osrfMessage.
141 @param msg Pointer to the osrfMessage.
142 @param o Pointer to a jsonObject representing the parameter(s) to a method.
144 Make a copy of a jsonObject and install it as the parameter list for a method call.
146 If the osrfMessage already has any parameters, discard them.
148 The @a o parameter should point to a jsonObject of type JSON_ARRAY, with each element
149 of the array being a parameter. If @a o points to any other type of jsonObject, create
150 a JSON_ARRAY as a wrapper for it, and install a copy of @a o as its only element.
152 Used for a REQUEST message, to pass parameters to a method. The alternative is to call
153 osrf_message_add_param() or osrf_message_add_object_param() repeatedly as needed to add
154 one parameter at a time.
156 void osrf_message_set_params( osrfMessage* msg, const jsonObject* o ) {
157 if(!msg || !o) return;
160 jsonObjectFree(msg->_params);
162 if(o->type == JSON_ARRAY) {
163 msg->_params = jsonObjectClone(o);
165 osrfLogDebug( OSRF_LOG_MARK, "passing non-array to osrf_message_set_params(), fixing...");
166 jsonObject* clone = jsonObjectClone(o);
167 msg->_params = jsonNewObjectType( JSON_ARRAY );
168 jsonObjectPush(msg->_params, clone);
175 @brief Add a JSON string to an osrfMessage as a parameter for a method call.
176 @param msg Pointer to the osrfMessage.
177 @param param_string A JSON string encoding the parameter to be added.
179 Translate the JSON string into a jsonObject, and append it to the parameter list.
180 If the parameter list doesn't already exist, create an empty one and add the new
183 Decode any class information in the raw JSON into classnames.
185 If the JSON string is not valid JSON, append a new jsonObject of type JSON_NULL.
187 Used for a REQUEST message, to pass a parameter to a method, The alternative is to
188 call osrf_message_set_params() to provide all the parameters at once. See also
189 osrf_message_add_object_param().
191 void osrf_message_add_param( osrfMessage* msg, const char* param_string ) {
192 if(msg == NULL || param_string == NULL) return;
193 if(!msg->_params) msg->_params = jsonNewObjectType( JSON_ARRAY );
194 jsonObjectPush(msg->_params, jsonParseString(param_string));
199 @brief Set the status_name, status_text, and status_code members of an osrfMessage.
200 @param msg Pointer to the osrfMessage to be populated.
201 @param status_name Status name (may be NULL).
202 @param status_text Status text (may be NULL).
203 @param status_code Status code.
205 If the @a status_name or @a status_text parameter is NULL, the corresponding member
208 Used for a RESULT or STATUS message.
210 void osrf_message_set_status_info( osrfMessage* msg,
211 const char* status_name, const char* status_text, int status_code ) {
214 if( status_name != NULL ) {
215 if( msg->status_name )
216 free( msg->status_name );
217 msg->status_name = strdup( status_name );
220 if( status_text != NULL ) {
221 if( msg->status_text )
222 free( msg->status_text );
223 msg->status_text = strdup( status_text );
226 msg->status_code = status_code;
231 @brief Populate the result_string and _result_content members of an osrfMessage.
232 @param msg Pointer to the osrfMessage to be populated.
233 @param json_string A JSON string encoding a result set.
235 Used for a RESULT message to return the results of a request, such as a database lookup.
237 void osrf_message_set_result_content( osrfMessage* msg, const char* json_string ) {
238 if( msg == NULL || json_string == NULL) return;
239 if( msg->result_string )
240 free( msg->result_string );
241 if( msg->_result_content )
242 jsonObjectFree( msg->_result_content );
244 msg->result_string = strdup(json_string);
245 msg->_result_content = jsonParseString(json_string);
250 @brief Free an osrfMessage and everything it owns.
251 @param msg Pointer to the osrfMessage to be freed.
253 void osrfMessageFree( osrfMessage* msg ) {
257 if( msg->status_name != NULL )
258 free(msg->status_name);
260 if( msg->status_text != NULL )
261 free(msg->status_text);
263 if( msg->_result_content != NULL )
264 jsonObjectFree( msg->_result_content );
266 if( msg->result_string != NULL )
267 free( msg->result_string);
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 msgs Pointer to an osrfList (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 = jsonParseString(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 = jsonParseString(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" );
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 // Get the sender's locale, or leave it NULL if not specified.
593 if ( current_locale )
594 free( current_locale );
596 tmp = jsonObjectGetKeyConst( obj, "locale" );
597 if(tmp && ( msg->sender_locale = jsonObjectToSimpleString(tmp))) {
598 current_locale = strdup( msg->sender_locale );
600 current_locale = NULL;
603 tmp = jsonObjectGetKeyConst( obj, "payload" );
605 // Get method name and parameters for a REQUEST
606 const jsonObject* tmp0 = jsonObjectGetKeyConst(tmp,"method");
607 const char* tmp_str = jsonObjectGetString(tmp0);
609 msg->method_name = strdup(tmp_str);
611 tmp0 = jsonObjectGetKeyConst(tmp,"params");
613 // Note that we use jsonObjectDecodeClass() instead of
614 // jsonObjectClone(). The classnames are already decoded,
615 // but jsonObjectDecodeClass removes the decoded classnames.
616 msg->_params = jsonObjectDecodeClass( tmp0 );
617 if(msg->_params && msg->_params->type == JSON_NULL)
618 msg->_params->type = JSON_ARRAY;
621 // Get status fields for a RESULT or STATUS
623 msg->status_name = strdup(tmp->classname);
625 tmp0 = jsonObjectGetKeyConst(tmp,"status");
626 tmp_str = jsonObjectGetString(tmp0);
628 msg->status_text = strdup(tmp_str);
630 tmp0 = jsonObjectGetKeyConst(tmp,"statusCode");
632 tmp_str = jsonObjectGetString(tmp0);
634 msg->status_code = atoi(tmp_str);
635 if(tmp0->type == JSON_NUMBER)
636 msg->status_code = (int) jsonObjectGetNumber(tmp0);
639 // Get the content for a RESULT
640 tmp0 = jsonObjectGetKeyConst(tmp,"content");
642 // Note that we use jsonObjectDecodeClass() instead of
643 // jsonObjectClone(). The classnames are already decoded,
644 // but jsonObjectDecodeClass removes the decoded classnames.
645 msg->_result_content = jsonObjectDecodeClass( tmp0 );
655 @brief Return a pointer to the result content of an osrfMessage.
656 @param msg Pointer to the osrfMessage whose result content is to be returned.
657 @return Pointer to the result content (or NULL if there is no such content, or if @a msg is
660 The returned pointer points into the innards of the osrfMessage. The calling code should
661 @em not call jsonObjectFree() on it, because the osrfMessage still owns it.
663 jsonObject* osrfMessageGetResult( osrfMessage* msg ) {
664 if(msg) return msg->_result_content;