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