]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/c-apps/oils_pcrud.c
Add OILS_SIP_MSG_BILL_ERR for when an error occurs getting bills.
[working/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
76         For each non-virtual class, create up to eight class-specific methods:
77
78         - create    (not for readonly classes)
79         - retrieve
80         - update    (not for readonly classes)
81         - delete    (not for readonly classes
82         - search    (atomic and non-atomic versions)
83         - id_list   (atomic and non-atomic versions)
84
85         The full method names follow the pattern "MODULENAME.method_type.classname".
86         In addition, the names of atomic methods have a suffix of ".atomic".
87
88         This function is called when the registering the application, and is executed by the
89         listener before spawning the drones.
90 */
91 int osrfAppInitialize( void ) {
92
93         osrfLogInfo(OSRF_LOG_MARK, "Initializing the PCRUD Server...");
94         osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
95
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         // first we register all the transaction and savepoint methods
127         buffer_reset(method_name);
128         OSRF_BUFFER_ADD(method_name, modulename );
129         OSRF_BUFFER_ADD(method_name, ".transaction.begin");
130         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
131                         "beginTransaction", "", 0, 0 );
132
133         buffer_reset(method_name);
134         OSRF_BUFFER_ADD(method_name, modulename );
135         OSRF_BUFFER_ADD(method_name, ".transaction.commit");
136         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
137                         "commitTransaction", "", 0, 0 );
138
139         buffer_reset(method_name);
140         OSRF_BUFFER_ADD(method_name, modulename );
141         OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
142         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
143                         "rollbackTransaction", "", 0, 0 );
144
145         buffer_reset(method_name);
146         OSRF_BUFFER_ADD(method_name, modulename );
147         OSRF_BUFFER_ADD(method_name, ".savepoint.set");
148         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
149                         "setSavepoint", "", 1, 0 );
150
151         buffer_reset(method_name);
152         OSRF_BUFFER_ADD(method_name, modulename );
153         OSRF_BUFFER_ADD(method_name, ".savepoint.release");
154         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
155                         "releaseSavepoint", "", 1, 0 );
156
157         buffer_reset(method_name);
158         OSRF_BUFFER_ADD(method_name, modulename );
159         OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
160         osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
161                         "rollbackSavepoint", "", 1, 0 );
162
163         static const char* global_method[] = {
164                 "create",
165                 "retrieve",
166                 "update",
167                 "delete",
168                 "search",
169                 "id_list"
170         };
171         const int global_method_count
172                 = sizeof( global_method ) / sizeof ( global_method[0] );
173
174         unsigned long class_count = osrfHashGetCount( oilsIDL() );
175         osrfLogDebug(OSRF_LOG_MARK, "%lu classes loaded", class_count );
176         osrfLogDebug(OSRF_LOG_MARK,
177                 "At most %lu methods will be generated",
178                 (unsigned long) (class_count * global_method_count) );
179
180         osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
181         osrfHash* idlClass = NULL;
182
183         // For each class in the IDL...
184         while( (idlClass = osrfHashIteratorNext( class_itr ) ) ) {
185
186                 const char* classname = osrfHashIteratorKey( class_itr );
187                 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
188
189                 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), modulename )) {
190                         osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on",
191                                         modulename, classname);
192                         continue;
193                 }
194
195                 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
196                         osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
197                         continue;
198                 }
199
200                 // Look up some other attributes of the current class
201                 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
202                 if( !idlClass_fieldmapper ) {
203                         osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL",
204                                         classname );
205                         continue;
206                 }
207
208                 osrfHash* idlClass_permacrud = NULL;
209
210                 // Ignore classes with no permacrud section
211                 idlClass_permacrud = osrfHashGet( idlClass, "permacrud" );
212                 if( !idlClass_permacrud ) {
213                         osrfLogDebug( OSRF_LOG_MARK,
214                                 "Skipping class \"%s\"; no permacrud in IDL", classname );
215                         continue;
216                 }
217
218                 const char* readonly = osrfHashGet( idlClass, "readonly" );
219
220                 int i;
221                 for( i = 0; i < global_method_count; ++i ) {  // for each global method
222                         const char* method_type = global_method[ i ];
223                         osrfLogDebug(OSRF_LOG_MARK,
224                                 "Using files to build %s class methods for %s", method_type, classname);
225
226                         // Treat "id_list" or "search" as forms of "retrieve"
227                         const char* tmp_method = method_type;
228                         if ( *tmp_method == 'i' || *tmp_method == 's') {  // "id_list" or "search"
229                                 tmp_method = "retrieve";
230                         }
231                         // Skip this method if there is no permacrud entry for it
232                         if (!osrfHashGet( idlClass_permacrud, tmp_method ))
233                                 continue;
234
235                         // No create, update, or delete methods for a readonly class
236                         if ( str_is_true( readonly )
237                                 && ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd') )
238                                 continue;
239
240                         buffer_reset( method_name );
241
242                         // Build the method name: MODULENAME.method_type.classname
243                         buffer_fadd(method_name, "%s.%s.%s", modulename, method_type, classname);
244
245                         // For an id_list or search method we specify the OSRF_METHOD_STREAMING option.
246                         // The consequence is that we implicitly create an atomic method in addition to
247                         // the usual non-atomic method.
248                         int flags = 0;
249                         if (*method_type == 'i' || *method_type == 's') {  // id_list or search
250                                 flags = flags | OSRF_METHOD_STREAMING;
251                         }
252
253                         osrfHash* method_meta = osrfNewHash();
254                         osrfHashSet( method_meta, idlClass, "class");
255                         osrfHashSet( method_meta, buffer_data( method_name ), "methodname" );
256                         osrfHashSet( method_meta, strdup(method_type), "methodtype" );
257
258                         // Register the method, with a pointer to an osrfHash to tell the method
259                         // its name, type, and class.
260                         osrfAppRegisterExtendedMethod(
261                                 modulename,
262                                 OSRF_BUFFER_C_STR( method_name ),
263                                 "dispatchCRUDMethod",
264                                 "",
265                                 1,
266                                 flags,
267                                 (void*)method_meta
268                         );
269
270                 } // end for each global method
271         } // end for each class in IDL
272
273         buffer_free( method_name );
274         osrfHashIteratorFree( class_itr );
275
276         return 0;
277 }
278
279 /**
280         @brief Initialize a server drone.
281         @return Zero if successful, -1 if not.
282
283         Connect to the database.
284
285         This function is called by a server drone shortly after it is spawned by the listener.
286 */
287 int osrfAppChildInit( void ) {
288
289         writehandle = oilsConnectDB( modulename );
290         if( !writehandle )
291                 return -1;
292
293         oilsSetDBConnection( writehandle );
294
295         return 0;
296 }
297
298 /**
299         @brief Implement the class-specific methods.
300         @param ctx Pointer to the method context.
301         @return Zero if successful, or -1 if not.
302
303         Branch on the method type: create, retrieve, update, delete, search, or id_list.
304
305         The method parameters and the type of value returned to the client depend on the method
306         type.  However, for all PCRUD methods, the first method parameter is an authkey.
307 */
308 int dispatchCRUDMethod( osrfMethodContext* ctx ) {
309
310         // Get the method type, then can branch on it
311         osrfHash* method_meta = (osrfHash*) ctx->method->userData;
312         const char* methodtype = osrfHashGet( method_meta, "methodtype" );
313
314         if( !strcmp( methodtype, "create" ))
315                 return doCreate( ctx );
316         else if( !strcmp(methodtype, "retrieve" ))
317                 return doRetrieve( ctx );
318         else if( !strcmp(methodtype, "update" ))
319                 return doUpdate( ctx );
320         else if( !strcmp(methodtype, "delete" ))
321                 return doDelete( ctx );
322         else if( !strcmp(methodtype, "search" ))
323                 return doSearch( ctx );
324         else if( !strcmp(methodtype, "id_list" ))
325                 return doIdList( ctx );
326         else {
327                 osrfAppRespondComplete( ctx, NULL );      // should be unreachable...
328                 return 0;
329         }
330 }