3 @brief As a server, perform database operations at the request of clients.
5 This server is similar to the cstore and reporter-store servers,
6 except that it enforces a permissions scheme.
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"
19 static dbi_conn writehandle; /* our MASTER db connection */
20 static dbi_conn dbhandle; /* our CURRENT db connection */
21 //static osrfHash * readHandles;
23 static int max_flesh_depth = 100;
25 static const int enforce_pcrud = 1; // Boolean
26 static const char modulename[] = "open-ils.pcrud";
29 @brief Disconnect from the database.
31 This function is called when the server drone is about to terminate.
33 void osrfAppChildExit() {
34 osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
37 if (writehandle == dbhandle)
41 dbi_conn_query(writehandle, "ROLLBACK;");
42 dbi_conn_close(writehandle);
45 if (dbhandle && !same)
46 dbi_conn_close(dbhandle);
48 // XXX add cleanup of readHandles whenever that gets used
54 @brief Initialize the application.
55 @return Zero if successful, or non-zero if not.
57 Load the IDL file into an internal data structure for future reference. Each non-virtual
58 class in the IDL corresponds to a table or view in the database, or to a subquery defined
59 in the IDL. Ignore all virtual tables and virtual fields.
61 Register a number of methods, some of them general-purpose and others specific for
64 The name of the application is given by the MODULENAME macro, whose value depends on
65 conditional compilation. The method names also incorporate MODULENAME, followed by a
66 dot, as a prefix. Some methods are registered or not registered depending on whether
67 the IDL includes permacrud entries for the class and method.
69 The general-purpose methods are as follows (minus their MODULENAME prefixes):
73 - transaction.rollback
78 For each non-virtual class, create up to eight class-specific methods:
80 - create (not for readonly classes)
82 - update (not for readonly classes)
83 - delete (not for readonly classes
84 - search (atomic and non-atomic versions)
85 - id_list (atomic and non-atomic versions)
87 The full method names follow the pattern "MODULENAME.method_type.classname".
88 In addition, the names of atomic methods have a suffix of ".atomic".
90 This function is called when the registering the application, and is executed by the
91 listener before spawning the drones.
93 int osrfAppInitialize() {
95 osrfLogInfo(OSRF_LOG_MARK, "Initializing the PCRUD Server...");
96 osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
98 if (!oilsIDLInit( osrf_settings_host_value("/IDL") ))
99 return 1; /* return non-zero to indicate error */
101 oilsSetSQLOptions( modulename, enforce_pcrud );
103 growing_buffer* method_name = buffer_init(64);
105 // first we register all the transaction and savepoint methods
106 buffer_reset(method_name);
107 OSRF_BUFFER_ADD(method_name, modulename );
108 OSRF_BUFFER_ADD(method_name, ".transaction.begin");
109 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
110 "beginTransaction", "", 0, 0 );
112 buffer_reset(method_name);
113 OSRF_BUFFER_ADD(method_name, modulename );
114 OSRF_BUFFER_ADD(method_name, ".transaction.commit");
115 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
116 "commitTransaction", "", 0, 0 );
118 buffer_reset(method_name);
119 OSRF_BUFFER_ADD(method_name, modulename );
120 OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
121 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
122 "rollbackTransaction", "", 0, 0 );
124 buffer_reset(method_name);
125 OSRF_BUFFER_ADD(method_name, modulename );
126 OSRF_BUFFER_ADD(method_name, ".savepoint.set");
127 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
128 "setSavepoint", "", 1, 0 );
130 buffer_reset(method_name);
131 OSRF_BUFFER_ADD(method_name, modulename );
132 OSRF_BUFFER_ADD(method_name, ".savepoint.release");
133 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
134 "releaseSavepoint", "", 1, 0 );
136 buffer_reset(method_name);
137 OSRF_BUFFER_ADD(method_name, modulename );
138 OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
139 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
140 "rollbackSavepoint", "", 1, 0 );
142 static const char* global_method[] = {
150 const int global_method_count
151 = sizeof( global_method ) / sizeof ( global_method[0] );
153 unsigned long class_count = osrfHashGetCount( oilsIDL() );
154 osrfLogDebug(OSRF_LOG_MARK, "%lu classes loaded", class_count );
155 osrfLogDebug(OSRF_LOG_MARK,
156 "At most %lu methods will be generated",
157 (unsigned long) (class_count * global_method_count) );
159 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
160 osrfHash* idlClass = NULL;
162 // For each class in the IDL...
163 while( (idlClass = osrfHashIteratorNext( class_itr ) ) ) {
165 const char* classname = osrfHashIteratorKey( class_itr );
166 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
168 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), modulename )) {
169 osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on",
170 modulename, classname);
174 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
175 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
179 // Look up some other attributes of the current class
180 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
181 if( !idlClass_fieldmapper ) {
182 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL",
187 osrfHash* idlClass_permacrud = NULL;
189 // Ignore classes with no permacrud section
190 idlClass_permacrud = osrfHashGet( idlClass, "permacrud" );
191 if( !idlClass_permacrud ) {
192 osrfLogDebug( OSRF_LOG_MARK,
193 "Skipping class \"%s\"; no permacrud in IDL", classname );
197 const char* readonly = osrfHashGet( idlClass, "readonly" );
200 for( i = 0; i < global_method_count; ++i ) { // for each global method
201 const char* method_type = global_method[ i ];
202 osrfLogDebug(OSRF_LOG_MARK,
203 "Using files to build %s class methods for %s", method_type, classname);
205 // Treat "id_list" or "search" as forms of "retrieve"
206 const char* tmp_method = method_type;
207 if ( *tmp_method == 'i' || *tmp_method == 's') { // "id_list" or "search"
208 tmp_method = "retrieve";
210 // Skip this method if there is no permacrud entry for it
211 if (!osrfHashGet( idlClass_permacrud, tmp_method ))
214 // No create, update, or delete methods for a readonly class
215 if ( str_is_true( readonly )
216 && ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd') )
219 buffer_reset( method_name );
221 // Build the method name: MODULENAME.method_type.classname
222 buffer_fadd(method_name, "%s.%s.%s", modulename, method_type, classname);
224 // For an id_list or search method we specify the OSRF_METHOD_STREAMING option.
225 // The consequence is that we implicitly create an atomic method in addition to
226 // the usual non-atomic method.
228 if (*method_type == 'i' || *method_type == 's') { // id_list or search
229 flags = flags | OSRF_METHOD_STREAMING;
232 osrfHash* method_meta = osrfNewHash();
233 osrfHashSet( method_meta, idlClass, "class");
234 osrfHashSet( method_meta, buffer_data( method_name ), "methodname" );
235 osrfHashSet( method_meta, strdup(method_type), "methodtype" );
237 // Register the method, with a pointer to an osrfHash to tell the method
238 // its name, type, and class.
239 osrfAppRegisterExtendedMethod(
241 OSRF_BUFFER_C_STR( method_name ),
242 "dispatchCRUDMethod",
249 } // end for each global method
250 } // end for each class in IDL
252 buffer_free( method_name );
253 osrfHashIteratorFree( class_itr );
259 @brief Initialize a server drone.
260 @return Zero if successful, -1 if not.
262 Connect to the database. For each non-virtual class in the IDL, execute a dummy "SELECT * "
263 query to get the datatype of each column. Record the datatypes in the loaded IDL.
265 This function is called by a server drone shortly after it is spawned by the listener.
267 int osrfAppChildInit() {
269 osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
270 dbi_initialize(NULL);
271 osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
273 char* driver = osrf_settings_host_value("/apps/%s/app_settings/driver", modulename );
274 char* user = osrf_settings_host_value("/apps/%s/app_settings/database/user", modulename );
275 char* host = osrf_settings_host_value("/apps/%s/app_settings/database/host", modulename );
276 char* port = osrf_settings_host_value("/apps/%s/app_settings/database/port", modulename );
277 char* db = osrf_settings_host_value("/apps/%s/app_settings/database/db", modulename );
278 char* pw = osrf_settings_host_value("/apps/%s/app_settings/database/pw", modulename );
279 char* md = osrf_settings_host_value("/apps/%s/app_settings/max_query_recursion",
282 osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
283 writehandle = dbi_conn_new(driver);
286 osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
289 osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
291 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
292 "port=%s, user=%s, db=%s", modulename, host, port, user, db );
294 if(host) dbi_conn_set_option(writehandle, "host", host );
295 if(port) dbi_conn_set_option_numeric( writehandle, "port", atoi(port) );
296 if(user) dbi_conn_set_option(writehandle, "username", user);
297 if(pw) dbi_conn_set_option(writehandle, "password", pw );
298 if(db) dbi_conn_set_option(writehandle, "dbname", db );
300 if(md) max_flesh_depth = atoi(md);
301 if(max_flesh_depth < 0) max_flesh_depth = 1;
302 if(max_flesh_depth > 1000) max_flesh_depth = 1000;
311 if (dbi_conn_connect(writehandle) < 0) {
313 if (dbi_conn_connect(writehandle) < 0) {
314 dbi_conn_error(writehandle, &err);
315 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
320 oilsSetDBConnection( writehandle );
321 osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", modulename );
323 // Add datatypes from database to the fields in the IDL
324 if( oilsExtendIDL() ) {
325 osrfLogError( OSRF_LOG_MARK, "Error extending the IDL" );
333 @brief Implement the class-specific methods.
334 @param ctx Pointer to the method context.
335 @return Zero if successful, or -1 if not.
337 Branch on the method type: create, retrieve, update, delete, search, or id_list.
339 The method parameters and the type of value returned to the client depend on the method
340 type. However, for all PCRUD methods, the first method parameter is an authkey.
342 int dispatchCRUDMethod( osrfMethodContext* ctx ) {
344 // Get the method type, then can branch on it
345 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
346 const char* methodtype = osrfHashGet( method_meta, "methodtype" );
348 if( !strcmp( methodtype, "create" ))
349 return doCreate( ctx );
350 else if( !strcmp(methodtype, "retrieve" ))
351 return doRetrieve( ctx );
352 else if( !strcmp(methodtype, "update" ))
353 return doUpdate( ctx );
354 else if( !strcmp(methodtype, "delete" ))
355 return doDelete( ctx );
356 else if( !strcmp(methodtype, "search" ))
357 return doSearch( ctx );
358 else if( !strcmp(methodtype, "id_list" ))
359 return doIdList( ctx );
361 osrfAppRespondComplete( ctx, NULL ); // should be unreachable...