1 #include "opensrf/osrf_application.h"
2 #include "opensrf/osrf_settings.h"
3 #include "opensrf/utils.h"
4 #include "objson/object.h"
5 #include "opensrf/log.h"
6 #include "oils_utils.h"
7 #include "oils_constants.h"
8 #include "oils_event.h"
14 #include <libxml/globals.h>
15 #include <libxml/xmlerror.h>
16 #include <libxml/parser.h>
17 #include <libxml/tree.h>
18 #include <libxml/debugXML.h>
19 #include <libxml/xmlmemory.h>
21 #define OILS_AUTH_CACHE_PRFX "oils_cstore_"
22 #define MODULENAME "open-ils.cstore"
23 #define PERSIST_NS "http://open-ils.org/spec/opensrf/IDL/persistance/v1"
24 #define OBJECT_NS "http://open-ils.org/spec/opensrf/IDL/objects/v1"
25 #define BASE_NS "http://opensrf.org/spec/IDL/base/v1"
27 int osrfAppChildInit();
28 int osrfAppInitialize();
30 int dispatchCRUDMethod ( osrfMethodContext* );
31 jsonObject* doCreate ( osrfHash*, jsonObject* );
32 jsonObject* doRetrieve ( osrfHash*, jsonObject* );
33 jsonObject* doUpdate ( osrfHash*, jsonObject* );
34 jsonObject* doDelete ( osrfHash*, jsonObject* );
35 jsonObject* doSearch ( osrfHash*, jsonObject* );
36 jsonObject* oilsMakeJSONFromResult( dbi_result, osrfHash* );
38 dbi_conn dbhandle; /* our db connection */
39 xmlDocPtr idlDoc = NULL; // parse and store the IDL here
42 /* parse and store the IDL here */
45 int osrfAppInitialize() {
47 idlHash = osrfNewHash();
48 osrfHash* usrData = NULL;
50 osrfLogInfo(OSRF_LOG_MARK, "Initializing the CStore Server...");
51 osrfLogInfo(OSRF_LOG_MARK, "Finding XML file...");
53 char * idl_filename = osrf_settings_host_value("/apps/%s/app_settings/IDL", MODULENAME);
54 osrfLogInfo(OSRF_LOG_MARK, "Found file:");
55 osrfLogInfo(OSRF_LOG_MARK, idl_filename);
57 osrfLogInfo(OSRF_LOG_MARK, "Parsing the IDL XML...");
58 idlDoc = xmlReadFile( idl_filename, NULL, XML_PARSE_XINCLUDE );
61 osrfLogError(OSRF_LOG_MARK, "Could not load or parse the IDL XML file!");
65 osrfLogInfo(OSRF_LOG_MARK, "...IDL XML parsed");
67 osrfStringArray* global_methods = osrfNewStringArray(1);
69 //osrfStringArrayAdd( global_methods, "create" );
70 osrfStringArrayAdd( global_methods, "retrieve" );
71 //osrfStringArrayAdd( global_methods, "update" );
72 //osrfStringArrayAdd( global_methods, "delete" );
73 osrfStringArrayAdd( global_methods, "search" );
75 xmlNodePtr docRoot = xmlDocGetRootElement(idlDoc);
76 xmlNodePtr kid = docRoot->children;
78 if (!strcmp( (char*)kid->name, "class" )) {
80 usrData = osrfNewHash();
81 osrfHashSet( usrData, xmlGetProp(kid, "id"), "classname");
82 osrfHashSet( usrData, xmlGetNsProp(kid, "tablename", PERSIST_NS), "tablename");
83 osrfHashSet( usrData, xmlGetNsProp(kid, "fieldmapper", OBJECT_NS), "fieldmapper");
85 osrfHashSet( idlHash, usrData, (char*)osrfHashGet(usrData, "classname") );
87 osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", osrfHashGet(usrData, "fieldmapper") );
90 osrfHash* links = osrfNewHash();
91 osrfHash* fields = osrfNewHash();
93 osrfHashSet( usrData, fields, "fields" );
94 osrfHashSet( usrData, links, "links" );
96 xmlNodePtr _cur = kid->children;
99 char* string_tmp = NULL;
101 if (!strcmp( (char*)_cur->name, "fields" )) {
103 if( (string_tmp = (char*)xmlGetNsProp(_cur, "primary", PERSIST_NS)) ) {
106 strdup( string_tmp ),
112 xmlNodePtr _f = _cur->children;
115 if (strcmp( (char*)_f->name, "field" )) {
120 _tmp = osrfNewHash();
122 if( (string_tmp = (char*)xmlGetNsProp(_f, "array_position", OBJECT_NS)) ) {
125 strdup( string_tmp ),
131 if( (string_tmp = (char*)xmlGetNsProp(_f, "virtual", PERSIST_NS)) ) {
134 strdup( string_tmp ),
140 if( (string_tmp = (char*)xmlGetProp(_f, "name")) ) {
143 strdup( string_tmp ),
148 osrfLogInfo(OSRF_LOG_MARK, "Found field %s for class %s", string_tmp, osrfHashGet(usrData, "classname") );
159 if (!strcmp( (char*)_cur->name, "links" )) {
160 xmlNodePtr _l = _cur->children;
163 if (strcmp( (char*)_l->name, "link" )) {
168 _tmp = osrfNewHash();
170 if( (string_tmp = (char*)xmlGetProp(_l, "reltype")) ) {
173 strdup( string_tmp ),
177 osrfLogInfo(OSRF_LOG_MARK, "Adding link with reltype %s", string_tmp );
180 if( (string_tmp = (char*)xmlGetProp(_l, "key")) ) {
183 strdup( string_tmp ),
187 osrfLogInfo(OSRF_LOG_MARK, "Link fkey is %s", string_tmp );
190 if( (string_tmp = (char*)xmlGetProp(_l, "class")) ) {
193 strdup( string_tmp ),
197 osrfLogInfo(OSRF_LOG_MARK, "Link fclass is %s", string_tmp );
200 osrfStringArray* map = osrfNewStringArray(0);
202 if( (string_tmp = (char*)xmlGetProp(_l, "map") )) {
203 char* map_list = strdup( string_tmp );
204 osrfLogInfo(OSRF_LOG_MARK, "Link mapping list is %s", string_tmp );
206 if (strlen( map_list ) > 0) {
208 char* _map_class = strtok_r(map_list, " ", &st_tmp);
209 osrfStringArrayAdd(map, strdup(_map_class));
211 while ((_map_class = strtok_r(NULL, " ", &st_tmp))) {
212 osrfStringArrayAdd(map, strdup(_map_class));
216 osrfHashSet( _tmp, map, "map");
218 if( (string_tmp = (char*)xmlGetProp(_l, "field")) ) {
221 strdup( string_tmp ),
232 osrfLogInfo(OSRF_LOG_MARK, "Found link %s for class %s", string_tmp, osrfHashGet(usrData, "classname") );
246 osrfHash* method_meta;
247 while ( (method_type = osrfStringArrayGetString(global_methods, i++)) ) {
249 if (!osrfHashGet(usrData, "fieldmapper")) continue;
251 method_meta = osrfNewHash();
252 osrfHashSet(method_meta, usrData, "class");
254 _fm = strdup( (char*)osrfHashGet(usrData, "fieldmapper") );
255 part = strtok_r(_fm, ":", &st_tmp);
257 growing_buffer* method_name = buffer_init(64);
258 buffer_fadd(method_name, "%s.direct.%s", MODULENAME, part);
260 while ((part = strtok_r(NULL, ":", &st_tmp))) {
261 buffer_fadd(method_name, ".%s", part);
263 buffer_fadd(method_name, ".%s", method_type);
266 char* method = buffer_data(method_name);
267 buffer_free(method_name);
270 osrfHashSet( method_meta, method, "methodname" );
271 osrfHashSet( method_meta, method_type, "methodtype" );
273 osrfAppRegisterExtendedMethod(
276 "dispatchCRUDMethod",
291 * Connects to the database
293 int osrfAppChildInit() {
295 osrfLogDebug(OSRF_LOG_MARK, "Attempting to initialize libdbi...");
296 dbi_initialize(NULL);
297 osrfLogDebug(OSRF_LOG_MARK, "... libdbi initialized.");
299 char* driver = osrf_settings_host_value("/apps/%s/app_settings/driver", MODULENAME);
300 char* user = osrf_settings_host_value("/apps/%s/app_settings/database/user", MODULENAME);
301 char* host = osrf_settings_host_value("/apps/%s/app_settings/database/host", MODULENAME);
302 char* port = osrf_settings_host_value("/apps/%s/app_settings/database/port", MODULENAME);
303 char* db = osrf_settings_host_value("/apps/%s/app_settings/database/db", MODULENAME);
304 char* pw = osrf_settings_host_value("/apps/%s/app_settings/database/pw", MODULENAME);
306 osrfLogDebug(OSRF_LOG_MARK, "Attempting to load the database driver [%s]...", driver);
307 dbhandle = dbi_conn_new(driver);
310 osrfLogError(OSRF_LOG_MARK, "Error loading database driver [%s]", driver);
313 osrfLogDebug(OSRF_LOG_MARK, "Database driver [%s] seems OK", driver);
315 osrfLogInfo(OSRF_LOG_MARK, "%s connecting to database. host=%s, "
316 "port=%s, user=%s, pw=%s, db=%s", MODULENAME, host, port, user, pw, db );
318 if(host) dbi_conn_set_option(dbhandle, "host", host );
319 if(port) dbi_conn_set_option_numeric( dbhandle, "port", atoi(port) );
320 if(user) dbi_conn_set_option(dbhandle, "username", user);
321 if(pw) dbi_conn_set_option(dbhandle, "password", pw );
322 if(db) dbi_conn_set_option(dbhandle, "dbname", db );
331 if (dbi_conn_connect(dbhandle) < 0) {
332 dbi_conn_error(dbhandle, &err);
333 osrfLogError( OSRF_LOG_MARK, "Error connecting to database: %s", err);
337 osrfLogInfo(OSRF_LOG_MARK, "%s successfully connected to the database", MODULENAME);
343 osrfStringArray* classes = osrfHashKeys( idlHash );
345 while ( (classname = osrfStringArrayGetString(classes, i++)) ) {
346 osrfHash* class = osrfHashGet( idlHash, classname );
347 osrfHash* fields = osrfHashGet( class, "fields" );
349 growing_buffer* sql_buf = buffer_init(32);
350 buffer_fadd( sql_buf, "SELECT * FROM %s WHERE 1=0;", osrfHashGet(class, "tablename") );
352 char* sql = buffer_data(sql_buf);
353 buffer_free(sql_buf);
354 osrfLogDebug(OSRF_LOG_MARK, "%s Investigatory SQL = %s", MODULENAME, sql);
356 dbi_result result = dbi_conn_query(dbhandle, sql);
362 const char* columnName;
364 while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
366 osrfLogDebug(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
368 /* fetch the fieldmapper index */
369 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
371 osrfLogDebug(OSRF_LOG_MARK, "Found [%s] in IDL hash...", (char*)columnName);
373 /* determine the field type and storage attributes */
374 type = dbi_result_get_field_type(result, columnName);
375 attr = dbi_result_get_field_attribs(result, columnName);
379 case DBI_TYPE_INTEGER :
381 osrfHashSet(_f,"number", "primitive");
383 if( attr & DBI_INTEGER_SIZE8 )
384 osrfHashSet(_f,"INT8", "datatype");
386 osrfHashSet(_f,"INT", "datatype");
389 case DBI_TYPE_DECIMAL :
390 osrfHashSet(_f,"number", "primitive");
391 osrfHashSet(_f,"NUMERIC", "datatype");
394 case DBI_TYPE_STRING :
395 osrfHashSet(_f,"string", "primitive");
396 osrfHashSet(_f,"TEXT", "datatype");
399 case DBI_TYPE_DATETIME :
400 osrfHashSet(_f,"string", "primitive");
401 osrfHashSet(_f,"TIMESTAMP", "datatype");
404 case DBI_TYPE_BINARY :
405 osrfHashSet(_f,"string", "primitive");
406 osrfHashSet(_f,"BYTEA", "datatype");
411 "Setting [%s] to primitive [%s] and datatype [%s]...",
413 osrfHashGet(_f, "primitive"),
414 osrfHashGet(_f, "datatype")
418 dbi_result_free(result);
420 osrfLogDebug(OSRF_LOG_MARK, "No data found for class [%s]...", (char*)classname);
424 osrfStringArrayFree(classes);
430 int dispatchCRUDMethod ( osrfMethodContext* ctx ) {
431 OSRF_METHOD_VERIFY_CONTEXT(ctx);
433 osrfHash* meta = (osrfHash*) ctx->method->userData;
434 osrfHash* class_obj = osrfHashGet( meta, "class" );
436 jsonObject * obj = NULL;
437 if (!strcmp( (char*)osrfHashGet(meta, "methodtype"), "create"))
438 obj = doCreate(class_obj, ctx->params);
440 if (!strcmp( (char*)osrfHashGet(meta, "methodtype"), "retrieve"))
441 obj = doRetrieve(class_obj, ctx->params);
443 if (!strcmp( (char*)osrfHashGet(meta, "methodtype"), "update"))
444 obj = doUpdate(class_obj, ctx->params);
446 if (!strcmp( (char*)osrfHashGet(meta, "methodtype"), "delete"))
447 obj = doDelete(class_obj, ctx->params);
449 if (!strcmp( (char*)osrfHashGet(meta, "methodtype"), "search"))
450 obj = doSearch(class_obj, ctx->params);
452 osrfAppRespondComplete( ctx, obj );
459 jsonObject* doCreate( osrfHash* meta, jsonObject* params ) { return NULL; }
461 jsonObject* doRetrieve( osrfHash* meta, jsonObject* params ) {
465 char* id = jsonObjectToSimpleString(jsonObjectGetIndex(params, 0));
466 jsonObject* order_hash = jsonObjectGetIndex(params, 1);
470 "%s retrieving %s object with id %s",
472 osrfHashGet(meta, "fieldmapper"),
476 jsonObject* fake_params = jsonParseString("[]");
477 jsonObjectPush(fake_params, jsonParseString("{}"));
480 jsonObjectGetIndex(fake_params, 0),
481 osrfHashGet(meta, "primarykey"),
485 if (order_hash) jsonObjectPush(fake_params, jsonObjectClone(order_hash) );
487 jsonObject* list = doSearch(meta, fake_params);
488 obj = jsonObjectClone( jsonObjectGetIndex(list, 0) );
490 jsonObjectFree( list );
491 jsonObjectFree( fake_params );
496 jsonObject* doSearch( osrfHash* meta, jsonObject* params ) {
500 jsonObject* search_hash = jsonObjectGetIndex(params, 0);
501 jsonObject* order_hash = jsonObjectGetIndex(params, 1);
503 growing_buffer* sql_buf = buffer_init(128);
504 buffer_fadd(sql_buf, "SELECT * FROM %s WHERE ", osrfHashGet(meta, "tablename") );
506 jsonObjectNode* node = NULL;
507 jsonObjectIterator* search_itr = jsonNewObjectIterator( search_hash );
510 while ( (node = jsonObjectIteratorNext( search_itr )) ) {
511 osrfHash* field = osrfHashGet( osrfHashGet(meta, "fields"), node->key );
513 if (!field) continue;
518 buffer_add(sql_buf, " AND ");
521 if ( !strcmp(osrfHashGet(field, "primitive"), "number") ) {
525 osrfHashGet(field, "name"),
526 atoi( jsonObjectToSimpleString(node->item) )
529 char* key_string = jsonObjectToSimpleString(node->item);
530 if ( dbi_conn_quote_string(dbhandle, &key_string) ) {
534 osrfHashGet(field, "name"),
539 osrfLogDebug(OSRF_LOG_MARK, "%s: Error quoting key string [%s]", MODULENAME, key_string);
547 _tmp = jsonObjectGetKey( order_hash, "order_by" );
549 string = jsonObjectToSimpleString(_tmp);
558 _tmp = jsonObjectGetKey( order_hash, "limit" );
560 string = jsonObjectToSimpleString(_tmp);
569 _tmp = jsonObjectGetKey( order_hash, "offset" );
571 string = jsonObjectToSimpleString(_tmp);
581 buffer_add(sql_buf, ";");
583 char* sql = buffer_data(sql_buf);
584 buffer_free(sql_buf);
586 osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql);
587 dbi_result result = dbi_conn_query(dbhandle, sql);
589 jsonObject* res_list = jsonParseString("[]");
591 osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors");
593 /* there should be one row at the most */
594 if (dbi_result_first_row(result)) {
595 /* JSONify the result */
596 osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row");
598 obj = oilsMakeJSONFromResult( result, meta );
599 jsonObjectPush(res_list, obj);
600 } while (dbi_result_next_row(result));
602 osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql);
605 /* clean up the query */
606 dbi_result_free(result);
609 osrfLogDebug(OSRF_LOG_MARK, "%s: Error retrieving %s with query [%s]", MODULENAME, osrfHashGet(meta, "fieldmapper"), sql);
615 _tmp = jsonObjectGetKey( order_hash, "flesh" );
617 double x = jsonObjectGetNumber(_tmp);
622 jsonObjectIterator* itr = jsonNewObjectIterator( res_list );
623 while ((cur = jsonObjectIteratorNext( itr ))) {
625 osrfHash* links = osrfHashGet(meta, "links");
626 osrfHash* fields = osrfHashGet(meta, "fields");
630 osrfStringArray* link_fields = osrfHashKeys( links );
631 while ( (link_field = osrfStringArrayGetString(link_fields, i++)) ) {
633 osrfLogDebug(OSRF_LOG_MARK, "Starting to flesh %s", link_field);
635 osrfHash* kid_link = osrfHashGet(links, link_field);
636 if (!kid_link) continue;
638 osrfHash* field = osrfHashGet(fields, link_field);
639 if (!field) continue;
641 osrfHash* value_field = field;
643 osrfHash* kid_idl = osrfHashGet(idlHash, osrfHashGet(kid_link, "class"));
644 if (!kid_idl) continue;
646 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
647 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
650 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "might_have" ))) { // might_have
651 value_field = osrfHashGet( fields, osrfHashGet(meta, "primarykey") );
656 "Link field: %s, remote class: %s, fkey: %s, reltype: %s",
657 osrfHashGet(kid_link, "field"),
658 osrfHashGet(kid_link, "class"),
659 osrfHashGet(kid_link, "key"),
660 osrfHashGet(kid_link, "reltype")
663 jsonObject* fake_params = jsonParseString("[]");
664 jsonObjectPush(fake_params, jsonParseString("{}")); // search hash
665 jsonObjectPush(fake_params, jsonParseString("{}")); // order/flesh hash
667 osrfLogDebug(OSRF_LOG_MARK, "Creating dummy params object...");
670 jsonObjectToSimpleString(
673 atoi( osrfHashGet(value_field, "array_position") )
678 osrfLogDebug(OSRF_LOG_MARK, "Nothing to search for!");
683 jsonObjectGetIndex(fake_params, 0),
684 osrfHashGet(kid_link, "key"),
685 jsonNewObject( search_key )
691 jsonObjectGetIndex(fake_params, 1),
693 jsonNewNumberObject( (double)((int)x - 1) )
696 jsonObject* kids = doSearch(kid_idl, fake_params);
698 osrfLogDebug(OSRF_LOG_MARK, "Search for %s return %d linked objects", osrfHashGet(kid_link, "class"), kids->size);
700 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_a" ))) {
701 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
704 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
705 jsonObjectClone( jsonObjectGetIndex(kids, 0) )
707 jsonObjectFree( kids );
710 if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many
711 osrfLogDebug(OSRF_LOG_MARK, "Storing fleshed objects in %s", osrfHashGet(kid_link, "field"));
714 (unsigned long)atoi( osrfHashGet( field, "array_position" ) ),
719 osrfLogDebug(OSRF_LOG_MARK, "Fleshing of %s complete", osrfHashGet(kid_link, "field"));
721 jsonObjectFree( fake_params );
732 jsonObject* doUpdate( osrfHash* meta, jsonObject* params ) { return NULL; }
734 jsonObject* doDelete( osrfHash* meta, jsonObject* params ) { return NULL; }
737 jsonObject* oilsMakeJSONFromResult( dbi_result result, osrfHash* meta) {
738 if(!(result && meta)) return NULL;
740 jsonObject* object = jsonParseString("[]");
741 jsonObjectSetClass(object, osrfHashGet(meta, "classname"));
743 osrfHash* fields = osrfHashGet(meta, "fields");
745 osrfLogDebug(OSRF_LOG_MARK, "Setting object class to %s ", object->classname);
756 const char* columnName;
758 /* cycle through the column list */
759 while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) {
761 osrfLogDebug(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName);
763 fmIndex = -1; // reset the position
765 /* determine the field type and storage attributes */
766 type = dbi_result_get_field_type(result, columnName);
767 attr = dbi_result_get_field_attribs(result, columnName);
769 /* fetch the fieldmapper index */
770 if( (_f = osrfHashGet(fields, (char*)columnName)) ) {
771 char* virt = (char*)osrfHashGet(_f, "virtual");
772 char* pos = (char*)osrfHashGet(_f, "array_position");
774 if ( !virt || !pos || !(strcmp( virt, "true" )) ) continue;
776 fmIndex = atoi( pos );
777 osrfLogDebug(OSRF_LOG_MARK, "... Found column at position [%s]...", pos);
780 if (dbi_result_field_is_null(result, columnName)) {
781 jsonObjectSetIndex( object, fmIndex, jsonNewObject(NULL) );
786 case DBI_TYPE_INTEGER :
788 if( attr & DBI_INTEGER_SIZE8 )
789 jsonObjectSetIndex( object, fmIndex,
790 jsonNewNumberObject(dbi_result_get_longlong(result, columnName)));
792 jsonObjectSetIndex( object, fmIndex,
793 jsonNewNumberObject(dbi_result_get_long(result, columnName)));
797 case DBI_TYPE_DECIMAL :
798 jsonObjectSetIndex( object, fmIndex,
799 jsonNewNumberObject(dbi_result_get_double(result, columnName)));
802 case DBI_TYPE_STRING :
808 jsonNewObject( dbi_result_get_string(result, columnName) )
813 case DBI_TYPE_DATETIME :
815 memset(dt_string, '\0', 256);
816 memset(&gmdt, '\0', sizeof(gmdt));
817 memset(&_tmp_dt, '\0', sizeof(_tmp_dt));
819 _tmp_dt = dbi_result_get_datetime(result, columnName);
821 localtime_r( &_tmp_dt, &gmdt );
823 if (!(attr & DBI_DATETIME_DATE)) {
824 strftime(dt_string, 255, "%T", &gmdt);
825 } else if (!(attr & DBI_DATETIME_TIME)) {
826 strftime(dt_string, 255, "%F", &gmdt);
828 /* XXX ARG! My eyes! The goggles, they do nothing! */
830 strftime(dt_string, 255, "%FT%T%z", &gmdt);
833 jsonObjectSetIndex( object, fmIndex, jsonNewObject(dt_string) );
837 case DBI_TYPE_BINARY :
838 osrfLogError( OSRF_LOG_MARK,
839 "Can't do binary at column %s : index %d", columnName, columnIndex - 1);