LP#1836254: Handle null authtoken in PCRUD
[Evergreen.git] / Open-ILS / src / c-apps / oils_sql.c
1 /**
2         @file oils_sql.c
3         @brief Utility routines for translating JSON into SQL.
4 */
5
6 #include <stdlib.h>
7 #include <string.h>
8 #include <time.h>
9 #include <ctype.h>
10 #include <dbi/dbi.h>
11 #include "opensrf/utils.h"
12 #include "opensrf/log.h"
13 #include "opensrf/osrf_application.h"
14 #include "openils/oils_utils.h"
15 #include "openils/oils_sql.h"
16
17 // The next four macros are OR'd together as needed to form a set
18 // of bitflags.  SUBCOMBO enables an extra pair of parentheses when
19 // nesting one UNION, INTERSECT or EXCEPT inside another.
20 // SUBSELECT tells us we're in a subquery, so don't add the
21 // terminal semicolon yet.
22 #define SUBCOMBO    8
23 #define SUBSELECT   4
24 #define DISABLE_I18N    2
25 #define SELECT_DISTINCT 1
26
27 #define AND_OP_JOIN     0
28 #define OR_OP_JOIN      1
29
30 struct ClassInfoStruct;
31 typedef struct ClassInfoStruct ClassInfo;
32
33 #define ALIAS_STORE_SIZE 16
34 #define CLASS_NAME_STORE_SIZE 16
35
36 struct ClassInfoStruct {
37         char* alias;
38         char* class_name;
39         char* source_def;
40         osrfHash* class_def;      // Points into IDL
41         osrfHash* fields;         // Points into IDL
42         osrfHash* links;          // Points into IDL
43
44         // The remaining members are private and internal.  Client code should not
45         // access them directly.
46
47         ClassInfo* next;          // Supports linked list of joined classes
48         int in_use;               // boolean
49
50         // We usually store the alias and class name in the following arrays, and
51         // point the corresponding pointers at them.  When the string is too big
52         // for the array (which will probably never happen in practice), we strdup it.
53
54         char alias_store[ ALIAS_STORE_SIZE + 1 ];
55         char class_name_store[ CLASS_NAME_STORE_SIZE + 1 ];
56 };
57
58 struct QueryFrameStruct;
59 typedef struct QueryFrameStruct QueryFrame;
60
61 struct QueryFrameStruct {
62         ClassInfo core;
63         ClassInfo* join_list;  // linked list of classes joined to the core class
64         QueryFrame* next;      // implements stack as linked list
65         int in_use;            // boolean
66 };
67
68 static int timeout_needs_resetting;
69 static time_t time_next_reset;
70
71 static int verifyObjectClass ( osrfMethodContext*, const jsonObject* );
72
73 static void setXactId( osrfMethodContext* ctx );
74 static inline const char* getXactId( osrfMethodContext* ctx );
75 static inline void clearXactId( osrfMethodContext* ctx );
76
77 static jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* class_meta,
78                 jsonObject* where_hash, jsonObject* query_hash, int* err );
79 static jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* );
80 static jsonObject* oilsMakeJSONFromResult( dbi_result );
81
82 static char* searchSimplePredicate ( const char* op, const char* class_alias,
83                                 osrfHash* field, const jsonObject* node );
84 static char* searchFunctionPredicate ( const char*, osrfHash*, const jsonObject*, const char* );
85 static char* searchFieldTransform ( const char*, osrfHash*, const jsonObject* );
86 static char* searchFieldTransformPredicate ( const ClassInfo*, osrfHash*, const jsonObject*,
87                 const char* );
88 static char* searchBETWEENRange( osrfHash* field, const jsonObject* node );
89 static char* searchBETWEENPredicate ( const char*, osrfHash*, const jsonObject* );
90 static char* searchINList( osrfHash* field, jsonObject* node, const char* op, osrfMethodContext* ctx );
91 static char* searchINPredicate ( const char*, osrfHash*,
92                                                                  jsonObject*, const char*, osrfMethodContext* );
93 static char* searchPredicate ( const ClassInfo*, osrfHash*, jsonObject*, osrfMethodContext* );
94 static char* searchJOIN ( const jsonObject*, const ClassInfo* left_info );
95 static char* searchWHERE ( const jsonObject* search_hash, const ClassInfo*, int, osrfMethodContext* );
96 static char* buildSELECT( const jsonObject*, jsonObject* rest_of_query,
97         osrfHash* meta, osrfMethodContext* ctx );
98 static char* buildOrderByFromArray( osrfMethodContext* ctx, const jsonObject* order_array );
99
100 char* buildQuery( osrfMethodContext* ctx, jsonObject* query, int flags );
101
102 char* SELECT ( osrfMethodContext*, jsonObject*, const jsonObject*, const jsonObject*,
103         const jsonObject*, const jsonObject*, const jsonObject*, const jsonObject*, int );
104
105 static osrfStringArray* getPermLocationCache( osrfMethodContext*, const char* );
106 static void setPermLocationCache( osrfMethodContext*, const char*, osrfStringArray* );
107
108 void userDataFree( void* );
109 static void sessionDataFree( char*, void* );
110 static void pcacheFree( char*, void* );
111 static int obj_is_true( const jsonObject* obj );
112 static const char* json_type( int code );
113 static const char* get_primitive( osrfHash* field );
114 static const char* get_datatype( osrfHash* field );
115 static void pop_query_frame( void );
116 static void push_query_frame( void );
117 static int add_query_core( const char* alias, const char* class_name );
118 static inline ClassInfo* search_alias( const char* target );
119 static ClassInfo* search_all_alias( const char* target );
120 static ClassInfo* add_joined_class( const char* alias, const char* classname );
121 static void clear_query_stack( void );
122
123 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
124 static const jsonObject* verifyUserPCRUDfull( osrfMethodContext*, int );
125 static int verifyObjectPCRUD( osrfMethodContext*, osrfHash*, const jsonObject*, int );
126 static const char* org_tree_root( osrfMethodContext* ctx );
127 static jsonObject* single_hash( const char* key, const char* value );
128
129 static int child_initialized = 0;   /* boolean */
130
131 static dbi_conn writehandle; /* our MASTER db connection */
132 static dbi_conn dbhandle; /* our CURRENT db connection */
133 //static osrfHash * readHandles;
134
135 // The following points to the top of a stack of QueryFrames.  It's a little
136 // confusing because the top level of the query is at the bottom of the stack.
137 static QueryFrame* curr_query = NULL;
138
139 static dbi_conn writehandle; /* our MASTER db connection */
140 static dbi_conn dbhandle; /* our CURRENT db connection */
141 //static osrfHash * readHandles;
142
143 static int max_flesh_depth = 100;
144
145 static int perm_at_threshold = 5;
146 static int enforce_pcrud = 0;     // Boolean
147 static char* modulename = NULL;
148
149 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id);
150
151 static char* _sanitize_tz_name( const char* tz );
152 static char* _sanitize_savepoint_name( const char* sp );
153
154 /**
155         @brief Connect to the database.
156         @return A database connection if successful, or NULL if not.
157 */
158 dbi_conn oilsConnectDB( const char* mod_name ) {
159
160         osrfLogDebug( OSRF_LOG_MARK, "Attempting to initialize libdbi..." );
161         if( dbi_initialize( NULL ) == -1 ) {
162                 osrfLogError( OSRF_LOG_MARK, "Unable to initialize libdbi" );
163                 return NULL;
164         } else
165                 osrfLogDebug( OSRF_LOG_MARK, "... libdbi initialized." );
166
167         char* driver = osrf_settings_host_value( "/apps/%s/app_settings/driver", mod_name );
168         char* user   = osrf_settings_host_value( "/apps/%s/app_settings/database/user", mod_name );
169         char* host   = osrf_settings_host_value( "/apps/%s/app_settings/database/host", mod_name );
170         char* port   = osrf_settings_host_value( "/apps/%s/app_settings/database/port", mod_name );
171         char* db     = osrf_settings_host_value( "/apps/%s/app_settings/database/db", mod_name );
172         char* pw     = osrf_settings_host_value( "/apps/%s/app_settings/database/pw", mod_name );
173         char* pg_app = osrf_settings_host_value( "/apps/%s/app_settings/database/application_name", mod_name );
174
175         osrfLogDebug( OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver );
176         dbi_conn handle = dbi_conn_new( driver );
177
178         if( !handle ) {
179                 osrfLogError( OSRF_LOG_MARK, "Error loading database driver [%s]", driver );
180                 return NULL;
181         }
182         osrfLogDebug( OSRF_LOG_MARK, "Database driver [%s] seems OK", driver );
183
184         osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database.  host=%s, "
185                 "port=%s, user=%s, db=%s", mod_name, host, port, user, db );
186
187         if( host )   dbi_conn_set_option( handle, "host", host );
188         if( port )   dbi_conn_set_option_numeric( handle, "port", atoi( port ));
189         if( user )   dbi_conn_set_option( handle, "username", user );
190         if( pw )     dbi_conn_set_option( handle, "password", pw );
191         if( db )     dbi_conn_set_option( handle, "dbname", db );
192         if( pg_app ) dbi_conn_set_option( handle, "pgsql_application_name", pg_app );
193
194         free( user );
195         free( host );
196         free( port );
197         free( db );
198         free( pw );
199         free( pg_app );
200
201         if( dbi_conn_connect( handle ) < 0 ) {
202                 sleep( 1 );
203                 if( dbi_conn_connect( handle ) < 0 ) {
204                         const char* msg;
205                         dbi_conn_error( handle, &msg );
206                         osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s",
207                                 msg ? msg : "(No description available)" );
208                         return NULL;
209                 }
210         }
211
212         osrfLogInfo( OSRF_LOG_MARK, "%s successfully connected to the database", mod_name );
213
214         return handle;
215 }
216
217 /**
218         @brief Select some options.
219         @param module_name: Name of the server.
220         @param do_pcrud: Boolean.  True if we are to enforce PCRUD permissions.
221
222         This source file is used (at this writing) to implement three different servers:
223         - open-ils.reporter-store
224         - open-ils.pcrud
225         - open-ils.cstore
226
227         These servers behave mostly the same, but they implement different combinations of
228         methods, and open-ils.pcrud enforces a permissions scheme that the other two don't.
229
230         Here we use the server name in messages to identify which kind of server issued them.
231         We use do_crud as a boolean to control whether or not to enforce the permissions scheme.
232 */
233 void oilsSetSQLOptions( const char* module_name, int do_pcrud, int flesh_depth ) {
234         if( !module_name )
235                 module_name = "open-ils.cstore";   // bulletproofing with a default
236
237         if( modulename )
238                 free( modulename );
239
240         modulename = strdup( module_name );
241         enforce_pcrud = do_pcrud;
242         max_flesh_depth = flesh_depth;
243 }
244
245 /**
246         @brief Install a database connection.
247         @param conn Pointer to a database connection.
248
249         In some contexts, @a conn may merely provide a driver so that we can process strings
250         properly, without providing an open database connection.
251 */
252 void oilsSetDBConnection( dbi_conn conn ) {
253         dbhandle = writehandle = conn;
254 }
255
256 /**
257         @brief Determine whether a database connection is alive.
258         @param handle Handle for a database connection.
259         @return 1 if the connection is alive, or zero if it isn't.
260 */
261 int oilsIsDBConnected( dbi_conn handle ) {
262         // Do an innocuous SELECT.  If it succeeds, the database connection is still good.
263         dbi_result result = dbi_conn_query( handle, "SELECT 1;" );
264         if( result ) {
265                 dbi_result_free( result );
266                 return 1;
267         } else {
268                 // This is a terrible, horrible, no good, very bad kludge.
269                 // Sometimes the SELECT 1 query fails, not because the database connection is dead,
270                 // but because (due to a previous error) the database is ignoring all commands,
271                 // even innocuous SELECTs, until the current transaction is rolled back.  The only
272                 // known way to detect this condition via the dbi library is by looking at the error
273                 // message.  This approach will break if the language or wording of the message ever
274                 // changes.
275                 // Note: the dbi_conn_ping function purports to determine whether the database
276                 // connection is live, but at this writing this function is unreliable and useless.
277                 static const char* ok_msg = "ERROR:  current transaction is aborted, commands "
278                         "ignored until end of transaction block\n";
279                 const char* msg;
280                 dbi_conn_error( handle, &msg );
281                 // Newer versions of dbi_conn_error return codes within the error msg.
282                 // E.g. 3624914: ERROR:  current transaction is aborted, commands ignored until end of transaction block
283                 // Substring test should work regardless.
284                 const char* substr = strstr(msg, ok_msg);
285                 if( substr == NULL ) {
286                         osrfLogError( OSRF_LOG_MARK, "Database connection isn't working : %s", msg );
287                         return 0;
288                 } else
289                         return 1;   // ignoring SELECT due to previous error; that's okay
290         }
291 }
292
293 /**
294         @brief Get a table name, view name, or subquery for use in a FROM clause.
295         @param class Pointer to the IDL class entry.
296         @return A table name, a view name, or a subquery in parentheses.
297
298         In some cases the IDL defines a class, not with a table name or a view name, but with
299         a SELECT statement, which may be used as a subquery.
300 */
301 char* oilsGetRelation( osrfHash* classdef ) {
302
303         char* source_def = NULL;
304         const char* tabledef = osrfHashGet( classdef, "tablename" );
305
306         if( tabledef ) {
307                 source_def = strdup( tabledef );   // Return the name of a table or view
308         } else {
309                 tabledef = osrfHashGet( classdef, "source_definition" );
310                 if( tabledef ) {
311                         // Return a subquery, enclosed in parentheses
312                         source_def = safe_malloc( strlen( tabledef ) + 3 );
313                         source_def[ 0 ] = '(';
314                         strcpy( source_def + 1, tabledef );
315                         strcat( source_def, ")" );
316                 } else {
317                         // Not found: return an error
318                         const char* classname = osrfHashGet( classdef, "classname" );
319                         if( !classname )
320                                 classname = "???";
321                         osrfLogError(
322                                 OSRF_LOG_MARK,
323                                 "%s ERROR No tablename or source_definition for class \"%s\"",
324                                 modulename,
325                                 classname
326                         );
327                 }
328         }
329
330         return source_def;
331 }
332
333 /**
334         @brief Add datatypes from the database to the fields in the IDL.
335         @param handle Handle for a database connection
336         @return Zero if successful, or 1 upon error.
337
338         For each relevant class in the IDL: ask the database for the datatype of every field.
339         In particular, determine which fields are text fields and which fields are numeric
340         fields, so that we know whether to enclose their values in quotes.
341 */
342 int oilsExtendIDL( dbi_conn handle ) {
343         osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
344         osrfHash* class = NULL;
345         growing_buffer* query_buf = buffer_init( 64 );
346         int results_found = 0;   // boolean
347
348         // For each class in the IDL...
349         while( (class = osrfHashIteratorNext( class_itr ) ) ) {
350                 const char* classname = osrfHashIteratorKey( class_itr );
351                 osrfHash* fields = osrfHashGet( class, "fields" );
352
353                 // If the class is virtual, ignore it
354                 if( str_is_true( osrfHashGet(class, "virtual") ) ) {
355                         osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
356                         continue;
357                 }
358
359                 char* tabledef = oilsGetRelation( class );
360                 if( !tabledef )
361                         continue;   // No such relation -- a query of it would be doomed to failure
362
363                 buffer_reset( query_buf );
364                 buffer_fadd( query_buf, "SELECT * FROM %s AS x WHERE 1=0;", tabledef );
365
366                 free(tabledef );
367
368                 osrfLogDebug( OSRF_LOG_MARK, "%s Investigatory SQL = %s",
369                                 modulename, OSRF_BUFFER_C_STR( query_buf ) );
370
371                 dbi_result result = dbi_conn_query( handle, OSRF_BUFFER_C_STR( query_buf ) );
372                 if( result ) {
373
374                         results_found = 1;
375                         int columnIndex = 1;
376                         const char* columnName;
377                         while( (columnName = dbi_result_get_field_name(result, columnIndex)) ) {
378
379                                 osrfLogInternal( OSRF_LOG_MARK, "Looking for column named [%s]...",
380                                                 columnName );
381
382                                 /* fetch the fieldmapper index */
383                                 osrfHash* _f = osrfHashGet(fields, columnName);
384                                 if( _f ) {
385
386                                         osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", columnName);
387
388                                         /* determine the field type and storage attributes */
389
390                                         switch( dbi_result_get_field_type_idx( result, columnIndex )) {
391
392                                                 case DBI_TYPE_INTEGER : {
393
394                                                         if( !osrfHashGet(_f, "primitive") )
395                                                                 osrfHashSet(_f, "number", "primitive");
396
397                                                         int attr = dbi_result_get_field_attribs_idx( result, columnIndex );
398                                                         if( attr & DBI_INTEGER_SIZE8 )
399                                                                 osrfHashSet( _f, "INT8", "datatype" );
400                                                         else
401                                                                 osrfHashSet( _f, "INT", "datatype" );
402                                                         break;
403                                                 }
404                                                 case DBI_TYPE_DECIMAL :
405                                                         if( !osrfHashGet( _f, "primitive" ))
406                                                                 osrfHashSet( _f, "number", "primitive" );
407
408                                                         osrfHashSet( _f, "NUMERIC", "datatype" );
409                                                         break;
410
411                                                 case DBI_TYPE_STRING :
412                                                         if( !osrfHashGet( _f, "primitive" ))
413                                                                 osrfHashSet( _f, "string", "primitive" );
414
415                                                         osrfHashSet( _f,"TEXT", "datatype" );
416                                                         break;
417
418                                                 case DBI_TYPE_DATETIME :
419                                                         if( !osrfHashGet( _f, "primitive" ))
420                                                                 osrfHashSet( _f, "string", "primitive" );
421
422                                                         osrfHashSet( _f, "TIMESTAMP", "datatype" );
423                                                         break;
424
425                                                 case DBI_TYPE_BINARY :
426                                                         if( !osrfHashGet( _f, "primitive" ))
427                                                                 osrfHashSet( _f, "string", "primitive" );
428
429                                                         osrfHashSet( _f, "BYTEA", "datatype" );
430                                         }
431
432                                         osrfLogDebug(
433                                                 OSRF_LOG_MARK,
434                                                 "Setting [%s] to primitive [%s] and datatype [%s]...",
435                                                 columnName,
436                                                 osrfHashGet( _f, "primitive" ),
437                                                 osrfHashGet( _f, "datatype" )
438                                         );
439                                 }
440                                 ++columnIndex;
441                         } // end while loop for traversing columns of result
442                         dbi_result_free( result  );
443                 } else {
444                         const char* msg;
445                         int errnum = dbi_conn_error( handle, &msg );
446                         osrfLogDebug( OSRF_LOG_MARK, "No data found for class [%s]: %d, %s", classname,
447                                 errnum, msg ? msg : "(No description available)" );
448                         // We don't check the database connection here.  It's routine to get failures at
449                         // this point; we routinely try to query tables that don't exist, because they
450                         // are defined in the IDL but not in the database.
451                 }
452         } // end for each class in IDL
453
454         buffer_free( query_buf );
455         osrfHashIteratorFree( class_itr );
456         child_initialized = 1;
457
458         if( !results_found ) {
459                 osrfLogError( OSRF_LOG_MARK,
460                         "No results found for any class -- bad database connection?" );
461                 return 1;
462         } else if( ! oilsIsDBConnected( handle )) {
463                 osrfLogError( OSRF_LOG_MARK,
464                         "Unable to extend IDL: database connection isn't working" );
465                 return 1;
466         }
467         else
468                 return 0;
469 }
470
471 /**
472         @brief Free an osrfHash that stores a transaction ID.
473         @param blob A pointer to the osrfHash to be freed, cast to a void pointer.
474
475         This function is a callback, to be called by the application session when it ends.
476         The application session stores the osrfHash via an opaque pointer.
477
478         If the osrfHash contains an entry for the key "xact_id", it means that an
479         uncommitted transaction is pending.  Roll it back.
480 */
481 void userDataFree( void* blob ) {
482         osrfHash* hash = (osrfHash*) blob;
483         if( osrfHashGet( hash, "xact_id" ) && writehandle ) {
484                 if( !dbi_conn_query( writehandle, "ROLLBACK;" )) {
485                         const char* msg;
486                         int errnum = dbi_conn_error( writehandle, &msg );
487                         osrfLogWarning( OSRF_LOG_MARK, "Unable to perform rollback: %d %s",
488                                 errnum, msg ? msg : "(No description available)" );
489                 };
490         }
491         if( writehandle ) {
492                 if( !dbi_conn_query( writehandle, "SELECT auditor.clear_audit_info();" ) ) {
493                         const char* msg;
494                         int errnum = dbi_conn_error( writehandle, &msg );
495                         osrfLogWarning( OSRF_LOG_MARK, "Unable to perform audit info clearing: %d %s",
496                                 errnum, msg ? msg : "(No description available)" );
497                 }
498         }
499
500         osrfHashFree( hash );
501 }
502
503 /**
504         @name Managing session data
505         @brief Maintain data stored via the userData pointer of the application session.
506
507         Currently, session-level data is stored in an osrfHash.  Other arrangements are
508         possible, and some would be more efficient.  The application session calls a
509         callback function to free userData before terminating.
510
511         Currently, the only data we store at the session level is the transaction id.  By this
512         means we can ensure that any pending transactions are rolled back before the application
513         session terminates.
514 */
515 /*@{*/
516
517 /**
518         @brief Free an item in the application session's userData.
519         @param key The name of a key for an osrfHash.
520         @param item An opaque pointer to the item associated with the key.
521
522         We store an osrfHash as userData with the application session, and arrange (by
523         installing userDataFree() as a different callback) for the session to free that
524         osrfHash before terminating.
525
526         This function is a callback for freeing items in the osrfHash.  Currently we store
527         two things:
528         - Transaction id of a pending transaction; a character string.  Key: "xact_id".
529         - Authkey; a character string.  Key: "authkey".
530         - User object from the authentication server; a jsonObject.  Key: "user_login".
531
532         If we ever store anything else in userData, we will need to revisit this function so
533         that it will free whatever else needs freeing.
534 */
535 static void sessionDataFree( char* key, void* item ) {
536         if( !strcmp( key, "xact_id" ) || !strcmp( key, "authkey" ) || !strncmp( key, "rs_size_", 8) ) 
537                 free( item );
538         else if( !strcmp( key, "user_login" ) )
539                 jsonObjectFree( (jsonObject*) item );
540         else if( !strcmp( key, "pcache" ) )
541                 osrfHashFree( (osrfHash*) item );
542 }
543
544 static void pcacheFree( char* key, void* item ) {
545         osrfStringArrayFree( (osrfStringArray*) item );
546 }
547
548 /**
549         @brief Initialize session cache.
550         @param ctx Pointer to the method context.
551
552         Create a cache for the session by making the session's userData member point
553         to an osrfHash instance.
554 */
555 static osrfHash* initSessionCache( osrfMethodContext* ctx ) {
556         ctx->session->userData = osrfNewHash();
557         osrfHashSetCallback( (osrfHash*) ctx->session->userData, &sessionDataFree );
558         ctx->session->userDataFree = &userDataFree;
559         return ctx->session->userData;
560 }
561
562 /**
563         @brief Save a transaction id.
564         @param ctx Pointer to the method context.
565
566         Save the session_id of the current application session as a transaction id.
567 */
568 static void setXactId( osrfMethodContext* ctx ) {
569         if( ctx && ctx->session ) {
570                 osrfAppSession* session = ctx->session;
571
572                 osrfHash* cache = session->userData;
573
574                 // If the session doesn't already have a hash, create one.  Make sure
575                 // that the application session frees the hash when it terminates.
576                 if( NULL == cache )
577                         cache = initSessionCache( ctx );
578
579                 // Save the transaction id in the hash, with the key "xact_id"
580                 osrfHashSet( cache, strdup( session->session_id ), "xact_id" );
581         }
582 }
583
584 /**
585         @brief Get the transaction ID for the current transaction, if any.
586         @param ctx Pointer to the method context.
587         @return Pointer to the transaction ID.
588
589         The return value points to an internal buffer, and will become invalid upon issuing
590         a commit or rollback.
591 */
592 static inline const char* getXactId( osrfMethodContext* ctx ) {
593         if( ctx && ctx->session && ctx->session->userData )
594                 return osrfHashGet( (osrfHash*) ctx->session->userData, "xact_id" );
595         else
596                 return NULL;
597 }
598
599 /**
600         @brief Clear the current transaction id.
601         @param ctx Pointer to the method context.
602 */
603 static inline void clearXactId( osrfMethodContext* ctx ) {
604         if( ctx && ctx->session && ctx->session->userData )
605                 osrfHashRemove( ctx->session->userData, "xact_id" );
606 }
607 /*@}*/
608
609 /**
610         @brief Stash the location for a particular perm in the sessionData cache
611         @param ctx Pointer to the method context.
612         @param perm Name of the permission we're looking at
613         @param array StringArray of perm location ids
614 */
615 static void setPermLocationCache( osrfMethodContext* ctx, const char* perm, osrfStringArray* locations ) {
616         if( ctx && ctx->session ) {
617                 osrfAppSession* session = ctx->session;
618
619                 osrfHash* cache = session->userData;
620
621                 // If the session doesn't already have a hash, create one.  Make sure
622                 // that the application session frees the hash when it terminates.
623                 if( NULL == cache )
624                         cache = initSessionCache( ctx );
625
626                 osrfHash* pcache = osrfHashGet(cache, "pcache");
627
628                 if( NULL == pcache ) {
629                         pcache = osrfNewHash();
630                         osrfHashSetCallback( pcache, &pcacheFree );
631                         osrfHashSet( cache, pcache, "pcache" );
632                 }
633
634                 if( perm && locations )
635                         osrfHashSet( pcache, locations, strdup(perm) );
636         }
637 }
638
639 /**
640         @brief Grab stashed location for a particular perm in the sessionData cache
641         @param ctx Pointer to the method context.
642         @param perm Name of the permission we're looking at
643 */
644 static osrfStringArray* getPermLocationCache( osrfMethodContext* ctx, const char* perm ) {
645         if( ctx && ctx->session ) {
646                 osrfAppSession* session = ctx->session;
647                 osrfHash* cache = session->userData;
648                 if( cache ) {
649                         osrfHash* pcache = osrfHashGet(cache, "pcache");
650                         if( pcache ) {
651                                 return osrfHashGet( pcache, perm );
652                         }
653                 }
654         }
655
656         return NULL;
657 }
658
659 /**
660         @brief Save the user's login in the userData for the current application session.
661         @param ctx Pointer to the method context.
662         @param user_login Pointer to the user login object to be cached (we cache the original,
663         not a copy of it).
664
665         If @a user_login is NULL, remove the user login if one is already cached.
666 */
667 static void setUserLogin( osrfMethodContext* ctx, jsonObject* user_login ) {
668         if( ctx && ctx->session ) {
669                 osrfAppSession* session = ctx->session;
670
671                 osrfHash* cache = session->userData;
672
673                 // If the session doesn't already have a hash, create one.  Make sure
674                 // that the application session frees the hash when it terminates.
675                 if( NULL == cache )
676                         cache = initSessionCache( ctx );
677
678                 if( user_login )
679                         osrfHashSet( cache, user_login, "user_login" );
680                 else
681                         osrfHashRemove( cache, "user_login" );
682         }
683 }
684
685 /**
686         @brief Get the user login object for the current application session, if any.
687         @param ctx Pointer to the method context.
688         @return Pointer to the user login object if found; otherwise NULL.
689
690         The user login object was returned from the authentication server, and then cached so
691         we don't have to call the authentication server again for the same user.
692 */
693 static const jsonObject* getUserLogin( osrfMethodContext* ctx ) {
694         if( ctx && ctx->session && ctx->session->userData )
695                 return osrfHashGet( (osrfHash*) ctx->session->userData, "user_login" );
696         else
697                 return NULL;
698 }
699
700 /**
701         @brief Save a copy of an authkey in the userData of the current application session.
702         @param ctx Pointer to the method context.
703         @param authkey The authkey to be saved.
704
705         If @a authkey is NULL, remove the authkey if one is already cached.
706 */
707 static void setAuthkey( osrfMethodContext* ctx, const char* authkey ) {
708         if( ctx && ctx->session && authkey ) {
709                 osrfAppSession* session = ctx->session;
710                 osrfHash* cache = session->userData;
711
712                 // If the session doesn't already have a hash, create one.  Make sure
713                 // that the application session frees the hash when it terminates.
714                 if( NULL == cache )
715                         cache = initSessionCache( ctx );
716
717                 // Save the transaction id in the hash, with the key "xact_id"
718                 if( authkey && *authkey )
719                         osrfHashSet( cache, strdup( authkey ), "authkey" );
720                 else
721                         osrfHashRemove( cache, "authkey" );
722         }
723 }
724
725 /**
726         @brief Reset the login timeout.
727         @param authkey The authentication key for the current login session.
728         @param now The current time.
729         @return Zero if successful, or 1 if not.
730
731         Tell the authentication server to reset the timeout so that the login session won't
732         expire for a while longer.
733
734         We could dispense with the @a now parameter by calling time().  But we just called
735         time() in order to decide whether to reset the timeout, so we might as well reuse
736         the result instead of calling time() again.
737 */
738 static int reset_timeout( const char* authkey, time_t now ) {
739         jsonObject* auth_object = jsonNewObject( authkey );
740
741         // Ask the authentication server to reset the timeout.  It returns an event
742         // indicating success or failure.
743         jsonObject* result = oilsUtilsQuickReq( "open-ils.auth",
744                 "open-ils.auth.session.reset_timeout", auth_object );
745         jsonObjectFree( auth_object );
746
747         if( !result || result->type != JSON_HASH ) {
748                 osrfLogError( OSRF_LOG_MARK,
749                          "Unexpected object type receieved from open-ils.auth.session.reset_timeout" );
750                 jsonObjectFree( result );
751                 return 1;       // Not the right sort of object returned
752         }
753
754         const jsonObject* ilsevent = jsonObjectGetKeyConst( result, "ilsevent" );
755         if( !ilsevent || ilsevent->type != JSON_NUMBER ) {
756                 osrfLogError( OSRF_LOG_MARK, "ilsevent is absent or malformed" );
757                 jsonObjectFree( result );
758                 return 1;    // Return code from method not available
759         }
760
761         if( jsonObjectGetNumber( ilsevent ) != 0.0 ) {
762                 const char* desc = jsonObjectGetString( jsonObjectGetKeyConst( result, "desc" ));
763                 if( !desc )
764                         desc = "(No reason available)";    // failsafe; shouldn't happen
765                 osrfLogInfo( OSRF_LOG_MARK, "Failure to reset timeout: %s", desc );
766                 jsonObjectFree( result );
767                 return 1;
768         }
769
770         // Revise our local proxy for the timeout deadline
771         // by a smallish fraction of the timeout interval
772         const char* timeout = jsonObjectGetString( jsonObjectGetKeyConst( result, "payload" ));
773         if( !timeout )
774                 timeout = "1";   // failsafe; shouldn't happen
775         time_next_reset = now + atoi( timeout ) / 15;
776
777         jsonObjectFree( result );
778         return 0;     // Successfully reset timeout
779 }
780
781 /**
782         @brief Get the authkey string for the current application session, if any.
783         @param ctx Pointer to the method context.
784         @return Pointer to the cached authkey if found; otherwise NULL.
785
786         If present, the authkey string was cached from a previous method call.
787 */
788 static const char* getAuthkey( osrfMethodContext* ctx ) {
789         if( ctx && ctx->session && ctx->session->userData ) {
790                 const char* authkey = osrfHashGet( (osrfHash*) ctx->session->userData, "authkey" );
791         // LFW recent changes mean the userData hash gets set up earlier, but
792         // doesn't necessarily have an authkey yet
793         if (!authkey)
794             return NULL;
795
796                 // Possibly reset the authentication timeout to keep the login alive.  We do so
797                 // no more than once per method call, and not at all if it has been only a short
798                 // time since the last reset.
799
800                 // Here we reset explicitly, if at all.  We also implicitly reset the timeout
801                 // whenever we call the "open-ils.auth.session.retrieve" method.
802                 if( timeout_needs_resetting ) {
803                         time_t now = time( NULL );
804                         if( now >= time_next_reset && reset_timeout( authkey, now ) )
805                                 authkey = NULL;    // timeout has apparently expired already
806                 }
807
808                 timeout_needs_resetting = 0;
809                 return authkey;
810         }
811         else
812                 return NULL;
813 }
814
815 /**
816         @brief Implement the transaction.begin method.
817         @param ctx Pointer to the method context.
818         @return Zero if successful, or -1 upon error.
819
820         Start a transaction.  Save a transaction ID for future reference.
821
822         Method parameters:
823         - authkey (PCRUD only)
824
825         Return to client: Transaction ID
826 */
827 int beginTransaction( osrfMethodContext* ctx ) {
828         if(osrfMethodVerifyContext( ctx )) {
829                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
830                 return -1;
831         }
832
833         const char* tz = _sanitize_tz_name(ctx->session->session_tz);
834
835         if( enforce_pcrud ) {
836                 timeout_needs_resetting = 1;
837                 const jsonObject* user = verifyUserPCRUD( ctx );
838                 if( !user )
839                         return -1;
840         }
841
842         dbi_result result = dbi_conn_query( writehandle, "START TRANSACTION;" );
843         if( !result ) {
844                 const char* msg;
845                 int errnum = dbi_conn_error( writehandle, &msg );
846                 osrfLogError( OSRF_LOG_MARK, "%s: Error starting transaction: %d %s",
847                         modulename, errnum, msg ? msg : "(No description available)" );
848                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
849                         "osrfMethodException", ctx->request, "Error starting transaction" );
850                 if( !oilsIsDBConnected( writehandle ))
851                         osrfAppSessionPanic( ctx->session );
852                 return -1;
853         } else {
854                 dbi_result_free( result );
855                 setXactId( ctx );
856                 jsonObject* ret = jsonNewObject( getXactId( ctx ) );
857                 osrfAppRespondComplete( ctx, ret );
858                 jsonObjectFree( ret );
859
860         }
861
862         if (tz) {
863                 setenv("TZ",tz,1);
864                 tzset();
865                 dbi_result tz_res = dbi_conn_queryf( writehandle, "SET LOCAL timezone TO '%s'; -- cstore", tz );
866                 if( !tz_res ) {
867                         osrfLogError( OSRF_LOG_MARK, "%s: Error setting timezone %s", modulename, tz);
868                         if( !oilsIsDBConnected( writehandle )) {
869                                 osrfAppSessionPanic( ctx->session );
870                                 return -1;
871                         }
872                 } else {
873                         dbi_result_free( tz_res );
874                 }
875         } else {
876                 unsetenv("TZ");
877                 tzset();
878                 dbi_result res = dbi_conn_queryf( writehandle, "SET timezone TO DEFAULT; -- no tz" );
879                 if( !res ) {
880                         osrfLogError( OSRF_LOG_MARK, "%s: Error resetting timezone", modulename);
881                         if( !oilsIsDBConnected( writehandle )) {
882                                 osrfAppSessionPanic( ctx->session );
883                                 return -1;
884                         }
885                 } else {
886                         dbi_result_free( res );
887                 }
888         }
889
890         return 0;
891 }
892
893 /**
894         @brief Implement the savepoint.set method.
895         @param ctx Pointer to the method context.
896         @return Zero if successful, or -1 if not.
897
898         Issue a SAVEPOINT to the database server.
899
900         Method parameters:
901         - authkey (PCRUD only)
902         - savepoint name
903
904         Return to client: Savepoint name
905 */
906 int setSavepoint( osrfMethodContext* ctx ) {
907         if(osrfMethodVerifyContext( ctx )) {
908                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
909                 return -1;
910         }
911
912         int spNamePos = 0;
913         if( enforce_pcrud ) {
914                 spNamePos = 1;
915                 timeout_needs_resetting = 1;
916                 const jsonObject* user = verifyUserPCRUD( ctx );
917                 if( !user )
918                         return -1;
919         }
920
921         // Verify that a transaction is pending
922         const char* trans_id = getXactId( ctx );
923         if( NULL == trans_id ) {
924                 osrfAppSessionStatus(
925                         ctx->session,
926                         OSRF_STATUS_INTERNALSERVERERROR,
927                         "osrfMethodException",
928                         ctx->request,
929                         "No active transaction -- required for savepoints"
930                 );
931                 return -1;
932         }
933
934         // Get the savepoint name from the method params
935         const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
936
937         if (!spName) {
938                 osrfLogWarning(OSRF_LOG_MARK, "savepoint.set called with no name");
939                 return -1;
940         }
941
942         char *safeSpName = _sanitize_savepoint_name( spName );
943
944         dbi_result result = dbi_conn_queryf( writehandle, "SAVEPOINT \"%s\";", safeSpName );
945         free( safeSpName );
946         if( !result ) {
947                 const char* msg;
948                 int errnum = dbi_conn_error( writehandle, &msg );
949                 osrfLogError(
950                         OSRF_LOG_MARK,
951                         "%s: Error creating savepoint %s in transaction %s: %d %s",
952                         modulename,
953                         spName,
954                         trans_id,
955                         errnum,
956                         msg ? msg : "(No description available)"
957                 );
958                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
959                         "osrfMethodException", ctx->request, "Error creating savepoint" );
960                 if( !oilsIsDBConnected( writehandle ))
961                         osrfAppSessionPanic( ctx->session );
962                 return -1;
963         } else {
964                 dbi_result_free( result );
965                 jsonObject* ret = jsonNewObject( spName );
966                 osrfAppRespondComplete( ctx, ret );
967                 jsonObjectFree( ret  );
968                 return 0;
969         }
970 }
971
972 /**
973         @brief Implement the savepoint.release method.
974         @param ctx Pointer to the method context.
975         @return Zero if successful, or -1 if not.
976
977         Issue a RELEASE SAVEPOINT to the database server.
978
979         Method parameters:
980         - authkey (PCRUD only)
981         - savepoint name
982
983         Return to client: Savepoint name
984 */
985 int releaseSavepoint( osrfMethodContext* ctx ) {
986         if(osrfMethodVerifyContext( ctx )) {
987                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
988                 return -1;
989         }
990
991         int spNamePos = 0;
992         if( enforce_pcrud ) {
993                 spNamePos = 1;
994                 timeout_needs_resetting = 1;
995                 const jsonObject* user = verifyUserPCRUD( ctx );
996                 if(  !user )
997                         return -1;
998         }
999
1000         // Verify that a transaction is pending
1001         const char* trans_id = getXactId( ctx );
1002         if( NULL == trans_id ) {
1003                 osrfAppSessionStatus(
1004                         ctx->session,
1005                         OSRF_STATUS_INTERNALSERVERERROR,
1006                         "osrfMethodException",
1007                         ctx->request,
1008                         "No active transaction -- required for savepoints"
1009                 );
1010                 return -1;
1011         }
1012
1013         // Get the savepoint name from the method params
1014         const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1015
1016         if (!spName) {
1017                 osrfLogWarning(OSRF_LOG_MARK, "savepoint.release called with no name");
1018                 return -1;
1019         }
1020
1021         char *safeSpName = _sanitize_savepoint_name( spName );
1022
1023         dbi_result result = dbi_conn_queryf( writehandle, "RELEASE SAVEPOINT \"%s\";", safeSpName );
1024         free( safeSpName );
1025         if( !result ) {
1026                 const char* msg;
1027                 int errnum = dbi_conn_error( writehandle, &msg );
1028                 osrfLogError(
1029                         OSRF_LOG_MARK,
1030                         "%s: Error releasing savepoint %s in transaction %s: %d %s",
1031                         modulename,
1032                         spName,
1033                         trans_id,
1034                         errnum,
1035                         msg ? msg : "(No description available)"
1036                 );
1037                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1038                         "osrfMethodException", ctx->request, "Error releasing savepoint" );
1039                 if( !oilsIsDBConnected( writehandle ))
1040                         osrfAppSessionPanic( ctx->session );
1041                 return -1;
1042         } else {
1043                 dbi_result_free( result );
1044                 jsonObject* ret = jsonNewObject( spName );
1045                 osrfAppRespondComplete( ctx, ret );
1046                 jsonObjectFree( ret );
1047                 return 0;
1048         }
1049 }
1050
1051 /**
1052         @brief Implement the savepoint.rollback method.
1053         @param ctx Pointer to the method context.
1054         @return Zero if successful, or -1 if not.
1055
1056         Issue a ROLLBACK TO SAVEPOINT to the database server.
1057
1058         Method parameters:
1059         - authkey (PCRUD only)
1060         - savepoint name
1061
1062         Return to client: Savepoint name
1063 */
1064 int rollbackSavepoint( osrfMethodContext* ctx ) {
1065         if(osrfMethodVerifyContext( ctx )) {
1066                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
1067                 return -1;
1068         }
1069
1070         int spNamePos = 0;
1071         if( enforce_pcrud ) {
1072                 spNamePos = 1;
1073                 timeout_needs_resetting = 1;
1074                 const jsonObject* user = verifyUserPCRUD( ctx );
1075                 if( !user )
1076                         return -1;
1077         }
1078
1079         // Verify that a transaction is pending
1080         const char* trans_id = getXactId( ctx );
1081         if( NULL == trans_id ) {
1082                 osrfAppSessionStatus(
1083                         ctx->session,
1084                         OSRF_STATUS_INTERNALSERVERERROR,
1085                         "osrfMethodException",
1086                         ctx->request,
1087                         "No active transaction -- required for savepoints"
1088                 );
1089                 return -1;
1090         }
1091
1092         // Get the savepoint name from the method params
1093         const char* spName = jsonObjectGetString( jsonObjectGetIndex(ctx->params, spNamePos) );
1094
1095         if (!spName) {
1096                 osrfLogWarning(OSRF_LOG_MARK, "savepoint.rollback called with no name");
1097                 return -1;
1098         }
1099
1100         char *safeSpName = _sanitize_savepoint_name( spName );
1101
1102         dbi_result result = dbi_conn_queryf( writehandle, "ROLLBACK TO SAVEPOINT \"%s\";", safeSpName );
1103         free( safeSpName );
1104         if( !result ) {
1105                 const char* msg;
1106                 int errnum = dbi_conn_error( writehandle, &msg );
1107                 osrfLogError(
1108                         OSRF_LOG_MARK,
1109                         "%s: Error rolling back savepoint %s in transaction %s: %d %s",
1110                         modulename,
1111                         spName,
1112                         trans_id,
1113                         errnum,
1114                         msg ? msg : "(No description available)"
1115                 );
1116                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1117                         "osrfMethodException", ctx->request, "Error rolling back savepoint" );
1118                 if( !oilsIsDBConnected( writehandle ))
1119                         osrfAppSessionPanic( ctx->session );
1120                 return -1;
1121         } else {
1122                 dbi_result_free( result );
1123                 jsonObject* ret = jsonNewObject( spName );
1124                 osrfAppRespondComplete( ctx, ret );
1125                 jsonObjectFree( ret );
1126                 return 0;
1127         }
1128 }
1129
1130 /**
1131         @brief Implement the transaction.commit method.
1132         @param ctx Pointer to the method context.
1133         @return Zero if successful, or -1 if not.
1134
1135         Issue a COMMIT to the database server.
1136
1137         Method parameters:
1138         - authkey (PCRUD only)
1139
1140         Return to client: Transaction ID.
1141 */
1142 int commitTransaction( osrfMethodContext* ctx ) {
1143         if(osrfMethodVerifyContext( ctx )) {
1144                 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1145                 return -1;
1146         }
1147
1148         if( enforce_pcrud ) {
1149                 timeout_needs_resetting = 1;
1150                 const jsonObject* user = verifyUserPCRUD( ctx );
1151                 if( !user )
1152                         return -1;
1153         }
1154
1155         // Verify that a transaction is pending
1156         const char* trans_id = getXactId( ctx );
1157         if( NULL == trans_id ) {
1158                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1159                                 "osrfMethodException", ctx->request, "No active transaction to commit" );
1160                 return -1;
1161         }
1162
1163         dbi_result result = dbi_conn_query( writehandle, "COMMIT;" );
1164         if( !result ) {
1165                 const char* msg;
1166                 int errnum = dbi_conn_error( writehandle, &msg );
1167                 osrfLogError( OSRF_LOG_MARK, "%s: Error committing transaction: %d %s",
1168                         modulename, errnum, msg ? msg : "(No description available)" );
1169                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1170                         "osrfMethodException", ctx->request, "Error committing transaction" );
1171                 if( !oilsIsDBConnected( writehandle ))
1172                         osrfAppSessionPanic( ctx->session );
1173                 return -1;
1174         } else {
1175                 dbi_result_free( result );
1176                 jsonObject* ret = jsonNewObject( trans_id );
1177                 osrfAppRespondComplete( ctx, ret );
1178                 jsonObjectFree( ret );
1179                 clearXactId( ctx );
1180                 return 0;
1181         }
1182 }
1183
1184 /**
1185         @brief Implement the transaction.rollback method.
1186         @param ctx Pointer to the method context.
1187         @return Zero if successful, or -1 if not.
1188
1189         Issue a ROLLBACK to the database server.
1190
1191         Method parameters:
1192         - authkey (PCRUD only)
1193
1194         Return to client: Transaction ID
1195 */
1196 int rollbackTransaction( osrfMethodContext* ctx ) {
1197         if( osrfMethodVerifyContext( ctx )) {
1198                 osrfLogError( OSRF_LOG_MARK,  "Invalid method context" );
1199                 return -1;
1200         }
1201
1202         if( enforce_pcrud ) {
1203                 timeout_needs_resetting = 1;
1204                 const jsonObject* user = verifyUserPCRUD( ctx );
1205                 if( !user )
1206                         return -1;
1207         }
1208
1209         // Verify that a transaction is pending
1210         const char* trans_id = getXactId( ctx );
1211         if( NULL == trans_id ) {
1212                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1213                                 "osrfMethodException", ctx->request, "No active transaction to roll back" );
1214                 return -1;
1215         }
1216
1217         dbi_result result = dbi_conn_query( writehandle, "ROLLBACK;" );
1218         if( !result ) {
1219                 const char* msg;
1220                 int errnum = dbi_conn_error( writehandle, &msg );
1221                 osrfLogError( OSRF_LOG_MARK, "%s: Error rolling back transaction: %d %s",
1222                         modulename, errnum, msg ? msg : "(No description available)" );
1223                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
1224                         "osrfMethodException", ctx->request, "Error rolling back transaction" );
1225                 if( !oilsIsDBConnected( writehandle ))
1226                         osrfAppSessionPanic( ctx->session );
1227                 return -1;
1228         } else {
1229                 dbi_result_free( result );
1230                 jsonObject* ret = jsonNewObject( trans_id );
1231                 osrfAppRespondComplete( ctx, ret );
1232                 jsonObjectFree( ret );
1233                 clearXactId( ctx );
1234                 return 0;
1235         }
1236 }
1237
1238 /**
1239         @brief Implement the "search" method.
1240         @param ctx Pointer to the method context.
1241         @return Zero if successful, or -1 if not.
1242
1243         Method parameters:
1244         - authkey (PCRUD only)
1245         - WHERE clause, as jsonObject
1246         - Other SQL clause(s), as a JSON_HASH: joins, SELECT list, LIMIT, etc.
1247
1248         Return to client: rows of the specified class that satisfy a specified WHERE clause.
1249         Optionally flesh linked fields.
1250 */
1251 int doSearch( osrfMethodContext* ctx ) {
1252         if( osrfMethodVerifyContext( ctx )) {
1253                 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1254                 return -1;
1255         }
1256
1257         if( enforce_pcrud )
1258                 timeout_needs_resetting = 1;
1259
1260         jsonObject* where_clause;
1261         jsonObject* rest_of_query;
1262
1263         if( enforce_pcrud ) {
1264                 where_clause  = jsonObjectGetIndex( ctx->params, 1 );
1265                 rest_of_query = jsonObjectGetIndex( ctx->params, 2 );
1266         } else {
1267                 where_clause  = jsonObjectGetIndex( ctx->params, 0 );
1268                 rest_of_query = jsonObjectGetIndex( ctx->params, 1 );
1269         }
1270
1271         if( !where_clause ) { 
1272                 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1273                 return -1;
1274         }
1275
1276         // Get the class metadata
1277         osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1278         osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1279
1280         // Do the query
1281         int err = 0;
1282         jsonObject* obj = doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1283         if( err ) {
1284                 osrfAppRespondComplete( ctx, NULL );
1285                 return -1;
1286         }
1287
1288         // doFieldmapperSearch() now takes care of our responding for us
1289 //      // Return each row to the client
1290 //      jsonObject* cur = 0;
1291 //      unsigned long res_idx = 0;
1292 //
1293 //      while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1294 //              // We used to discard based on perms here, but now that's
1295 //              // inside doFieldmapperSearch()
1296 //              osrfAppRespond( ctx, cur );
1297 //      }
1298
1299         jsonObjectFree( obj );
1300
1301         osrfAppRespondComplete( ctx, NULL );
1302         return 0;
1303 }
1304
1305 /**
1306         @brief Implement the "id_list" method.
1307         @param ctx Pointer to the method context.
1308         @param err Pointer through which to return an error code.
1309         @return Zero if successful, or -1 if not.
1310
1311         Method parameters:
1312         - authkey (PCRUD only)
1313         - WHERE clause, as jsonObject
1314         - Other SQL clause(s), as a JSON_HASH: joins, LIMIT, etc.
1315
1316         Return to client: The primary key values for all rows of the relevant class that
1317         satisfy a specified WHERE clause.
1318
1319         This method relies on the assumption that every class has a primary key consisting of
1320         a single column.
1321 */
1322 int doIdList( osrfMethodContext* ctx ) {
1323         if( osrfMethodVerifyContext( ctx )) {
1324                 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
1325                 return -1;
1326         }
1327
1328         if( enforce_pcrud )
1329                 timeout_needs_resetting = 1;
1330
1331         jsonObject* where_clause;
1332         jsonObject* rest_of_query;
1333
1334         // We use the where clause without change.  But we need to massage the rest of the
1335         // query, so we work with a copy of it instead of modifying the original.
1336
1337         if( enforce_pcrud ) {
1338                 where_clause  = jsonObjectGetIndex( ctx->params, 1 );
1339                 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 2 ) );
1340         } else {
1341                 where_clause  = jsonObjectGetIndex( ctx->params, 0 );
1342                 rest_of_query = jsonObjectClone( jsonObjectGetIndex( ctx->params, 1 ) );
1343         }
1344
1345         if( !where_clause ) { 
1346                 osrfLogError( OSRF_LOG_MARK, "No WHERE clause parameter supplied" );
1347                 return -1;
1348         }
1349
1350         // Eliminate certain SQL clauses, if present.
1351         if( rest_of_query ) {
1352                 jsonObjectRemoveKey( rest_of_query, "select" );
1353                 jsonObjectRemoveKey( rest_of_query, "no_i18n" );
1354                 jsonObjectRemoveKey( rest_of_query, "flesh" );
1355                 jsonObjectRemoveKey( rest_of_query, "flesh_fields" );
1356         } else {
1357                 rest_of_query = jsonNewObjectType( JSON_HASH );
1358         }
1359
1360         jsonObjectSetKey( rest_of_query, "no_i18n", jsonNewBoolObject( 1 ) );
1361
1362         // Get the class metadata
1363         osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1364         osrfHash* class_meta = osrfHashGet( method_meta, "class" );
1365
1366         // Build a SELECT list containing just the primary key,
1367         // i.e. like { "classname":["keyname"] }
1368         jsonObject* col_list_obj = jsonNewObjectType( JSON_ARRAY );
1369
1370         // Load array with name of primary key
1371         jsonObjectPush( col_list_obj, jsonNewObject( osrfHashGet( class_meta, "primarykey" ) ) );
1372         jsonObject* select_clause = jsonNewObjectType( JSON_HASH );
1373         jsonObjectSetKey( select_clause, osrfHashGet( class_meta, "classname" ), col_list_obj );
1374
1375         jsonObjectSetKey( rest_of_query, "select", select_clause );
1376
1377         // Do the query
1378         int err = 0;
1379         jsonObject* obj =
1380                 doFieldmapperSearch( ctx, class_meta, where_clause, rest_of_query, &err );
1381
1382         jsonObjectFree( rest_of_query );
1383         if( err ) {
1384                 osrfAppRespondComplete( ctx, NULL );
1385                 return -1;
1386         }
1387
1388         // Return each primary key value to the client
1389         jsonObject* cur;
1390         unsigned long res_idx = 0;
1391         while((cur = jsonObjectGetIndex( obj, res_idx++ ) )) {
1392                 // We used to discard based on perms here, but now that's
1393                 // inside doFieldmapperSearch()
1394                 osrfAppRespond( ctx,
1395                         oilsFMGetObject( cur, osrfHashGet( class_meta, "primarykey" ) ) );
1396         }
1397
1398         jsonObjectFree( obj );
1399         osrfAppRespondComplete( ctx, NULL );
1400         return 0;
1401 }
1402
1403 /**
1404         @brief Verify that we have a valid class reference.
1405         @param ctx Pointer to the method context.
1406         @param param Pointer to the method parameters.
1407         @return 1 if the class reference is valid, or zero if it isn't.
1408
1409         The class of the method params must match the class to which the method id devoted.
1410         For PCRUD there are additional restrictions.
1411 */
1412 static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param ) {
1413
1414         osrfHash* method_meta = (osrfHash*) ctx->method->userData;
1415         osrfHash* class = osrfHashGet( method_meta, "class" );
1416
1417         // Compare the method's class to the parameters' class
1418         if( !param->classname || (strcmp( osrfHashGet(class, "classname"), param->classname ))) {
1419
1420                 // Oops -- they don't match.  Complain.
1421                 growing_buffer* msg = buffer_init( 128 );
1422                 buffer_fadd(
1423                         msg,
1424                         "%s: %s method for type %s was passed a %s",
1425                         modulename,
1426                         osrfHashGet( method_meta, "methodtype" ),
1427                         osrfHashGet( class, "classname" ),
1428                         param->classname ? param->classname : "(null)"
1429                 );
1430
1431                 char* m = buffer_release( msg );
1432                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_BADREQUEST, "osrfMethodException",
1433                                 ctx->request, m );
1434                 free( m );
1435
1436                 return 0;
1437         }
1438
1439         if( enforce_pcrud )
1440                 return verifyObjectPCRUD( ctx, class, param, 1 );
1441         else
1442                 return 1;
1443 }
1444
1445 /**
1446         @brief (PCRUD only) Verify that the user is properly logged in.
1447         @param ctx Pointer to the method context.
1448         @return If the user is logged in, a pointer to the user object from the authentication
1449         server; otherwise NULL.
1450 */
1451 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
1452         return verifyUserPCRUDfull( ctx, 0 );
1453 }
1454
1455 static const jsonObject* verifyUserPCRUDfull( osrfMethodContext* ctx, int anon_ok ) {
1456
1457         // Get the authkey (the first method parameter)
1458         const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
1459
1460         jsonObject* user = NULL;
1461         int notify_failed_verify = 1; // assume failure
1462
1463         if (!auth) {
1464                 // auth token is null, fail by default
1465         } else if( strcmp( "ANONYMOUS", auth ) ) {
1466                 // If we are /not/ in anonymous mode
1467                 // See if we have the same authkey, and a user object,
1468                 // locally cached from a previous call
1469                 const char* cached_authkey = getAuthkey( ctx );
1470                 if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
1471                         const jsonObject* cached_user = getUserLogin( ctx );
1472                         if( cached_user )
1473                                 return cached_user;
1474                 }
1475
1476                 // We have no matching authentication data in the cache.  Authenticate from scratch.
1477                 jsonObject* auth_object = jsonNewObject( auth );
1478         
1479                 // Fetch the user object from the authentication server
1480                 user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve", auth_object );
1481                 jsonObjectFree( auth_object );
1482         
1483                 if( !user->classname || strcmp(user->classname, "au" )) {
1484                         jsonObjectFree( user );
1485                         user = NULL;
1486                 } else if( writeAuditInfo( ctx, oilsFMGetStringConst( user, "id" ), oilsFMGetStringConst( user, "wsid" ) ) ) {
1487                         // Failed to set audit information - But note that write_audit_info already set error information.
1488                         notify_failed_verify = 0;
1489                         jsonObjectFree( user );
1490                         user = NULL;
1491                 } else { // succeeded
1492                         notify_failed_verify = 0;
1493                 }
1494
1495         } else if ( anon_ok ) { // we /are/ (attempting to be) anonymous
1496                 user = jsonNewObjectType(JSON_ARRAY);
1497                 jsonObjectSetClass( user, "aou" );
1498                 oilsFMSetString(user, "id", "-1");
1499                 notify_failed_verify = 0;
1500         }
1501
1502         if (notify_failed_verify) {
1503                 growing_buffer* msg = buffer_init( 128 );
1504                 buffer_fadd(
1505                         msg,
1506                         "%s: permacrud received a bad auth token: %s",
1507                         modulename,
1508                         auth
1509                 );
1510
1511                 char* m = buffer_release( msg );
1512                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
1513                                 ctx->request, m );
1514                 free( m );
1515         }
1516
1517         setUserLogin( ctx, user );
1518         setAuthkey( ctx, auth );
1519
1520         // Allow ourselves up to a second before we have to reset the login timeout.
1521         // It would be nice to use some fraction of the timeout interval enforced by the
1522         // authentication server, but that value is not readily available at this point.
1523         // Instead, we use a conservative default interval.
1524         time_next_reset = time( NULL ) + 1;
1525
1526         return user;
1527 }
1528
1529 /**
1530         @brief For PCRUD: Determine whether the current user may access the current row.
1531         @param ctx Pointer to the method context.
1532         @param class Same as ctx->method->userData's item for key "class" except when called in recursive doFieldmapperSearch
1533         @param obj Pointer to the row being potentially accessed.
1534         @return 1 if access is permitted, or 0 if it isn't.
1535
1536         The @a obj parameter points to a JSON_HASH of column values, keyed on column name.
1537 */
1538 static int verifyObjectPCRUD ( osrfMethodContext* ctx, osrfHash *class, const jsonObject* obj, int rs_size ) {
1539
1540         dbhandle = writehandle;
1541
1542         // Figure out what class and method are involved
1543         osrfHash* method_metadata = (osrfHash*) ctx->method->userData;
1544         const char* method_type = osrfHashGet( method_metadata, "methodtype" );
1545
1546         if (!rs_size) {
1547                 int *rs_size_from_hash = osrfHashGetFmt( (osrfHash *) ctx->session->userData, "rs_size_req_%d", ctx->request );
1548                 if (rs_size_from_hash) {
1549                         rs_size = *rs_size_from_hash;
1550                         osrfLogDebug(OSRF_LOG_MARK, "used rs_size from request-scoped hash: %d", rs_size);
1551                 }
1552         }
1553
1554         // Set fetch to 1 in all cases except for inserts, meaning that for local or foreign
1555         // contexts we will do another lookup of the current row, even if we already have a
1556         // previously fetched row image, because the row image in hand may not include the
1557         // foreign key(s) that we need.
1558
1559         // This is a quick fix with a bludgeon.  There are ways to avoid the extra lookup,
1560         // but they aren't implemented yet.
1561
1562         int fetch = 0;
1563         if( *method_type == 's' || *method_type == 'i' ) {
1564                 method_type = "retrieve"; // search and id_list are equivalent to retrieve for this
1565                 fetch = 1;
1566         } else if( *method_type == 'u' || *method_type == 'd' ) {
1567                 fetch = 1; // MUST go to the db for the object for update and delete
1568         }
1569
1570         // In retrieve or search ONLY we allow anon. Later perm checks will fail as they should,
1571         // in the face of a fake user but required permissions.
1572         int anon_ok = 0;
1573         if( *method_type == 'r' )
1574                 anon_ok = 1;
1575
1576         // Get the appropriate permacrud entry from the IDL, depending on method type
1577         osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
1578         if( !pcrud ) {
1579                 // No permacrud for this method type on this class
1580
1581                 growing_buffer* msg = buffer_init( 128 );
1582                 buffer_fadd(
1583                         msg,
1584                         "%s: %s on class %s has no permacrud IDL entry",
1585                         modulename,
1586                         osrfHashGet( method_metadata, "methodtype" ),
1587                         osrfHashGet( class, "classname" )
1588                 );
1589
1590                 char* m = buffer_release( msg );
1591                 osrfAppSessionStatus( ctx->session, OSRF_STATUS_FORBIDDEN,
1592                                 "osrfMethodException", ctx->request, m );
1593
1594                 free( m );
1595
1596                 return 0;
1597         }
1598
1599         // Get the user id, and make sure the user is logged in
1600         const jsonObject* user = verifyUserPCRUDfull( ctx, anon_ok );
1601         if( !user )
1602                 return 0;    // Not logged in or anon?  No access.
1603
1604         int userid = atoi( oilsFMGetStringConst( user, "id" ) );
1605
1606         // Get a list of permissions from the permacrud entry.
1607         osrfStringArray* permission = osrfHashGet( pcrud, "permission" );
1608         if( permission->size == 0 ) {
1609                 osrfLogDebug(
1610                         OSRF_LOG_MARK,
1611                         "No permissions required for this action (class %s), passing through",
1612                         osrfHashGet(class, "classname")
1613                 );
1614                 return 1;
1615         }
1616
1617         // But, if there are perms and the user is anonymous ... FAIL
1618         if ( -1 == userid )
1619                 return 0;
1620
1621         // Build a list of org units that own the row.  This is fairly convoluted because there
1622         // are several different ways that an org unit may own the row, as defined by the
1623         // permacrud entry.
1624
1625         // Local context means that the row includes a foreign key pointing to actor.org_unit,
1626         // identifying an owning org_unit..
1627         osrfStringArray* local_context = osrfHashGet( pcrud, "local_context" );
1628
1629         // Foreign context adds a layer of indirection.  The row points to some other row that
1630         // an org unit may own.  The "jump" attribute, if present, adds another layer of
1631         // indirection.
1632         osrfHash* foreign_context = osrfHashGet( pcrud, "foreign_context" );
1633
1634         // The following string array stores the list of org units.  (We don't have a thingie
1635         // for storing lists of integers, so we fake it with a list of strings.)
1636         osrfStringArray* context_org_array = osrfNewStringArray( 1 );
1637
1638         const char* context_org = NULL;
1639     const char* pkey = NULL;
1640     jsonObject *param = NULL;
1641         const char* perm = NULL;
1642         int OK = 0;
1643         int i = 0;
1644         int err = 0;
1645         const char* pkey_value = NULL;
1646         if( str_is_true( osrfHashGet(pcrud, "global_required") ) ) {
1647                 // If the global_required attribute is present and true, then the only owning
1648                 // org unit is the root org unit, i.e. the one with no parent.
1649                 osrfLogDebug( OSRF_LOG_MARK,
1650                                 "global-level permissions required, fetching top of the org tree" );
1651
1652                 // no need to check perms for org tree root retrieval
1653                 osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1654                 // check for perm at top of org tree
1655                 const char* org_tree_root_id = org_tree_root( ctx );
1656                 osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1657
1658                 if( org_tree_root_id ) {
1659                         osrfStringArrayAdd( context_org_array, org_tree_root_id );
1660                         osrfLogDebug( OSRF_LOG_MARK, "top of the org tree is %s", org_tree_root_id );
1661                 } else  {
1662                         osrfStringArrayFree( context_org_array );
1663                         return 0;
1664                 }
1665
1666         } else {
1667                 // If the global_required attribute is absent or false, then we look for
1668                 // local and/or foreign context.  In order to find the relevant foreign
1669                 // keys, we must either read the relevant row from the database, or look at
1670                 // the image of the row that we already have in memory.
1671
1672                 // Even if we have an image of the row in memory, that image may not include the
1673                 // foreign key column(s) that we need.  So whenever possible, we do a fresh read
1674                 // of the row to make sure that we have what we need.
1675
1676             osrfLogDebug( OSRF_LOG_MARK, "global-level permissions not required, "
1677                                 "fetching context org ids" );
1678
1679         pkey = osrfHashGet( class, "primarykey" );
1680
1681                 if( !pkey ) {
1682                         // There is no primary key, so we can't do a fresh lookup.  Use the row
1683                         // image that we already have.  If it doesn't have everything we need, too bad.
1684                         fetch = 0;
1685                         param = jsonObjectClone( obj );
1686                         osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1687                 } else if( obj->classname ) {
1688                         pkey_value = oilsFMGetStringConst( obj, pkey );
1689                         if( !fetch )
1690                                 param = jsonObjectClone( obj );
1691                         osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1692                                 pkey_value );
1693                 } else {
1694                         pkey_value = jsonObjectGetString( obj );
1695                         fetch = 1;
1696                         osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1697                                 "of %s and retrieving from the database", pkey_value );
1698                 }
1699
1700                 if( fetch ) {
1701                         // Fetch the row so that we can look at the foreign key(s)
1702                         osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1703                         jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1704                         jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1705                         jsonObjectFree( _tmp_params );
1706                         osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1707
1708                         param = jsonObjectExtractIndex( _list, 0 );
1709                         jsonObjectFree( _list );
1710
1711             fetch = 0;
1712                 }
1713
1714                 if( !param ) {
1715                         // The row doesn't exist.  Complain, and deny access.
1716                         osrfLogDebug( OSRF_LOG_MARK,
1717                                         "Object not found in the database with primary key %s of %s",
1718                                         pkey, pkey_value );
1719
1720                         growing_buffer* msg = buffer_init( 128 );
1721                         buffer_fadd(
1722                                 msg,
1723                                 "%s: no object found with primary key %s of %s",
1724                                 modulename,
1725                                 pkey,
1726                                 pkey_value
1727                         );
1728
1729                         char* m = buffer_release( msg );
1730                         osrfAppSessionStatus(
1731                                 ctx->session,
1732                                 OSRF_STATUS_INTERNALSERVERERROR,
1733                                 "osrfMethodException",
1734                                 ctx->request,
1735                                 m
1736                         );
1737
1738                         free( m );
1739                         return 0;
1740                 }
1741
1742                 if( local_context && local_context->size > 0 ) {
1743                         // The IDL provides a list of column names for the foreign keys denoting
1744                         // local context, i.e. columns identifying owing org units directly.  Look up
1745                         // the value of each one, and if it isn't null, add it to the list of org units.
1746                         osrfLogDebug( OSRF_LOG_MARK, "%d class-local context field(s) specified",
1747                                 local_context->size );
1748                         int i = 0;
1749                         const char* lcontext = NULL;
1750                         while ( (lcontext = osrfStringArrayGetString(local_context, i++)) ) {
1751                                 const char* fkey_value = oilsFMGetStringConst( param, lcontext );
1752                                 if( fkey_value ) {    // if not null
1753                                         osrfStringArrayAdd( context_org_array, fkey_value );
1754                                         osrfLogDebug(
1755                                                 OSRF_LOG_MARK,
1756                                                 "adding class-local field %s (value: %s) to the context org list",
1757                                                 lcontext,
1758                                                 osrfStringArrayGetString( context_org_array, context_org_array->size - 1 )
1759                                         );
1760                                 }
1761                         }
1762                 }
1763
1764                 if( foreign_context ) {
1765                         unsigned long class_count = osrfHashGetCount( foreign_context );
1766                         osrfLogDebug( OSRF_LOG_MARK, "%d foreign context classes(s) specified", class_count );
1767
1768                         if( class_count > 0 ) {
1769
1770                                 // The IDL provides a list of foreign key columns pointing to rows that
1771                                 // an org unit may own.  Follow each link, identify the owning org unit,
1772                                 // and add it to the list.
1773                                 osrfHash* fcontext = NULL;
1774                                 osrfHashIterator* class_itr = osrfNewHashIterator( foreign_context );
1775                                 while( (fcontext = osrfHashIteratorNext( class_itr )) ) {
1776                                         // For each class to which a foreign key points:
1777                                         const char* class_name = osrfHashIteratorKey( class_itr );
1778                                         osrfHash* fcontext = osrfHashGet( foreign_context, class_name );
1779
1780                                         osrfLogDebug(
1781                                                 OSRF_LOG_MARK,
1782                                                 "%d foreign context fields(s) specified for class %s",
1783                                                 ((osrfStringArray*)osrfHashGet(fcontext,"context"))->size,
1784                                                 class_name
1785                                         );
1786
1787                                         // Get the name of the key field in the foreign table
1788                                         const char* foreign_pkey = osrfHashGet( fcontext, "field" );
1789
1790                                         // Get the value of the foreign key pointing to the foreign table
1791                                         char* foreign_pkey_value =
1792                                                         oilsFMGetString( param, osrfHashGet( fcontext, "fkey" ));
1793                                         if( !foreign_pkey_value )
1794                                                 continue;    // Foreign key value is null; skip it
1795
1796                                         // Look up the row to which the foreign key points
1797                                         jsonObject* _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1798
1799                                         osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1800                                         jsonObject* _list = doFieldmapperSearch(
1801                                                 ctx, osrfHashGet( oilsIDL(), class_name ), _tmp_params, NULL, &err );
1802                                         osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1803
1804                                         jsonObject* _fparam = NULL;
1805                                         if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1806                                                 _fparam = jsonObjectExtractIndex( _list, 0 );
1807
1808                                         jsonObjectFree( _tmp_params );
1809                                         jsonObjectFree( _list );
1810
1811                                         // At this point _fparam either points to the row identified by the
1812                                         // foreign key, or it's NULL (no such row found).
1813
1814                                         osrfStringArray* jump_list = osrfHashGet( fcontext, "jump" );
1815
1816                                         const char* bad_class = NULL;  // For noting failed lookups
1817                                         if( ! _fparam )
1818                                                 bad_class = class_name;    // Referenced row not found
1819                                         else if( jump_list ) {
1820                                                 // Follow a chain of rows, linked by foreign keys, to find an owner
1821                                                 const char* flink = NULL;
1822                                                 int k = 0;
1823                                                 while ( (flink = osrfStringArrayGetString(jump_list, k++)) && _fparam ) {
1824                                                         // For each entry in the jump list.  Each entry (i.e. flink) is
1825                                                         // the name of a foreign key column in the current row.
1826
1827                                                         // From the IDL, get the linkage information for the next jump
1828                                                         osrfHash* foreign_link_hash =
1829                                                                         oilsIDLFindPath( "/%s/links/%s", _fparam->classname, flink );
1830
1831                                                         // Get the class metadata for the class
1832                                                         // to which the foreign key points
1833                                                         osrfHash* foreign_class_meta = osrfHashGet( oilsIDL(),
1834                                                                         osrfHashGet( foreign_link_hash, "class" ));
1835
1836                                                         // Get the name of the referenced key of that class
1837                                                         foreign_pkey = osrfHashGet( foreign_link_hash, "key" );
1838
1839                                                         // Get the value of the foreign key pointing to that class
1840                                                         free( foreign_pkey_value );
1841                                                         foreign_pkey_value = oilsFMGetString( _fparam, flink );
1842                                                         if( !foreign_pkey_value )
1843                                                                 break;    // Foreign key is null; quit looking
1844
1845                                                         // Build a WHERE clause for the lookup
1846                                                         _tmp_params = single_hash( foreign_pkey, foreign_pkey_value );
1847
1848                                                         // Do the lookup
1849                                                         osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1850                                                         _list = doFieldmapperSearch( ctx, foreign_class_meta,
1851                                                                         _tmp_params, NULL, &err );
1852                                                         osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1853
1854                                                         // Get the resulting row
1855                                                         jsonObjectFree( _fparam );
1856                                                         if( _list && JSON_ARRAY == _list->type && _list->size > 0 )
1857                                                                 _fparam = jsonObjectExtractIndex( _list, 0 );
1858                                                         else {
1859                                                                 // Referenced row not found
1860                                                                 _fparam = NULL;
1861                                                                 bad_class = osrfHashGet( foreign_link_hash, "class" );
1862                                                         }
1863
1864                                                         jsonObjectFree( _tmp_params );
1865                                                         jsonObjectFree( _list );
1866                                                 }
1867                                         }
1868
1869                                         if( bad_class ) {
1870
1871                                                 // We had a foreign key pointing to such-and-such a row, but then
1872                                                 // we couldn't fetch that row.  The data in the database are in an
1873                                                 // inconsistent state; the database itself may even be corrupted.
1874                                                 growing_buffer* msg = buffer_init( 128 );
1875                                                 buffer_fadd(
1876                                                         msg,
1877                                                         "%s: no object of class %s found with primary key %s of %s",
1878                                                         modulename,
1879                                                         bad_class,
1880                                                         foreign_pkey,
1881                                                         foreign_pkey_value ? foreign_pkey_value : "(null)"
1882                                                 );
1883
1884                                                 char* m = buffer_release( msg );
1885                                                 osrfAppSessionStatus(
1886                                                         ctx->session,
1887                                                         OSRF_STATUS_INTERNALSERVERERROR,
1888                                                         "osrfMethodException",
1889                                                         ctx->request,
1890                                                         m
1891                                                 );
1892
1893                                                 free( m );
1894                                                 osrfHashIteratorFree( class_itr );
1895                                                 free( foreign_pkey_value );
1896                                                 jsonObjectFree( param );
1897
1898                                                 return 0;
1899                                         }
1900
1901                                         free( foreign_pkey_value );
1902
1903                                         if( _fparam ) {
1904                                                 // Examine each context column of the foreign row,
1905                                                 // and add its value to the list of org units.
1906                                                 int j = 0;
1907                                                 const char* foreign_field = NULL;
1908                                                 osrfStringArray* ctx_array = osrfHashGet( fcontext, "context" );
1909                                                 while ( (foreign_field = osrfStringArrayGetString( ctx_array, j++ )) ) {
1910                                                         osrfStringArrayAdd( context_org_array,
1911                                                                 oilsFMGetStringConst( _fparam, foreign_field ));
1912                                                         osrfLogDebug( OSRF_LOG_MARK,
1913                                                                 "adding foreign class %s field %s (value: %s) "
1914                                                                         "to the context org list",
1915                                                                 class_name,
1916                                                                 foreign_field,
1917                                                                 osrfStringArrayGetString(
1918                                                                         context_org_array, context_org_array->size - 1 )
1919                                                         );
1920                                                 }
1921
1922                                                 jsonObjectFree( _fparam );
1923                                         }
1924                                 }
1925
1926                                 osrfHashIteratorFree( class_itr );
1927                         }
1928                 }
1929         }
1930
1931     // If there is an owning_user attached to the action, we allow that user and users with
1932     // object perms on the object. CREATE can't use this. We only do this when we're not
1933     // ignoring object perms.
1934     char* owning_user_field = osrfHashGet( pcrud, "owning_user" );
1935     if (
1936         *method_type != 'c' &&
1937         (!str_is_true( osrfHashGet(pcrud, "ignore_object_perms") ) || // Always honor
1938         owning_user_field)
1939     ) {
1940         if (owning_user_field) { // see if we can short-cut by comparing the owner to the requestor
1941
1942             if (!param) { // We didn't get it during the context lookup
1943                             pkey = osrfHashGet( class, "primarykey" );
1944                 
1945                                 if( !pkey  ) {
1946                                         // There is no primary key, so we can't do a fresh lookup.  Use the row
1947                                         // image that we already have.  If it doesn't have everything we need, too bad.
1948                                         fetch = 0;
1949                                         param = jsonObjectClone( obj );
1950                                         osrfLogDebug( OSRF_LOG_MARK, "No primary key; using clone of object" );
1951                                 } else if( obj->classname ) {
1952                                         pkey_value = oilsFMGetStringConst( obj, pkey );
1953                                         if( !fetch )
1954                                                 param = jsonObjectClone( obj );
1955                                         osrfLogDebug( OSRF_LOG_MARK, "Object supplied, using primary key value of %s",
1956                                                 pkey_value );
1957                                 } else {
1958                                         pkey_value = jsonObjectGetString( obj );
1959                                         fetch = 1;
1960                                         osrfLogDebug( OSRF_LOG_MARK, "Object not supplied, using primary key value "
1961                                                 "of %s and retrieving from the database", pkey_value );
1962                                 }
1963                 
1964                                 if( fetch ) {
1965                                         // Fetch the row so that we can look at the foreign key(s)
1966                                         osrfHashSet((osrfHash*) ctx->session->userData, "1", "inside_verify");
1967                                         jsonObject* _tmp_params = single_hash( pkey, pkey_value );
1968                                         jsonObject* _list = doFieldmapperSearch( ctx, class, _tmp_params, NULL, &err );
1969                                         jsonObjectFree( _tmp_params );
1970                                         osrfHashSet((osrfHash*) ctx->session->userData, "0", "inside_verify");
1971                 
1972                                         param = jsonObjectExtractIndex( _list, 0 );
1973                                         jsonObjectFree( _list );
1974                                 }
1975             }
1976         
1977                         if( !param ) {
1978                                 // The row doesn't exist.  Complain, and deny access.
1979                                 osrfLogDebug( OSRF_LOG_MARK,
1980                                                 "Object not found in the database with primary key %s of %s",
1981                                                 pkey, pkey_value );
1982         
1983                                 growing_buffer* msg = buffer_init( 128 );
1984                                 buffer_fadd(
1985                                         msg,
1986                                         "%s: no object found with primary key %s of %s",
1987                                         modulename,
1988                                         pkey,
1989                                         pkey_value
1990                                 );
1991         
1992                                 char* m = buffer_release( msg );
1993                                 osrfAppSessionStatus(
1994                                         ctx->session,
1995                                         OSRF_STATUS_INTERNALSERVERERROR,
1996                                         "osrfMethodException",
1997                                         ctx->request,
1998                                         m
1999                                 );
2000         
2001                                 free( m );
2002                                 return 0;
2003                         } else {
2004
2005                 int ownerid = atoi( oilsFMGetStringConst( param, owning_user_field ) );
2006
2007                 // Allow the owner to do whatever
2008                 if (ownerid == userid)
2009                     OK = 1;
2010                         }
2011                 }
2012
2013         i = 0;
2014         while(  !OK &&
2015                                 (perm = osrfStringArrayGetString(permission, i++)) &&
2016                                 !str_is_true( osrfHashGet(pcrud, "ignore_object_perms"))
2017                 ) {
2018                 dbi_result result;
2019
2020                         osrfLogDebug(
2021                                 OSRF_LOG_MARK,
2022                                 "Checking object permission [%s] for user %d "
2023                                                 "on object %s (class %s)",
2024                                 perm,
2025                                 userid,
2026                                 pkey_value,
2027                                 osrfHashGet( class, "classname" )
2028                         );
2029
2030                         result = dbi_conn_queryf(
2031                                 writehandle,
2032                                 "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s') AS has_perm;",
2033                                 userid,
2034                                 perm,
2035                                 osrfHashGet( class, "classname" ),
2036                                 pkey_value
2037                         );
2038
2039                         if( result ) {
2040                                 osrfLogDebug(
2041                                         OSRF_LOG_MARK,
2042                                         "Received a result for object permission [%s] "
2043                                                         "for user %d on object %s (class %s)",
2044                                         perm,
2045                                         userid,
2046                                         pkey_value,
2047                                         osrfHashGet( class, "classname" )
2048                                 );
2049
2050                                 if( dbi_result_first_row( result )) {
2051                                         jsonObject* return_val = oilsMakeJSONFromResult( result );
2052                                         const char* has_perm = jsonObjectGetString(
2053                                                         jsonObjectGetKeyConst( return_val, "has_perm" ));
2054
2055                                         osrfLogDebug(
2056                                                 OSRF_LOG_MARK,
2057                                                 "Status of object permission [%s] for user %d "
2058                                                                 "on object %s (class %s) is %s",
2059                                                 perm,
2060                                                 userid,
2061                                                 pkey_value,
2062                                                 osrfHashGet(class, "classname"),
2063                                                 has_perm
2064                                         );
2065
2066                                         if( *has_perm == 't' )
2067                                                 OK = 1;
2068                                         jsonObjectFree( return_val );
2069                                 }
2070
2071                                 dbi_result_free( result );
2072                                 if( OK )
2073                     break;
2074                         } else {
2075                                 const char* msg;
2076                                 int errnum = dbi_conn_error( writehandle, &msg );
2077                                 osrfLogWarning( OSRF_LOG_MARK,
2078                                         "Unable to call check object permissions: %d, %s",
2079                                         errnum, msg ? msg : "(No description available)" );
2080                                 if( !oilsIsDBConnected( writehandle ))
2081                                         osrfAppSessionPanic( ctx->session );
2082                         }
2083         }
2084     }
2085
2086         // For every combination of permission and context org unit: call a stored procedure
2087         // to determine if the user has this permission in the context of this org unit.
2088         // If the answer is yes at any point, then we're done, and the user has permission.
2089         // In other words permissions are additive.
2090         i = 0;
2091         while( !OK && (perm = osrfStringArrayGetString(permission, i++)) ) {
2092                 dbi_result result;
2093
2094         osrfStringArray* pcache = NULL;
2095         if (rs_size > perm_at_threshold) { // grab and cache locations of user perms
2096                         pcache = getPermLocationCache(ctx, perm);
2097
2098                         if (!pcache) {
2099                         pcache = osrfNewStringArray(0);
2100         
2101                                 result = dbi_conn_queryf(
2102                                         writehandle,
2103                                         "SELECT permission.usr_has_perm_at_all(%d, '%s') AS at;",
2104                                         userid,
2105                                         perm
2106                                 );
2107                 
2108                                 if( result ) {
2109                                         osrfLogDebug(
2110                                                 OSRF_LOG_MARK,
2111                                                 "Received a result for permission [%s] for user %d",
2112                                                 perm,
2113                                                 userid
2114                                         );
2115                 
2116                                         if( dbi_result_first_row( result )) {
2117                             do {
2118                                                 jsonObject* return_val = oilsMakeJSONFromResult( result );
2119                                                 osrfStringArrayAdd( pcache, jsonObjectGetString( jsonObjectGetKeyConst( return_val, "at" ) ) );
2120                                 jsonObjectFree( return_val );
2121                                             } while( dbi_result_next_row( result ));
2122
2123                                                 setPermLocationCache(ctx, perm, pcache);
2124                                         }
2125                 
2126                                         dbi_result_free( result );
2127                     }
2128                         }
2129         }
2130
2131                 int j = 0;
2132                 while( (context_org = osrfStringArrayGetString( context_org_array, j++ )) ) {
2133
2134             if (rs_size > perm_at_threshold) {
2135                 if (osrfStringArrayContains( pcache, context_org )) {
2136                     OK = 1;
2137                     break;
2138                 }
2139             }
2140
2141                         if(
2142                 pkey_value &&
2143                 !str_is_true( osrfHashGet(pcrud, "ignore_object_perms") ) && // Always honor
2144                 (
2145                     !str_is_true( osrfHashGet(pcrud, "global_required") ) ||
2146                     osrfHashGet(pcrud, "owning_user") 
2147                 )
2148             ) {
2149                                 osrfLogDebug(
2150                                         OSRF_LOG_MARK,
2151                                         "Checking object permission [%s] for user %d "
2152                                                         "on object %s (class %s) at org %d",
2153                                         perm,
2154                                         userid,
2155                                         pkey_value,
2156                                         osrfHashGet( class, "classname" ),
2157                                         atoi( context_org )
2158                                 );
2159
2160                                 result = dbi_conn_queryf(
2161                                         writehandle,
2162                                         "SELECT permission.usr_has_object_perm(%d, '%s', '%s', '%s', %d) AS has_perm;",
2163                                         userid,
2164                                         perm,
2165                                         osrfHashGet( class, "classname" ),
2166                                         pkey_value,
2167                                         atoi( context_org )
2168                                 );
2169
2170                                 if( result ) {
2171                                         osrfLogDebug(
2172                                                 OSRF_LOG_MARK,
2173                                                 "Received a result for object permission [%s] "
2174                                                                 "for user %d on object %s (class %s) at org %d",
2175                                                 perm,
2176                                                 userid,
2177                                                 pkey_value,
2178                                                 osrfHashGet( class, "classname" ),
2179                                                 atoi( context_org )
2180                                         );
2181
2182                                         if( dbi_result_first_row( result )) {
2183                                                 jsonObject* return_val = oilsMakeJSONFromResult( result );
2184                                                 const char* has_perm = jsonObjectGetString(
2185                                                                 jsonObjectGetKeyConst( return_val, "has_perm" ));
2186
2187                                                 osrfLogDebug(
2188                                                         OSRF_LOG_MARK,
2189                                                         "Status of object permission [%s] for user %d "
2190                                                                         "on object %s (class %s) at org %d is %s",
2191                                                         perm,
2192                                                         userid,
2193                                                         pkey_value,
2194                                                         osrfHashGet(class, "classname"),
2195                                                         atoi(context_org),
2196                                                         has_perm
2197                                                 );
2198
2199                                                 if( *has_perm == 't' )
2200                                                         OK = 1;
2201                                                 jsonObjectFree( return_val );
2202                                         }
2203
2204                                         dbi_result_free( result );
2205                                         if( OK )
2206                                                 break;
2207                                 } else {
2208                                         const char* msg;
2209                                         int errnum = dbi_conn_error( writehandle, &msg );
2210                                         osrfLogWarning( OSRF_LOG_MARK,
2211                                                 "Unable to call check object permissions: %d, %s",
2212                                                 errnum, msg ? msg : "(No description available)" );
2213                                         if( !oilsIsDBConnected( writehandle ))
2214                                                 osrfAppSessionPanic( ctx->session );
2215                                 }
2216                         }
2217
2218             if (rs_size > perm_at_threshold) break;
2219
2220                         osrfLogDebug( OSRF_LOG_MARK,
2221                                         "Checking non-object permission [%s] for user %d at org %d",
2222                                         perm, userid, atoi(context_org) );
2223                         result = dbi_conn_queryf(
2224                                 writehandle,
2225                                 "SELECT permission.usr_has_perm(%d, '%s', %d) AS has_perm;",
2226                                 userid,
2227                                 perm,
2228                                 atoi( context_org )
2229                         );
2230
2231                         if( result ) {
2232                                 osrfLogDebug( OSRF_LOG_MARK,
2233                                         "Received a result for permission [%s] for user %d at org %d",
2234                                         perm, userid, atoi( context_org ));
2235                                 if( dbi_result_first_row( result )) {
2236                                         jsonObject* return_val = oilsMakeJSONFromResult( result );
2237                                         const char* has_perm = jsonObjectGetString(
2238                                                 jsonObjectGetKeyConst( return_val, "has_perm" ));
2239                                         osrfLogDebug( OSRF_LOG_MARK,
2240                                                 "Status of permission [%s] for user %d at org %d is [%s]",
2241                                                 perm, userid, atoi( context_org ), has_perm );
2242                                         if( *has_perm == 't' )
2243                                                 OK = 1;
2244                                         jsonObjectFree( return_val );
2245                                 }
2246
2247                                 dbi_result_free( result );
2248                                 if( OK )
2249                                         break;
2250                         } else {
2251                                 const char* msg;
2252                                 int errnum = dbi_conn_error( writehandle, &msg );
2253                                 osrfLogWarning( OSRF_LOG_MARK, "Unable to call user object permissions: %d, %s",
2254                                         errnum, msg ? msg : "(No description available)" );
2255                                 if( !oilsIsDBConnected( writehandle ))
2256                                         osrfAppSessionPanic( ctx->session );
2257                         }
2258
2259                 }
2260
2261                 if( OK )
2262                         break;
2263         }
2264
2265         osrfStringArrayFree( context_org_array );
2266
2267         return OK;
2268 }
2269
2270 /**
2271         @brief Look up the root of the org_unit tree.
2272         @param ctx Pointer to the method context.
2273         @return The id of the root org unit, as a character string.
2274
2275         Query actor.org_unit where parent_ou is null, and return the id as a string.
2276
2277         This function assumes that there is only one root org unit, i.e. that we
2278         have a single tree, not a forest.
2279
2280         The calling code is responsible for freeing the returned string.
2281 */
2282 static const char* org_tree_root( osrfMethodContext* ctx ) {
2283
2284         static char cached_root_id[ 32 ] = "";  // extravagantly large buffer
2285         static time_t last_lookup_time = 0;
2286         time_t current_time = time( NULL );
2287
2288         if( cached_root_id[ 0 ] && ( current_time - last_lookup_time < 3600 ) ) {
2289                 // We successfully looked this up less than an hour ago.
2290                 // It's not likely to have changed since then.
2291                 return strdup( cached_root_id );
2292         }
2293         last_lookup_time = current_time;
2294
2295         int err = 0;
2296         jsonObject* where_clause = single_hash( "parent_ou", NULL );
2297         jsonObject* result = doFieldmapperSearch(
2298                 ctx, osrfHashGet( oilsIDL(), "aou" ), where_clause, NULL, &err );
2299         jsonObjectFree( where_clause );
2300
2301         jsonObject* tree_top = jsonObjectGetIndex( result, 0 );
2302
2303         if( !tree_top ) {
2304                 jsonObjectFree( result );
2305
2306                 growing_buffer* msg = buffer_init( 128 );
2307                 OSRF_BUFFER_ADD( msg, modulename );
2308                 OSRF_BUFFER_ADD( msg,
2309                                 ": Internal error, could not find the top of the org tree (parent_ou = NULL)" );
2310
2311                 char* m = buffer_release( msg );
2312                 osrfAppSessionStatus( ctx->session,
2313                                 OSRF_STATUS_INTERNALSERVERERROR, "osrfMethodException", ctx->request, m );
2314                 free( m );
2315
2316                 cached_root_id[ 0 ] = '\0';
2317                 return NULL;
2318         }
2319
2320         const char* root_org_unit_id = oilsFMGetStringConst( tree_top, "id" );
2321         osrfLogDebug( OSRF_LOG_MARK, "Top of the org tree is %s", root_org_unit_id );
2322
2323         strcpy( cached_root_id, root_org_unit_id );
2324         jsonObjectFree( result );
2325         return cached_root_id;
2326 }
2327
2328 /**
2329         @brief Create a JSON_HASH with a single key/value pair.
2330         @param key The key of the key/value pair.
2331         @param value the value of the key/value pair.
2332         @return Pointer to a newly created jsonObject of type JSON_HASH.
2333
2334         The value of the key/value is either a string or (if @a value is NULL) a null.
2335 */
2336 static jsonObject* single_hash( const char* key, const char* value ) {
2337         // Sanity check
2338         if( ! key ) key = "";
2339
2340         jsonObject* hash = jsonNewObjectType( JSON_HASH );
2341         jsonObjectSetKey( hash, key, jsonNewObject( value ) );
2342         return hash;
2343 }
2344
2345
2346 int doCreate( osrfMethodContext* ctx ) {
2347         if(osrfMethodVerifyContext( ctx )) {
2348                 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2349                 return -1;
2350         }
2351
2352         if( enforce_pcrud )
2353                 timeout_needs_resetting = 1;
2354
2355         osrfHash* meta = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2356         jsonObject* target = NULL;
2357         jsonObject* options = NULL;
2358
2359         if( enforce_pcrud ) {
2360                 target = jsonObjectGetIndex( ctx->params, 1 );
2361                 options = jsonObjectGetIndex( ctx->params, 2 );
2362         } else {
2363                 target = jsonObjectGetIndex( ctx->params, 0 );
2364                 options = jsonObjectGetIndex( ctx->params, 1 );
2365         }
2366
2367         if( !verifyObjectClass( ctx, target )) {
2368                 osrfAppRespondComplete( ctx, NULL );
2369                 return -1;
2370         }
2371
2372         osrfLogDebug( OSRF_LOG_MARK, "Object seems to be of the correct type" );
2373
2374         const char* trans_id = getXactId( ctx );
2375         if( !trans_id ) {
2376                 osrfLogError( OSRF_LOG_MARK, "No active transaction -- required for CREATE" );
2377
2378                 osrfAppSessionStatus(
2379                         ctx->session,
2380                         OSRF_STATUS_BADREQUEST,
2381                         "osrfMethodException",
2382                         ctx->request,
2383                         "No active transaction -- required for CREATE"
2384                 );
2385                 osrfAppRespondComplete( ctx, NULL );
2386                 return -1;
2387         }
2388
2389         // The following test is harmless but redundant.  If a class is
2390         // readonly, we don't register a create method for it.
2391         if( str_is_true( osrfHashGet( meta, "readonly" ) ) ) {
2392                 osrfAppSessionStatus(
2393                         ctx->session,
2394                         OSRF_STATUS_BADREQUEST,
2395                         "osrfMethodException",
2396                         ctx->request,
2397                         "Cannot INSERT readonly class"
2398                 );
2399                 osrfAppRespondComplete( ctx, NULL );
2400                 return -1;
2401         }
2402
2403         // Set the last_xact_id
2404         int index = oilsIDL_ntop( target->classname, "last_xact_id" );
2405         if( index > -1 ) {
2406                 osrfLogDebug(OSRF_LOG_MARK, "Setting last_xact_id to %s on %s at position %d",
2407                         trans_id, target->classname, index);
2408                 jsonObjectSetIndex( target, index, jsonNewObject( trans_id ));
2409         }
2410
2411         osrfLogDebug( OSRF_LOG_MARK, "There is a transaction running..." );
2412
2413         dbhandle = writehandle;
2414
2415         osrfHash* fields = osrfHashGet( meta, "fields" );
2416         char* pkey       = osrfHashGet( meta, "primarykey" );
2417         char* seq        = osrfHashGet( meta, "sequence" );
2418
2419         growing_buffer* table_buf = buffer_init( 128 );
2420         growing_buffer* col_buf   = buffer_init( 128 );
2421         growing_buffer* val_buf   = buffer_init( 128 );
2422
2423         OSRF_BUFFER_ADD( table_buf, "INSERT INTO " );
2424         OSRF_BUFFER_ADD( table_buf, osrfHashGet( meta, "tablename" ));
2425         OSRF_BUFFER_ADD_CHAR( col_buf, '(' );
2426         buffer_add( val_buf,"VALUES (" );
2427
2428
2429         int first = 1;
2430         osrfHash* field = NULL;
2431         osrfHashIterator* field_itr = osrfNewHashIterator( fields );
2432         while( (field = osrfHashIteratorNext( field_itr ) ) ) {
2433
2434                 const char* field_name = osrfHashIteratorKey( field_itr );
2435
2436                 if( str_is_true( osrfHashGet( field, "virtual" ) ) )
2437                         continue;
2438
2439                 const jsonObject* field_object = oilsFMGetObject( target, field_name );
2440
2441                 char* value;
2442                 if( field_object && field_object->classname ) {
2443                         value = oilsFMGetString(
2444                                 field_object,
2445                                 (char*)oilsIDLFindPath( "/%s/primarykey", field_object->classname )
2446                         );
2447                 } else if( field_object && JSON_BOOL == field_object->type ) {
2448                         if( jsonBoolIsTrue( field_object ) )
2449                                 value = strdup( "t" );
2450                         else
2451                                 value = strdup( "f" );
2452                 } else {
2453                         value = jsonObjectToSimpleString( field_object );
2454                 }
2455
2456                 if( first ) {
2457                         first = 0;
2458                 } else {
2459                         OSRF_BUFFER_ADD_CHAR( col_buf, ',' );
2460                         OSRF_BUFFER_ADD_CHAR( val_buf, ',' );
2461                 }
2462
2463                 buffer_add( col_buf, field_name );
2464
2465                 if( !field_object || field_object->type == JSON_NULL ) {
2466                         buffer_add( val_buf, "DEFAULT" );
2467
2468                 } else if( !strcmp( get_primitive( field ), "number" )) {
2469                         const char* numtype = get_datatype( field );
2470                         if( !strcmp( numtype, "INT8" )) {
2471                                 buffer_fadd( val_buf, "%lld", atoll( value ));
2472
2473                         } else if( !strcmp( numtype, "INT" )) {
2474                                 buffer_fadd( val_buf, "%d", atoi( value ));
2475
2476                         } else if( !strcmp( numtype, "NUMERIC" )) {
2477                                 buffer_fadd( val_buf, "%f", atof( value ));
2478                         }
2479                 } else {
2480                         if( dbi_conn_quote_string( writehandle, &value )) {
2481                                 OSRF_BUFFER_ADD( val_buf, value );
2482
2483                         } else {
2484                                 osrfLogError( OSRF_LOG_MARK, "%s: Error quoting string [%s]", modulename, value );
2485                                 osrfAppSessionStatus(
2486                                         ctx->session,
2487                                         OSRF_STATUS_INTERNALSERVERERROR,
2488                                         "osrfMethodException",
2489                                         ctx->request,
2490                                         "Error quoting string -- please see the error log for more details"
2491                                 );
2492                                 free( value );
2493                                 buffer_free( table_buf );
2494                                 buffer_free( col_buf );
2495                                 buffer_free( val_buf );
2496                                 osrfAppRespondComplete( ctx, NULL );
2497                                 return -1;
2498                         }
2499                 }
2500
2501                 free( value );
2502         }
2503
2504         osrfHashIteratorFree( field_itr );
2505
2506         OSRF_BUFFER_ADD_CHAR( col_buf, ')' );
2507         OSRF_BUFFER_ADD_CHAR( val_buf, ')' );
2508
2509         char* table_str = buffer_release( table_buf );
2510         char* col_str   = buffer_release( col_buf );
2511         char* val_str   = buffer_release( val_buf );
2512         growing_buffer* sql = buffer_init( 128 );
2513         buffer_fadd( sql, "%s %s %s;", table_str, col_str, val_str );
2514         free( table_str );
2515         free( col_str );
2516         free( val_str );
2517
2518         char* query = buffer_release( sql );
2519
2520         osrfLogDebug( OSRF_LOG_MARK, "%s: Insert SQL [%s]", modulename, query );
2521
2522         jsonObject* obj = NULL;
2523         int rc = 0;
2524
2525         dbi_result result = dbi_conn_query( writehandle, query );
2526         if( !result ) {
2527                 obj = jsonNewObject( NULL );
2528                 const char* msg;
2529                 int errnum = dbi_conn_error( writehandle, &msg );
2530                 osrfLogError(
2531                         OSRF_LOG_MARK,
2532                         "%s ERROR inserting %s object using query [%s]: %d %s",
2533                         modulename,
2534                         osrfHashGet(meta, "fieldmapper"),
2535                         query,
2536                         errnum,
2537                         msg ? msg : "(No description available)"
2538                 );
2539                 osrfAppSessionStatus(
2540                         ctx->session,
2541                         OSRF_STATUS_INTERNALSERVERERROR,
2542                         "osrfMethodException",
2543                         ctx->request,
2544                         "INSERT error -- please see the error log for more details"
2545                 );
2546                 if( !oilsIsDBConnected( writehandle ))
2547                         osrfAppSessionPanic( ctx->session );
2548                 rc = -1;
2549         } else {
2550                 dbi_result_free( result );
2551
2552                 char* id = oilsFMGetString( target, pkey );
2553                 if( !id ) {
2554                         unsigned long long new_id = dbi_conn_sequence_last( writehandle, seq );
2555                         growing_buffer* _id = buffer_init( 10 );
2556                         buffer_fadd( _id, "%lld", new_id );
2557                         id = buffer_release( _id );
2558                 }
2559
2560                 // Find quietness specification, if present
2561                 const char* quiet_str = NULL;
2562                 if( options ) {
2563                         const jsonObject* quiet_obj = jsonObjectGetKeyConst( options, "quiet" );
2564                         if( quiet_obj )
2565                                 quiet_str = jsonObjectGetString( quiet_obj );
2566                 }
2567
2568                 if( str_is_true( quiet_str )) {  // if quietness is specified
2569                         obj = jsonNewObject( id );
2570                 }
2571                 else {
2572
2573                         // Fetch the row that we just inserted, so that we can return it to the client
2574                         jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2575                         jsonObjectSetKey( where_clause, pkey, jsonNewObject( id ));
2576
2577                         int err = 0;
2578                         jsonObject* list = doFieldmapperSearch( ctx, meta, where_clause, NULL, &err );
2579                         if( err )
2580                                 rc = -1;
2581                         else
2582                                 obj = jsonObjectClone( jsonObjectGetIndex( list, 0 ));
2583
2584                         jsonObjectFree( list );
2585                         jsonObjectFree( where_clause );
2586                 }
2587
2588                 free( id );
2589         }
2590
2591         free( query );
2592         osrfAppRespondComplete( ctx, obj );
2593         jsonObjectFree( obj );
2594         return rc;
2595 }
2596
2597 /**
2598         @brief Implement the retrieve method.
2599         @param ctx Pointer to the method context.
2600         @param err Pointer through which to return an error code.
2601         @return If successful, a pointer to the result to be returned to the client;
2602         otherwise NULL.
2603
2604         From the method's class, fetch a row with a specified value in the primary key.  This
2605         method relies on the database design convention that a primary key consists of a single
2606         column.
2607
2608         Method parameters:
2609         - authkey (PCRUD only)
2610         - value of the primary key for the desired row, for building the WHERE clause
2611         - a JSON_HASH containing any other SQL clauses: select, join, etc.
2612
2613         Return to client: One row from the query.
2614 */
2615 int doRetrieve( osrfMethodContext* ctx ) {
2616         if(osrfMethodVerifyContext( ctx )) {
2617                 osrfLogError( OSRF_LOG_MARK, "Invalid method context" );
2618                 return -1;
2619         }
2620
2621         if( enforce_pcrud )
2622                 timeout_needs_resetting = 1;
2623
2624         int id_pos = 0;
2625         int order_pos = 1;
2626
2627         if( enforce_pcrud ) {
2628                 id_pos = 1;
2629                 order_pos = 2;
2630         }
2631
2632         // Get the class metadata
2633         osrfHash* class_def = osrfHashGet( (osrfHash*) ctx->method->userData, "class" );
2634
2635         // Get the value of the primary key, from a method parameter
2636         const jsonObject* id_obj = jsonObjectGetIndex( ctx->params, id_pos );
2637
2638         osrfLogDebug(
2639                 OSRF_LOG_MARK,
2640                 "%s retrieving %s object with primary key value of %s",
2641                 modulename,
2642                 osrfHashGet( class_def, "fieldmapper" ),
2643                 jsonObjectGetString( id_obj )
2644         );
2645
2646         // Build a WHERE clause based on the key value
2647         jsonObject* where_clause = jsonNewObjectType( JSON_HASH );
2648         jsonObjectSetKey(
2649                 where_clause,
2650                 osrfHashGet( class_def, "primarykey" ),  // name of key column
2651                 jsonObjectClone( id_obj )                // value of key column
2652         );
2653
2654         jsonObject* rest_of_query = jsonObjectGetIndex( ctx->params, order_pos );
2655
2656         // Do the query
2657         int err = 0;
2658         jsonObject* list = doFieldmapperSearch( ctx, class_def, where_clause, rest_of_query, &err );
2659
2660         jsonObjectFree( where_clause );
2661         if( err ) {
2662                 osrfAppRespondComplete( ctx, NULL );
2663                 return -1;
2664         }
2665
2666         jsonObject* obj = jsonObjectExtractIndex( list, 0 );
2667         jsonObjectFree( list );
2668
2669         if( enforce_pcrud ) {
2670                 // no result, skip this entirely
2671                 if(NULL != obj && !verifyObjectPCRUD( ctx, class_def, obj, 1 )) {
2672                         jsonObjectFree( obj );
2673
2674                         growing_buffer* msg = buffer_init( 128 );
2675                         OSRF_BUFFER_ADD( msg, modulename );
2676                         OSRF_BUFFER_ADD( msg, ": Insufficient permissions to retrieve object" );
2677
2678                         char* m = buffer_release( msg );
2679                         osrfAppSessionStatus( ctx->session, OSRF_STATUS_NOTALLOWED, "osrfMethodException",
2680                                         ctx->request, m );
2681                         free( m );
2682
2683                         osrfAppRespondComplete( ctx, NULL );
2684                         return -1;
2685                 }
2686         }
2687
2688         // doFieldmapperSearch() now does the responding for us
2689         //osrfAppRespondComplete( ctx, obj );
2690         osrfAppRespondComplete( ctx, NULL );
2691
2692         jsonObjectFree( obj );
2693         return 0;
2694 }
2695
2696 /**
2697         @brief Translate a numeric value to a string representation for the database.
2698         @param field Pointer to the IDL field definition.
2699         @param value Pointer to a jsonObject holding the value of a field.
2700         @return Pointer to a newly allocated string.
2701
2702         The input object is typically a JSON_NUMBER, but it may be a JSON_STRING as long as
2703         its contents are numeric.  A non-numeric string is likely to result in invalid SQL.
2704
2705         If the datatype of the receiving field is not numeric, wrap the value in quotes.
2706
2707         The calling code is responsible for freeing the resulting string by calling free().
2708 */
2709 static char* jsonNumberToDBString( osrfHash* field, const jsonObject* value ) {
2710         growing_buffer* val_buf = buffer_init( 32 );
2711
2712         // If the value is a number and the DB field is numeric, no quotes needed
2713         if( value->type == JSON_NUMBER && !strcmp( get_primitive( field ), "number") ) {
2714                 buffer_fadd( val_buf, jsonObjectGetString( value ) );
2715         } else {
2716                 // Presumably this was really intended to be a string, so quote it
2717                 char* str = jsonObjectToSimpleString( value );
2718                 if( dbi_conn_quote_string( dbhandle, &str )) {
2719                         OSRF_BUFFER_ADD( val_buf, str );
2720                         free( str );
2721                 } else {
2722                         osrfLogError( OSRF_LOG_MARK, "%s: Error quoting key string [%s]", modulename, str );
2723                         free( str );
2724                         buffer_free( val_buf );
2725                         return NULL;
2726                 }
2727         }
2728
2729         return buffer_release( val_buf );
2730 }
2731
2732 static char* searchINPredicate( const char* class_alias, osrfHash* field,
2733                 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2734
2735         char* field_transform = searchFieldTransform( class_alias, field, node );
2736         if( ! field_transform )
2737                 return NULL;
2738
2739
2740         char* in_list = searchINList(field, node, op, ctx);
2741         if( !in_list )
2742                 return NULL;
2743
2744         growing_buffer* sql_buf = buffer_init( 32 );
2745         buffer_add( sql_buf, field_transform );
2746
2747         if( !op ) {
2748                 buffer_add( sql_buf, " IN (" );
2749         } else if( !strcasecmp( op,"not in" )) {
2750                 buffer_add( sql_buf, " NOT IN (" );
2751         } else {
2752                 buffer_add( sql_buf, " IN (" );
2753         }
2754
2755         buffer_add( sql_buf, in_list);
2756         OSRF_BUFFER_ADD_CHAR( sql_buf, ')' );
2757
2758         free(field_transform);
2759         free(in_list);
2760
2761         return buffer_release( sql_buf );
2762 }
2763
2764 static char* searchINList( osrfHash* field,
2765                 jsonObject* node, const char* op, osrfMethodContext* ctx ) {
2766         growing_buffer* sql_buf = buffer_init( 32 );
2767
2768         const jsonObject* local_node = node;
2769         if( local_node->type == JSON_HASH ) { // may be the case that the node tranforms the field
2770                 // if so, grab the "value" property
2771                 local_node = jsonObjectGetKeyConst( node, "value" );
2772                 if (!local_node) local_node = node;
2773         }
2774
2775         if( local_node->type == JSON_HASH ) {
2776                 // subquery predicate
2777
2778                 char* subpred = buildQuery( ctx, (jsonObject*) local_node, SUBSELECT );
2779                 if( ! subpred ) {
2780                         buffer_free( sql_buf );
2781                         return NULL;
2782                 }
2783
2784                 buffer_add( sql_buf, subpred );
2785                 free( subpred );
2786
2787         } else if( local_node->type == JSON_ARRAY ) {
2788                 // literal value list
2789                 int in_item_index = 0;
2790                 int in_item_first = 1;
2791                 const jsonObject* in_item;
2792                 while( (in_item = jsonObjectGetIndex( local_node, in_item_index++ )) ) {
2793
2794                         if( in_item_first )
2795                                 in_item_first = 0;
2796                         else
2797                                 buffer_add( sql_buf, ", " );
2798
2799                         // Sanity check
2800                         if( in_item->type != JSON_STRING && in_item->type != JSON_NUMBER ) {
2801                                 osrfLogError( OSRF_LOG_MARK,
2802                                                 "%s: Expected string or number within IN list; found %s",
2803                                                 modulename, json_type( in_item->type ) );
2804                                 buffer_free( sql_buf );
2805                                 return NULL;
2806                         }
2807
2808                         // Append the literal value -- quoted if not a number
2809                         if( JSON_NUMBER == in_item->type ) {
2810                                 char* val = jsonNumberToDBString( field, in_item );
2811                                 OSRF_BUFFER_ADD( sql_buf, val );
2812                                 free( val );
2813
2814                         } else if( !strcmp( get_primitive( field ), "number" )) {
2815                                 char* val = jsonNumberToDBString( field, in_item );
2816                                 OSRF_BUFFER_ADD( sql_buf, val );
2817                                 free( val );
2818
2819                         } else {
2820                                 char* key_string = jsonObjectToSimpleString( in_item );
2821                                 if( dbi_conn_quote_string( dbhandle, &key_string )) {
2822                                         OSRF_BUFFER_ADD( sql_buf, key_string );
2823                                         free( key_string );
2824                                 } else {
2825                                         osrfLogError( OSRF_LOG_MARK,
2826                                                         "%s: Error quoting key string [%s]", modulename, key_string );
2827                                         free( key_string );
2828                                         buffer_free( sql_buf );
2829                                         return NULL;
2830                                 }
2831                         }
2832                 }
2833
2834                 if( in_item_first ) {
2835                         osrfLogError(OSRF_LOG_MARK, "%s: Empty IN list", modulename );
2836                         buffer_free( sql_buf );
2837                         return NULL;
2838                 }
2839         } else {
2840                 osrfLogError( OSRF_LOG_MARK, "%s: Expected object or array for IN clause; found %s",
2841                         modulename, json_type( local_node->type ));
2842                 buffer_free( sql_buf );
2843                 return NULL;
2844         }
2845
2846         return buffer_release( sql_buf );
2847 }
2848
2849 // Receive a JSON_ARRAY representing a function call.  The first
2850 // entry in the array is the function name.  The rest are parameters.
2851 static char* searchValueTransform( const jsonObject* array ) {
2852
2853         if( array->size < 1 ) {
2854                 osrfLogError( OSRF_LOG_MARK, "%s: Empty array for value transform", modulename );
2855                 return NULL;
2856         }
2857
2858         // Get the function name
2859         jsonObject* func_item = jsonObjectGetIndex( array, 0 );
2860         if( func_item->type != JSON_STRING ) {
2861                 osrfLogError( OSRF_LOG_MARK, "%s: Error: expected function name, found %s",
2862                         modulename, json_type( func_item->type ));
2863                 return NULL;
2864         }
2865
2866         growing_buffer* sql_buf = buffer_init( 32 );
2867
2868         OSRF_BUFFER_ADD( sql_buf, jsonObjectGetString( func_item ) );
2869         OSRF_BUFFER_ADD( sql_buf, "( " );
2870
2871         // Get the parameters
2872         int func_item_index = 1;   // We already grabbed the zeroth entry
2873         while( (func_item = jsonObjectGetIndex( array, func_item_index++ )) ) {
2874
2875                 // Add a separator comma, if we need one
2876                 if( func_item_index > 2 )
2877                         buffer_add( sql_buf, ", " );
2878
2879                 // Add the current parameter
2880                 if( func_item->type == JSON_NULL ) {
2881                         buffer_add( sql_buf, "NULL" );
2882                 } else {
2883                         if( func_item->type == JSON_BOOL ) {
2884                                 if( jsonBoolIsTrue(func_item) ) {
2885                                         buffer_add( sql_buf, "TRUE" );
2886                                 } else {
2887                                         buffer_add( sql_buf, "FALSE" );
2888                                 }
2889                         } else {
2890                                 char* val = jsonObjectToSimpleString( func_item );
2891                                 if( dbi_conn_quote_string( dbhandle, &val )) {
2892                                         OSRF_BUFFER_ADD( sql_buf, val );
2893                                         free( val );
2894                                 } else {
2895                                         osrfLogError( OSRF_LOG_MARK, 
2896                                                 "%s: Error quoting key string [%s]", modulename, val );
2897                                         buffer_free( sql_buf );
2898                                         free( val );
2899                                         return NULL;
2900                                 }
2901                         }
2902                 }
2903         }