]> git.evergreen-ils.org Git - OpenSRF.git/blob - src/libopensrf/osrf_message.c
0e97dd3f5b86c8d93b1d9aad9409df65528ac336
[OpenSRF.git] / src / libopensrf / osrf_message.c
1 #include <opensrf/osrf_message.h>
2
3 /**
4         @file osrf_message.c
5         @brief Implementation of osrfMessage.
6 */
7
8 static jsonObject* osrfMessageToJSON( const osrfMessage* msg );
9 static osrfMessage* deserialize_one_message( const jsonObject* message );
10
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;
13
14 /**
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.
20
21         The calling code is responsible for freeing the osrfMessage by calling osrfMessageFree().
22 */
23 osrfMessage* osrf_message_init( enum M_TYPE type, int thread_trace, int protocol ) {
24
25         osrfMessage* msg            = (osrfMessage*) safe_malloc(sizeof(osrfMessage));
26         msg->m_type                 = type;
27         msg->thread_trace           = thread_trace;
28         msg->protocol               = protocol;
29         msg->status_name            = NULL;
30         msg->status_text            = NULL;
31         msg->status_code            = 0;
32         msg->next                   = NULL;
33         msg->is_exception           = 0;
34         msg->_params                = NULL;
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;
40
41         return msg;
42 }
43
44
45 /**
46         @brief Return the previous locale.
47         @return A pointer to the locale specified by the last message to have been deserialized,
48                 or NULL.
49
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.
52
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.
56 */
57 const char* osrf_message_get_last_locale() {
58         return current_locale;
59 }
60
61 /**
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
66                 parameter is NULL.
67
68         If no locale is specified for an osrfMessage, we use the default locale.
69
70         Used for a REQUEST message.
71 */
72 const char* osrf_message_set_locale( osrfMessage* msg, const char* locale ) {
73         if( msg == NULL || locale == NULL )
74                 return NULL;
75         if( msg->sender_locale )
76                 free( msg->sender_locale );
77         return msg->sender_locale = strdup( locale );
78 }
79
80 /**
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.
84
85         The function returns NULL if the parameter is NULL, or if the proposed new locale is
86         longer than 16 characters.
87
88         At this writing, nothing calls this function.
89 */
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;
93
94         strcpy( default_locale, locale );
95         return default_locale;
96 }
97
98 /**
99         @brief Populate the method_name member of an osrfMessage.
100         @param msg Pointer to the osrfMessage.
101         @param method_name The method name.
102
103         Used for a REQUEST message to specify what method to invoke.
104 */
105 void osrf_message_set_method( osrfMessage* msg, const char* method_name ) {
106         if( msg == NULL || method_name == NULL )
107                 return;
108         if( msg->method_name )
109                 free( msg->method_name );
110         msg->method_name = strdup( method_name );
111 }
112
113
114 /**
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.
118
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.
121
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.
124
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.
127
128         See also osrf_message_add_param().
129
130         At this writing, nothing calls this function.
131 */
132 void osrf_message_add_object_param( osrfMessage* msg, const jsonObject* o ) {
133         if(!msg|| !o) return;
134         if(!msg->_params)
135                 msg->_params = jsonNewObjectType( JSON_ARRAY );
136         jsonObjectPush(msg->_params, jsonObjectDecodeClass( o ));
137 }
138
139 /**
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.
143
144         Make a copy of a jsonObject and install it as the parameter list for a method call.
145
146         If the osrfMessage already has any parameters, discard them.
147
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.
151
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.
155 */
156 void osrf_message_set_params( osrfMessage* msg, const jsonObject* o ) {
157         if(!msg || !o) return;
158
159         if(msg->_params)
160                 jsonObjectFree(msg->_params);
161
162         if(o->type == JSON_ARRAY) {
163                 msg->_params = jsonObjectClone(o);
164         } else {
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);
169                 return;
170         }
171 }
172
173
174 /**
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.
178
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
181         parameter to it.
182
183         Decode any class information in the raw JSON into classnames.
184
185         If the JSON string is not valid JSON, append a new jsonObject of type JSON_NULL.
186
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().
190 */
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));
195 }
196
197
198 /**
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.
204
205         If the @a status_name or @a status_text parameter is NULL, the corresponding member
206         is left unchanged.
207
208         Used for a RESULT or STATUS message.
209 */
210 void osrf_message_set_status_info( osrfMessage* msg,
211                 const char* status_name, const char* status_text, int status_code ) {
212         if(!msg) return;
213
214         if( status_name != NULL ) {
215                 if( msg->status_name )
216                         free( msg->status_name );
217                 msg->status_name = strdup( status_name );
218         }
219
220         if( status_text != NULL ) {
221                 if( msg->status_text )
222                         free( msg->status_text );
223                 msg->status_text = strdup( status_text );
224         }
225
226         msg->status_code = status_code;
227 }
228
229
230 /**
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.
234
235         Used for a RESULT message to return the results of a request, such as a database lookup.
236 */
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 );
243
244         msg->result_string   = strdup(json_string);
245         msg->_result_content = jsonParseString(json_string);
246 }
247
248
249 /**
250         @brief Free an osrfMessage and everything it owns.
251         @param msg Pointer to the osrfMessage to be freed.
252 */
253 void osrfMessageFree( osrfMessage* msg ) {
254         if( msg == NULL )
255                 return;
256
257         if( msg->status_name != NULL )
258                 free(msg->status_name);
259
260         if( msg->status_text != NULL )
261                 free(msg->status_text);
262
263         if( msg->_result_content != NULL )
264                 jsonObjectFree( msg->_result_content );
265
266         if( msg->result_string != NULL )
267                 free( msg->result_string);
268
269         if( msg->method_name != NULL )
270                 free(msg->method_name);
271
272         if( msg->sender_locale != NULL )
273                 free(msg->sender_locale);
274
275         if( msg->_params != NULL )
276                 jsonObjectFree(msg->_params);
277
278         free(msg);
279 }
280
281
282 /**
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.
287
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.
291
292         The calling code is responsible for freeing the returned string.
293 */
294 char* osrfMessageSerializeBatch( osrfMessage* msgs [], int count ) {
295         if( !msgs ) return NULL;
296
297         jsonObject* wrapper = jsonNewObjectType(JSON_ARRAY);
298
299         int i = 0;
300         while( (i < count) && msgs[i] ) {
301                 jsonObjectPush(wrapper, osrfMessageToJSON( msgs[i] ));
302                 ++i;
303         }
304
305         char* j = jsonObjectToJSON(wrapper);
306         jsonObjectFree(wrapper);
307
308         return j;
309 }
310
311
312 /**
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.
316
317         Translate the osrfMessage into JSON, wrapped in a JSON array.
318
319         This function is equivalent to osrfMessageSerializeBatch() for an array of one pointer.
320
321         The calling code is responsible for freeing the returned string.
322 */
323 char* osrf_message_serialize(const osrfMessage* msg) {
324
325         if( msg == NULL ) return NULL;
326         char* j = NULL;
327
328         jsonObject* json = osrfMessageToJSON( msg );
329
330         if(json) {
331                 jsonObject* wrapper = jsonNewObjectType(JSON_ARRAY);
332                 jsonObjectPush(wrapper, json);
333                 j = jsonObjectToJSON(wrapper);
334                 jsonObjectFree(wrapper);
335         }
336
337         return j;
338 }
339
340
341 /**
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.
345
346         The resulting jsonObject is a JSON_HASH with a classname of "osrfMessage", and the following keys:
347         - "threadTrace"
348         - "locale"
349         - "type"
350         - "payload" (only for STATUS, REQUEST, and RESULT messages)
351
352         The data for "payload" is also a JSON_HASH, whose structure depends on the message type:
353
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).
356
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
359         to the method call).
360
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).
364
365         The calling code is responsible for freeing the returned jsonObject.
366 */
367 static jsonObject* osrfMessageToJSON( const osrfMessage* msg ) {
368
369         jsonObject* json = jsonNewObjectType(JSON_HASH);
370         jsonObjectSetClass(json, "osrfMessage");
371         jsonObject* payload;
372         char sc[64];
373         osrf_clearbuf(sc, sizeof(sc));
374
375         INT_TO_STRING(msg->thread_trace);
376         jsonObjectSetKey(json, "threadTrace", jsonNewObject(INTSTR));
377
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));
382         } else {
383                 jsonObjectSetKey(json, "locale", jsonNewObject(default_locale));
384         }
385
386         switch(msg->m_type) {
387
388                 case CONNECT:
389                         jsonObjectSetKey(json, "type", jsonNewObject("CONNECT"));
390                         break;
391
392                 case DISCONNECT:
393                         jsonObjectSetKey(json, "type", jsonNewObject("DISCONNECT"));
394                         break;
395
396                 case STATUS:
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);
404                         break;
405
406                 case REQUEST:
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);
413
414                         break;
415
416                 case RESULT:
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);
425                         break;
426         }
427
428         return json;
429 }
430
431
432 /**
433         @brief Translate a JSON array into an array of osrfMessages.
434         @param string The JSON string to be translated.
435         @param msgs Pointer to an array of pointers to osrfMessage, to receive the results.
436         @param count How many slots are available in the @a msgs array.
437         @return The number of osrfMessages created.
438
439         The JSON string is expected to be a JSON array, with each element encoding an osrfMessage.
440
441         If there are too many messages in the JSON array to fit into the pointer array, we
442         silently ignore the excess.
443 */
444 int osrf_message_deserialize(const char* string, osrfMessage* msgs[], int count) {
445
446         if(!string || !msgs || count <= 0) return 0;
447         int numparsed = 0;
448
449         // Parse the JSON
450         jsonObject* json = jsonParseString(string);
451
452         if(!json) {
453                 osrfLogWarning( OSRF_LOG_MARK,
454                         "osrf_message_deserialize() unable to parse data: \n%s\n", string);
455                 return 0;
456         }
457
458         // Traverse the JSON_ARRAY, turning each element into an osrfMessage
459         int x;
460         for( x = 0; x < json->size && x < count; x++ ) {
461
462                 const jsonObject* message = jsonObjectGetIndex( json, x );
463
464                 if( message && message->type != JSON_NULL &&
465                         message->classname && !strcmp(message->classname, "osrfMessage" )) {
466                         msgs[numparsed++] = deserialize_one_message( message );
467                 }
468         }
469
470         jsonObjectFree( json );
471         return numparsed;
472 }
473
474
475 /**
476         @brief Translate a jsonObject into a single osrfMessage.
477         @param obj Pointer to the jsonObject to be translated.
478         @return Pointer to a newly created osrfMessage.
479
480         It is assumed that @a obj is non-NULL and points to a valid representation of a message.
481         For a description of the expected structure of this representations, see osrfMessageToJSON().
482
483         The calling code is responsible for freeing the osrfMessage by calling osrfMessageFree().
484 */
485 static osrfMessage* deserialize_one_message( const jsonObject* obj ) {
486
487         // Get the message type.  If it isn't present, default to CONNECT.
488         const jsonObject* tmp = jsonObjectGetKeyConst( obj, "type" );
489
490         enum M_TYPE type;
491         const char* t = jsonObjectGetString( tmp );
492         if( t ) {
493
494                 if(      !strcmp( t, "CONNECT"    ))   type = CONNECT;
495                 else if( !strcmp( t, "DISCONNECT" ))   type = DISCONNECT;
496                 else if( !strcmp( t, "STATUS"     ))   type = STATUS;
497                 else if( !strcmp( t, "REQUEST"    ))   type = REQUEST;
498                 else if( !strcmp( t, "RESULT"     ))   type = RESULT;
499         }
500
501         // Get the thread trace, defaulting to zero.
502         int trace = 0;
503         tmp = jsonObjectGetKeyConst( obj, "threadTrace" );
504         if( tmp ) {
505                 const char* tt = jsonObjectGetString( tmp );
506                 if( tt ) {
507                         trace = atoi( tt );
508                 }
509         }
510
511         // Get the protocol, defaulting to zero.
512         int protocol = 0;
513         tmp = jsonObjectGetKeyConst( obj, "protocol" );
514         if(tmp) {
515                 const char* proto = jsonObjectGetString(tmp);
516                 if( proto ) {
517                         protocol = atoi( proto );
518                 }
519         }
520
521         // Now that we have the essentials, create an osrfMessage
522         osrfMessage* msg = osrf_message_init( type, trace, protocol );
523
524         // Get the sender's locale, or leave it NULL if not specified.
525         if ( current_locale )
526                 free( current_locale );
527
528         tmp = jsonObjectGetKeyConst( obj, "locale" );
529         if(tmp && ( msg->sender_locale = jsonObjectToSimpleString(tmp))) {
530                 current_locale = strdup( msg->sender_locale );
531         } else {
532                 current_locale = NULL;
533         }
534
535         tmp = jsonObjectGetKeyConst( obj, "payload" );
536         if(tmp) {
537                 // Get method name and parameters for a REQUEST
538                 const jsonObject* tmp0 = jsonObjectGetKeyConst(tmp,"method");
539                 const char* tmp_str = jsonObjectGetString(tmp0);
540                 if(tmp_str)
541                         msg->method_name = strdup(tmp_str);
542
543                 tmp0 = jsonObjectGetKeyConst(tmp,"params");
544                 if(tmp0) {
545                         // Note that we use jsonObjectDecodeClass() instead of
546                         // jsonObjectClone().  The classnames are already decoded,
547                         // but jsonObjectDecodeClass removes the decoded classnames.
548                         msg->_params = jsonObjectDecodeClass( tmp0 );
549                         if(msg->_params && msg->_params->type == JSON_NULL)
550                                 msg->_params->type = JSON_ARRAY;
551                 }
552
553                 // Get status fields for a RESULT or STATUS
554                 if(tmp->classname)
555                         msg->status_name = strdup(tmp->classname);
556
557                 tmp0 = jsonObjectGetKeyConst(tmp,"status");
558                 tmp_str = jsonObjectGetString(tmp0);
559                 if(tmp_str)
560                         msg->status_text = strdup(tmp_str);
561
562                 tmp0 = jsonObjectGetKeyConst(tmp,"statusCode");
563                 if(tmp0) {
564                         tmp_str = jsonObjectGetString(tmp0);
565                         if(tmp_str)
566                                 msg->status_code = atoi(tmp_str);
567                         if(tmp0->type == JSON_NUMBER)
568                                 msg->status_code = (int) jsonObjectGetNumber(tmp0);
569                 }
570
571                 // Get the content for a RESULT
572                 tmp0 = jsonObjectGetKeyConst(tmp,"content");
573                 if(tmp0) {
574                         // Note that we use jsonObjectDecodeClass() instead of
575                         // jsonObjectClone().  The classnames are already decoded,
576                         // but jsonObjectDecodeClass removes the decoded classnames.
577                         msg->_result_content = jsonObjectDecodeClass( tmp0 );
578                 }
579
580         }
581
582         return msg;
583 }
584
585
586 /**
587         @brief Return a pointer to the result content of an osrfMessage.
588         @param msg Pointer to the osrfMessage whose result content is to be returned.
589         @return Pointer to the result content (or NULL if there is no such content, or if @a msg is
590         NULL).
591
592         The returned pointer points into the innards of the osrfMessage.  The calling code should
593         @em not call jsonObjectFree() on it, because the osrfMessage still owns it.
594 */
595 jsonObject* osrfMessageGetResult( osrfMessage* msg ) {
596         if(msg) return msg->_result_content;
597         return NULL;
598 }