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