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