]> git.evergreen-ils.org Git - OpenSRF.git/blob - src/libopensrf/osrf_message.c
4b9567caa11c1e4b849af0e87238bdda9691db7e
[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
10 static char default_locale[17] = "en-US\0\0\0\0\0\0\0\0\0\0\0\0";
11 static char* current_locale = NULL;
12
13 /**
14         @brief Allocate and initialize an osrfMessage.
15         @param type One of CONNECT, REQUEST, RESULT, STATUS, or DISCONNECT.
16         @param thread_trace Thread trace.
17         @param protocol Protocol.
18         @return Pointer to the newly allocated osrfMessage.
19
20         The calling code is responsible for freeing the osrfMessage by calling osrfMessageFree().
21 */
22 osrfMessage* osrf_message_init( enum M_TYPE type, int thread_trace, int protocol ) {
23
24         osrfMessage* msg            = (osrfMessage*) safe_malloc(sizeof(osrfMessage));
25         msg->m_type                 = type;
26         msg->thread_trace           = thread_trace;
27         msg->protocol               = protocol;
28         msg->status_name            = NULL;
29         msg->status_text            = NULL;
30         msg->status_code            = 0;
31         msg->next                   = NULL;
32         msg->is_exception           = 0;
33         msg->_params                = NULL;
34         msg->_result_content        = NULL;
35         msg->result_string          = NULL;
36         msg->method_name            = NULL;
37         msg->sender_locale          = NULL;
38         msg->sender_tz_offset       = 0;
39
40         return msg;
41 }
42
43
44 /**
45         @brief Return the previous locale.
46         @return A pointer to the locale specified by the last message to have been deserialized,
47                 or NULL.
48
49         A JSON message may specify a locale string, which is saved as the current locale.  If
50         the message does not specify a locale string, then the current locale becomes NULL.
51
52         This function returns a pointer to an internal buffer.  Since both the address and the
53         contents of this buffer may change from one call to the next, the calling code should
54         either use the result immediately or make a copy of the string for later use.
55 */
56 const char* osrf_message_get_last_locale() {
57         return current_locale;
58 }
59
60 /**
61         @brief Set the locale for a specified osrfMessage.
62         @param msg Pointer to the osrfMessage.
63         @param locale Pointer to the locale string to be installed in the osrfMessage.
64         @return Pointer to the new locale string for the osrfMessage, or NULL if either
65                 parameter is NULL.
66
67         If no locale is specified for an osrfMessage, we use the default locale.
68
69         Used for a REQUEST message.
70 */
71 const char* osrf_message_set_locale( osrfMessage* msg, const char* locale ) {
72         if( msg == NULL || locale == NULL )
73                 return NULL;
74         if( msg->sender_locale )
75                 free( msg->sender_locale );
76         return msg->sender_locale = strdup( locale );
77 }
78
79 /**
80         @brief Change the default locale.
81         @param locale The new default locale.
82         @return A pointer to the new default locale if successful, or NULL if not.
83
84         The function returns NULL if the parameter is NULL, or if the proposed new locale is
85         longer than 16 characters.
86
87         At this writing, nothing calls this function.
88 */
89 const char* osrf_message_set_default_locale( const char* locale ) {
90         if( locale == NULL ) return NULL;
91         if( strlen(locale) > sizeof(default_locale) - 1 ) return NULL;
92
93         strcpy( default_locale, locale );
94         return default_locale;
95 }
96
97 /**
98         @brief Populate the method_name member of an osrfMessage.
99         @param msg Pointer to the osrfMessage.
100         @param method_name The method name.
101
102         Used for a REQUEST message to specify what method to invoke.
103 */
104 void osrf_message_set_method( osrfMessage* msg, const char* method_name ) {
105         if( msg == NULL || method_name == NULL )
106                 return;
107         if( msg->method_name )
108                 free( msg->method_name );
109         msg->method_name = strdup( method_name );
110 }
111
112
113 /**
114         @brief Add a copy of a jsonObject to an osrfMessage as a parameter for a method call.
115         @param msg Pointer to the osrfMessage.
116         @param o Pointer to the jsonObject of which a copy is to be stored.
117
118         Make a copy of the input jsonObject, with all classnames encoded with JSON_CLASS_KEY and
119         JSON_DATA_KEY.  Append it to a JSON_ARRAY stored at msg->_params.
120
121         If there is nothing at msg->_params, create a new JSON_ARRAY for it and add the new object
122         as the first element.
123
124         If the jsonObject is raw (i.e. the class information has not been decoded into classnames),
125         decode it.  If the class information has already been decoded, discard it.
126
127         See also osrf_message_add_param().
128
129         At this writing, nothing calls this function.
130 */
131 void osrf_message_add_object_param( osrfMessage* msg, const jsonObject* o ) {
132         if(!msg|| !o) return;
133         if(!msg->_params)
134                 msg->_params = jsonNewObjectType( JSON_ARRAY );
135         jsonObjectPush(msg->_params, jsonObjectDecodeClass( o ));
136 }
137
138 /**
139         @brief Populate the params member of an osrfMessage.
140         @param msg Pointer to the osrfMessage.
141         @param o Pointer to a jsonObject representing the parameter(s) to a method.
142
143         Make a copy of a jsonObject and install it as the parameter list for a method call.
144
145         If the osrfMessage already has any parameters, discard them.
146
147         The @a o parameter should point to a jsonObject of type JSON_ARRAY, with each element
148         of the array being a parameter.  If @a o points to any other type of jsonObject, create
149         a JSON_ARRAY as a wrapper for it, and install a copy of @a o as its only element.
150
151         Used for a REQUEST message, to pass parameters to a method.  The alternative is to call
152         osrf_message_add_param() or osrf_message_add_object_param() repeatedly as needed to add
153         one parameter at a time.
154 */
155 void osrf_message_set_params( osrfMessage* msg, const jsonObject* o ) {
156         if(!msg || !o) return;
157
158         if(msg->_params)
159                 jsonObjectFree(msg->_params);
160
161         if(o->type == JSON_ARRAY) {
162                 msg->_params = jsonObjectClone(o);
163         } else {
164                 osrfLogDebug( OSRF_LOG_MARK, "passing non-array to osrf_message_set_params(), fixing...");
165                 jsonObject* clone = jsonObjectClone(o);
166                 msg->_params = jsonNewObjectType( JSON_ARRAY );
167                 jsonObjectPush(msg->_params, clone);
168                 return;
169         }
170 }
171
172
173 /**
174         @brief Add a JSON string to an osrfMessage as a parameter for a method call.
175         @param msg Pointer to the osrfMessage.
176         @param param_string A JSON string encoding the parameter to be added.
177
178         Translate the JSON string into a jsonObject, and append it to the parameter list.
179         If the parameter list doesn't already exist, create an empty one and add the new
180         parameter to it.
181
182         Decode any class information in the raw JSON into classnames.
183
184         If the JSON string is not valid JSON, append a new jsonObject of type JSON_NULL.
185
186         Used for a REQUEST message, to pass a parameter to a method,  The alternative is to
187         call osrf_message_set_params() to provide all the parameters at once.  See also
188         osrf_message_add_object_param().
189 */
190 void osrf_message_add_param( osrfMessage* msg, const char* param_string ) {
191         if(msg == NULL || param_string == NULL) return;
192         if(!msg->_params) msg->_params = jsonNewObjectType( JSON_ARRAY );
193         jsonObjectPush(msg->_params, jsonParseString(param_string));
194 }
195
196
197 /**
198         @brief Set the status_name, status_text, and status_code members of an osrfMessage.
199         @param msg Pointer to the osrfMessage to be populated.
200         @param status_name Status name (may be NULL).
201         @param status_text Status text (may be NULL).
202         @param status_code Status code.
203
204         If the @a status_name or @a status_text parameter is NULL, the corresponding member
205         is left unchanged.
206
207         Used for a RESULT or STATUS message.
208 */
209 void osrf_message_set_status_info( osrfMessage* msg,
210                 const char* status_name, const char* status_text, int status_code ) {
211         if(!msg) return;
212
213         if( status_name != NULL ) {
214                 if( msg->status_name )
215                         free( msg->status_name );
216                 msg->status_name = strdup( status_name );
217         }
218
219         if( status_text != NULL ) {
220                 if( msg->status_text )
221                         free( msg->status_text );
222                 msg->status_text = strdup( status_text );
223         }
224
225         msg->status_code = status_code;
226 }
227
228
229 /**
230         @brief Populate the result_string and _result_content members of an osrfMessage.
231         @param msg Pointer to the osrfMessage to be populated.
232         @param json_string A JSON string encoding a result set.
233
234         Used for a RESULT message to return the results of a request, such as a database lookup.
235 */
236 void osrf_message_set_result_content( osrfMessage* msg, const char* json_string ) {
237         if( msg == NULL || json_string == NULL) return;
238         if( msg->result_string )
239                 free( msg->result_string );
240         if( msg->_result_content )
241                 jsonObjectFree( msg->_result_content );
242
243         msg->result_string   = strdup(json_string);
244         msg->_result_content = jsonParseString(json_string);
245 }
246
247
248 /**
249         @brief Free an osrfMessage and everything it owns.
250         @param msg Pointer to the osrfMessage to be freed.
251 */
252 void osrfMessageFree( osrfMessage* msg ) {
253         if( msg == NULL )
254                 return;
255
256         if( msg->status_name != NULL )
257                 free(msg->status_name);
258
259         if( msg->status_text != NULL )
260                 free(msg->status_text);
261
262         if( msg->_result_content != NULL )
263                 jsonObjectFree( msg->_result_content );
264
265         if( msg->result_string != NULL )
266                 free( msg->result_string);
267
268         if( msg->method_name != NULL )
269                 free(msg->method_name);
270
271         if( msg->sender_locale != NULL )
272                 free(msg->sender_locale);
273
274         if( msg->_params != NULL )
275                 jsonObjectFree(msg->_params);
276
277         free(msg);
278 }
279
280
281 /**
282         @brief Turn a collection of osrfMessages into one big JSON string.
283         @param msgs Pointer to an array of osrfMessages.
284         @param count Maximum number of messages to serialize.
285         @return Pointer to the JSON string.
286
287         Traverse the array, adding each osrfMessage in turn to a JSON_ARRAY.  Stop when you have added the
288         maximum number of messages, or when you encounter a NULL pointer in the array.  Then translate the
289         JSON_ARRAY into a JSON string.
290
291         The calling code is responsible for freeing the returned string.
292 */
293 char* osrfMessageSerializeBatch( osrfMessage* msgs [], int count ) {
294         if( !msgs ) return NULL;
295
296         jsonObject* wrapper = jsonNewObjectType(JSON_ARRAY);
297
298         int i = 0;
299         while( (i < count) && msgs[i] ) {
300                 jsonObjectPush(wrapper, osrfMessageToJSON( msgs[i] ));
301                 ++i;
302         }
303
304         char* j = jsonObjectToJSON(wrapper);
305         jsonObjectFree(wrapper);
306
307         return j;
308 }
309
310
311 /**
312         @brief Turn a single osrfMessage into a JSON string.
313         @param msg Pointer to the osrfMessage to be serialized.
314         @return Pointer to the resulting JSON string.
315
316         Translate the osrfMessage into JSON, wrapped in a JSON array.
317
318         This function is equivalent to osrfMessageSerializeBatch() for an array of one pointer.
319
320         The calling code is responsible for freeing the returned string.
321 */
322 char* osrf_message_serialize(const osrfMessage* msg) {
323
324         if( msg == NULL ) return NULL;
325         char* j = NULL;
326
327         jsonObject* json = osrfMessageToJSON( msg );
328
329         if(json) {
330                 jsonObject* wrapper = jsonNewObjectType(JSON_ARRAY);
331                 jsonObjectPush(wrapper, json);
332                 j = jsonObjectToJSON(wrapper);
333                 jsonObjectFree(wrapper);
334         }
335
336         return j;
337 }
338
339
340 /**
341         @brief Translate an osrfMessage into a jsonObject.
342         @param msg Pointer to the osrfMessage to be translated.
343         @return Pointer to a newly created jsonObject.
344
345         The resulting jsonObject is a JSON_HASH with a classname of "osrfMessage", and the following keys:
346         - "threadTrace"
347         - "locale"
348         - "type"
349         - "payload" (only for STATUS, REQUEST, and RESULT messages)
350
351         The data for "payload" is also a JSON_HASH, whose structure depends on the message type:
352
353         For a STATUS message, the payload's classname is msg->status_name.  The keys are "status" (carrying
354         msg->status_text) and "statusCode" (carrying the status code as a string).
355
356         For a REQUEST message, the payload's classname is "osrfMethod".  The keys are "method" (carrying
357         msg->method_name) and "params" (carrying a jsonObject to pass any parameters to the method call).
358
359         For a RESULT message, the payload's classname is "osrfResult".  The keys are "status" (carrying
360         msg->status_text), "statusCode" (carrying the status code as a string), and "content" (carrying a jsonObject 
361         to return any results from the method call).
362
363         The calling code is responsible for freeing the returned jsonObject.
364 */
365 static jsonObject* osrfMessageToJSON( const osrfMessage* msg ) {
366
367         jsonObject* json = jsonNewObjectType(JSON_HASH);
368         jsonObjectSetClass(json, "osrfMessage");
369         jsonObject* payload;
370         char sc[64];
371         osrf_clearbuf(sc, sizeof(sc));
372
373         INT_TO_STRING(msg->thread_trace);
374         jsonObjectSetKey(json, "threadTrace", jsonNewObject(INTSTR));
375
376         if (msg->sender_locale != NULL) {
377                 jsonObjectSetKey(json, "locale", jsonNewObject(msg->sender_locale));
378         } else if (current_locale != NULL) {
379                 jsonObjectSetKey(json, "locale", jsonNewObject(current_locale));
380         } else {
381                 jsonObjectSetKey(json, "locale", jsonNewObject(default_locale));
382         }
383
384         switch(msg->m_type) {
385
386                 case CONNECT:
387                         jsonObjectSetKey(json, "type", jsonNewObject("CONNECT"));
388                         break;
389
390                 case DISCONNECT:
391                         jsonObjectSetKey(json, "type", jsonNewObject("DISCONNECT"));
392                         break;
393
394                 case STATUS:
395                         jsonObjectSetKey(json, "type", jsonNewObject("STATUS"));
396                         payload = jsonNewObject(NULL);
397                         jsonObjectSetClass(payload, msg->status_name);
398                         jsonObjectSetKey(payload, "status", jsonNewObject(msg->status_text));
399                         snprintf(sc, sizeof(sc), "%d", msg->status_code);
400                         jsonObjectSetKey(payload, "statusCode", jsonNewObject(sc));
401                         jsonObjectSetKey(json, "payload", payload);
402                         break;
403
404                 case REQUEST:
405                         jsonObjectSetKey(json, "type", jsonNewObject("REQUEST"));
406                         payload = jsonNewObject(NULL);
407                         jsonObjectSetClass(payload, "osrfMethod");
408                         jsonObjectSetKey(payload, "method", jsonNewObject(msg->method_name));
409                         jsonObjectSetKey( payload, "params", jsonObjectDecodeClass( msg->_params ) );
410                         jsonObjectSetKey(json, "payload", payload);
411
412                         break;
413
414                 case RESULT:
415                         jsonObjectSetKey(json, "type", jsonNewObject("RESULT"));
416                         payload = jsonNewObject(NULL);
417                         jsonObjectSetClass(payload,"osrfResult");
418                         jsonObjectSetKey(payload, "status", jsonNewObject(msg->status_text));
419                         snprintf(sc, sizeof(sc), "%d", msg->status_code);
420                         jsonObjectSetKey(payload, "statusCode", jsonNewObject(sc));
421                         jsonObjectSetKey(payload, "content", jsonObjectDecodeClass( msg->_result_content ));
422                         jsonObjectSetKey(json, "payload", payload);
423                         break;
424         }
425
426         return json;
427 }
428
429
430 /**
431         @brief Translate a JSON array into an array of osrfMessages.
432         @param string The JSON string to be translated.
433         @param msgs Pointer to an array of pointers to osrfMessage, to receive the results.
434         @param count How many slots are available in the @a msgs array.
435         @return The number of osrfMessages created.
436
437         The JSON string is expected to be a JSON array, with each element encoding an osrfMessage.
438
439         If there are too many messages in the JSON array to fit into the pointer array, we
440         silently ignore the excess.
441 */
442 int osrf_message_deserialize(const char* string, osrfMessage* msgs[], int count) {
443
444         if(!string || !msgs || count <= 0) return 0;
445         int numparsed = 0;
446
447         // Parse the JSON
448         jsonObject* json = jsonParseString(string);
449
450         if(!json) {
451                 osrfLogWarning( OSRF_LOG_MARK,
452                         "osrf_message_deserialize() unable to parse data: \n%s\n", string);
453                 return 0;
454         }
455
456         // Traverse the JSON_ARRAY, turning each element into an osrfMessage
457         int x;
458         for( x = 0; x < json->size && x < count; x++ ) {
459
460                 const jsonObject* message = jsonObjectGetIndex(json, x);
461
462                 if(message && message->type != JSON_NULL &&
463                         message->classname && !strcmp(message->classname, "osrfMessage")) {
464
465                         osrfMessage* new_msg = safe_malloc(sizeof(osrfMessage));
466                         new_msg->m_type = 0;
467                         new_msg->thread_trace = 0;
468                         new_msg->protocol = 0;
469                         new_msg->status_name = NULL;
470                         new_msg->status_text = NULL;
471                         new_msg->status_code = 0;
472                         new_msg->is_exception = 0;
473                         new_msg->_result_content = NULL;
474                         new_msg->result_string = NULL;
475                         new_msg->method_name = NULL;
476                         new_msg->_params = NULL;
477                         new_msg->next = NULL;
478                         new_msg->sender_locale = NULL;
479                         new_msg->sender_tz_offset = 0;
480
481                         // Get the message type.  If not specified, default to CONNECT.
482                         const jsonObject* tmp = jsonObjectGetKeyConst(message, "type");
483
484                         const char* t;
485                         if( ( t = jsonObjectGetString(tmp)) ) {
486
487                                 if(!strcmp(t, "CONNECT"))           new_msg->m_type = CONNECT;
488                                 else if(!strcmp(t, "DISCONNECT"))   new_msg->m_type = DISCONNECT;
489                                 else if(!strcmp(t, "STATUS"))       new_msg->m_type = STATUS;
490                                 else if(!strcmp(t, "REQUEST"))      new_msg->m_type = REQUEST;
491                                 else if(!strcmp(t, "RESULT"))       new_msg->m_type = RESULT;
492                         }
493
494                         // Get the thread trace, defaulting to zero.
495                         tmp = jsonObjectGetKeyConst(message, "threadTrace");
496                         if(tmp) {
497                                 const char* tt = jsonObjectGetString(tmp);
498                                 if(tt) {
499                                         new_msg->thread_trace = atoi(tt);
500                                 }
501                         }
502
503                         // Get the sender's locale, or leave it NULL if not specified.
504                         if (current_locale)
505                                 free( current_locale );
506
507                         tmp = jsonObjectGetKeyConst(message, "locale");
508
509                         if(tmp && (new_msg->sender_locale = jsonObjectToSimpleString(tmp))) {
510                                 current_locale = strdup( new_msg->sender_locale );
511                         } else {
512                                 current_locale = NULL;
513                         }
514
515                         // Get the protocol, defaulting to zero.
516                         tmp = jsonObjectGetKeyConst(message, "protocol");
517
518                         if(tmp) {
519                                 const char* proto = jsonObjectGetString(tmp);
520                                 if(proto) {
521                                         new_msg->protocol = atoi(proto);
522                                 }
523                         }
524
525                         tmp = jsonObjectGetKeyConst(message, "payload");
526                         if(tmp) {
527                                 // Get method name and parameters for a REQUEST
528                                 const jsonObject* tmp0 = jsonObjectGetKeyConst(tmp,"method");
529                                 const char* tmp_str = jsonObjectGetString(tmp0);
530                                 if(tmp_str)
531                                         new_msg->method_name = strdup(tmp_str);
532
533                                 tmp0 = jsonObjectGetKeyConst(tmp,"params");
534                                 if(tmp0) {
535                                         // Note that we use jsonObjectDecodeClass() instead of
536                                         // jsonObjectClone().  The classnames are already decoded,
537                                         // but jsonObjectDecodeClass removes the decoded classnames.
538                                         new_msg->_params = jsonObjectDecodeClass( tmp0 );
539                                         if(new_msg->_params && new_msg->_params->type == JSON_NULL)
540                                                 new_msg->_params->type = JSON_ARRAY;
541                                 }
542
543                                 // Get status fields for a RESULT or STATUS
544                                 if(tmp->classname)
545                                         new_msg->status_name = strdup(tmp->classname);
546
547                                 tmp0 = jsonObjectGetKeyConst(tmp,"status");
548                                 tmp_str = jsonObjectGetString(tmp0);
549                                 if(tmp_str)
550                                         new_msg->status_text = strdup(tmp_str);
551
552                                 tmp0 = jsonObjectGetKeyConst(tmp,"statusCode");
553                                 if(tmp0) {
554                                         tmp_str = jsonObjectGetString(tmp0);
555                                         if(tmp_str)
556                                                 new_msg->status_code = atoi(tmp_str);
557                                         if(tmp0->type == JSON_NUMBER)
558                                                 new_msg->status_code = (int) jsonObjectGetNumber(tmp0);
559                                 }
560
561                                 // Get the content for a RESULT
562                                 tmp0 = jsonObjectGetKeyConst(tmp,"content");
563                                 if(tmp0) {
564                                         // Note that we use jsonObjectDecodeClass() instead of
565                                         // jsonObjectClone().  The classnames are already decoded,
566                                         // but jsonObjectDecodeClass removes the decoded classnames.
567                                         new_msg->_result_content = jsonObjectDecodeClass( tmp0 );
568                                 }
569
570                         }
571                         msgs[numparsed++] = new_msg;
572                 }
573         }
574
575         jsonObjectFree(json);
576         return numparsed;
577 }
578
579
580
581 /**
582         @brief Return a pointer to the result content of an osrfMessage.
583         @param msg Pointer to the osrfMessage whose result content is to be returned.
584         @return Pointer to the result content (or NULL if there is no such content, or if @a msg is NULL).
585
586         The returned pointer points into the innards of the osrfMessage.  The calling code should @em not call
587         jsonObjectFree() on it, because the osrfMessage still owns it.
588 */
589 jsonObject* osrfMessageGetResult( osrfMessage* msg ) {
590         if(msg) return msg->_result_content;
591         return NULL;
592 }
593