Split the cstore servers into several pieces.
[working/Evergreen.git] / Open-ILS / src / c-apps / oils_cstore.c
1 /**
2         @file oils_cstore.c
3         @brief As a server, perform database operations at the request of clients.
4 */
5
6 #include <stdlib.h>
7 #include <string.h>
8 #include <ctype.h>
9 #include <dbi/dbi.h>
10 #include "opensrf/utils.h"
11 #include "opensrf/log.h"
12 #include "opensrf/osrf_application.h"
13 #include "openils/oils_utils.h"
14 #include "openils/oils_sql.h"
15
16 static dbi_conn writehandle; /* our MASTER db connection */
17 static dbi_conn dbhandle; /* our CURRENT db connection */
18 //static osrfHash * readHandles;
19
20 static int max_flesh_depth = 100;
21
22 static const int enforce_pcrud = 0;     // Boolean
23 static const char modulename[] = "open-ils.cstore";
24
25 /**
26         @brief Disconnect from the database.
27
28         This function is called when the server drone is about to terminate.
29 */
30 void osrfAppChildExit() {
31         osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
32
33         int same = 0;
34         if (writehandle == dbhandle)
35                 same = 1;
36
37         if (writehandle) {
38                 dbi_conn_query(writehandle, "ROLLBACK;");
39                 dbi_conn_close(writehandle);
40                 writehandle = NULL;
41         }
42         if (dbhandle && !same)
43                 dbi_conn_close(dbhandle);
44
45         // XXX add cleanup of readHandles whenever that gets used
46
47         return;
48 }
49
50 /**
51         @brief Initialize the application.
52         @return Zero if successful, or non-zero if not.
53
54         Load the IDL file into an internal data structure for future reference.  Each non-virtual
55         class in the IDL corresponds to a table or view in the database, or to a subquery defined
56         in the IDL.  Ignore all virtual tables and virtual fields.
57
58         Register a number of methods, some of them general-purpose and others specific for
59         particular classes.
60
61         The name of the application is given by the MODULENAME macro, whose value depends on
62         conditional compilation.  The method names also incorporate MODULENAME, followed by a
63         dot, as a prefix.
64
65         The general-purpose methods are as follows (minus their MODULENAME prefixes):
66
67         - json_query
68         - transaction.begin
69         - transaction.commit
70         - transaction.rollback
71         - savepoint.set
72         - savepoint.release
73         - savepoint.rollback
74
75         For each non-virtual class, create up to eight class-specific methods:
76
77         - create    (not for readonly classes)
78         - retrieve
79         - update    (not for readonly classes)
80         - delete    (not for readonly classes
81         - search    (atomic and non-atomic versions)
82         - id_list   (atomic and non-atomic versions)
83
84         The full method names follow the pattern "MODULENAME.direct.XXX.method_type", where XXX
85         is the fieldmapper name from the IDL, with every run of one or more consecutive colons
86         replaced by a period.  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 CStore 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         oilsSetSQLOptions( modulename, enforce_pcrud );
100
101         growing_buffer* method_name = buffer_init(64);
102
103         // Generic search thingy
104         buffer_add( method_name, modulename );
105         buffer_add( method_name, ".json_query" );
106         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
107                 "doJSONSearch", "", 1, OSRF_METHOD_STREAMING );
108
109         // first we register all the transaction and savepoint methods
110         buffer_reset(method_name);
111         OSRF_BUFFER_ADD(method_name, modulename );
112         OSRF_BUFFER_ADD(method_name, ".transaction.begin");
113         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
114                         "beginTransaction", "", 0, 0 );
115
116         buffer_reset(method_name);
117         OSRF_BUFFER_ADD(method_name, modulename );
118         OSRF_BUFFER_ADD(method_name, ".transaction.commit");
119         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
120                         "commitTransaction", "", 0, 0 );
121
122         buffer_reset(method_name);
123         OSRF_BUFFER_ADD(method_name, modulename );
124         OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
125         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
126                         "rollbackTransaction", "", 0, 0 );
127
128         buffer_reset(method_name);
129         OSRF_BUFFER_ADD(method_name, modulename );
130         OSRF_BUFFER_ADD(method_name, ".savepoint.set");
131         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
132                         "setSavepoint", "", 1, 0 );
133
134         buffer_reset(method_name);
135         OSRF_BUFFER_ADD(method_name, modulename );
136         OSRF_BUFFER_ADD(method_name, ".savepoint.release");
137         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
138                         "releaseSavepoint", "", 1, 0 );
139
140         buffer_reset(method_name);
141         OSRF_BUFFER_ADD(method_name, modulename );
142         OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
143         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
144                         "rollbackSavepoint", "", 1, 0 );
145
146         static const char* global_method[] = {
147                 "create",
148                 "retrieve",
149                 "update",
150                 "delete",
151                 "search",
152                 "id_list"
153         };
154         const int global_method_count
155                 = sizeof( global_method ) / sizeof ( global_method[0] );
156
157         unsigned long class_count = osrfHashGetCount( oilsIDL() );
158         osrfLogDebug(OSRF_LOG_MARK, "%lu classes loaded", class_count );
159         osrfLogDebug(OSRF_LOG_MARK,
160                 "At most %lu methods will be generated",
161                 (unsigned long) (class_count * global_method_count) );
162
163         osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
164         osrfHash* idlClass = NULL;
165
166         // For each class in the IDL...
167         while( (idlClass = osrfHashIteratorNext( class_itr ) ) ) {
168
169                 const char* classname = osrfHashIteratorKey( class_itr );
170                 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
171
172                 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), modulename )) {
173                         osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on",
174                                         modulename, classname);
175                         continue;
176                 }
177
178                 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
179                         osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
180                         continue;
181                 }
182
183                 // Look up some other attributes of the current class
184                 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
185                 if( !idlClass_fieldmapper ) {
186                         osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL",
187                                         classname );
188                         continue;
189                 }
190
191                 const char* readonly = osrfHashGet(idlClass, "readonly");
192
193                 int i;
194                 for( i = 0; i < global_method_count; ++i ) {  // for each global method
195                         const char* method_type = global_method[ i ];
196                         osrfLogDebug(OSRF_LOG_MARK,
197                                 "Using files to build %s class methods for %s", method_type, classname);
198
199                         // No create, update, or delete methods for a readonly class
200                         if ( str_is_true( readonly )
201                                 && ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd') )
202                                 continue;
203
204                         buffer_reset( method_name );
205
206                         // Build the method name: MODULENAME.MODULENAME.direct.XXX.method_type
207                         // where XXX is the fieldmapper name from the IDL, with every run of
208                         // one or more consecutive colons replaced by a period.
209                         char* st_tmp = NULL;
210                         char* part = NULL;
211                         char* _fm = strdup( idlClass_fieldmapper );
212                         part = strtok_r(_fm, ":", &st_tmp);
213
214                         buffer_fadd(method_name, "%s.direct.%s", modulename, part);
215
216                         while ((part = strtok_r(NULL, ":", &st_tmp))) {
217                                 OSRF_BUFFER_ADD_CHAR(method_name, '.');
218                                 OSRF_BUFFER_ADD(method_name, part);
219                         }
220                         OSRF_BUFFER_ADD_CHAR(method_name, '.');
221                         OSRF_BUFFER_ADD(method_name, method_type);
222                         free(_fm);
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.
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 }