LP1779158 Angular7 and ng-lint updates
[Evergreen.git] / Open-ILS / src / c-apps / oils_pcrud.c
1 /**
2         @file oils_pcrud.c
3         @brief As a server, perform database operations at the request of clients.
4
5         This server is similar to the cstore and reporter-store servers,
6         except that it enforces a permissions scheme.
7 */
8
9 #include <stdlib.h>
10 #include <string.h>
11 #include <ctype.h>
12 #include <dbi/dbi.h>
13 #include "opensrf/utils.h"
14 #include "opensrf/log.h"
15 #include "opensrf/osrf_application.h"
16 #include "openils/oils_utils.h"
17 #include "openils/oils_sql.h"
18
19 static dbi_conn writehandle; /* our MASTER db connection */
20 static dbi_conn dbhandle; /* our CURRENT db connection */
21 //static osrfHash * readHandles;
22
23 static const int enforce_pcrud = 1;     // Boolean
24 static const char modulename[] = "open-ils.pcrud";
25
26 /**
27         @brief Disconnect from the database.
28
29         This function is called when the server drone is about to terminate.
30 */
31 void osrfAppChildExit( void ) {
32         osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
33
34         int same = 0;
35         if (writehandle == dbhandle)
36                 same = 1;
37
38         if (writehandle) {
39                 dbi_conn_query(writehandle, "ROLLBACK;");
40                 dbi_conn_close(writehandle);
41                 writehandle = NULL;
42         }
43         if (dbhandle && !same)
44                 dbi_conn_close(dbhandle);
45
46         // XXX add cleanup of readHandles whenever that gets used
47
48         return;
49 }
50
51 /**
52         @brief Initialize the application.
53         @return Zero if successful, or non-zero if not.
54
55         Load the IDL file into an internal data structure for future reference.  Each non-virtual
56         class in the IDL corresponds to a table or view in the database, or to a subquery defined
57         in the IDL.  Ignore all virtual tables and virtual fields.
58
59         Register a number of methods, some of them general-purpose and others specific for
60         particular classes.
61
62         The name of the application is given by the MODULENAME macro, whose value depends on
63         conditional compilation.  The method names also incorporate MODULENAME, followed by a
64         dot, as a prefix.  Some methods are registered or not registered depending on whether
65         the IDL includes permacrud entries for the class and method.
66
67         The general-purpose methods are as follows (minus their MODULENAME prefixes):
68
69         - transaction.begin
70         - transaction.commit
71         - transaction.rollback
72         - savepoint.set
73         - savepoint.release
74         - savepoint.rollback
75         - set_audit_info
76
77         For each non-virtual class, create up to eight class-specific methods:
78
79         - create    (not for readonly classes)
80         - retrieve
81         - update    (not for readonly classes)
82         - delete    (not for readonly classes
83         - search    (atomic and non-atomic versions)
84         - id_list   (atomic and non-atomic versions)
85
86         The full method names follow the pattern "MODULENAME.method_type.classname".
87         In addition, the names of atomic methods have a suffix of ".atomic".
88
89         This function is called when the registering the application, and is executed by the
90         listener before spawning the drones.
91 */
92 int osrfAppInitialize( void ) {
93
94         osrfLogInfo(OSRF_LOG_MARK, "Initializing the PCRUD Server...");
95         osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
96
97         if ( !oilsIDLInit( osrf_settings_host_value( "/IDL" )))
98                 return 1; /* return non-zero to indicate error */
99
100         // Open the database temporarily.  Look up the datatypes of all
101         // the non-virtual fields and record them with the IDL data.
102         dbi_conn handle = oilsConnectDB( modulename );
103         if( !handle )
104                 return -1;
105         else if( oilsExtendIDL( handle )) {
106                 osrfLogError( OSRF_LOG_MARK, "Error extending the IDL" );
107                 return -1;
108         }
109         dbi_conn_close( handle );
110
111         // Get the maximum flesh depth from the settings
112         char* md = osrf_settings_host_value(
113                 "/apps/%s/app_settings/max_query_recursion", modulename );
114         int max_flesh_depth = 100;
115         if( md )
116                 max_flesh_depth = atoi( md );
117         if( max_flesh_depth < 0 )
118                 max_flesh_depth = 1;
119         else if( max_flesh_depth > 1000 )
120                 max_flesh_depth = 1000;
121
122         oilsSetSQLOptions( modulename, enforce_pcrud, max_flesh_depth );
123
124         // Now register all the methods
125         growing_buffer* method_name = buffer_init(64);
126
127         // first we register all the transaction and savepoint methods
128         buffer_reset(method_name);
129         OSRF_BUFFER_ADD(method_name, modulename );
130         OSRF_BUFFER_ADD(method_name, ".transaction.begin");
131         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
132                         "beginTransaction", "", 0, 0 );
133
134         buffer_reset(method_name);
135         OSRF_BUFFER_ADD(method_name, modulename );
136         OSRF_BUFFER_ADD(method_name, ".transaction.commit");
137         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
138                         "commitTransaction", "", 0, 0 );
139
140         buffer_reset(method_name);
141         OSRF_BUFFER_ADD(method_name, modulename );
142         OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
143         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
144                         "rollbackTransaction", "", 0, 0 );
145
146         buffer_reset(method_name);
147         OSRF_BUFFER_ADD(method_name, modulename );
148         OSRF_BUFFER_ADD(method_name, ".savepoint.set");
149         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
150                         "setSavepoint", "", 1, 0 );
151
152         buffer_reset(method_name);
153         OSRF_BUFFER_ADD(method_name, modulename );
154         OSRF_BUFFER_ADD(method_name, ".savepoint.release");
155         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
156                         "releaseSavepoint", "", 1, 0 );
157
158         buffer_reset(method_name);
159         OSRF_BUFFER_ADD(method_name, modulename );
160         OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
161         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
162                         "rollbackSavepoint", "", 1, 0 );
163
164         buffer_reset(method_name);
165         OSRF_BUFFER_ADD(method_name, modulename );
166         OSRF_BUFFER_ADD(method_name, ".set_audit_info");
167         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
168                         "setAuditInfo", "", 3, 0 );
169
170         static const char* global_method[] = {
171                 "create",
172                 "retrieve",
173                 "update",
174                 "delete",
175                 "search",
176                 "id_list"
177         };
178         const int global_method_count
179                 = sizeof( global_method ) / sizeof ( global_method[0] );
180
181         unsigned long class_count = osrfHashGetCount( oilsIDL() );
182         osrfLogDebug(OSRF_LOG_MARK, "%lu classes loaded", class_count );
183         osrfLogDebug(OSRF_LOG_MARK,
184                 "At most %lu methods will be generated",
185                 (unsigned long) (class_count * global_method_count) );
186
187         osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
188         osrfHash* idlClass = NULL;
189
190         // For each class in the IDL...
191         while( (idlClass = osrfHashIteratorNext( class_itr ) ) ) {
192
193                 const char* classname = osrfHashIteratorKey( class_itr );
194                 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
195
196                 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), modulename )) {
197                         osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on",
198                                         modulename, classname);
199                         continue;
200                 }
201
202                 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
203                         osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
204                         continue;
205                 }
206
207                 // Look up some other attributes of the current class
208                 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
209                 if( !idlClass_fieldmapper ) {
210                         osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL",
211                                         classname );
212                         continue;
213                 }
214
215                 osrfHash* idlClass_permacrud = NULL;
216
217                 // Ignore classes with no permacrud section
218                 idlClass_permacrud = osrfHashGet( idlClass, "permacrud" );
219                 if( !idlClass_permacrud ) {
220                         osrfLogDebug( OSRF_LOG_MARK,
221                                 "Skipping class \"%s\"; no permacrud in IDL", classname );
222                         continue;
223                 }
224
225                 const char* readonly = osrfHashGet( idlClass, "readonly" );
226
227                 int i;
228                 for( i = 0; i < global_method_count; ++i ) {  // for each global method
229                         const char* method_type = global_method[ i ];
230                         osrfLogDebug(OSRF_LOG_MARK,
231                                 "Using files to build %s class methods for %s", method_type, classname);
232
233                         // Treat "id_list" or "search" as forms of "retrieve"
234                         const char* tmp_method = method_type;
235                         if ( *tmp_method == 'i' || *tmp_method == 's') {  // "id_list" or "search"
236                                 tmp_method = "retrieve";
237                         }
238                         // Skip this method if there is no permacrud entry for it
239                         if (!osrfHashGet( idlClass_permacrud, tmp_method ))
240                                 continue;
241
242                         // No create, update, or delete methods for a readonly class
243                         if ( str_is_true( readonly )
244                                 && ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd') )
245                                 continue;
246
247                         buffer_reset( method_name );
248
249                         // Build the method name: MODULENAME.method_type.classname
250                         buffer_fadd(method_name, "%s.%s.%s", modulename, method_type, classname);
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.  However, for all PCRUD methods, the first method parameter is an authkey.
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 }