]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/c-apps/oils_rstore.c
Merge branch 'master' of git.evergreen-ils.org:Evergreen-DocBook into doc_consolidati...
[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( 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         - set_audit_info
73
74         For each non-virtual class, create up to eight class-specific methods:
75
76         - create    (not for readonly classes)
77         - retrieve
78         - update    (not for readonly classes)
79         - delete    (not for readonly classes
80         - search    (atomic and non-atomic versions)
81         - id_list   (atomic and non-atomic versions)
82
83         The full method names follow the pattern "MODULENAME.direct.XXX.method_type", where XXX
84         is the fieldmapper name from the IDL, with every run of one or more consecutive colons
85         replaced by a period.  In addition, the names of atomic methods have a suffix of ".atomic".
86
87         This function is called when the registering the application, and is executed by the
88         listener before spawning the drones.
89 */
90 int osrfAppInitialize( void ) {
91
92         osrfLogInfo(OSRF_LOG_MARK, "Initializing the RStore Server...");
93         osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
94
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         // first 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         buffer_reset(method_name);
169         OSRF_BUFFER_ADD(method_name, modulename );
170         OSRF_BUFFER_ADD(method_name, ".set_audit_info");
171         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
172                         "setAuditInfo", "", 3, 0 );
173
174         static const char* global_method[] = {
175                 "create",
176                 "retrieve",
177                 "update",
178                 "delete",
179                 "search",
180                 "id_list"
181         };
182         const int global_method_count
183                 = sizeof( global_method ) / sizeof ( global_method[0] );
184
185         unsigned long class_count = osrfHashGetCount( oilsIDL() );
186         osrfLogDebug(OSRF_LOG_MARK, "%lu classes loaded", class_count );
187         osrfLogDebug(OSRF_LOG_MARK,
188                 "At most %lu methods will be generated",
189                 (unsigned long) (class_count * global_method_count) );
190
191         osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
192         osrfHash* idlClass = NULL;
193
194         // For each class in the IDL...
195         while( (idlClass = osrfHashIteratorNext( class_itr ) ) ) {
196
197                 const char* classname = osrfHashIteratorKey( class_itr );
198                 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
199
200                 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), modulename )) {
201                         osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on",
202                                         modulename, classname);
203                         continue;
204                 }
205
206                 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
207                         osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
208                         continue;
209                 }
210
211                 // Look up some other attributes of the current class
212                 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
213                 if( !idlClass_fieldmapper ) {
214                         osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL",
215                                         classname );
216                         continue;
217                 }
218
219                 const char* readonly = osrfHashGet(idlClass, "readonly");
220
221                 int i;
222                 for( i = 0; i < global_method_count; ++i ) {  // for each global method
223                         const char* method_type = global_method[ i ];
224                         osrfLogDebug(OSRF_LOG_MARK,
225                                 "Using files to build %s class methods for %s", method_type, classname);
226
227                         // No create, update, or delete methods for a readonly class
228                         if ( str_is_true( readonly )
229                                 && ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd') )
230                                 continue;
231
232                         buffer_reset( method_name );
233
234                         // Build the method name: MODULENAME.MODULENAME.direct.XXX.method_type
235                         // where XXX is the fieldmapper name from the IDL, with every run of
236                         // one or more consecutive colons replaced by a period.
237                         char* st_tmp = NULL;
238                         char* part = NULL;
239                         char* _fm = strdup( idlClass_fieldmapper );
240                         part = strtok_r(_fm, ":", &st_tmp);
241
242                         buffer_fadd(method_name, "%s.direct.%s", modulename, part);
243
244                         while ((part = strtok_r(NULL, ":", &st_tmp))) {
245                                 OSRF_BUFFER_ADD_CHAR(method_name, '.');
246                                 OSRF_BUFFER_ADD(method_name, part);
247                         }
248                         OSRF_BUFFER_ADD_CHAR(method_name, '.');
249                         OSRF_BUFFER_ADD(method_name, method_type);
250                         free(_fm);
251
252                         // For an id_list or search method we specify the OSRF_METHOD_STREAMING option.
253                         // The consequence is that we implicitly create an atomic method in addition to
254                         // the usual non-atomic method.
255                         int flags = 0;
256                         if (*method_type == 'i' || *method_type == 's') {  // id_list or search
257                                 flags = flags | OSRF_METHOD_STREAMING;
258                         }
259
260                         osrfHash* method_meta = osrfNewHash();
261                         osrfHashSet( method_meta, idlClass, "class");
262                         osrfHashSet( method_meta, buffer_data( method_name ), "methodname" );
263                         osrfHashSet( method_meta, strdup(method_type), "methodtype" );
264
265                         // Register the method, with a pointer to an osrfHash to tell the method
266                         // its name, type, and class.
267                         osrfAppRegisterExtendedMethod(
268                                 modulename,
269                                 OSRF_BUFFER_C_STR( method_name ),
270                                 "dispatchCRUDMethod",
271                                 "",
272                                 1,
273                                 flags,
274                                 (void*)method_meta
275                         );
276
277                 } // end for each global method
278         } // end for each class in IDL
279
280         buffer_free( method_name );
281         osrfHashIteratorFree( class_itr );
282
283         return 0;
284 }
285
286 /**
287         @brief Initialize a server drone.
288         @return Zero if successful, -1 if not.
289
290         Connect to the database.
291
292         This function is called by a server drone shortly after it is spawned by the listener.
293 */
294 int osrfAppChildInit( void ) {
295
296         writehandle = oilsConnectDB( modulename );
297         if( !writehandle )
298                 return -1;
299
300         oilsSetDBConnection( writehandle );
301
302         return 0;
303 }
304
305 /**
306         @brief Implement the class-specific methods.
307         @param ctx Pointer to the method context.
308         @return Zero if successful, or -1 if not.
309
310         Branch on the method type: create, retrieve, update, delete, search, or id_list.
311
312         The method parameters and the type of value returned to the client depend on the method
313         type.
314 */
315 int dispatchCRUDMethod( osrfMethodContext* ctx ) {
316
317         // Get the method type, then can branch on it
318         osrfHash* method_meta = (osrfHash*) ctx->method->userData;
319         const char* methodtype = osrfHashGet( method_meta, "methodtype" );
320
321         if( !strcmp( methodtype, "create" ))
322                 return doCreate( ctx );
323         else if( !strcmp(methodtype, "retrieve" ))
324                 return doRetrieve( ctx );
325         else if( !strcmp(methodtype, "update" ))
326                 return doUpdate( ctx );
327         else if( !strcmp(methodtype, "delete" ))
328                 return doDelete( ctx );
329         else if( !strcmp(methodtype, "search" ))
330                 return doSearch( ctx );
331         else if( !strcmp(methodtype, "id_list" ))
332                 return doIdList( ctx );
333         else {
334                 osrfAppRespondComplete( ctx, NULL );      // should be unreachable...
335                 return 0;
336         }
337 }