Implement context storage handles.
[shibboleth/cpp-xmltooling.git] / xmltooling / impl / MemoryStorageService.cpp
1 /*\r
2  *  Copyright 2001-2005 Internet2\r
3  * \r
4  * Licensed under the Apache License, Version 2.0 (the "License");\r
5  * you may not use this file except in compliance with the License.\r
6  * You may obtain a copy of the License at\r
7  *\r
8  *     http://www.apache.org/licenses/LICENSE-2.0\r
9  *\r
10  * Unless required by applicable law or agreed to in writing, software\r
11  * distributed under the License is distributed on an "AS IS" BASIS,\r
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
13  * See the License for the specific language governing permissions and\r
14  * limitations under the License.\r
15  */\r
16 \r
17 /**\r
18  * MemoryStorageService.cpp\r
19  * \r
20  * In-memory "persistent" storage, suitable for simple applications.\r
21  */\r
22 \r
23 #include "internal.h"\r
24 #include "util/NDC.h"\r
25 #include "util/StorageService.h"\r
26 #include "util/Threads.h"\r
27 #include "util/XMLHelper.h"\r
28 \r
29 #include <log4cpp/Category.hh>\r
30 #include <xercesc/util/XMLUniDefs.hpp>\r
31 \r
32 using namespace xmltooling;\r
33 using namespace log4cpp;\r
34 using namespace std;\r
35 \r
36 namespace xmltooling {\r
37     class XMLTOOL_DLLLOCAL MemoryStorageService : public StorageService\r
38     {\r
39     public:\r
40         MemoryStorageService(const DOMElement* e);\r
41         virtual ~MemoryStorageService();\r
42         \r
43         StorageHandle* createHandle();\r
44         \r
45         void createString(StorageHandle* handle, const char* key, const char* value, time_t expiration);\r
46         bool readString(StorageHandle* handle, const char* key, string& value, time_t modifiedSince=0);\r
47         bool updateString(StorageHandle* handle, const char* key, const char* value=NULL, time_t expiration=0);\r
48         bool deleteString(StorageHandle* handle, const char* key);\r
49         \r
50         void createText(StorageHandle* handle, const char* key, const char* value, time_t expiration) {\r
51             return createString(handle, key, value, expiration);\r
52         }\r
53         bool readText(StorageHandle* handle, const char* key, string& value, time_t modifiedSince=0) {\r
54             return readString(handle, key, value, modifiedSince);\r
55         }\r
56         bool updateText(StorageHandle* handle, const char* key, const char* value=NULL, time_t expiration=0) {\r
57             return updateString(handle, key, value, expiration);\r
58         }\r
59         bool deleteText(StorageHandle* handle, const char* key) {\r
60             return deleteString(handle, key);\r
61         }\r
62         \r
63         void reap(StorageHandle* handle);\r
64 \r
65         void removeHandle(StorageHandle* handle);\r
66 \r
67     private:\r
68         void cleanup();\r
69     \r
70         struct XMLTOOL_DLLLOCAL Record {\r
71             Record() : modified(0), expiration(0) {}\r
72             Record(string s, time_t t1, time_t t2) : data(s), modified(t1), expiration(t2) {}\r
73             string data;\r
74             time_t modified, expiration;\r
75         };\r
76         \r
77         struct XMLTOOL_DLLLOCAL MemoryHandle : public StorageHandle {\r
78             MemoryHandle(StorageService* storage) : StorageHandle(storage), m_lock(RWLock::create()) {}\r
79             virtual ~MemoryHandle() {\r
80                 delete m_lock;\r
81                 static_cast<MemoryStorageService*>(m_storage)->removeHandle(this);\r
82             }\r
83             map<string,Record> m_dataMap;\r
84             multimap<time_t,string> m_expMap;\r
85             RWLock* m_lock;\r
86             unsigned long reap();\r
87         };\r
88         \r
89         vector<MemoryHandle*> m_handles;\r
90         Mutex* mutex;\r
91         CondWait* shutdown_wait;\r
92         Thread* cleanup_thread;\r
93         static void* cleanup_fn(void*);\r
94         bool shutdown;\r
95         int m_cleanupInterval;\r
96         Category& m_log;\r
97     };\r
98 \r
99     StorageService* XMLTOOL_DLLLOCAL MemoryStorageServiceFactory(const DOMElement* const & e)\r
100     {\r
101         return new MemoryStorageService(e);\r
102     }\r
103 \r
104 };\r
105 \r
106 static const XMLCh cleanupInterval[] = UNICODE_LITERAL_15(c,l,e,a,n,u,p,I,n,t,e,r,v,a,l);\r
107 \r
108 MemoryStorageService::MemoryStorageService(const DOMElement* e)\r
109     : mutex(NULL), shutdown_wait(NULL), cleanup_thread(NULL), shutdown(false), m_cleanupInterval(0),\r
110         m_log(Category::getInstance(XMLTOOLING_LOGCAT".StorageService"))\r
111 {\r
112     mutex = Mutex::create();\r
113     shutdown_wait = CondWait::create();\r
114     cleanup_thread = Thread::create(&cleanup_fn, (void*)this);\r
115 \r
116     const XMLCh* tag=e ? e->getAttributeNS(NULL,cleanupInterval) : NULL;\r
117     if (tag && *tag) {\r
118         m_cleanupInterval = XMLString::parseInt(tag);\r
119     }\r
120     if (!m_cleanupInterval)\r
121         m_cleanupInterval=300;\r
122 }\r
123 \r
124 MemoryStorageService::~MemoryStorageService()\r
125 {\r
126     // Shut down the cleanup thread and let it know...\r
127     shutdown = true;\r
128     shutdown_wait->signal();\r
129     cleanup_thread->join(NULL);\r
130 \r
131     delete shutdown_wait;\r
132     delete mutex;\r
133     for_each(m_handles.begin(), m_handles.end(), xmltooling::cleanup<MemoryHandle>());\r
134 }\r
135 \r
136 StorageService::StorageHandle* MemoryStorageService::createHandle()\r
137 {\r
138     Lock wrapper(mutex);\r
139     MemoryHandle* ret = new MemoryHandle(this);\r
140     m_handles.push_back(ret);\r
141     return ret;\r
142 }\r
143 \r
144 void MemoryStorageService::removeHandle(StorageHandle* handle)\r
145 {\r
146     Lock wrapper(mutex);\r
147     for (vector<MemoryHandle*>::iterator i=m_handles.begin(); i!=m_handles.end(); ++i) {\r
148         if (*i == handle) {\r
149             m_handles.erase(i);\r
150             return;\r
151         }\r
152     }\r
153 }\r
154 \r
155 void* MemoryStorageService::cleanup_fn(void* cache_p)\r
156 {\r
157     MemoryStorageService* cache = reinterpret_cast<MemoryStorageService*>(cache_p);\r
158 \r
159 #ifndef WIN32\r
160     // First, let's block all signals \r
161     Thread::mask_all_signals();\r
162 #endif\r
163 \r
164     // Now run the cleanup process.\r
165     cache->cleanup();\r
166     return NULL;\r
167 }\r
168 \r
169 void MemoryStorageService::cleanup()\r
170 {\r
171 #ifdef _DEBUG\r
172     NDC ndc("cleanup");\r
173 #endif\r
174     \r
175 \r
176     Mutex* mutex = Mutex::create();\r
177     mutex->lock();\r
178 \r
179     m_log.info("cleanup thread started...running every %d seconds", m_cleanupInterval);\r
180 \r
181     while (!shutdown) {\r
182         shutdown_wait->timedwait(mutex, m_cleanupInterval);\r
183         if (shutdown)\r
184             break;\r
185         \r
186         unsigned long count=0;\r
187         for (vector<MemoryHandle*>::iterator i=m_handles.begin(); i!=m_handles.end(); ++i)\r
188             count += (*i)->reap();\r
189         \r
190         if (count)\r
191             m_log.info("purged %d record(s) from storage", count);\r
192     }\r
193 \r
194     m_log.info("cleanup thread finished");\r
195 \r
196     mutex->unlock();\r
197     delete mutex;\r
198     Thread::exit(NULL);\r
199 }\r
200 \r
201 void MemoryStorageService::reap(StorageHandle* handle)\r
202 {\r
203     if (!isValid(handle))\r
204         throw IOException("Invalid storage handle.");\r
205     static_cast<MemoryHandle*>(handle)->reap();\r
206 }\r
207 \r
208 unsigned long MemoryStorageService::MemoryHandle::reap()\r
209 {\r
210     // Lock the "database".\r
211     m_lock->wrlock();\r
212     SharedLock wrapper(m_lock, false);\r
213     \r
214     // Garbage collect any expired entries.\r
215     unsigned long count=0;\r
216     time_t now=time(NULL)-XMLToolingConfig::getConfig().clock_skew_secs;\r
217     multimap<time_t,string>::iterator stop=m_expMap.upper_bound(now);\r
218     for (multimap<time_t,string>::iterator i=m_expMap.begin(); i!=stop; m_expMap.erase(i++)) {\r
219         m_dataMap.erase(i->second);\r
220         ++count;\r
221     }\r
222 \r
223     return count;\r
224 }\r
225 \r
226 void MemoryStorageService::createString(StorageHandle* handle, const char* key, const char* value, time_t expiration)\r
227 {\r
228     if (!isValid(handle))\r
229         throw IOException("Invalid storage handle.");\r
230     MemoryHandle* h = static_cast<MemoryHandle*>(handle);\r
231 \r
232     // Lock the maps.\r
233     h->m_lock->wrlock();\r
234     SharedLock wrapper(h->m_lock, false);\r
235     \r
236     // Check for a duplicate.\r
237     map<string,Record>::iterator i=h->m_dataMap.find(key);\r
238     if (i!=h->m_dataMap.end())\r
239         throw IOException("attempted to insert a record with duplicate key ($1)", params(1,key));\r
240     \r
241     h->m_dataMap[key]=Record(value,time(NULL),expiration);\r
242     h->m_expMap.insert(multimap<time_t,string>::value_type(expiration,key));\r
243     \r
244     m_log.debug("inserted record (%s)", key);\r
245 }\r
246 \r
247 bool MemoryStorageService::readString(StorageHandle* handle, const char* key, string& value, time_t modifiedSince)\r
248 {\r
249     if (!isValid(handle))\r
250         throw IOException("Invalid storage handle.");\r
251     MemoryHandle* h = static_cast<MemoryHandle*>(handle);\r
252 \r
253     SharedLock wrapper(h->m_lock);\r
254     map<string,Record>::iterator i=h->m_dataMap.find(key);\r
255     if (i==h->m_dataMap.end())\r
256         return false;\r
257     else if (modifiedSince >= i->second.modified)\r
258         return false;\r
259     value = i->second.data;\r
260     return true;\r
261 }\r
262 \r
263 bool MemoryStorageService::updateString(StorageHandle* handle, const char* key, const char* value, time_t expiration)\r
264 {\r
265     if (!isValid(handle))\r
266         throw IOException("Invalid storage handle.");\r
267     MemoryHandle* h = static_cast<MemoryHandle*>(handle);\r
268 \r
269     // Lock the maps.\r
270     h->m_lock->wrlock();\r
271     SharedLock wrapper(h->m_lock, false);\r
272 \r
273     map<string,Record>::iterator i=h->m_dataMap.find(key);\r
274     if (i==h->m_dataMap.end())\r
275         return false;\r
276         \r
277     if (value)\r
278         i->second.data = value;\r
279         \r
280     if (expiration && expiration != i->second.expiration) {\r
281         // Update secondary map.\r
282         pair<multimap<time_t,string>::iterator,multimap<time_t,string>::iterator> range =\r
283             h->m_expMap.equal_range(i->second.expiration);\r
284         for (; range.first != range.second; ++range.first) {\r
285             if (range.first->second == i->first) {\r
286                 h->m_expMap.erase(range.first);\r
287                 break;\r
288             }\r
289         }\r
290         i->second.expiration = expiration;\r
291        h->m_expMap.insert(multimap<time_t,string>::value_type(expiration,key));\r
292     }\r
293 \r
294     i->second.modified = time(NULL);\r
295     m_log.debug("updated record (%s)", key);\r
296     return true;\r
297 }\r
298 \r
299 bool MemoryStorageService::deleteString(StorageHandle* handle, const char* key)\r
300 {\r
301     if (!isValid(handle))\r
302         throw IOException("Invalid storage handle.");\r
303     MemoryHandle* h = static_cast<MemoryHandle*>(handle);\r
304 \r
305     // Lock the maps.\r
306     h->m_lock->wrlock();\r
307     SharedLock wrapper(h->m_lock, false);\r
308     \r
309     // Find the record.\r
310     map<string,Record>::iterator i=h->m_dataMap.find(key);\r
311     if (i!=h->m_dataMap.end()) {\r
312         // Now find the reversed index of expiration to key, so we can clear it.\r
313         pair<multimap<time_t,string>::iterator,multimap<time_t,string>::iterator> range =\r
314             h->m_expMap.equal_range(i->second.expiration);\r
315         for (; range.first != range.second; ++range.first) {\r
316             if (range.first->second == i->first) {\r
317                 h->m_expMap.erase(range.first);\r
318                 break;\r
319             }\r
320         }\r
321         // And finally delete the record itself.\r
322         h->m_dataMap.erase(i);\r
323         m_log.debug("deleted record (%s)", key);\r
324         return true;\r
325     }\r
326 \r
327     m_log.debug("deleting record (%s)....not found", key);\r
328     return false;\r
329 }\r