Simplify storage context mgmt.
[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         void createString(const char* context, const char* key, const char* value, time_t expiration);\r
44         bool readString(const char* context, const char* key, string& value, time_t modifiedSince=0);\r
45         bool updateString(const char* context, const char* key, const char* value=NULL, time_t expiration=0);\r
46         bool deleteString(const char* context, const char* key);\r
47         \r
48         void createText(const char* context, const char* key, const char* value, time_t expiration) {\r
49             return createString(context, key, value, expiration);\r
50         }\r
51         bool readText(const char* context, const char* key, string& value, time_t modifiedSince=0) {\r
52             return readString(context, key, value, modifiedSince);\r
53         }\r
54         bool updateText(const char* context, const char* key, const char* value=NULL, time_t expiration=0) {\r
55             return updateString(context, key, value, expiration);\r
56         }\r
57         bool deleteText(const char* context, const char* key) {\r
58             return deleteString(context, key);\r
59         }\r
60         \r
61         void reap(const char* context);\r
62         void deleteContext(const char* context) {\r
63             Lock wrapper(contextLock);\r
64             m_contextMap.erase(context);\r
65         }\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 Context {\r
78             Context() : m_lock(RWLock::create()) {}\r
79             Context(const Context& src) {\r
80                 m_dataMap = src.m_dataMap;\r
81                 m_expMap = src.m_expMap;\r
82                 m_lock = RWLock::create();\r
83             }\r
84             ~Context() { delete m_lock; }\r
85             map<string,Record> m_dataMap;\r
86             multimap<time_t,string> m_expMap;\r
87             RWLock* m_lock;\r
88             unsigned long reap();\r
89         };\r
90 \r
91         Context& getContext(const char* context) {\r
92             Lock wrapper(contextLock);\r
93             return m_contextMap[context];\r
94         }\r
95 \r
96         map<string,Context> m_contextMap;\r
97         Mutex* contextLock;\r
98         CondWait* shutdown_wait;\r
99         Thread* cleanup_thread;\r
100         static void* cleanup_fn(void*);\r
101         bool shutdown;\r
102         int m_cleanupInterval;\r
103         Category& m_log;\r
104     };\r
105 \r
106     StorageService* XMLTOOL_DLLLOCAL MemoryStorageServiceFactory(const DOMElement* const & e)\r
107     {\r
108         return new MemoryStorageService(e);\r
109     }\r
110 \r
111 };\r
112 \r
113 static const XMLCh cleanupInterval[] = UNICODE_LITERAL_15(c,l,e,a,n,u,p,I,n,t,e,r,v,a,l);\r
114 \r
115 MemoryStorageService::MemoryStorageService(const DOMElement* e)\r
116     : contextLock(NULL), shutdown_wait(NULL), cleanup_thread(NULL), shutdown(false), m_cleanupInterval(0),\r
117         m_log(Category::getInstance(XMLTOOLING_LOGCAT".StorageService"))\r
118 {\r
119     contextLock = Mutex::create();\r
120     shutdown_wait = CondWait::create();\r
121     cleanup_thread = Thread::create(&cleanup_fn, (void*)this);\r
122 \r
123     const XMLCh* tag=e ? e->getAttributeNS(NULL,cleanupInterval) : NULL;\r
124     if (tag && *tag) {\r
125         m_cleanupInterval = XMLString::parseInt(tag);\r
126     }\r
127     if (!m_cleanupInterval)\r
128         m_cleanupInterval=300;\r
129 }\r
130 \r
131 MemoryStorageService::~MemoryStorageService()\r
132 {\r
133     // Shut down the cleanup thread and let it know...\r
134     shutdown = true;\r
135     shutdown_wait->signal();\r
136     cleanup_thread->join(NULL);\r
137 \r
138     delete shutdown_wait;\r
139     delete contextLock;\r
140 }\r
141 \r
142 void* MemoryStorageService::cleanup_fn(void* cache_p)\r
143 {\r
144     MemoryStorageService* cache = reinterpret_cast<MemoryStorageService*>(cache_p);\r
145 \r
146 #ifndef WIN32\r
147     // First, let's block all signals \r
148     Thread::mask_all_signals();\r
149 #endif\r
150 \r
151     // Now run the cleanup process.\r
152     cache->cleanup();\r
153     return NULL;\r
154 }\r
155 \r
156 void MemoryStorageService::cleanup()\r
157 {\r
158 #ifdef _DEBUG\r
159     NDC ndc("cleanup");\r
160 #endif\r
161     \r
162 \r
163     Mutex* mutex = Mutex::create();\r
164     mutex->lock();\r
165 \r
166     m_log.info("cleanup thread started...running every %d seconds", m_cleanupInterval);\r
167 \r
168     while (!shutdown) {\r
169         shutdown_wait->timedwait(mutex, m_cleanupInterval);\r
170         if (shutdown)\r
171             break;\r
172         \r
173         unsigned long count=0;\r
174         Lock wrapper(contextLock);\r
175         for (map<string,Context>::iterator i=m_contextMap.begin(); i!=m_contextMap.end(); ++i)\r
176             count += i->second.reap();\r
177         \r
178         if (count)\r
179             m_log.info("purged %d record(s) from storage", count);\r
180     }\r
181 \r
182     m_log.info("cleanup thread finished");\r
183 \r
184     mutex->unlock();\r
185     delete mutex;\r
186     Thread::exit(NULL);\r
187 }\r
188 \r
189 void MemoryStorageService::reap(const char* context)\r
190 {\r
191     getContext(context).reap();\r
192 }\r
193 \r
194 unsigned long MemoryStorageService::Context::reap()\r
195 {\r
196     // Lock the "database".\r
197     m_lock->wrlock();\r
198     SharedLock wrapper(m_lock, false);\r
199     \r
200     // Garbage collect any expired entries.\r
201     unsigned long count=0;\r
202     time_t now=time(NULL)-XMLToolingConfig::getConfig().clock_skew_secs;\r
203     multimap<time_t,string>::iterator stop=m_expMap.upper_bound(now);\r
204     for (multimap<time_t,string>::iterator i=m_expMap.begin(); i!=stop; m_expMap.erase(i++)) {\r
205         m_dataMap.erase(i->second);\r
206         ++count;\r
207     }\r
208 \r
209     return count;\r
210 }\r
211 \r
212 void MemoryStorageService::createString(const char* context, const char* key, const char* value, time_t expiration)\r
213 {\r
214     Context& ctx = getContext(context);\r
215 \r
216     // Lock the maps.\r
217     ctx.m_lock->wrlock();\r
218     SharedLock wrapper(ctx.m_lock, false);\r
219     \r
220     // Check for a duplicate.\r
221     map<string,Record>::iterator i=ctx.m_dataMap.find(key);\r
222     if (i!=ctx.m_dataMap.end())\r
223         throw IOException("attempted to insert a record with duplicate key ($1)", params(1,key));\r
224     \r
225     ctx.m_dataMap[key]=Record(value,time(NULL),expiration);\r
226     ctx.m_expMap.insert(multimap<time_t,string>::value_type(expiration,key));\r
227     \r
228     m_log.debug("inserted record (%s)", key);\r
229 }\r
230 \r
231 bool MemoryStorageService::readString(const char* context, const char* key, string& value, time_t modifiedSince)\r
232 {\r
233     Context& ctx = getContext(context);\r
234 \r
235     SharedLock wrapper(ctx.m_lock);\r
236     map<string,Record>::iterator i=ctx.m_dataMap.find(key);\r
237     if (i==ctx.m_dataMap.end())\r
238         return false;\r
239     else if (modifiedSince >= i->second.modified)\r
240         return false;\r
241     value = i->second.data;\r
242     return true;\r
243 }\r
244 \r
245 bool MemoryStorageService::updateString(const char* context, const char* key, const char* value, time_t expiration)\r
246 {\r
247     Context& ctx = getContext(context);\r
248 \r
249     // Lock the maps.\r
250     ctx.m_lock->wrlock();\r
251     SharedLock wrapper(ctx.m_lock, false);\r
252 \r
253     map<string,Record>::iterator i=ctx.m_dataMap.find(key);\r
254     if (i==ctx.m_dataMap.end())\r
255         return false;\r
256         \r
257     if (value)\r
258         i->second.data = value;\r
259         \r
260     if (expiration && expiration != i->second.expiration) {\r
261         // Update secondary map.\r
262         pair<multimap<time_t,string>::iterator,multimap<time_t,string>::iterator> range =\r
263             ctx.m_expMap.equal_range(i->second.expiration);\r
264         for (; range.first != range.second; ++range.first) {\r
265             if (range.first->second == i->first) {\r
266                 ctx.m_expMap.erase(range.first);\r
267                 break;\r
268             }\r
269         }\r
270         i->second.expiration = expiration;\r
271        ctx.m_expMap.insert(multimap<time_t,string>::value_type(expiration,key));\r
272     }\r
273 \r
274     i->second.modified = time(NULL);\r
275     m_log.debug("updated record (%s)", key);\r
276     return true;\r
277 }\r
278 \r
279 bool MemoryStorageService::deleteString(const char* context, const char* key)\r
280 {\r
281     Context& ctx = getContext(context);\r
282 \r
283     // Lock the maps.\r
284     ctx.m_lock->wrlock();\r
285     SharedLock wrapper(ctx.m_lock, false);\r
286     \r
287     // Find the record.\r
288     map<string,Record>::iterator i=ctx.m_dataMap.find(key);\r
289     if (i!=ctx.m_dataMap.end()) {\r
290         // Now find the reversed index of expiration to key, so we can clear it.\r
291         pair<multimap<time_t,string>::iterator,multimap<time_t,string>::iterator> range =\r
292             ctx.m_expMap.equal_range(i->second.expiration);\r
293         for (; range.first != range.second; ++range.first) {\r
294             if (range.first->second == i->first) {\r
295                 ctx.m_expMap.erase(range.first);\r
296                 break;\r
297             }\r
298         }\r
299         // And finally delete the record itself.\r
300         ctx.m_dataMap.erase(i);\r
301         m_log.debug("deleted record (%s)", key);\r
302         return true;\r
303     }\r
304 \r
305     m_log.debug("deleting record (%s)....not found", key);\r
306     return false;\r
307 }\r