New function oilsUtilsIntervalToSeconds() to translate a string into a
[working/Evergreen.git] / Open-ILS / src / c-apps / oils_utils.c
1 #include <ctype.h>
2 #include "openils/oils_utils.h"
3 #include "openils/oils_idl.h"
4
5 osrfHash* oilsInitIDL(const char* idl_filename) {
6
7         char* freeable_filename = NULL;
8         const char* filename;
9
10         if(idl_filename)
11                 filename = idl_filename;
12         else {
13                 freeable_filename = osrf_settings_host_value("/IDL");
14                 filename = freeable_filename;
15         }
16
17         if (!filename) {
18                 osrfLogError(OSRF_LOG_MARK, "No settings config for '/IDL'");
19                 return NULL;
20         }
21
22         osrfLogInfo(OSRF_LOG_MARK, "Parsing IDL %s", filename);
23
24         if (!oilsIDLInit( filename )) {
25                 osrfLogError(OSRF_LOG_MARK, "Problem loading IDL file [%s]!", filename);
26                 if(freeable_filename)
27                         free(freeable_filename);
28                 return NULL;
29         }
30
31         if(freeable_filename)
32                 free(freeable_filename);
33         return oilsIDL();
34 }
35
36 /**
37         @brief Return a const string with the value of a specified column in a row object.
38         @param object Pointer to the row object.
39         @param field Name of the column.
40         @return Pointer to a const string representing the value of the specified column,
41                 or NULL in case of error.
42
43         The row object must be a JSON_ARRAY with a classname.  The column value must be a
44         JSON_STRING or a JSON_NUMBER.  Any other object type results in a return of NULL.
45
46         The return value points into the internal contents of the row object, which
47         retains ownership.
48 */
49 const char* oilsFMGetStringConst( const jsonObject* object, const char* field ) {
50         return jsonObjectGetString(oilsFMGetObject( object, field ));
51 }
52
53 /**
54         @brief Return a string with the value of a specified column in a row object.
55         @param object Pointer to the row object.
56         @param field Name of the column.
57         @return Pointer to a newly allocated string representing the value of the specified column,
58                 or NULL in case of error.
59
60         The row object must be a JSON_ARRAY with a classname.  The column value must be a
61         JSON_STRING or a JSON_NUMBER.  Any other object type results in a return of NULL.
62
63         The calling code is responsible for freeing the returned string by calling free().
64  */
65 char* oilsFMGetString( const jsonObject* object, const char* field ) {
66         return jsonObjectToSimpleString(oilsFMGetObject( object, field ));
67 }
68
69 /**
70         @brief Return a pointer to the value of a specified column in a row object.
71         @param object Pointer to the row object.
72         @param field Name of the column.
73         @return Pointer to the jsonObject representing the value of the specified column, or NULL
74                 in case of error.
75
76         The row object must be a JSON_ARRAY with a classname.
77
78         The return value may point to a JSON_NULL, JSON_STRING, JSON_NUMBER, or JSON_ARRAY.  It
79         points into the internal contents of the row object, which retains ownership.
80 */
81 const jsonObject* oilsFMGetObject( const jsonObject* object, const char* field ) {
82         if(!(object && field)) return NULL;
83         if( object->type != JSON_ARRAY || !object->classname ) return NULL;
84         int pos = fm_ntop(object->classname, field);
85         if( pos > -1 )
86                 return jsonObjectGetIndex( object, pos );
87         return NULL;
88 }
89
90
91 int oilsFMSetString( jsonObject* object, const char* field, const char* string ) {
92         if(!(object && field && string)) return -1;
93         osrfLogInternal(OSRF_LOG_MARK, "oilsFMSetString(): Collecing position for field %s", field);
94         int pos = fm_ntop(object->classname, field);
95         if( pos > -1 ) {
96                 osrfLogInternal(OSRF_LOG_MARK, "oilsFMSetString(): Setting string "
97                                 "%s at field %s [position %d]", string, field, pos );
98                 jsonObjectSetIndex( object, pos, jsonNewObject(string) );
99                 return 0;
100         }
101         return -1;
102 }
103
104
105 int oilsUtilsIsDBTrue( const char* val ) {
106         if( val && strcasecmp(val, "f") && strcmp(val, "0") ) return 1;
107         return 0;
108 }
109
110
111 long oilsFMGetObjectId( const jsonObject* obj ) {
112         long id = -1;
113         if(!obj) return id;
114         char* ids = oilsFMGetString( obj, "id" );
115         if(ids) {
116                 id = atol(ids);
117                 free(ids);
118         }
119         return id;
120 }
121
122
123 oilsEvent* oilsUtilsCheckPerms( int userid, int orgid, char* permissions[], int size ) {
124         if (!permissions) return NULL;
125         int i;
126         oilsEvent* evt = NULL;
127
128         // Find the root org unit, i.e. the one with no parent.
129         // Assumption: there is only one org unit with no parent.
130         if (orgid == -1) {
131                 jsonObject* where_clause = jsonParse( "{\"parent_ou\":null}" );
132                 jsonObject* org = oilsUtilsQuickReq(
133                         "open-ils.cstore",
134                         "open-ils.cstore.direct.actor.org_unit.search",
135                         where_clause
136                 );
137                 jsonObjectFree( where_clause );
138
139                 orgid = (int)jsonObjectGetNumber( oilsFMGetObject( org, "id" ) );
140
141                 jsonObjectFree(org);
142         }
143
144         for( i = 0; i < size && permissions[i]; i++ ) {
145
146                 char* perm = permissions[i];
147                 jsonObject* params = jsonParseFmt("[%d, \"%s\", %d]", userid, perm, orgid);
148                 jsonObject* o = oilsUtilsQuickReq( "open-ils.storage",
149                         "open-ils.storage.permission.user_has_perm", params );
150
151                 char* r = jsonObjectToSimpleString(o);
152
153                 if(r && !strcmp(r, "0"))
154                         evt = oilsNewEvent3( OSRF_LOG_MARK, OILS_EVENT_PERM_FAILURE, perm, orgid );
155
156                 jsonObjectFree(params);
157                 jsonObjectFree(o);
158                 free(r);
159
160                 if(evt)
161                         break;
162         }
163
164         return evt;
165 }
166
167 /**
168         @brief Perform a remote procedure call.
169         @param service The name of the service to invoke.
170         @param method The name of the method to call.
171         @param params The parameters to be passed to the method, if any.
172         @return A copy of whatever the method returns as a result, or a JSON_NULL if the method
173         doesn't return anything.
174
175         If the @a params parameter points to a JSON_ARRAY, pass each element of the array
176         as a separate parameter.  If it points to any other kind of jsonObject, pass it as a
177         single parameter.  If it is NULL, pass no parameters.
178
179         The calling code is responsible for freeing the returned object by calling jsonObjectFree().
180 */
181 jsonObject* oilsUtilsQuickReq( const char* service, const char* method,
182                 const jsonObject* params ) {
183         if(!(service && method)) return NULL;
184
185         osrfLogDebug(OSRF_LOG_MARK, "oilsUtilsQuickReq(): %s - %s", service, method );
186
187         // Open an application session with the service, and send the request
188         osrfAppSession* session = osrfAppSessionClientInit( service );
189         int reqid = osrfAppSessionSendRequest( session, params, method, 1 );
190
191         // Get the response
192         osrfMessage* omsg = osrfAppSessionRequestRecv( session, reqid, 60 );
193         jsonObject* result = jsonObjectClone( osrfMessageGetResult(omsg) );
194
195         // Clean up
196         osrfMessageFree(omsg);
197         osrfAppSessionFree(session);
198         return result;
199 }
200
201 /**
202         @brief Call a method of the open-ils.storage service.
203         @param method Name of the method.
204         @param params Parameters to be passed to the method, if any.
205         @return A copy of whatever the method returns as a result, or a JSON_NULL if the method
206         doesn't return anything.
207
208         If the @a params parameter points to a JSON_ARRAY, pass each element of the array
209         as a separate parameter.  If it points to any other kind of jsonObject, pass it as a
210         single parameter.  If it is NULL, pass no parameters.
211
212         The calling code is responsible for freeing the returned object by calling jsonObjectFree().
213 */
214 jsonObject* oilsUtilsStorageReq( const char* method, const jsonObject* params ) {
215         return oilsUtilsQuickReq( "open-ils.storage", method, params );
216 }
217
218 /**
219         @brief Call a method of the open-ils.cstore service.
220         @param method Name of the method.
221         @param params Parameters to be passed to the method, if any.
222         @return A copy of whatever the method returns as a result, or a JSON_NULL if the method
223         doesn't return anything.
224
225         If the @a params parameter points to a JSON_ARRAY, pass each element of the array
226         as a separate parameter.  If it points to any other kind of jsonObject, pass it as a
227         single parameter.  If it is NULL, pass no parameters.
228
229         The calling code is responsible for freeing the returned object by calling jsonObjectFree().
230 */
231 jsonObject* oilsUtilsCStoreReq( const char* method, const jsonObject* params ) {
232         return oilsUtilsQuickReq("open-ils.cstore", method, params);
233 }
234
235
236
237 /**
238         @brief Given a username, fetch the corresponding row from the actor.usr table, if any.
239         @param name The username for which to search.
240         @return A Fieldmapper object for the relevant row in the actor.usr table, if it exists;
241         or a JSON_NULL if it doesn't.
242
243         The calling code is responsible for freeing the returned object by calling jsonObjectFree().
244 */
245 jsonObject* oilsUtilsFetchUserByUsername( const char* name ) {
246         if(!name) return NULL;
247         jsonObject* params = jsonParseFmt("{\"usrname\":\"%s\"}", name);
248         jsonObject* user = oilsUtilsQuickReq(
249                 "open-ils.cstore", "open-ils.cstore.direct.actor.user.search", params );
250
251         jsonObjectFree(params);
252         long id = oilsFMGetObjectId(user);
253         osrfLogDebug(OSRF_LOG_MARK, "Fetched user %s:%ld", name, id);
254         return user;
255 }
256
257 /**
258         @brief Given a barcode, fetch the corresponding row from the actor.usr table, if any.
259         @param name The barcode for which to search.
260         @return A Fieldmapper object for the relevant row in the actor.usr table, if it exists;
261         or a JSON_NULL if it doesn't.
262
263         Look up the barcode in actor.card.  Follow a foreign key from there to get a row in
264         actor.usr.
265
266         The calling code is responsible for freeing the returned object by calling jsonObjectFree().
267 */
268 jsonObject* oilsUtilsFetchUserByBarcode(const char* barcode) {
269         if(!barcode) return NULL;
270
271         osrfLogInfo(OSRF_LOG_MARK, "Fetching user by barcode %s", barcode);
272
273         jsonObject* params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode);
274         jsonObject* card = oilsUtilsQuickReq(
275                 "open-ils.cstore", "open-ils.cstore.direct.actor.card.search", params );
276         jsonObjectFree(params);
277
278         if(!card)
279                 return NULL;   // No such card
280
281         // Get the user's id as a double
282         char* usr = oilsFMGetString(card, "usr");
283         jsonObjectFree(card);
284         if(!usr)
285                 return NULL;   // No user id (shouldn't happen)
286         double iusr = strtod(usr, NULL);
287         free(usr);
288
289         // Look up the user in actor.usr
290         params = jsonParseFmt("[%f]", iusr);
291         jsonObject* user = oilsUtilsQuickReq(
292                 "open-ils.cstore", "open-ils.cstore.direct.actor.user.retrieve", params);
293
294         jsonObjectFree(params);
295         return user;
296 }
297
298 char* oilsUtilsFetchOrgSetting( int orgid, const char* setting ) {
299         if(!setting) return NULL;
300
301         jsonObject* params = jsonParseFmt("[%d, \"%s\"]", orgid, setting );
302
303         jsonObject* set = oilsUtilsQuickReq(
304                 "open-ils.actor",
305                 "open-ils.actor.ou_setting.ancestor_default", params);
306
307         char* value = jsonObjectToSimpleString(jsonObjectGetKey(set, "value"));
308         jsonObjectFree(params);
309         jsonObjectFree(set);
310         osrfLogDebug(OSRF_LOG_MARK, "Fetched org [%d] setting: %s => %s", orgid, setting, value);
311         return value;
312 }
313
314
315
316 char* oilsUtilsLogin( const char* uname, const char* passwd, const char* type, int orgId ) {
317         if(!(uname && passwd)) return NULL;
318
319         osrfLogDebug(OSRF_LOG_MARK, "Logging in with username %s", uname );
320         char* token = NULL;
321
322         jsonObject* params = jsonParseFmt("[\"%s\"]", uname);
323
324         jsonObject* o = oilsUtilsQuickReq(
325                 "open-ils.auth", "open-ils.auth.authenticate.init", params );
326
327         const char* seed = jsonObjectGetString(o);
328         char* passhash = md5sum(passwd);
329         char buf[256];
330         snprintf(buf, sizeof(buf), "%s%s", seed, passhash);
331         char* fullhash = md5sum(buf);
332
333         jsonObjectFree(o);
334         jsonObjectFree(params);
335         free(passhash);
336
337         params = jsonParseFmt( "[\"%s\", \"%s\", \"%s\", \"%d\"]", uname, fullhash, type, orgId );
338         o = oilsUtilsQuickReq( "open-ils.auth",
339                 "open-ils.auth.authenticate.complete", params );
340
341         if(o) {
342                 const char* tok = jsonObjectGetString(
343                         jsonObjectGetKey(jsonObjectGetKey(o,"payload"), "authtoken"));
344                 if( tok )
345                         token = strdup( tok );
346         }
347
348         free(fullhash);
349         jsonObjectFree(params);
350         jsonObjectFree(o);
351
352         return token;
353 }
354
355
356 jsonObject* oilsUtilsFetchWorkstation( long id ) {
357         jsonObject* p = jsonParseFmt("[%ld]", id);
358         jsonObject* r = oilsUtilsQuickReq(
359                 "open-ils.storage",
360                 "open-ils.storage.direct.actor.workstation.retrieve", p );
361         jsonObjectFree(p);
362         return r;
363 }
364
365 jsonObject* oilsUtilsFetchWorkstationByName( const char* name ) {
366         jsonObject* p = jsonParseFmt("{\"name\":\"%s\"}", name);
367         jsonObject* r = oilsUtilsCStoreReq(
368                 "open-ils.cstore.direct.actor.workstation.search", p);
369         jsonObjectFree(p);
370         return r;
371 }
372
373 /**
374         @brief Convert a string to a number representing a time interval in seconds.
375         @param interval Pointer to string, e.g. "420" or "2 weeks".
376         @return If successful, the number of seconds that the string represents; otherwise -1.
377
378         If the string is all digits (apart from any leading or trailing white space), convert
379         it directly.  Otherwise pass it to PostgreSQL for translation.
380
381         The result is the same as if we were to pass every string to PostgreSQL, except that,
382         depending on the value of LONG_MAX, we return values for some strings that represent
383         intervals too long for PostgreSQL to represent (i.e. more than 2147483647 seconds).
384
385         WARNING: a valid interval of -1 second will be indistinguishable from an error.  If
386         such an interval is a plausible possibility, don't use this function.
387 */
388 long oilsUtilsIntervalToSeconds( const char* s ) {
389
390         if( !s ) {
391                 osrfLogWarning( OSRF_LOG_MARK, "String to be converted is NULL" );
392                 return -1;
393         }
394
395         // Skip leading white space
396         while( isspace( (unsigned char) *s ))
397                 ++s;
398
399         if( '\0' == *s ) {
400                 osrfLogWarning( OSRF_LOG_MARK, "String to be converted is empty or all white space" );
401                 return -1;
402         }
403
404         // See if the string is a raw number, i.e. all digits
405         // (apart from any leading or trailing white space)
406
407         const char* p = s;   // For traversing and examining the remaining string
408         if( isdigit( (unsigned char) *p )) {
409                 // Looks like a number so far...skip over the digits
410                 do {
411                         ++p;
412                 } while( isdigit( (unsigned char) *p ));
413                 // Skip over any following white space
414                 while( isspace( (unsigned char) *p ))
415                         ++p;
416                 if( '\0' == *p ) {
417                         // This string is a raw number.  Convert it directly.
418                         long n = strtol( s, NULL, 10 );
419                         if( LONG_MAX == n ) {
420                                 // numeric overflow
421                                 osrfLogWarning( OSRF_LOG_MARK,
422                                         "String \"%s\"represents a number too big for a long", s );
423                                 return -1;
424                         } else
425                                 return n;
426                 }
427         }
428
429         // If we get to this point, the string is not all digits.  Pass it to PostgreSQL.
430
431         // Build the query
432         jsonObject* query_obj = jsonParseFmt(
433                 "{\"from\":[\"config.interval_to_seconds\",\"%s\"]}", s );
434
435         // Execute the query
436         jsonObject* result = oilsUtilsCStoreReq(
437                 "open-ils.cstore.json_query", query_obj );
438         jsonObjectFree( query_obj );
439
440         // Get the results
441         jsonObject* seconds_obj = jsonObjectGetKey( result, "config.interval_to_seconds" );
442         long seconds = -1;
443         if( seconds_obj && JSON_NUMBER == seconds_obj->type )
444                 seconds = (long) jsonObjectGetNumber( seconds_obj );
445         else
446                 osrfLogError( OSRF_LOG_MARK,
447                         "Error calling json_query to convert \"%s\" to seconds", s );
448
449         jsonObjectFree( result );
450         return seconds;
451 }