ODBC storage plugin building on Windows...
[shibboleth/sp.git] / odbc-store / odbc-store.cpp
1 /*
2  *  Copyright 2001-2007 Internet2
3  * 
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /**
18  * odbc-store.cpp
19  *
20  * Storage Service using ODBC
21  */
22
23 #if defined (_MSC_VER) || defined(__BORLANDC__)\r
24 # include "config_win32.h"\r
25 #else\r
26 # include "config.h"\r
27 #endif\r
28 \r
29 #ifdef WIN32\r
30 # define _CRT_NONSTDC_NO_DEPRECATE 1\r
31 # define _CRT_SECURE_NO_DEPRECATE 1\r
32 #endif\r
33
34 #ifdef WIN32
35 # define ODBCSTORE_EXPORTS __declspec(dllexport)
36 #else
37 # define ODBCSTORE_EXPORTS
38 #endif
39
40 #include <log4cpp/Category.hh>
41 #include <xercesc/util/XMLUniDefs.hpp>
42 #include <xmltooling/XMLToolingConfig.h>
43 #include <xmltooling/util/NDC.h>
44 #include <xmltooling/util/StorageService.h>
45 #include <xmltooling/util/Threads.h>
46 #include <xmltooling/util/XMLHelper.h>
47
48 #include <sql.h>
49 #include <sqlext.h>
50
51 using namespace xmltooling;
52 using namespace xercesc;
53 using namespace log4cpp;
54 using namespace std;
55
56 #define PLUGIN_VER_MAJOR 1
57 #define PLUGIN_VER_MINOR 0
58
59 #define LONGDATA_BUFLEN 16384
60
61 #define COLSIZE_KEY 255
62 #define COLSIZE_CONTEXT 255
63 #define COLSIZE_STRING_VALUE 255
64
65 #define STRING_TABLE "STRING_TABLE"
66 #define TEXT_TABLE "TEXT_TABLE"
67
68 /* tables definitions - not used here
69
70 #define STRING_TABLE \
71   "CREATE TABLE STRING_TABLE ( " \
72     "context varchar(255), " \
73     "key varchar(255), " \
74     "value varchar(255), " \
75     "expires datetime, " \
76     "version smallint, " \
77     "PRIMARY KEY (context, key)" \
78     ")"
79
80
81 #define TEXT_TABLE \
82   "CREATE TABLE TEXT_TABLE ( "\
83     "context varchar(255), " \
84     "key varchar(255), " \
85     "value text, " \
86     "expires datetime, " \
87     "version smallint, " \
88     "PRIMARY KEY (context, key)" \
89     ")"
90 */
91
92 namespace {
93     static const XMLCh cleanupInterval[] =  UNICODE_LITERAL_15(c,l,e,a,n,u,p,I,n,t,e,r,v,a,l);
94     static const XMLCh ConnectionString[] = UNICODE_LITERAL_16(C,o,n,n,e,c,t,i,o,n,S,t,r,i,n,g);
95
96     // RAII for ODBC handles
97     struct ODBCConn {
98         ODBCConn(SQLHDBC conn) : handle(conn) {}
99         ~ODBCConn() {
100             SQLRETURN sr = SQLEndTran(SQL_HANDLE_DBC, handle, SQL_COMMIT);
101             SQLFreeHandle(SQL_HANDLE_DBC,handle);
102             if (!SQL_SUCCEEDED(sr))
103                 throw IOException("Failed to commit connection.");
104         }
105         operator SQLHDBC() {return handle;}
106         SQLHDBC handle;
107     };
108
109     struct ODBCStatement {
110         ODBCStatement(SQLHSTMT statement) : handle(statement) {}
111         ~ODBCStatement() {SQLFreeHandle(SQL_HANDLE_STMT,handle);}
112         operator SQLHSTMT() {return handle;}
113         SQLHSTMT handle;
114     };
115
116     class ODBCStorageService : public StorageService
117     {
118     public:
119         ODBCStorageService(const DOMElement* e);
120         virtual ~ODBCStorageService();
121
122         void createString(const char* context, const char* key, const char* value, time_t expiration) {
123             return createRow(STRING_TABLE, context, key, value, expiration);
124         }
125         int readString(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL, int version=0) {
126             return readRow(STRING_TABLE, context, key, pvalue, pexpiration, version, false);
127         }
128         int updateString(const char* context, const char* key, const char* value=NULL, time_t expiration=0, int version=0) {
129             return updateRow(STRING_TABLE, context, key, value, expiration, version);
130         }
131         bool deleteString(const char* context, const char* key) {
132             return deleteRow(STRING_TABLE, context, key);
133         }
134
135         void createText(const char* context, const char* key, const char* value, time_t expiration) {
136             return createRow(TEXT_TABLE, context, key, value, expiration);
137         }
138         int readText(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL, int version=0) {
139             return readRow(TEXT_TABLE, context, key, pvalue, pexpiration, version, true);
140         }
141         int updateText(const char* context, const char* key, const char* value=NULL, time_t expiration=0, int version=0) {
142             return updateRow(TEXT_TABLE, context, key, value, expiration, version);
143         }
144         bool deleteText(const char* context, const char* key) {
145             return deleteRow(TEXT_TABLE, context, key);
146         }
147
148         void reap(const char* context) {
149             reap(STRING_TABLE, context);
150             reap(TEXT_TABLE, context);
151         }
152
153         void updateContext(const char* context, time_t expiration) {
154             updateContext(STRING_TABLE, context, expiration);
155             updateContext(TEXT_TABLE, context, expiration);
156         }
157
158         void deleteContext(const char* context) {
159             deleteContext(STRING_TABLE, context);
160             deleteContext(TEXT_TABLE, context);
161         }
162          
163
164     private:
165         void createRow(const char *table, const char* context, const char* key, const char* value, time_t expiration);
166         int readRow(const char *table, const char* context, const char* key, string* pvalue, time_t* pexpiration, int version, bool text);
167         int updateRow(const char *table, const char* context, const char* key, const char* value, time_t expiration, int version);
168         bool deleteRow(const char *table, const char* context, const char* key);
169
170         void reap(const char* table, const char* context);
171         void updateContext(const char* table, const char* context, time_t expiration);
172         void deleteContext(const char* table, const char* context);
173
174         SQLHDBC getHDBC();
175         SQLHSTMT getHSTMT(SQLHDBC);
176         pair<int,int> getVersion(SQLHDBC);
177         void log_error(SQLHANDLE handle, SQLSMALLINT htype);
178
179         static void* cleanup_fn(void*); 
180         void cleanup();
181
182         Category& m_log;
183         int m_cleanupInterval;
184         CondWait* shutdown_wait;
185         Thread* cleanup_thread;
186         bool shutdown;
187
188         SQLHENV m_henv;
189         string m_connstring;
190     };
191
192     StorageService* ODBCStorageServiceFactory(const DOMElement* const & e)
193     {
194         return new ODBCStorageService(e);
195     }
196
197     // convert SQL timestamp to time_t 
198     time_t timeFromTimestamp(SQL_TIMESTAMP_STRUCT expires)
199     {
200         time_t ret;
201         struct tm t;
202         t.tm_sec=expires.second;
203         t.tm_min=expires.minute;
204         t.tm_hour=expires.hour;
205         t.tm_mday=expires.day;
206         t.tm_mon=expires.month-1;
207         t.tm_year=expires.year-1900;
208         t.tm_isdst=0;
209 #if defined(HAVE_TIMEGM)
210         ret = timegm(&t);
211 #else
212         ret = mktime(&t) - timezone;
213 #endif
214         return (ret);
215     }
216
217     // conver time_t to SQL string
218     void timestampFromTime(time_t t, char* ret)
219     {
220 #ifdef HAVE_GMTIME_R
221         struct tm res;
222         struct tm* ptime=gmtime_r(&t,&res);
223 #else
224         struct tm* ptime=gmtime(&t);
225 #endif
226         strftime(ret,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
227     }
228
229     // make a string safe for SQL command
230     // result to be free'd only if it isn't the input
231     static char *makeSafeSQL(const char *src)
232     {
233        int ns = 0;
234        int nc = 0;
235        char *s;
236     
237        // see if any conversion needed
238        for (s=(char*)src; *s; nc++,s++) if (*s=='\''||*s=='\\') ns++;
239        if (ns==0) return ((char*)src);
240     
241        char *safe = new char[(nc+2*ns+1)];
242        for (s=safe; *src; src++) {
243            if (*src=='\''||*src=='\\') *s++ = '\\';
244            *s++ = (char)*src;
245        }
246        *s = '\0';
247        return (safe);
248     }
249
250     void freeSafeSQL(char *safe, const char *src)
251     {
252         if (safe!=src)
253             delete[](safe);
254     }
255 };
256
257 ODBCStorageService::ODBCStorageService(const DOMElement* e) : m_log(Category::getInstance("XMLTooling.StorageService")),
258    m_cleanupInterval(900), shutdown_wait(NULL), cleanup_thread(NULL), shutdown(false), m_henv(SQL_NULL_HANDLE)
259 {
260 #ifdef _DEBUG
261     xmltooling::NDC ndc("ODBCStorageService");
262 #endif
263
264     const XMLCh* tag=e ? e->getAttributeNS(NULL,cleanupInterval) : NULL;
265     if (tag && *tag)
266         m_cleanupInterval = XMLString::parseInt(tag);
267     if (!m_cleanupInterval)
268         m_cleanupInterval = 900;
269
270     if (m_henv == SQL_NULL_HANDLE) {
271         // Enable connection pooling.
272         SQLSetEnvAttr(SQL_NULL_HANDLE, SQL_ATTR_CONNECTION_POOLING, (void*)SQL_CP_ONE_PER_HENV, 0);
273
274         // Allocate the environment.
275         if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_henv)))
276             throw XMLToolingException("ODBC failed to initialize.");
277
278         // Specify ODBC 3.x
279         SQLSetEnvAttr(m_henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
280
281         m_log.info("ODBC initialized");
282     }
283
284     // Grab connection string from the configuration.
285     e = e ? XMLHelper::getFirstChildElement(e,ConnectionString) : NULL;
286     if (!e || !e->hasChildNodes()) {
287         SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
288         throw XMLToolingException("ODBC StorageService requires ConnectionString element in configuration.");
289     }
290     auto_ptr_char arg(e->getFirstChild()->getNodeValue());
291     m_connstring=arg.get();
292
293     // Connect and check version.
294     ODBCConn conn(getHDBC());
295     pair<int,int> v=getVersion(conn);
296
297     // Make sure we've got the right version.
298     if (v.first != PLUGIN_VER_MAJOR) {
299         SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
300         m_log.crit("unknown database version: %d.%d", v.first, v.second);
301         throw XMLToolingException("Unknown database version for ODBC StorageService.");
302     }
303
304     // Initialize the cleanup thread
305     shutdown_wait = CondWait::create();
306     cleanup_thread = Thread::create(&cleanup_fn, (void*)this);
307 }
308
309 ODBCStorageService::~ODBCStorageService()
310 {
311     shutdown = true;
312     shutdown_wait->signal();
313     cleanup_thread->join(NULL);
314     delete shutdown_wait;
315     SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
316 }
317
318 void ODBCStorageService::log_error(SQLHANDLE handle, SQLSMALLINT htype)
319 {
320     SQLSMALLINT  i = 0;
321     SQLINTEGER   native;
322     SQLCHAR      state[7];
323     SQLCHAR      text[256];
324     SQLSMALLINT  len;
325     SQLRETURN    ret;
326
327     do {
328         ret = SQLGetDiagRec(htype, handle, ++i, state, &native, text, sizeof(text), &len);
329         if (SQL_SUCCEEDED(ret))
330             m_log.error("ODBC Error: %s:%ld:%ld:%s", state, i, native, text);
331     } while(SQL_SUCCEEDED(ret));
332 }
333
334 SQLHDBC ODBCStorageService::getHDBC()
335 {
336 #ifdef _DEBUG
337     xmltooling::NDC ndc("getHDBC");
338 #endif
339
340     // Get a handle.
341     SQLHDBC handle;
342     SQLRETURN sr=SQLAllocHandle(SQL_HANDLE_DBC, m_henv, &handle);
343     if (!SQL_SUCCEEDED(sr)) {
344         m_log.error("failed to allocate connection handle");
345         log_error(m_henv, SQL_HANDLE_ENV);
346         throw IOException("ODBC StorageService failed to allocate a connection handle.");
347     }
348
349     sr=SQLDriverConnect(handle,NULL,(SQLCHAR*)m_connstring.c_str(),m_connstring.length(),NULL,0,NULL,SQL_DRIVER_NOPROMPT);
350     if (!SQL_SUCCEEDED(sr)) {
351         m_log.error("failed to connect to database");
352         log_error(handle, SQL_HANDLE_DBC);
353         throw IOException("ODBC StorageService failed to connect to database.");
354     }
355
356     sr = SQLSetConnectAttr(handle, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, NULL);
357     if (!SQL_SUCCEEDED(sr))
358         throw IOException("ODBC StorageService failed to disable auto-commit mode.");
359     sr = SQLSetConnectAttr(handle, SQL_ATTR_TXN_ISOLATION, (SQLPOINTER)SQL_TXN_SERIALIZABLE, NULL);
360     if (!SQL_SUCCEEDED(sr))
361         throw IOException("ODBC StorageService failed to enable transaction isolation.");
362
363     return handle;
364 }
365
366 SQLHSTMT ODBCStorageService::getHSTMT(SQLHDBC conn)
367 {
368     SQLHSTMT hstmt;
369     SQLRETURN sr=SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
370     if (!SQL_SUCCEEDED(sr)) {
371         m_log.error("failed to allocate statement handle");
372         log_error(conn, SQL_HANDLE_DBC);
373         throw IOException("ODBC StorageService failed to allocate a statement handle.");
374     }
375     return hstmt;
376 }
377
378 pair<int,int> ODBCStorageService::getVersion(SQLHDBC conn)
379 {
380     // Grab the version number from the database.
381     ODBCStatement stmt(getHSTMT(conn));
382     
383     SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)"SELECT major,minor FROM version", SQL_NTS);
384     if (!SQL_SUCCEEDED(sr)) {
385         m_log.error("failed to read version from database");
386         log_error(stmt, SQL_HANDLE_STMT);
387         throw IOException("ODBC StorageService failed to read version from database.");
388     }
389
390     SQLINTEGER major;
391     SQLINTEGER minor;
392     SQLBindCol(stmt,1,SQL_C_SLONG,&major,0,NULL);
393     SQLBindCol(stmt,2,SQL_C_SLONG,&minor,0,NULL);
394
395     if ((sr=SQLFetch(stmt)) != SQL_NO_DATA)
396         return pair<int,int>(major,minor);
397
398     m_log.error("no rows returned in version query");
399     throw IOException("ODBC StorageService failed to read version from database.");
400 }
401
402 void ODBCStorageService::createRow(const char *table, const char* context, const char* key, const char* value, time_t expiration)
403 {
404 #ifdef _DEBUG
405     xmltooling::NDC ndc("createRow");
406 #endif
407
408     char timebuf[32];
409     timestampFromTime(expiration, timebuf);
410
411     // Get statement handle.
412     ODBCConn conn(getHDBC());
413     ODBCStatement stmt(getHSTMT(conn));
414
415     // Prepare and exectute insert statement.
416     char *scontext = makeSafeSQL(context);
417     char *skey = makeSafeSQL(key);
418     char *svalue = makeSafeSQL(value);
419     string q  = string("INSERT ") + table + " VALUES ('" + scontext + "','" + skey + "','" + svalue + "'," + timebuf + "', 1)";
420     freeSafeSQL(scontext, context);
421     freeSafeSQL(skey, key);
422     freeSafeSQL(svalue, value);
423     m_log.debug("SQL: %s", q.c_str());
424
425     SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
426     if (!SQL_SUCCEEDED(sr)) {
427         m_log.error("insert record failed (t=%s, c=%s, k=%s)", table, context, key);
428         log_error(stmt, SQL_HANDLE_STMT);
429         throw IOException("ODBC StorageService failed to insert record.");
430     }
431 }
432
433 int ODBCStorageService::readRow(
434     const char *table, const char* context, const char* key, string* pvalue, time_t* pexpiration, int version, bool text
435     )
436 {
437 #ifdef _DEBUG
438     xmltooling::NDC ndc("readRow");
439 #endif
440
441     SQLCHAR *tvalue = NULL;
442
443     // Get statement handle.
444     ODBCConn conn(getHDBC());
445     ODBCStatement stmt(getHSTMT(conn));
446
447     // Prepare and exectute select statement.
448     char *scontext = makeSafeSQL(context);
449     char *skey = makeSafeSQL(key);
450     string q("SELECT version");
451     if (pexpiration)
452         q += ",expires";
453     if (pvalue)
454         q += ",value";
455     q = q + " FROM " + table + " WHERE context='" + scontext + "' AND key='" + skey + "' AND expires > NOW()";
456     freeSafeSQL(scontext, context);
457     freeSafeSQL(skey, key);
458     m_log.debug("SQL: %s", q.c_str());
459
460     SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
461     if (!SQL_SUCCEEDED(sr)) {
462         m_log.error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
463         log_error(stmt, SQL_HANDLE_STMT);
464         throw IOException("ODBC StorageService search failed.");
465     }
466
467     SQLSMALLINT ver;
468     SQL_TIMESTAMP_STRUCT expiration;
469
470     SQLBindCol(stmt,1,SQL_C_SSHORT,&ver,0,NULL);
471     if (pexpiration)
472         SQLBindCol(stmt,2,SQL_C_TYPE_TIMESTAMP,&expiration,0,NULL);
473
474     if ((sr=SQLFetch(stmt)) == SQL_NO_DATA)
475         return 0;
476
477     if (pexpiration)
478         *pexpiration = timeFromTimestamp(expiration);
479
480     if (version == ver)
481         return version; // nothing's changed, so just echo back the version
482
483     if (pvalue) {
484         SQLINTEGER len;\r
485         SQLCHAR buf[LONGDATA_BUFLEN];\r
486         while ((sr=SQLGetData(stmt,pexpiration ? 3 : 2,SQL_C_CHAR,buf,sizeof(buf),&len)) != SQL_NO_DATA) {\r
487             if (!SQL_SUCCEEDED(sr)) {\r
488                 m_log.error("error while reading text field from result set");\r
489                 log_error(stmt, SQL_HANDLE_STMT);\r
490                 throw IOException("ODBC StorageService search failed to read data from result set.");\r
491             }\r
492             pvalue->append((char*)buf);\r
493         }\r
494     }
495     
496     return ver;
497 }
498
499 int ODBCStorageService::updateRow(const char *table, const char* context, const char* key, const char* value, time_t expiration, int version)
500 {
501 #ifdef _DEBUG
502     xmltooling::NDC ndc("updateRow");
503 #endif
504
505     if (!value && !expiration)
506         throw IOException("ODBC StorageService given invalid update instructions.");
507
508     // Get statement handle.
509     ODBCConn conn(getHDBC());
510     ODBCStatement stmt(getHSTMT(conn));
511
512     // First, fetch the current version for later, which also ensures the record still exists.
513     char *scontext = makeSafeSQL(context);
514     char *skey = makeSafeSQL(key);
515     string q("SELECT version FROM ");
516     q = q + table + " WHERE context='" + scontext + "' AND key='" + key + "' AND expires > NOW()";
517
518     m_log.debug("SQL: %s", q.c_str());
519
520     SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
521     if (!SQL_SUCCEEDED(sr)) {
522         freeSafeSQL(scontext, context);
523         freeSafeSQL(skey, key);
524         m_log.error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
525         log_error(stmt, SQL_HANDLE_STMT);
526         throw IOException("ODBC StorageService search failed.");
527     }
528
529     SQLSMALLINT ver;
530     SQLBindCol(stmt,1,SQL_C_SSHORT,&ver,0,NULL);
531     if ((sr=SQLFetch(stmt)) == SQL_NO_DATA) {
532         freeSafeSQL(scontext, context);
533         freeSafeSQL(skey, key);
534         return 0;
535     }
536
537     // Check version?
538     if (version > 0 && version != ver) {
539         freeSafeSQL(scontext, context);
540         freeSafeSQL(skey, key);
541         return -1;
542     }
543
544     // Prepare and exectute update statement.
545     q = string("UPDATE ") + table + " SET ";
546
547     if (value) {
548         char *svalue = makeSafeSQL(value);
549         q = q + "value='" + svalue + "'" + ",version=version+1";
550         freeSafeSQL(svalue, value);
551     }
552
553     if (expiration) {
554         char timebuf[32];
555         timestampFromTime(expiration, timebuf);
556         if (value)
557             q += ',';
558         q = q + "expires = '" + timebuf + "' ";
559     }
560
561     q = q + " WHERE context='" + scontext + "' AND key='" + key + "'";
562     freeSafeSQL(scontext, context);
563     freeSafeSQL(skey, key);
564
565     m_log.debug("SQL: %s", q.c_str());
566     sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
567     if (sr==SQL_NO_DATA)
568         return 0;   // went missing?
569     else if (!SQL_SUCCEEDED(sr)) {
570         m_log.error("update of record failed (t=%s, c=%s, k=%s", table, context, key);
571         log_error(stmt, SQL_HANDLE_STMT);
572         throw IOException("ODBC StorageService failed to update record.");
573     }
574
575     return ver + 1;
576 }
577
578 bool ODBCStorageService::deleteRow(const char *table, const char *context, const char* key)
579 {
580 #ifdef _DEBUG
581     xmltooling::NDC ndc("deleteRow");
582 #endif
583
584     // Get statement handle.
585     ODBCConn conn(getHDBC());
586     ODBCStatement stmt(getHSTMT(conn));
587
588     // Prepare and execute delete statement.
589     char *scontext = makeSafeSQL(context);
590     char *skey = makeSafeSQL(key);
591     string q = string("DELETE FROM ") + table + " WHERE context='" + scontext + "' AND key='" + skey + "'";
592     freeSafeSQL(scontext, context);
593     freeSafeSQL(skey, key);
594     m_log.debug("SQL: %s", q.c_str());
595
596     SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
597      if (sr==SQL_NO_DATA)
598         return false;
599     else if (!SQL_SUCCEEDED(sr)) {
600         m_log.error("error deleting record (t=%s, c=%s, k=%s)", table, context, key);
601         log_error(stmt, SQL_HANDLE_STMT);
602         throw IOException("ODBC StorageService failed to delete record.");
603     }
604
605     return true;
606 }
607
608
609 void ODBCStorageService::cleanup()
610 {
611 #ifdef _DEBUG
612     xmltooling::NDC ndc("cleanup");
613 #endif
614
615     Mutex* mutex = Mutex::create();
616
617     mutex->lock();
618
619     m_log.info("cleanup thread started... running every %d secs", m_cleanupInterval);
620
621     while (!shutdown) {
622         shutdown_wait->timedwait(mutex, m_cleanupInterval);
623         if (shutdown)
624             break;
625         try {
626             reap(NULL);
627         }
628         catch (exception& ex) {
629             m_log.error("cleanup thread swallowed exception: %s", ex.what());
630         }
631     }
632
633     m_log.info("cleanup thread exiting...");
634
635     mutex->unlock();
636     delete mutex;
637     Thread::exit(NULL);
638 }
639
640 void* ODBCStorageService::cleanup_fn(void* cache_p)
641 {
642   ODBCStorageService* cache = (ODBCStorageService*)cache_p;
643
644 #ifndef WIN32
645   // First, let's block all signals
646   Thread::mask_all_signals();
647 #endif
648
649   // Now run the cleanup process.
650   cache->cleanup();
651   return NULL;
652 }
653
654 void ODBCStorageService::updateContext(const char *table, const char* context, time_t expiration)
655 {
656 #ifdef _DEBUG
657     xmltooling::NDC ndc("updateContext");
658 #endif
659
660     // Get statement handle.
661     ODBCConn conn(getHDBC());
662     ODBCStatement stmt(getHSTMT(conn));
663
664     char timebuf[32];
665     timestampFromTime(expiration, timebuf);
666
667     char *scontext = makeSafeSQL(context);
668     string q("UPDATE ");
669     q = q + table + " SET expires = '" + timebuf + "' WHERE context='" + scontext + "' AND expires > NOW()";
670     freeSafeSQL(scontext, context);
671
672     m_log.debug("SQL: %s", q.c_str());
673
674     SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
675     if ((sr!=SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
676         m_log.error("error updating records (t=%s, c=%s)", table, context ? context : "all");
677         log_error(stmt, SQL_HANDLE_STMT);
678         throw IOException("ODBC StorageService failed to update context expiration.");
679     }
680 }
681
682 void ODBCStorageService::reap(const char *table, const char* context)
683 {
684 #ifdef _DEBUG
685     xmltooling::NDC ndc("reap");
686 #endif
687
688     // Get statement handle.
689     ODBCConn conn(getHDBC());
690     ODBCStatement stmt(getHSTMT(conn));
691
692     // Prepare and execute delete statement.
693     string q;
694     if (context) {
695         char *scontext = makeSafeSQL(context);
696         q = string("DELETE FROM ") + table + " WHERE context='" + scontext + "' AND expires <= NOW()";
697         freeSafeSQL(scontext, context);
698     }
699     else {
700         q = string("DELETE FROM ") + table + " WHERE expires <= NOW()";
701     }
702     m_log.debug("SQL: %s", q.c_str());
703
704     SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
705     if ((sr!=SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
706         m_log.error("error expiring records (t=%s, c=%s)", table, context ? context : "all");
707         log_error(stmt, SQL_HANDLE_STMT);
708         throw IOException("ODBC StorageService failed to purge expired records.");
709     }
710 }
711
712 void ODBCStorageService::deleteContext(const char *table, const char* context)
713 {
714 #ifdef _DEBUG
715     xmltooling::NDC ndc("deleteContext");
716 #endif
717
718     // Get statement handle.
719     ODBCConn conn(getHDBC());
720     ODBCStatement stmt(getHSTMT(conn));
721
722     // Prepare and execute delete statement.
723     char *scontext = makeSafeSQL(context);
724     string q = string("DELETE FROM ") + table + " WHERE context='" + scontext + "'";
725     freeSafeSQL(scontext, context);
726     m_log.debug("SQL: %s", q.c_str());
727
728     SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
729     if ((sr!=SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
730         m_log.error("error deleting context (t=%s, c=%s)", table, context);
731         log_error(stmt, SQL_HANDLE_STMT);
732         throw IOException("ODBC StorageService failed to delete context.");
733     }
734 }
735
736 extern "C" int ODBCSTORE_EXPORTS xmltooling_extension_init(void*)\r
737 {\r
738     // Register this SS type\r
739     XMLToolingConfig::getConfig().StorageServiceManager.registerFactory("ODBC", ODBCStorageServiceFactory);\r
740     return 0;\r
741 }\r
742 \r
743 extern "C" void ODBCSTORE_EXPORTS xmltooling_extension_term()\r
744 {\r
745     XMLToolingConfig::getConfig().StorageServiceManager.deregisterFactory("ODBC");\r
746 }\r