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>
15 static jsonObject* osrfMessageToJSON( const osrfMessage* msg );
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->result_string = NULL;
44 msg->method_name = NULL;
45 msg->sender_locale = NULL;
46 msg->sender_tz_offset = 0;
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 Change the default locale.
89 @param locale The new default locale.
90 @return A pointer to the new default locale if successful, or NULL if not.
92 The function returns NULL if the parameter is NULL, or if the proposed new locale is
93 longer than 16 characters.
95 At this writing, nothing calls this function.
97 const char* osrf_message_set_default_locale( const char* locale ) {
98 if( locale == NULL ) return NULL;
99 if( strlen(locale) > sizeof(default_locale) - 1 ) return NULL;
101 strcpy( default_locale, locale );
102 return default_locale;
106 @brief Populate the method_name member of an osrfMessage.
107 @param msg Pointer to the osrfMessage.
108 @param method_name The method name.
110 Used for a REQUEST message to specify what method to invoke.
112 void osrf_message_set_method( osrfMessage* msg, const char* method_name ) {
113 if( msg == NULL || method_name == NULL )
115 if( msg->method_name )
116 free( msg->method_name );
117 msg->method_name = strdup( method_name );
122 @brief Add a copy of a jsonObject to an osrfMessage as a parameter for a method call.
123 @param msg Pointer to the osrfMessage.
124 @param o Pointer to the jsonObject of which a copy is to be stored.
126 Make a copy of the input jsonObject, with all classnames encoded with JSON_CLASS_KEY and
127 JSON_DATA_KEY. Append it to a JSON_ARRAY stored at msg->_params.
129 If there is nothing at msg->_params, create a new JSON_ARRAY for it and add the new object
130 as the first element.
132 If the jsonObject is raw (i.e. the class information has not been decoded into classnames),
133 decode it. If the class information has already been decoded, discard it.
135 See also osrf_message_add_param().
137 At this writing, nothing calls this function.
139 void osrf_message_add_object_param( osrfMessage* msg, const jsonObject* o ) {
140 if(!msg|| !o) return;
142 msg->_params = jsonNewObjectType( JSON_ARRAY );
143 jsonObjectPush(msg->_params, jsonObjectDecodeClass( o ));
147 @brief Populate the params member of an osrfMessage.
148 @param msg Pointer to the osrfMessage.
149 @param o Pointer to a jsonObject representing the parameter(s) to a method.
151 Make a copy of a jsonObject and install it as the parameter list for a method call.
153 If the osrfMessage already has any parameters, discard them.
155 The @a o parameter should point to a jsonObject of type JSON_ARRAY, with each element
156 of the array being a parameter. If @a o points to any other type of jsonObject, create
157 a JSON_ARRAY as a wrapper for it, and install a copy of @a o as its only element.
159 Used for a REQUEST message, to pass parameters to a method. The alternative is to call
160 osrf_message_add_param() or osrf_message_add_object_param() repeatedly as needed to add
161 one parameter at a time.
163 void osrf_message_set_params( osrfMessage* msg, const jsonObject* o ) {
164 if(!msg || !o) return;
167 jsonObjectFree(msg->_params);
169 if(o->type == JSON_ARRAY) {
170 msg->_params = jsonObjectClone(o);
172 osrfLogDebug( OSRF_LOG_MARK, "passing non-array to osrf_message_set_params(), fixing...");
173 jsonObject* clone = jsonObjectClone(o);
174 msg->_params = jsonNewObjectType( JSON_ARRAY );
175 jsonObjectPush(msg->_params, clone);
182 @brief Add a JSON string to an osrfMessage as a parameter for a method call.
183 @param msg Pointer to the osrfMessage.
184 @param param_string A JSON string encoding the parameter to be added.
186 Translate the JSON string into a jsonObject, and append it to the parameter list.
187 If the parameter list doesn't already exist, create an empty one and add the new
190 Decode any class information in the raw JSON into classnames.
192 If the JSON string is not valid JSON, append a new jsonObject of type JSON_NULL.
194 Used for a REQUEST message, to pass a parameter to a method, The alternative is to
195 call osrf_message_set_params() to provide all the parameters at once. See also
196 osrf_message_add_object_param().
198 void osrf_message_add_param( osrfMessage* msg, const char* param_string ) {
199 if(msg == NULL || param_string == NULL) return;
200 if(!msg->_params) msg->_params = jsonNewObjectType( JSON_ARRAY );
201 jsonObjectPush(msg->_params, jsonParseString(param_string));
206 @brief Set the status_name, status_text, and status_code members of an osrfMessage.
207 @param msg Pointer to the osrfMessage to be populated.
208 @param status_name Status name (may be NULL).
209 @param status_text Status text (may be NULL).
210 @param status_code Status code.
212 If the @a status_name or @a status_text parameter is NULL, the corresponding member
215 Used for a RESULT or STATUS message.
217 void osrf_message_set_status_info( osrfMessage* msg,
218 const char* status_name, const char* status_text, int status_code ) {
221 if( status_name != NULL ) {
222 if( msg->status_name )
223 free( msg->status_name );
224 msg->status_name = strdup( status_name );
227 if( status_text != NULL ) {
228 if( msg->status_text )
229 free( msg->status_text );
230 msg->status_text = strdup( status_text );
233 msg->status_code = status_code;
238 @brief Populate the result_string and _result_content members of an osrfMessage.
239 @param msg Pointer to the osrfMessage to be populated.
240 @param json_string A JSON string encoding a result set.
242 Used for a RESULT message to return the results of a request, such as a database lookup.
244 void osrf_message_set_result_content( osrfMessage* msg, const char* json_string ) {
245 if( msg == NULL || json_string == NULL) return;
246 if( msg->result_string )
247 free( msg->result_string );
248 if( msg->_result_content )
249 jsonObjectFree( msg->_result_content );
251 msg->result_string = strdup(json_string);
252 msg->_result_content = jsonParseString(json_string);
257 @brief Free an osrfMessage and everything it owns.
258 @param msg Pointer to the osrfMessage to be freed.
260 void osrfMessageFree( osrfMessage* msg ) {
264 if( msg->status_name != NULL )
265 free(msg->status_name);
267 if( msg->status_text != NULL )
268 free(msg->status_text);
270 if( msg->_result_content != NULL )
271 jsonObjectFree( msg->_result_content );
273 if( msg->result_string != NULL )
274 free( msg->result_string);
276 if( msg->method_name != NULL )
277 free(msg->method_name);
279 if( msg->sender_locale != NULL )
280 free(msg->sender_locale);
282 if( msg->_params != NULL )
283 jsonObjectFree(msg->_params);
290 @brief Turn a collection of osrfMessages into one big JSON string.
291 @param msgs Pointer to an array of osrfMessages.
292 @param count Maximum number of messages to serialize.
293 @return Pointer to the JSON string.
295 Traverse the array, adding each osrfMessage in turn to a JSON_ARRAY. Stop when you have added
296 the maximum number of messages, or when you encounter a NULL pointer in the array. Then
297 translate the JSON_ARRAY into a JSON string.
299 The calling code is responsible for freeing the returned string.
301 char* osrfMessageSerializeBatch( osrfMessage* msgs [], int count ) {
302 if( !msgs ) return NULL;
304 jsonObject* wrapper = jsonNewObjectType(JSON_ARRAY);
307 while( (i < count) && msgs[i] ) {
308 jsonObjectPush(wrapper, osrfMessageToJSON( msgs[i] ));
312 char* j = jsonObjectToJSON(wrapper);
313 jsonObjectFree(wrapper);
320 @brief Turn a single osrfMessage into a JSON string.
321 @param msg Pointer to the osrfMessage to be serialized.
322 @return Pointer to the resulting JSON string.
324 Translate the osrfMessage into JSON, wrapped in a JSON array.
326 This function is equivalent to osrfMessageSerializeBatch() for an array of one pointer.
328 The calling code is responsible for freeing the returned string.
330 char* osrf_message_serialize(const osrfMessage* msg) {
332 if( msg == NULL ) return NULL;
335 jsonObject* json = osrfMessageToJSON( msg );
338 jsonObject* wrapper = jsonNewObjectType(JSON_ARRAY);
339 jsonObjectPush(wrapper, json);
340 j = jsonObjectToJSON(wrapper);
341 jsonObjectFree(wrapper);
349 @brief Translate an osrfMessage into a jsonObject.
350 @param msg Pointer to the osrfMessage to be translated.
351 @return Pointer to a newly created jsonObject.
353 The resulting jsonObject is a JSON_HASH with a classname of "osrfMessage", and the following keys:
357 - "payload" (only for STATUS, REQUEST, and RESULT messages)
359 The data for "payload" is also a JSON_HASH, whose structure depends on the message type:
361 For a STATUS message, the payload's classname is msg->status_name. The keys are "status"
362 (carrying msg->status_text) and "statusCode" (carrying the status code as a string).
364 For a REQUEST message, the payload's classname is "osrfMethod". The keys are "method"
365 (carrying msg->method_name) and "params" (carrying a jsonObject to pass any parameters
368 For a RESULT message, the payload's classname is "osrfResult". The keys are "status"
369 (carrying msg->status_text), "statusCode" (carrying the status code as a string), and
370 "content" (carrying a jsonObject to return any results from the method call).
372 The calling code is responsible for freeing the returned jsonObject.
374 static jsonObject* osrfMessageToJSON( const osrfMessage* msg ) {
376 jsonObject* json = jsonNewObjectType(JSON_HASH);
377 jsonObjectSetClass(json, "osrfMessage");
380 osrf_clearbuf(sc, sizeof(sc));
382 INT_TO_STRING(msg->thread_trace);
383 jsonObjectSetKey(json, "threadTrace", jsonNewObject(INTSTR));
385 if (msg->sender_locale != NULL) {
386 jsonObjectSetKey(json, "locale", jsonNewObject(msg->sender_locale));
387 } else if (current_locale != NULL) {
388 jsonObjectSetKey(json, "locale", jsonNewObject(current_locale));
390 jsonObjectSetKey(json, "locale", jsonNewObject(default_locale));
393 switch(msg->m_type) {
396 jsonObjectSetKey(json, "type", jsonNewObject("CONNECT"));
400 jsonObjectSetKey(json, "type", jsonNewObject("DISCONNECT"));
404 jsonObjectSetKey(json, "type", jsonNewObject("STATUS"));
405 payload = jsonNewObject(NULL);
406 jsonObjectSetClass(payload, msg->status_name);
407 jsonObjectSetKey(payload, "status", jsonNewObject(msg->status_text));
408 snprintf(sc, sizeof(sc), "%d", msg->status_code);
409 jsonObjectSetKey(payload, "statusCode", jsonNewObject(sc));
410 jsonObjectSetKey(json, "payload", payload);
414 jsonObjectSetKey(json, "type", jsonNewObject("REQUEST"));
415 payload = jsonNewObject(NULL);
416 jsonObjectSetClass(payload, "osrfMethod");
417 jsonObjectSetKey(payload, "method", jsonNewObject(msg->method_name));
418 jsonObjectSetKey( payload, "params", jsonObjectDecodeClass( msg->_params ) );
419 jsonObjectSetKey(json, "payload", payload);
424 jsonObjectSetKey(json, "type", jsonNewObject("RESULT"));
425 payload = jsonNewObject(NULL);
426 jsonObjectSetClass(payload,"osrfResult");
427 jsonObjectSetKey(payload, "status", jsonNewObject(msg->status_text));
428 snprintf(sc, sizeof(sc), "%d", msg->status_code);
429 jsonObjectSetKey(payload, "statusCode", jsonNewObject(sc));
430 jsonObjectSetKey(payload, "content", jsonObjectDecodeClass( msg->_result_content ));
431 jsonObjectSetKey(json, "payload", payload);
439 @brief Translate a JSON array into an osrfList of osrfMessages.
440 @param string The JSON string to be translated.
441 @param list Pointer to an osrfList of osrfMessages (may be NULL)
442 @return Pointer to an osrfList containing pointers to osrfMessages.
444 The JSON string is expected to be a JSON array, with each element encoding an osrfMessage.
446 Translate each element of the JSON array into an osrfMessage, and store a pointer to the
447 osrfMessage in an osrfList.
449 If the @a list parameter is NULL, create a new osrfList (with osrfMessageFree() as the
450 callback function for freeing items), populate it, and return a pointer to it. Otherwise
451 clear the osrfList provided and reuse it.
453 When calling osrfMessageDeserialize repeatedly, a reasonable strategy is to pass a NULL
454 for the @a list parameter on the first call, and pass the value returned from the first
455 call on subsequent calls.
457 The calling code is responsible for eventually freeing the returned osrfList by calling
460 osrfList* osrfMessageDeserialize( const char* string, osrfList* list ) {
463 osrfListClear( list );
465 if( ! string || ! *string ) {
467 list = osrfNewList( 1 );
468 list->freeItem = (void(*)(void*)) osrfMessageFree;
470 return list; // No string? Return empty list.
474 jsonObject* json = jsonParseString(string);
476 osrfLogWarning( OSRF_LOG_MARK,
477 "osrfMessageDeserialize() unable to parse data: \n%s\n", string);
479 list = osrfNewList( 1 );
480 list->freeItem = (void(*)(void*)) osrfMessageFree;
482 return list; // Bad JSON? Return empty list.
485 const unsigned int count = (int) json->size;
487 // Create a right-sized osrfList
488 list = osrfNewList( count );
489 list->freeItem = (void(*)(void*)) osrfMessageFree;
492 // Traverse the JSON_ARRAY, turning each element into an osrfMessage
494 for( i = 0; i < count; ++i ) {
496 const jsonObject* message = jsonObjectGetIndex( json, i );
497 if( message && message->type != JSON_NULL &&
498 message->classname && !strcmp(message->classname, "osrfMessage" )) {
499 osrfListPush( list, deserialize_one_message( message ) );
503 jsonObjectFree( json );
508 @brief Translate a JSON array into an array of osrfMessages.
509 @param string The JSON string to be translated.
510 @param msgs Pointer to an array of pointers to osrfMessage, to receive the results.
511 @param count How many slots are available in the @a msgs array.
512 @return The number of osrfMessages created.
514 The JSON string is expected to be a JSON array, with each element encoding an osrfMessage.
516 If there are too many messages in the JSON array to fit into the pointer array, we
517 silently ignore the excess.
519 int osrf_message_deserialize(const char* string, osrfMessage* msgs[], int count) {
521 if(!string || !msgs || count <= 0) return 0;
525 jsonObject* json = jsonParseString(string);
528 osrfLogWarning( OSRF_LOG_MARK,
529 "osrf_message_deserialize() unable to parse data: \n%s\n", string);
533 // Traverse the JSON_ARRAY, turning each element into an osrfMessage
535 for( x = 0; x < json->size && x < count; x++ ) {
537 const jsonObject* message = jsonObjectGetIndex( json, x );
539 if( message && message->type != JSON_NULL &&
540 message->classname && !strcmp(message->classname, "osrfMessage" )) {
541 msgs[numparsed++] = deserialize_one_message( message );
545 jsonObjectFree( json );
551 @brief Translate a jsonObject into a single osrfMessage.
552 @param obj Pointer to the jsonObject to be translated.
553 @return Pointer to a newly created osrfMessage.
555 It is assumed that @a obj is non-NULL and points to a valid representation of a message.
556 For a description of the expected structure of this representations, see osrfMessageToJSON().
558 The calling code is responsible for freeing the osrfMessage by calling osrfMessageFree().
560 static osrfMessage* deserialize_one_message( const jsonObject* obj ) {
562 // Get the message type. If it isn't present, default to CONNECT.
563 const jsonObject* tmp = jsonObjectGetKeyConst( obj, "type" );
565 enum M_TYPE type = CONNECT;
566 const char* t = jsonObjectGetString( tmp );
569 if( !strcmp( t, "CONNECT" )) type = CONNECT;
570 else if( !strcmp( t, "DISCONNECT" )) type = DISCONNECT;
571 else if( !strcmp( t, "STATUS" )) type = STATUS;
572 else if( !strcmp( t, "REQUEST" )) type = REQUEST;
573 else if( !strcmp( t, "RESULT" )) type = RESULT;
576 // Get the thread trace, defaulting to zero.
578 tmp = jsonObjectGetKeyConst( obj, "threadTrace" );
580 const char* tt = jsonObjectGetString( tmp );
586 // Get the protocol, defaulting to zero.
588 tmp = jsonObjectGetKeyConst( obj, "protocol" );
590 const char* proto = jsonObjectGetString(tmp);
592 protocol = atoi( proto );
596 // Now that we have the essentials, create an osrfMessage
597 osrfMessage* msg = osrf_message_init( type, trace, protocol );
599 // Get the sender's locale, or leave it NULL if not specified.
600 if ( current_locale )
601 free( current_locale );
603 tmp = jsonObjectGetKeyConst( obj, "locale" );
604 if(tmp && ( msg->sender_locale = jsonObjectToSimpleString(tmp))) {
605 current_locale = strdup( msg->sender_locale );
607 current_locale = NULL;
610 tmp = jsonObjectGetKeyConst( obj, "payload" );
612 // Get method name and parameters for a REQUEST
613 const jsonObject* tmp0 = jsonObjectGetKeyConst(tmp,"method");
614 const char* tmp_str = jsonObjectGetString(tmp0);
616 msg->method_name = strdup(tmp_str);
618 tmp0 = jsonObjectGetKeyConst(tmp,"params");
620 // Note that we use jsonObjectDecodeClass() instead of
621 // jsonObjectClone(). The classnames are already decoded,
622 // but jsonObjectDecodeClass removes the decoded classnames.
623 msg->_params = jsonObjectDecodeClass( tmp0 );
624 if(msg->_params && msg->_params->type == JSON_NULL)
625 msg->_params->type = JSON_ARRAY;
628 // Get status fields for a RESULT or STATUS
630 msg->status_name = strdup(tmp->classname);
632 tmp0 = jsonObjectGetKeyConst(tmp,"status");
633 tmp_str = jsonObjectGetString(tmp0);
635 msg->status_text = strdup(tmp_str);
637 tmp0 = jsonObjectGetKeyConst(tmp,"statusCode");
639 tmp_str = jsonObjectGetString(tmp0);
641 msg->status_code = atoi(tmp_str);
642 if(tmp0->type == JSON_NUMBER)
643 msg->status_code = (int) jsonObjectGetNumber(tmp0);
646 // Get the content for a RESULT
647 tmp0 = jsonObjectGetKeyConst(tmp,"content");
649 // Note that we use jsonObjectDecodeClass() instead of
650 // jsonObjectClone(). The classnames are already decoded,
651 // but jsonObjectDecodeClass removes the decoded classnames.
652 msg->_result_content = jsonObjectDecodeClass( tmp0 );
662 @brief Return a pointer to the result content of an osrfMessage.
663 @param msg Pointer to the osrfMessage whose result content is to be returned.
664 @return Pointer to the result content (or NULL if there is no such content, or if @a msg is
667 The returned pointer points into the innards of the osrfMessage. The calling code should
668 @em not call jsonObjectFree() on it, because the osrfMessage still owns it.
670 jsonObject* osrfMessageGetResult( osrfMessage* msg ) {
671 if(msg) return msg->_result_content;