]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/c-apps/oils_rstore.c
Use the oilsConnectDB function to connect to the database, instead
[Evergreen.git] / Open-ILS / src / c-apps / oils_rstore.c
1 /**
2         @file oils_rstore.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 const int enforce_pcrud = 0;     // Boolean
21 static const char modulename[] = "open-ils.reporter-store";
22
23 /**
24         @brief Disconnect from the database.
25
26         This function is called when the server drone is about to terminate.
27 */
28 void osrfAppChildExit() {
29         osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
30
31         int same = 0;
32         if (writehandle == dbhandle)
33                 same = 1;
34
35         if (writehandle) {
36                 dbi_conn_query(writehandle, "ROLLBACK;");
37                 dbi_conn_close(writehandle);
38                 writehandle = NULL;
39         }
40         if (dbhandle && !same)
41                 dbi_conn_close(dbhandle);
42
43         // XXX add cleanup of readHandles whenever that gets used
44
45         return;
46 }
47
48 /**
49         @brief Initialize the application.
50         @return Zero if successful, or non-zero if not.
51
52         Load the IDL file into an internal data structure for future reference.  Each non-virtual
53         class in the IDL corresponds to a table or view in the database, or to a subquery defined
54         in the IDL.  Ignore all virtual tables and virtual fields.
55
56         Register a number of methods, some of them general-purpose and others specific for
57         particular classes.
58
59         The name of the application is given by the MODULENAME macro, whose value depends on
60         conditional compilation.  The method names also incorporate MODULENAME, followed by a
61         dot, as a prefix.
62
63         The general-purpose methods are as follows (minus their MODULENAME prefixes):
64
65         - json_query
66         - transaction.begin
67         - transaction.commit
68         - transaction.rollback
69         - savepoint.set
70         - savepoint.release
71         - savepoint.rollback
72
73         For each non-virtual class, create up to eight class-specific methods:
74
75         - create    (not for readonly classes)
76         - retrieve
77         - update    (not for readonly classes)
78         - delete    (not for readonly classes
79         - search    (atomic and non-atomic versions)
80         - id_list   (atomic and non-atomic versions)
81
82         The full method names follow the pattern "MODULENAME.direct.XXX.method_type", where XXX
83         is the fieldmapper name from the IDL, with every run of one or more consecutive colons
84         replaced by a period.  In addition, the names of atomic methods have a suffix of ".atomic".
85
86         This function is called when the registering the application, and is executed by the
87         listener before spawning the drones.
88 */
89 int osrfAppInitialize() {
90
91         osrfLogInfo(OSRF_LOG_MARK, "Initializing the RStore Server...");
92         osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
93
94         if ( !oilsIDLInit( osrf_settings_host_value( "/IDL" )))
95                 return 1; /* return non-zero to indicate error */
96
97         char* md = osrf_settings_host_value(
98                 "/apps/%s/app_settings/max_query_recursion", modulename );
99         int max_flesh_depth = 100;
100         if( md )
101                 max_flesh_depth = atoi( md );
102         if( max_flesh_depth < 0 )
103                 max_flesh_depth = 1;
104         else if( max_flesh_depth > 1000 )
105                 max_flesh_depth = 1000;
106
107         oilsSetSQLOptions( modulename, enforce_pcrud, max_flesh_depth );
108
109         growing_buffer* method_name = buffer_init(64);
110
111         // Generic search thingy
112         buffer_add( method_name, modulename );
113         buffer_add( method_name, ".json_query" );
114         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
115                 "doJSONSearch", "", 1, OSRF_METHOD_STREAMING );
116
117         // first we register all the transaction and savepoint methods
118         buffer_reset(method_name);
119         OSRF_BUFFER_ADD(method_name, modulename );
120         OSRF_BUFFER_ADD(method_name, ".transaction.begin");
121         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
122                         "beginTransaction", "", 0, 0 );
123
124         buffer_reset(method_name);
125         OSRF_BUFFER_ADD(method_name, modulename );
126         OSRF_BUFFER_ADD(method_name, ".transaction.commit");
127         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
128                         "commitTransaction", "", 0, 0 );
129
130         buffer_reset(method_name);
131         OSRF_BUFFER_ADD(method_name, modulename );
132         OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
133         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
134                         "rollbackTransaction", "", 0, 0 );
135
136         buffer_reset(method_name);
137         OSRF_BUFFER_ADD(method_name, modulename );
138         OSRF_BUFFER_ADD(method_name, ".savepoint.set");
139         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
140                         "setSavepoint", "", 1, 0 );
141
142         buffer_reset(method_name);
143         OSRF_BUFFER_ADD(method_name, modulename );
144         OSRF_BUFFER_ADD(method_name, ".savepoint.release");
145         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
146                         "releaseSavepoint", "", 1, 0 );
147
148         buffer_reset(method_name);
149         OSRF_BUFFER_ADD(method_name, modulename );
150         OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
151         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
152                         "rollbackSavepoint", "", 1, 0 );
153
154         static const char* global_method[] = {
155                 "create",
156                 "retrieve",
157                 "update",
158                 "delete",
159                 "search",
160                 "id_list"
161         };
162         const int global_method_count
163                 = sizeof( global_method ) / sizeof ( global_method[0] );
164
165         unsigned long class_count = osrfHashGetCount( oilsIDL() );
166         osrfLogDebug(OSRF_LOG_MARK, "%lu classes loaded", class_count );
167         osrfLogDebug(OSRF_LOG_MARK,
168                 "At most %lu methods will be generated",
169                 (unsigned long) (class_count * global_method_count) );
170
171         osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
172         osrfHash* idlClass = NULL;
173
174         // For each class in the IDL...
175         while( (idlClass = osrfHashIteratorNext( class_itr ) ) ) {
176
177                 const char* classname = osrfHashIteratorKey( class_itr );
178                 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
179
180                 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), modulename )) {
181                         osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on",
182                                         modulename, classname);
183                         continue;
184                 }
185
186                 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
187                         osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
188                         continue;
189                 }
190
191                 // Look up some other attributes of the current class
192                 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
193                 if( !idlClass_fieldmapper ) {
194                         osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL",
195                                         classname );
196                         continue;
197                 }
198
199                 const char* readonly = osrfHashGet(idlClass, "readonly");
200
201                 int i;
202                 for( i = 0; i < global_method_count; ++i ) {  // for each global method
203                         const char* method_type = global_method[ i ];
204                         osrfLogDebug(OSRF_LOG_MARK,
205                                 "Using files to build %s class methods for %s", method_type, classname);
206
207                         // No create, update, or delete methods for a readonly class
208                         if ( str_is_true( readonly )
209                                 && ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd') )
210                                 continue;
211
212                         buffer_reset( method_name );
213
214                         // Build the method name: MODULENAME.MODULENAME.direct.XXX.method_type
215                         // where XXX is the fieldmapper name from the IDL, with every run of
216                         // one or more consecutive colons replaced by a period.
217                         char* st_tmp = NULL;
218                         char* part = NULL;
219                         char* _fm = strdup( idlClass_fieldmapper );
220                         part = strtok_r(_fm, ":", &st_tmp);
221
222                         buffer_fadd(method_name, "%s.direct.%s", modulename, part);
223
224                         while ((part = strtok_r(NULL, ":", &st_tmp))) {
225                                 OSRF_BUFFER_ADD_CHAR(method_name, '.');
226                                 OSRF_BUFFER_ADD(method_name, part);
227                         }
228                         OSRF_BUFFER_ADD_CHAR(method_name, '.');
229                         OSRF_BUFFER_ADD(method_name, method_type);
230                         free(_fm);
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         writehandle = oilsConnectDB( modulename );
278         if( !writehandle )
279                 return -1;
280
281         oilsSetDBConnection( writehandle );
282
283         // Add datatypes from database to the fields in the IDL
284         if( oilsExtendIDL() ) {
285                 osrfLogError( OSRF_LOG_MARK, "Error extending the IDL" );
286                 return -1;
287         }
288         else
289                 return 0;
290 }
291
292 /**
293         @brief Implement the class-specific methods.
294         @param ctx Pointer to the method context.
295         @return Zero if successful, or -1 if not.
296
297         Branch on the method type: create, retrieve, update, delete, search, or id_list.
298
299         The method parameters and the type of value returned to the client depend on the method
300         type.
301 */
302 int dispatchCRUDMethod( osrfMethodContext* ctx ) {
303
304         // Get the method type, then can branch on it
305         osrfHash* method_meta = (osrfHash*) ctx->method->userData;
306         const char* methodtype = osrfHashGet( method_meta, "methodtype" );
307
308         if( !strcmp( methodtype, "create" ))
309                 return doCreate( ctx );
310         else if( !strcmp(methodtype, "retrieve" ))
311                 return doRetrieve( ctx );
312         else if( !strcmp(methodtype, "update" ))
313                 return doUpdate( ctx );
314         else if( !strcmp(methodtype, "delete" ))
315                 return doDelete( ctx );
316         else if( !strcmp(methodtype, "search" ))
317                 return doSearch( ctx );
318         else if( !strcmp(methodtype, "id_list" ))
319                 return doIdList( ctx );
320         else {
321                 osrfAppRespondComplete( ctx, NULL );      // should be unreachable...
322                 return 0;
323         }
324 }