]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/c-apps/oils_utils.c
LP1818288 WS Option to pre-fetch record holds
[working/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(osrfMethodContext* ctx, 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 static int rootOrgId = 0; // cache the ID of the root org unit.
168 int oilsUtilsGetRootOrgId() {
169
170     // return the cached value if we have it.
171     if (rootOrgId > 0) return rootOrgId;
172
173     jsonObject* where_clause = jsonParse("{\"parent_ou\":null}");
174     jsonObject* org = oilsUtilsQuickReq(
175         "open-ils.cstore",
176         "open-ils.cstore.direct.actor.org_unit.search",
177         where_clause
178     );
179
180     rootOrgId = (int) 
181         jsonObjectGetNumber(oilsFMGetObject(org, "id"));
182
183     jsonObjectFree(where_clause);
184     jsonObjectFree(org);
185
186     return rootOrgId;
187 }
188
189 oilsEvent* oilsUtilsCheckPerms( int userid, int orgid, char* permissions[], int size ) {
190     if (!permissions) return NULL;
191     int i;
192
193     // Check perms against the root org unit if no org unit is provided.
194     if (orgid == -1)
195         orgid = oilsUtilsGetRootOrgId();
196
197     for( i = 0; i < size && permissions[i]; i++ ) {
198         oilsEvent* evt = NULL;
199         char* perm = permissions[i];
200
201         jsonObject* params = jsonParseFmt(
202             "{\"from\":[\"permission.usr_has_perm\",\"%d\",\"%s\",\"%d\"]}",
203             userid, perm, orgid
204         );
205
206         // Execute the query
207         jsonObject* result = oilsUtilsCStoreReq(
208             "open-ils.cstore.json_query", params);
209
210         const jsonObject* hasPermStr = 
211             jsonObjectGetKeyConst(result, "permission.usr_has_perm");
212
213         if (!oilsUtilsIsDBTrue(jsonObjectGetString(hasPermStr))) {
214             evt = oilsNewEvent3(
215                 OSRF_LOG_MARK, OILS_EVENT_PERM_FAILURE, perm, orgid);
216         }
217
218         jsonObjectFree(params);
219         jsonObjectFree(result);
220
221         // return first failed permission check.
222         if (evt) return evt;
223     }
224
225     return NULL; // all perm checks succeeded
226 }
227
228 /**
229         @brief Perform a remote procedure call.
230         @param service The name of the service to invoke.
231         @param method The name of the method to call.
232         @param params The parameters to be passed to the method, if any.
233         @return A copy of whatever the method returns as a result, or a JSON_NULL if the method
234         doesn't return anything.
235
236         If the @a params parameter points to a JSON_ARRAY, pass each element of the array
237         as a separate parameter.  If it points to any other kind of jsonObject, pass it as a
238         single parameter.  If it is NULL, pass no parameters.
239
240         The calling code is responsible for freeing the returned object by calling jsonObjectFree().
241 */
242 jsonObject* oilsUtilsQuickReq( const char* service, const char* method,
243                 const jsonObject* params ) {
244         if(!(service && method)) return NULL;
245
246         osrfLogDebug(OSRF_LOG_MARK, "oilsUtilsQuickReq(): %s - %s", service, method );
247
248         // Open an application session with the service, and send the request
249         osrfAppSession* session = osrfAppSessionClientInit( service );
250         int reqid = osrfAppSessionSendRequest( session, params, method, 1 );
251
252         // Get the response
253         osrfMessage* omsg = osrfAppSessionRequestRecv( session, reqid, 60 );
254         jsonObject* result = jsonObjectClone( osrfMessageGetResult(omsg) );
255
256         // Clean up
257         osrfMessageFree(omsg);
258         osrfAppSessionFree(session);
259         return result;
260 }
261
262 /**
263         @brief Perform a remote procedure call, propagating session
264         locale and timezone
265         @param service The name of the service to invoke.
266         @param method The name of the method to call.
267         @param params The parameters to be passed to the method, if any.
268         @return A copy of whatever the method returns as a result, or a JSON_NULL if the method
269         doesn't return anything.
270
271         If the @a params parameter points to a JSON_ARRAY, pass each element of the array
272         as a separate parameter.  If it points to any other kind of jsonObject, pass it as a
273         single parameter.  If it is NULL, pass no parameters.
274
275         The calling code is responsible for freeing the returned object by calling jsonObjectFree().
276 */
277 jsonObject* oilsUtilsQuickReqCtx( osrfMethodContext* ctx, const char* service,
278                 const char* method, const jsonObject* params ) {
279         if(!(service && method && ctx)) return NULL;
280
281         osrfLogDebug(OSRF_LOG_MARK, "oilsUtilsQuickReqCtx(): %s - %s (%s)", service, method, ctx->session->session_tz );
282
283         // Open an application session with the service, and send the request
284         osrfAppSession* session = osrfAppSessionClientInit( service );
285         osrf_app_session_set_tz(session, ctx->session->session_tz);
286         int reqid = osrfAppSessionSendRequest( session, params, method, 1 );
287
288         // Get the response
289         osrfMessage* omsg = osrfAppSessionRequestRecv( session, reqid, 60 );
290         jsonObject* result = jsonObjectClone( osrfMessageGetResult(omsg) );
291
292         // Clean up
293         osrfMessageFree(omsg);
294         osrfAppSessionFree(session);
295         return result;
296 }
297
298 /**
299         @brief Call a method of the open-ils.storage service.
300         @param method Name of the method.
301         @param params Parameters to be passed to the method, if any.
302         @return A copy of whatever the method returns as a result, or a JSON_NULL if the method
303         doesn't return anything.
304
305         If the @a params parameter points to a JSON_ARRAY, pass each element of the array
306         as a separate parameter.  If it points to any other kind of jsonObject, pass it as a
307         single parameter.  If it is NULL, pass no parameters.
308
309         The calling code is responsible for freeing the returned object by calling jsonObjectFree().
310 */
311 jsonObject* oilsUtilsStorageReq( const char* method, const jsonObject* params ) {
312         return oilsUtilsQuickReq( "open-ils.storage", method, params );
313 }
314
315 /**
316         @brief Call a method of the open-ils.cstore service.
317         @param method Name of the method.
318         @param params Parameters to be passed to the method, if any.
319         @return A copy of whatever the method returns as a result, or a JSON_NULL if the method
320         doesn't return anything.
321
322         If the @a params parameter points to a JSON_ARRAY, pass each element of the array
323         as a separate parameter.  If it points to any other kind of jsonObject, pass it as a
324         single parameter.  If it is NULL, pass no parameters.
325
326         The calling code is responsible for freeing the returned object by calling jsonObjectFree().
327 */
328 jsonObject* oilsUtilsCStoreReq( const char* method, const jsonObject* params ) {
329         return oilsUtilsQuickReq("open-ils.cstore", method, params);
330 }
331
332 /**
333         @brief Call a method of the open-ils.cstore service, context aware.
334         @param ctx Method context object.
335         @param method Name of the method.
336         @param params Parameters to be passed to the method, if any.
337         @return A copy of whatever the method returns as a result, or a JSON_NULL if the method
338         doesn't return anything.
339
340         If the @a params parameter points to a JSON_ARRAY, pass each element of the array
341         as a separate parameter.  If it points to any other kind of jsonObject, pass it as a
342         single parameter.  If it is NULL, pass no parameters.
343
344         The calling code is responsible for freeing the returned object by calling jsonObjectFree().
345 */
346 jsonObject* oilsUtilsCStoreReqCtx( osrfMethodContext* ctx, const char* method, const jsonObject* params ) {
347         return oilsUtilsQuickReqCtx(ctx, "open-ils.cstore", method, params);
348 }
349
350
351
352 /**
353         @brief Given a username, fetch the corresponding row from the actor.usr table, if any.
354         @param name The username for which to search.
355         @return A Fieldmapper object for the relevant row in the actor.usr table, if it exists;
356         or a JSON_NULL if it doesn't.
357
358         The calling code is responsible for freeing the returned object by calling jsonObjectFree().
359 */
360 jsonObject* oilsUtilsFetchUserByUsername( osrfMethodContext* ctx, const char* name ) {
361         if(!name) return NULL;
362         jsonObject* params = jsonParseFmt("{\"usrname\":\"%s\"}", name);
363         jsonObject* user = oilsUtilsQuickReqCtx(
364                 ctx, "open-ils.cstore", "open-ils.cstore.direct.actor.user.search", params );
365
366         jsonObjectFree(params);
367         long id = oilsFMGetObjectId(user);
368         osrfLogDebug(OSRF_LOG_MARK, "Fetched user %s:%ld", name, id);
369         return user;
370 }
371
372 /**
373         @brief Given a barcode, fetch the corresponding row from the actor.usr table, if any.
374         @param name The barcode for which to search.
375         @return A Fieldmapper object for the relevant row in the actor.usr table, if it exists;
376         or a JSON_NULL if it doesn't.
377
378         Look up the barcode in actor.card.  Follow a foreign key from there to get a row in
379         actor.usr.
380
381         The calling code is responsible for freeing the returned object by calling jsonObjectFree().
382 */
383 jsonObject* oilsUtilsFetchUserByBarcode(osrfMethodContext* ctx, const char* barcode) {
384         if(!barcode) return NULL;
385
386         osrfLogInfo(OSRF_LOG_MARK, "Fetching user by barcode %s", barcode);
387
388         jsonObject* params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode);
389         jsonObject* card = oilsUtilsQuickReqCtx(
390                 ctx, "open-ils.cstore", "open-ils.cstore.direct.actor.card.search", params );
391         jsonObjectFree(params);
392
393         if(!card)
394                 return NULL;   // No such card
395
396         // Get the user's id as a long
397         char* usr = oilsFMGetString(card, "usr");
398         jsonObjectFree(card);
399         if(!usr)
400                 return NULL;   // No user id (shouldn't happen)
401         long iusr = strtol(usr, NULL, 10);
402         free(usr);
403
404         // Look up the user in actor.usr
405         params = jsonParseFmt("[%d]", iusr);
406         jsonObject* user = oilsUtilsQuickReqCtx(
407                 ctx, "open-ils.cstore", "open-ils.cstore.direct.actor.user.retrieve", params);
408
409         jsonObjectFree(params);
410         return user;
411 }
412
413 char* oilsUtilsFetchOrgSetting( int orgid, const char* setting ) {
414         if(!setting) return NULL;
415
416         jsonObject* params = jsonParseFmt("[%d, \"%s\"]", orgid, setting );
417
418         jsonObject* set = oilsUtilsQuickReq(
419                 "open-ils.actor",
420                 "open-ils.actor.ou_setting.ancestor_default", params);
421
422         char* value = jsonObjectToSimpleString( jsonObjectGetKeyConst( set, "value" ));
423         jsonObjectFree(params);
424         jsonObjectFree(set);
425         osrfLogDebug(OSRF_LOG_MARK, "Fetched org [%d] setting: %s => %s", orgid, setting, value);
426         return value;
427 }
428
429
430
431 char* oilsUtilsLogin( const char* uname, const char* passwd, const char* type, int orgId ) {
432         if(!(uname && passwd)) return NULL;
433
434         osrfLogDebug(OSRF_LOG_MARK, "Logging in with username %s", uname );
435         char* token = NULL;
436
437         jsonObject* params = jsonParseFmt("[\"%s\"]", uname);
438
439         jsonObject* o = oilsUtilsQuickReq(
440                 "open-ils.auth", "open-ils.auth.authenticate.init", params );
441
442         const char* seed = jsonObjectGetString(o);
443         char* passhash = md5sum(passwd);
444         char buf[256];
445         snprintf(buf, sizeof(buf), "%s%s", seed, passhash);
446         char* fullhash = md5sum(buf);
447
448         jsonObjectFree(o);
449         jsonObjectFree(params);
450         free(passhash);
451
452         params = jsonParseFmt( "[\"%s\", \"%s\", \"%s\", \"%d\"]", uname, fullhash, type, orgId );
453         o = oilsUtilsQuickReq( "open-ils.auth",
454                 "open-ils.auth.authenticate.complete", params );
455
456         if(o) {
457                 const char* tok = jsonObjectGetString(
458                         jsonObjectGetKeyConst( jsonObjectGetKey( o,"payload" ), "authtoken" ));
459                 if( tok )
460                         token = strdup( tok );
461         }
462
463         free(fullhash);
464         jsonObjectFree(params);
465         jsonObjectFree(o);
466
467         return token;
468 }
469
470
471 jsonObject* oilsUtilsFetchWorkstation( long id ) {
472         jsonObject* p = jsonParseFmt("[%ld]", id);
473         jsonObject* r = oilsUtilsQuickReq(
474                 "open-ils.storage",
475                 "open-ils.storage.direct.actor.workstation.retrieve", p );
476         jsonObjectFree(p);
477         return r;
478 }
479
480 jsonObject* oilsUtilsFetchWorkstationByName( const char* name ) {
481         jsonObject* p = jsonParseFmt("{\"name\":\"%s\"}", name);
482         jsonObject* r = oilsUtilsCStoreReq(
483                 "open-ils.cstore.direct.actor.workstation.search", p);
484         jsonObjectFree(p);
485         return r;
486 }
487
488 /**
489         @brief Convert a string to a number representing a time interval in seconds.
490         @param interval Pointer to string, e.g. "420" or "2 weeks".
491         @return If successful, the number of seconds that the string represents; otherwise -1.
492
493         If the string is all digits (apart from any leading or trailing white space), convert
494         it directly.  Otherwise pass it to PostgreSQL for translation.
495
496         The result is the same as if we were to pass every string to PostgreSQL, except that,
497         depending on the value of LONG_MAX, we return values for some strings that represent
498         intervals too long for PostgreSQL to represent (i.e. more than 2147483647 seconds).
499
500         WARNING: a valid interval of -1 second will be indistinguishable from an error.  If
501         such an interval is a plausible possibility, don't use this function.
502 */
503 long oilsUtilsIntervalToSeconds( const char* s ) {
504
505         if( !s ) {
506                 osrfLogWarning( OSRF_LOG_MARK, "String to be converted is NULL" );
507                 return -1;
508         }
509
510         // Skip leading white space
511         while( isspace( (unsigned char) *s ))
512                 ++s;
513
514         if( '\0' == *s ) {
515                 osrfLogWarning( OSRF_LOG_MARK, "String to be converted is empty or all white space" );
516                 return -1;
517         }
518
519         // See if the string is a raw number, i.e. all digits
520         // (apart from any leading or trailing white space)
521
522         const char* p = s;   // For traversing and examining the remaining string
523         if( isdigit( (unsigned char) *p )) {
524                 // Looks like a number so far...skip over the digits
525                 do {
526                         ++p;
527                 } while( isdigit( (unsigned char) *p ));
528                 // Skip over any following white space
529                 while( isspace( (unsigned char) *p ))
530                         ++p;
531                 if( '\0' == *p ) {
532                         // This string is a raw number.  Convert it directly.
533                         long n = strtol( s, NULL, 10 );
534                         if( LONG_MAX == n ) {
535                                 // numeric overflow
536                                 osrfLogWarning( OSRF_LOG_MARK,
537                                         "String \"%s\"represents a number too big for a long", s );
538                                 return -1;
539                         } else
540                                 return n;
541                 }
542         }
543
544         // If we get to this point, the string is not all digits.  Pass it to PostgreSQL.
545
546         // Build the query
547         jsonObject* query_obj = jsonParseFmt(
548                 "{\"from\":[\"config.interval_to_seconds\",\"%s\"]}", s );
549
550         // Execute the query
551         jsonObject* result = oilsUtilsCStoreReq(
552                 "open-ils.cstore.json_query", query_obj );
553         jsonObjectFree( query_obj );
554
555         // Get the results
556         const jsonObject* seconds_obj = jsonObjectGetKeyConst( result, "config.interval_to_seconds" );
557         long seconds = -1;
558         if( seconds_obj && JSON_NUMBER == seconds_obj->type )
559                 seconds = (long) jsonObjectGetNumber( seconds_obj );
560         else
561                 osrfLogError( OSRF_LOG_MARK,
562                         "Error calling json_query to convert \"%s\" to seconds", s );
563
564         jsonObjectFree( result );
565         return seconds;
566 }