]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/c-apps/oils_pcrud.c
02653c8c339d6c12b3878a9487893b7e045c737e
[working/Evergreen.git] / Open-ILS / src / c-apps / oils_pcrud.c
1 /**
2         @file oils_pcrud.c
3         @brief As a server, perform database operations at the request of clients.
4
5         This server is similar to the cstore and reporter-store servers,
6         except that it enforces a permissions scheme.
7 */
8
9 #include <stdlib.h>
10 #include <string.h>
11 #include <ctype.h>
12 #include <dbi/dbi.h>
13 #include "opensrf/utils.h"
14 #include "opensrf/log.h"
15 #include "opensrf/osrf_application.h"
16 #include "openils/oils_utils.h"
17 #include "openils/oils_sql.h"
18
19 static dbi_conn writehandle; /* our MASTER db connection */
20 static dbi_conn dbhandle; /* our CURRENT db connection */
21 //static osrfHash * readHandles;
22
23 static const int enforce_pcrud = 1;     // Boolean
24 static const char modulename[] = "open-ils.pcrud";
25
26 /**
27         @brief Disconnect from the database.
28
29         This function is called when the server drone is about to terminate.
30 */
31 void osrfAppChildExit() {
32         osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
33
34         int same = 0;
35         if (writehandle == dbhandle)
36                 same = 1;
37
38         if (writehandle) {
39                 dbi_conn_query(writehandle, "ROLLBACK;");
40                 dbi_conn_close(writehandle);
41                 writehandle = NULL;
42         }
43         if (dbhandle && !same)
44                 dbi_conn_close(dbhandle);
45
46         // XXX add cleanup of readHandles whenever that gets used
47
48         return;
49 }
50
51 /**
52         @brief Initialize the application.
53         @return Zero if successful, or non-zero if not.
54
55         Load the IDL file into an internal data structure for future reference.  Each non-virtual
56         class in the IDL corresponds to a table or view in the database, or to a subquery defined
57         in the IDL.  Ignore all virtual tables and virtual fields.
58
59         Register a number of methods, some of them general-purpose and others specific for
60         particular classes.
61
62         The name of the application is given by the MODULENAME macro, whose value depends on
63         conditional compilation.  The method names also incorporate MODULENAME, followed by a
64         dot, as a prefix.  Some methods are registered or not registered depending on whether
65         the IDL includes permacrud entries for the class and method.
66
67         The general-purpose methods are as follows (minus their MODULENAME prefixes):
68
69         - transaction.begin
70         - transaction.commit
71         - transaction.rollback
72         - savepoint.set
73         - savepoint.release
74         - savepoint.rollback
75
76         For each non-virtual class, create up to eight class-specific methods:
77
78         - create    (not for readonly classes)
79         - retrieve
80         - update    (not for readonly classes)
81         - delete    (not for readonly classes
82         - search    (atomic and non-atomic versions)
83         - id_list   (atomic and non-atomic versions)
84
85         The full method names follow the pattern "MODULENAME.method_type.classname".
86         In addition, the names of atomic methods have a suffix of ".atomic".
87
88         This function is called when the registering the application, and is executed by the
89         listener before spawning the drones.
90 */
91 int osrfAppInitialize() {
92
93         osrfLogInfo(OSRF_LOG_MARK, "Initializing the PCRUD Server...");
94         osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
95
96         if ( !oilsIDLInit( osrf_settings_host_value( "/IDL" )))
97                 return 1; /* return non-zero to indicate error */
98
99         char* md = osrf_settings_host_value(
100                 "/apps/%s/app_settings/max_query_recursion", modulename );
101         int max_flesh_depth = 100;
102         if( md )
103                 max_flesh_depth = atoi( md );
104         if( max_flesh_depth < 0 )
105                 max_flesh_depth = 1;
106         else if( max_flesh_depth > 1000 )
107                 max_flesh_depth = 1000;
108
109         oilsSetSQLOptions( modulename, enforce_pcrud, max_flesh_depth );
110
111         growing_buffer* method_name = buffer_init(64);
112
113         // first we register all the transaction and savepoint methods
114         buffer_reset(method_name);
115         OSRF_BUFFER_ADD(method_name, modulename );
116         OSRF_BUFFER_ADD(method_name, ".transaction.begin");
117         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
118                         "beginTransaction", "", 0, 0 );
119
120         buffer_reset(method_name);
121         OSRF_BUFFER_ADD(method_name, modulename );
122         OSRF_BUFFER_ADD(method_name, ".transaction.commit");
123         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
124                         "commitTransaction", "", 0, 0 );
125
126         buffer_reset(method_name);
127         OSRF_BUFFER_ADD(method_name, modulename );
128         OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
129         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
130                         "rollbackTransaction", "", 0, 0 );
131
132         buffer_reset(method_name);
133         OSRF_BUFFER_ADD(method_name, modulename );
134         OSRF_BUFFER_ADD(method_name, ".savepoint.set");
135         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
136                         "setSavepoint", "", 1, 0 );
137
138         buffer_reset(method_name);
139         OSRF_BUFFER_ADD(method_name, modulename );
140         OSRF_BUFFER_ADD(method_name, ".savepoint.release");
141         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
142                         "releaseSavepoint", "", 1, 0 );
143
144         buffer_reset(method_name);
145         OSRF_BUFFER_ADD(method_name, modulename );
146         OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
147         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
148                         "rollbackSavepoint", "", 1, 0 );
149
150         static const char* global_method[] = {
151                 "create",
152                 "retrieve",
153                 "update",
154                 "delete",
155                 "search",
156                 "id_list"
157         };
158         const int global_method_count
159                 = sizeof( global_method ) / sizeof ( global_method[0] );
160
161         unsigned long class_count = osrfHashGetCount( oilsIDL() );
162         osrfLogDebug(OSRF_LOG_MARK, "%lu classes loaded", class_count );
163         osrfLogDebug(OSRF_LOG_MARK,
164                 "At most %lu methods will be generated",
165                 (unsigned long) (class_count * global_method_count) );
166
167         osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
168         osrfHash* idlClass = NULL;
169
170         // For each class in the IDL...
171         while( (idlClass = osrfHashIteratorNext( class_itr ) ) ) {
172
173                 const char* classname = osrfHashIteratorKey( class_itr );
174                 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
175
176                 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), modulename )) {
177                         osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on",
178                                         modulename, classname);
179                         continue;
180                 }
181
182                 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
183                         osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
184                         continue;
185                 }
186
187                 // Look up some other attributes of the current class
188                 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
189                 if( !idlClass_fieldmapper ) {
190                         osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL",
191                                         classname );
192                         continue;
193                 }
194
195                 osrfHash* idlClass_permacrud = NULL;
196
197                 // Ignore classes with no permacrud section
198                 idlClass_permacrud = osrfHashGet( idlClass, "permacrud" );
199                 if( !idlClass_permacrud ) {
200                         osrfLogDebug( OSRF_LOG_MARK,
201                                 "Skipping class \"%s\"; no permacrud in IDL", classname );
202                         continue;
203                 }
204
205                 const char* readonly = osrfHashGet( idlClass, "readonly" );
206
207                 int i;
208                 for( i = 0; i < global_method_count; ++i ) {  // for each global method
209                         const char* method_type = global_method[ i ];
210                         osrfLogDebug(OSRF_LOG_MARK,
211                                 "Using files to build %s class methods for %s", method_type, classname);
212
213                         // Treat "id_list" or "search" as forms of "retrieve"
214                         const char* tmp_method = method_type;
215                         if ( *tmp_method == 'i' || *tmp_method == 's') {  // "id_list" or "search"
216                                 tmp_method = "retrieve";
217                         }
218                         // Skip this method if there is no permacrud entry for it
219                         if (!osrfHashGet( idlClass_permacrud, tmp_method ))
220                                 continue;
221
222                         // No create, update, or delete methods for a readonly class
223                         if ( str_is_true( readonly )
224                                 && ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd') )
225                                 continue;
226
227                         buffer_reset( method_name );
228
229                         // Build the method name: MODULENAME.method_type.classname
230                         buffer_fadd(method_name, "%s.%s.%s", modulename, method_type, classname);
231
232                         // For an id_list or search method we specify the OSRF_METHOD_STREAMING option.
233                         // The consequence is that we implicitly create an atomic method in addition to
234                         // the usual non-atomic method.
235                         int flags = 0;
236                         if (*method_type == 'i' || *method_type == 's') {  // id_list or search
237                                 flags = flags | OSRF_METHOD_STREAMING;
238                         }
239
240                         osrfHash* method_meta = osrfNewHash();
241                         osrfHashSet( method_meta, idlClass, "class");
242                         osrfHashSet( method_meta, buffer_data( method_name ), "methodname" );
243                         osrfHashSet( method_meta, strdup(method_type), "methodtype" );
244
245                         // Register the method, with a pointer to an osrfHash to tell the method
246                         // its name, type, and class.
247                         osrfAppRegisterExtendedMethod(
248                                 modulename,
249                                 OSRF_BUFFER_C_STR( method_name ),
250                                 "dispatchCRUDMethod",
251                                 "",
252                                 1,
253                                 flags,
254                                 (void*)method_meta
255                         );
256
257                 } // end for each global method
258         } // end for each class in IDL
259
260         buffer_free( method_name );
261         osrfHashIteratorFree( class_itr );
262
263         return 0;
264 }
265
266 /**
267         @brief Initialize a server drone.
268         @return Zero if successful, -1 if not.
269
270         Connect to the database.  For each non-virtual class in the IDL, execute a dummy "SELECT * "
271         query to get the datatype of each column.  Record the datatypes in the loaded IDL.
272
273         This function is called by a server drone shortly after it is spawned by the listener.
274 */
275 int osrfAppChildInit() {
276
277         osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
278         dbi_initialize(NULL);
279         osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
280
281         char* driver = osrf_settings_host_value("/apps/%s/app_settings/driver", modulename );
282         char* user   = osrf_settings_host_value("/apps/%s/app_settings/database/user", modulename );
283         char* host   = osrf_settings_host_value("/apps/%s/app_settings/database/host", modulename );
284         char* port   = osrf_settings_host_value("/apps/%s/app_settings/database/port", modulename );
285         char* db     = osrf_settings_host_value("/apps/%s/app_settings/database/db", modulename );
286         char* pw     = osrf_settings_host_value("/apps/%s/app_settings/database/pw", modulename );
287
288         osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
289         writehandle = dbi_conn_new(driver);
290
291         if(!writehandle) {
292                 osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
293                 return -1;
294         }
295         osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
296
297         osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database.  host=%s, "
298                         "port=%s, user=%s, db=%s", modulename, host, port, user, db );
299
300         if(host) dbi_conn_set_option(writehandle, "host", host );
301         if(port) dbi_conn_set_option_numeric( writehandle, "port", atoi(port) );
302         if(user) dbi_conn_set_option(writehandle, "username", user);
303         if(pw)   dbi_conn_set_option(writehandle, "password", pw );
304         if(db)   dbi_conn_set_option(writehandle, "dbname", db );
305
306         free(user);
307         free(host);
308         free(port);
309         free(db);
310         free(pw);
311
312         const char* err;
313         if (dbi_conn_connect(writehandle) < 0) {
314                 sleep(1);
315                 if (dbi_conn_connect(writehandle) < 0) {
316                         dbi_conn_error(writehandle, &err);
317                         osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
318                         return -1;
319                 }
320         }
321
322         oilsSetDBConnection( writehandle );
323         osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", modulename );
324
325         // Add datatypes from database to the fields in the IDL
326         if( oilsExtendIDL() ) {
327                 osrfLogError( OSRF_LOG_MARK, "Error extending the IDL" );
328                 return -1;
329         }
330         else
331                 return 0;
332 }
333
334 /**
335         @brief Implement the class-specific methods.
336         @param ctx Pointer to the method context.
337         @return Zero if successful, or -1 if not.
338
339         Branch on the method type: create, retrieve, update, delete, search, or id_list.
340
341         The method parameters and the type of value returned to the client depend on the method
342         type.  However, for all PCRUD methods, the first method parameter is an authkey.
343 */
344 int dispatchCRUDMethod( osrfMethodContext* ctx ) {
345
346         // Get the method type, then can branch on it
347         osrfHash* method_meta = (osrfHash*) ctx->method->userData;
348         const char* methodtype = osrfHashGet( method_meta, "methodtype" );
349
350         if( !strcmp( methodtype, "create" ))
351                 return doCreate( ctx );
352         else if( !strcmp(methodtype, "retrieve" ))
353                 return doRetrieve( ctx );
354         else if( !strcmp(methodtype, "update" ))
355                 return doUpdate( ctx );
356         else if( !strcmp(methodtype, "delete" ))
357                 return doDelete( ctx );
358         else if( !strcmp(methodtype, "search" ))
359                 return doSearch( ctx );
360         else if( !strcmp(methodtype, "id_list" ))
361                 return doIdList( ctx );
362         else {
363                 osrfAppRespondComplete( ctx, NULL );      // should be unreachable...
364                 return 0;
365         }
366 }