]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/c-apps/oils_cstore.c
Whitespace. gah.
[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 const int enforce_pcrud = 0;     // Boolean
21 static const char modulename[] = "open-ils.cstore";
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( void ) {
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( void ) {
90
91         osrfLogInfo(OSRF_LOG_MARK, "Initializing the CStore Server...");
92         osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
93
94         // Load the IDL into memory
95         if ( !oilsIDLInit( osrf_settings_host_value( "/IDL" )))
96                 return 1; /* return non-zero to indicate error */
97
98         // Open the database temporarily.  Look up the datatypes of all
99         // the non-virtual fields and record them with the IDL data.
100         dbi_conn handle = oilsConnectDB( modulename );
101         if( !handle )
102                 return -1;
103         else if( oilsExtendIDL( handle )) {
104                 osrfLogError( OSRF_LOG_MARK, "Error extending the IDL" );
105                 return -1;
106         }
107         dbi_conn_close( handle );
108
109         // Get the maximum flesh depth from the settings
110         char* md = osrf_settings_host_value(
111                 "/apps/%s/app_settings/max_query_recursion", modulename );
112         int max_flesh_depth = 100;
113         if( md )
114                 max_flesh_depth = atoi( md );
115         if( max_flesh_depth < 0 )
116                 max_flesh_depth = 1;
117         else if( max_flesh_depth > 1000 )
118                 max_flesh_depth = 1000;
119
120         oilsSetSQLOptions( modulename, enforce_pcrud, max_flesh_depth );
121
122         // Now register all the methods
123         growing_buffer* method_name = buffer_init(64);
124
125         // Generic search thingy
126         buffer_add( method_name, modulename );
127         buffer_add( method_name, ".json_query" );
128         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
129                 "doJSONSearch", "", 1, OSRF_METHOD_STREAMING );
130
131         // Next we register all the transaction and savepoint methods
132         buffer_reset(method_name);
133         OSRF_BUFFER_ADD(method_name, modulename );
134         OSRF_BUFFER_ADD(method_name, ".transaction.begin");
135         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
136                         "beginTransaction", "", 0, 0 );
137
138         buffer_reset(method_name);
139         OSRF_BUFFER_ADD(method_name, modulename );
140         OSRF_BUFFER_ADD(method_name, ".transaction.commit");
141         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
142                         "commitTransaction", "", 0, 0 );
143
144         buffer_reset(method_name);
145         OSRF_BUFFER_ADD(method_name, modulename );
146         OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
147         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
148                         "rollbackTransaction", "", 0, 0 );
149
150         buffer_reset(method_name);
151         OSRF_BUFFER_ADD(method_name, modulename );
152         OSRF_BUFFER_ADD(method_name, ".savepoint.set");
153         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
154                         "setSavepoint", "", 1, 0 );
155
156         buffer_reset(method_name);
157         OSRF_BUFFER_ADD(method_name, modulename );
158         OSRF_BUFFER_ADD(method_name, ".savepoint.release");
159         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
160                         "releaseSavepoint", "", 1, 0 );
161
162         buffer_reset(method_name);
163         OSRF_BUFFER_ADD(method_name, modulename );
164         OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
165         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
166                         "rollbackSavepoint", "", 1, 0 );
167
168         static const char* global_method[] = {
169                 "create",
170                 "retrieve",
171                 "update",
172                 "delete",
173                 "search",
174                 "id_list"
175         };
176         const int global_method_count
177                 = sizeof( global_method ) / sizeof ( global_method[0] );
178
179         unsigned long class_count = osrfHashGetCount( oilsIDL() );
180         osrfLogDebug(OSRF_LOG_MARK, "%lu classes loaded", class_count );
181         osrfLogDebug(OSRF_LOG_MARK,
182                 "At most %lu methods will be generated",
183                 (unsigned long) (class_count * global_method_count) );
184
185         osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
186         osrfHash* idlClass = NULL;
187
188         // For each class in the IDL...
189         while( (idlClass = osrfHashIteratorNext( class_itr ) ) ) {
190
191                 const char* classname = osrfHashIteratorKey( class_itr );
192                 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
193
194                 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), modulename )) {
195                         osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on",
196                                         modulename, classname);
197                         continue;
198                 }
199
200                 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
201                         osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
202                         continue;
203                 }
204
205                 // Look up some other attributes of the current class
206                 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
207                 if( !idlClass_fieldmapper ) {
208                         osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL",
209                                         classname );
210                         continue;
211                 }
212
213                 const char* readonly = osrfHashGet(idlClass, "readonly");
214
215                 int i;
216                 for( i = 0; i < global_method_count; ++i ) {  // for each global method
217                         const char* method_type = global_method[ i ];
218                         osrfLogDebug(OSRF_LOG_MARK,
219                                 "Using files to build %s class methods for %s", method_type, classname);
220
221                         // No create, update, or delete methods for a readonly class
222                         if ( str_is_true( readonly )
223                                 && ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd') )
224                                 continue;
225
226                         buffer_reset( method_name );
227
228                         // Build the method name: MODULENAME.MODULENAME.direct.XXX.method_type
229                         // where XXX is the fieldmapper name from the IDL, with every run of
230                         // one or more consecutive colons replaced by a period.
231                         char* st_tmp = NULL;
232                         char* part = NULL;
233                         char* _fm = strdup( idlClass_fieldmapper );
234                         part = strtok_r(_fm, ":", &st_tmp);
235
236                         buffer_fadd(method_name, "%s.direct.%s", modulename, part);
237
238                         while ((part = strtok_r(NULL, ":", &st_tmp))) {
239                                 OSRF_BUFFER_ADD_CHAR(method_name, '.');
240                                 OSRF_BUFFER_ADD(method_name, part);
241                         }
242                         OSRF_BUFFER_ADD_CHAR(method_name, '.');
243                         OSRF_BUFFER_ADD(method_name, method_type);
244                         free(_fm);
245
246                         // For an id_list or search method we specify the OSRF_METHOD_STREAMING option.
247                         // The consequence is that we implicitly create an atomic method in addition to
248                         // the usual non-atomic method.
249                         int flags = 0;
250                         if (*method_type == 'i' || *method_type == 's') {  // id_list or search
251                                 flags = flags | OSRF_METHOD_STREAMING;
252                         }
253
254                         osrfHash* method_meta = osrfNewHash();
255                         osrfHashSet( method_meta, idlClass, "class");
256                         osrfHashSet( method_meta, buffer_data( method_name ), "methodname" );
257                         osrfHashSet( method_meta, strdup(method_type), "methodtype" );
258
259                         // Register the method, with a pointer to an osrfHash to tell the method
260                         // its name, type, and class.
261                         osrfAppRegisterExtendedMethod(
262                                 modulename,
263                                 OSRF_BUFFER_C_STR( method_name ),
264                                 "dispatchCRUDMethod",
265                                 "",
266                                 1,
267                                 flags,
268                                 (void*)method_meta
269                         );
270
271                 } // end for each global method
272         } // end for each class in IDL
273
274         buffer_free( method_name );
275         osrfHashIteratorFree( class_itr );
276
277         return 0;
278 }
279
280 /**
281         @brief Initialize a server drone.
282         @return Zero if successful, -1 if not.
283
284         Connect to the database.
285
286         This function is called by a server drone shortly after it is spawned by the listener.
287 */
288 int osrfAppChildInit( void ) {
289
290         writehandle = oilsConnectDB( modulename );
291         if( !writehandle )
292                 return -1;
293
294         oilsSetDBConnection( writehandle );
295
296         return 0;
297 }
298
299 /**
300         @brief Implement the class-specific methods.
301         @param ctx Pointer to the method context.
302         @return Zero if successful, or -1 if not.
303
304         Branch on the method type: create, retrieve, update, delete, search, or id_list.
305
306         The method parameters and the type of value returned to the client depend on the method
307         type.
308 */
309 int dispatchCRUDMethod( osrfMethodContext* ctx ) {
310
311         // Get the method type, then can branch on it
312         osrfHash* method_meta = (osrfHash*) ctx->method->userData;
313         const char* methodtype = osrfHashGet( method_meta, "methodtype" );
314
315         if( !strcmp( methodtype, "create" ))
316                 return doCreate( ctx );
317         else if( !strcmp(methodtype, "retrieve" ))
318                 return doRetrieve( ctx );
319         else if( !strcmp(methodtype, "update" ))
320                 return doUpdate( ctx );
321         else if( !strcmp(methodtype, "delete" ))
322                 return doDelete( ctx );
323         else if( !strcmp(methodtype, "search" ))
324                 return doSearch( ctx );
325         else if( !strcmp(methodtype, "id_list" ))
326                 return doIdList( ctx );
327         else {
328                 osrfAppRespondComplete( ctx, NULL );      // should be unreachable...
329                 return 0;
330         }
331 }