3 @brief As a server, perform database operations at the request of clients.
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"
16 static dbi_conn writehandle; /* our MASTER db connection */
17 static dbi_conn dbhandle; /* our CURRENT db connection */
18 //static osrfHash * readHandles;
20 static const int enforce_pcrud = 0; // Boolean
21 static const char modulename[] = "open-ils.cstore";
24 @brief Disconnect from the database.
26 This function is called when the server drone is about to terminate.
28 void osrfAppChildExit() {
29 osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database...");
32 if (writehandle == dbhandle)
36 dbi_conn_query(writehandle, "ROLLBACK;");
37 dbi_conn_close(writehandle);
40 if (dbhandle && !same)
41 dbi_conn_close(dbhandle);
43 // XXX add cleanup of readHandles whenever that gets used
49 @brief Initialize the application.
50 @return Zero if successful, or non-zero if not.
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.
56 Register a number of methods, some of them general-purpose and others specific for
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
63 The general-purpose methods are as follows (minus their MODULENAME prefixes):
68 - transaction.rollback
73 For each non-virtual class, create up to eight class-specific methods:
75 - create (not for readonly classes)
77 - update (not for readonly classes)
78 - delete (not for readonly classes
79 - search (atomic and non-atomic versions)
80 - id_list (atomic and non-atomic versions)
82 The full method names follow the pattern "MODULENAME.direct.XXX.method_type", where XXX
83 is the fieldmapper name from the IDL, with every run of one or more consecutive colons
84 replaced by a period. In addition, the names of atomic methods have a suffix of ".atomic".
86 This function is called when the registering the application, and is executed by the
87 listener before spawning the drones.
89 int osrfAppInitialize() {
91 osrfLogInfo(OSRF_LOG_MARK, "Initializing the CStore Server...");
92 osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
94 if ( !oilsIDLInit( osrf_settings_host_value( "/IDL" )))
95 return 1; /* return non-zero to indicate error */
97 char* md = osrf_settings_host_value(
98 "/apps/%s/app_settings/max_query_recursion", modulename );
99 int max_flesh_depth = 100;
101 max_flesh_depth = atoi( md );
102 if( max_flesh_depth < 0 )
104 else if( max_flesh_depth > 1000 )
105 max_flesh_depth = 1000;
107 oilsSetSQLOptions( modulename, enforce_pcrud, max_flesh_depth );
109 growing_buffer* method_name = buffer_init(64);
111 // Generic search thingy
112 buffer_add( method_name, modulename );
113 buffer_add( method_name, ".json_query" );
114 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
115 "doJSONSearch", "", 1, OSRF_METHOD_STREAMING );
117 // first we register all the transaction and savepoint methods
118 buffer_reset(method_name);
119 OSRF_BUFFER_ADD(method_name, modulename );
120 OSRF_BUFFER_ADD(method_name, ".transaction.begin");
121 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR( method_name ),
122 "beginTransaction", "", 0, 0 );
124 buffer_reset(method_name);
125 OSRF_BUFFER_ADD(method_name, modulename );
126 OSRF_BUFFER_ADD(method_name, ".transaction.commit");
127 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
128 "commitTransaction", "", 0, 0 );
130 buffer_reset(method_name);
131 OSRF_BUFFER_ADD(method_name, modulename );
132 OSRF_BUFFER_ADD(method_name, ".transaction.rollback");
133 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
134 "rollbackTransaction", "", 0, 0 );
136 buffer_reset(method_name);
137 OSRF_BUFFER_ADD(method_name, modulename );
138 OSRF_BUFFER_ADD(method_name, ".savepoint.set");
139 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
140 "setSavepoint", "", 1, 0 );
142 buffer_reset(method_name);
143 OSRF_BUFFER_ADD(method_name, modulename );
144 OSRF_BUFFER_ADD(method_name, ".savepoint.release");
145 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
146 "releaseSavepoint", "", 1, 0 );
148 buffer_reset(method_name);
149 OSRF_BUFFER_ADD(method_name, modulename );
150 OSRF_BUFFER_ADD(method_name, ".savepoint.rollback");
151 osrfAppRegisterMethod( modulename, OSRF_BUFFER_C_STR(method_name),
152 "rollbackSavepoint", "", 1, 0 );
154 static const char* global_method[] = {
162 const int global_method_count
163 = sizeof( global_method ) / sizeof ( global_method[0] );
165 unsigned long class_count = osrfHashGetCount( oilsIDL() );
166 osrfLogDebug(OSRF_LOG_MARK, "%lu classes loaded", class_count );
167 osrfLogDebug(OSRF_LOG_MARK,
168 "At most %lu methods will be generated",
169 (unsigned long) (class_count * global_method_count) );
171 osrfHashIterator* class_itr = osrfNewHashIterator( oilsIDL() );
172 osrfHash* idlClass = NULL;
174 // For each class in the IDL...
175 while( (idlClass = osrfHashIteratorNext( class_itr ) ) ) {
177 const char* classname = osrfHashIteratorKey( class_itr );
178 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname);
180 if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), modulename )) {
181 osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on",
182 modulename, classname);
186 if ( str_is_true( osrfHashGet(idlClass, "virtual") ) ) {
187 osrfLogDebug(OSRF_LOG_MARK, "Class %s is virtual, skipping", classname );
191 // Look up some other attributes of the current class
192 const char* idlClass_fieldmapper = osrfHashGet(idlClass, "fieldmapper");
193 if( !idlClass_fieldmapper ) {
194 osrfLogDebug( OSRF_LOG_MARK, "Skipping class \"%s\"; no fieldmapper in IDL",
199 const char* readonly = osrfHashGet(idlClass, "readonly");
202 for( i = 0; i < global_method_count; ++i ) { // for each global method
203 const char* method_type = global_method[ i ];
204 osrfLogDebug(OSRF_LOG_MARK,
205 "Using files to build %s class methods for %s", method_type, classname);
207 // No create, update, or delete methods for a readonly class
208 if ( str_is_true( readonly )
209 && ( *method_type == 'c' || *method_type == 'u' || *method_type == 'd') )
212 buffer_reset( method_name );
214 // Build the method name: MODULENAME.MODULENAME.direct.XXX.method_type
215 // where XXX is the fieldmapper name from the IDL, with every run of
216 // one or more consecutive colons replaced by a period.
219 char* _fm = strdup( idlClass_fieldmapper );
220 part = strtok_r(_fm, ":", &st_tmp);
222 buffer_fadd(method_name, "%s.direct.%s", modulename, part);
224 while ((part = strtok_r(NULL, ":", &st_tmp))) {
225 OSRF_BUFFER_ADD_CHAR(method_name, '.');
226 OSRF_BUFFER_ADD(method_name, part);
228 OSRF_BUFFER_ADD_CHAR(method_name, '.');
229 OSRF_BUFFER_ADD(method_name, method_type);
232 // For an id_list or search method we specify the OSRF_METHOD_STREAMING option.
233 // The consequence is that we implicitly create an atomic method in addition to
234 // the usual non-atomic method.
236 if (*method_type == 'i' || *method_type == 's') { // id_list or search
237 flags = flags | OSRF_METHOD_STREAMING;
240 osrfHash* method_meta = osrfNewHash();
241 osrfHashSet( method_meta, idlClass, "class");
242 osrfHashSet( method_meta, buffer_data( method_name ), "methodname" );
243 osrfHashSet( method_meta, strdup(method_type), "methodtype" );
245 // Register the method, with a pointer to an osrfHash to tell the method
246 // its name, type, and class.
247 osrfAppRegisterExtendedMethod(
249 OSRF_BUFFER_C_STR( method_name ),
250 "dispatchCRUDMethod",
257 } // end for each global method
258 } // end for each class in IDL
260 buffer_free( method_name );
261 osrfHashIteratorFree( class_itr );
267 @brief Initialize a server drone.
268 @return Zero if successful, -1 if not.
270 Connect to the database. For each non-virtual class in the IDL, execute a dummy "SELECT * "
271 query to get the datatype of each column. Record the datatypes in the loaded IDL.
273 This function is called by a server drone shortly after it is spawned by the listener.
275 int osrfAppChildInit() {
277 osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
278 dbi_initialize(NULL);
279 osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
281 char* driver = osrf_settings_host_value("/apps/%s/app_settings/driver", modulename );
282 char* user = osrf_settings_host_value("/apps/%s/app_settings/database/user", modulename );
283 char* host = osrf_settings_host_value("/apps/%s/app_settings/database/host", modulename );
284 char* port = osrf_settings_host_value("/apps/%s/app_settings/database/port", modulename );
285 char* db = osrf_settings_host_value("/apps/%s/app_settings/database/db", modulename );
286 char* pw = osrf_settings_host_value("/apps/%s/app_settings/database/pw", modulename );
288 osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
289 writehandle = dbi_conn_new(driver);
292 osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
295 osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
297 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
298 "port=%s, user=%s, db=%s", modulename, host, port, user, db );
300 if(host) dbi_conn_set_option(writehandle, "host", host );
301 if(port) dbi_conn_set_option_numeric( writehandle, "port", atoi(port) );
302 if(user) dbi_conn_set_option(writehandle, "username", user);
303 if(pw) dbi_conn_set_option(writehandle, "password", pw );
304 if(db) dbi_conn_set_option(writehandle, "dbname", db );
313 if (dbi_conn_connect(writehandle) < 0) {
315 if (dbi_conn_connect(writehandle) < 0) {
316 dbi_conn_error(writehandle, &err);
317 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
322 oilsSetDBConnection( writehandle );
323 osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", modulename );
325 // Add datatypes from database to the fields in the IDL
326 if( oilsExtendIDL() ) {
327 osrfLogError( OSRF_LOG_MARK, "Error extending the IDL" );
335 @brief Implement the class-specific methods.
336 @param ctx Pointer to the method context.
337 @return Zero if successful, or -1 if not.
339 Branch on the method type: create, retrieve, update, delete, search, or id_list.
341 The method parameters and the type of value returned to the client depend on the method
344 int dispatchCRUDMethod( osrfMethodContext* ctx ) {
346 // Get the method type, then can branch on it
347 osrfHash* method_meta = (osrfHash*) ctx->method->userData;
348 const char* methodtype = osrfHashGet( method_meta, "methodtype" );
350 if( !strcmp( methodtype, "create" ))
351 return doCreate( ctx );
352 else if( !strcmp(methodtype, "retrieve" ))
353 return doRetrieve( ctx );
354 else if( !strcmp(methodtype, "update" ))
355 return doUpdate( ctx );
356 else if( !strcmp(methodtype, "delete" ))
357 return doDelete( ctx );
358 else if( !strcmp(methodtype, "search" ))
359 return doSearch( ctx );
360 else if( !strcmp(methodtype, "id_list" ))
361 return doIdList( ctx );
363 osrfAppRespondComplete( ctx, NULL ); // should be unreachable...