Simplify access to records and prevent reads of expired data.
[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* pvalue=NULL, time_t* pexpiration=NULL);\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* pvalue=NULL, time_t* pexpiration=NULL) {\r
52             return readString(context, key, pvalue, pexpiration);\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() : expiration(0) {}\r
72             Record(string s, time_t t) : data(s), expiration(t) {}\r
73             string data;\r
74             time_t 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     const XMLCh* tag=e ? e->getAttributeNS(NULL,cleanupInterval) : NULL;\r
120     if (tag && *tag) {\r
121         m_cleanupInterval = XMLString::parseInt(tag);\r
122     }\r
123     if (!m_cleanupInterval)\r
124         m_cleanupInterval=300;\r
125 \r
126     contextLock = Mutex::create();\r
127     shutdown_wait = CondWait::create();\r
128     cleanup_thread = Thread::create(&cleanup_fn, (void*)this);\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     multimap<time_t,string>::iterator stop=m_expMap.upper_bound(time(NULL));\r
203     for (multimap<time_t,string>::iterator i=m_expMap.begin(); i!=stop; m_expMap.erase(i++)) {\r
204         m_dataMap.erase(i->second);\r
205         ++count;\r
206     }\r
207 \r
208     return count;\r
209 }\r
210 \r
211 void MemoryStorageService::createString(const char* context, const char* key, const char* value, time_t expiration)\r
212 {\r
213     Context& ctx = getContext(context);\r
214 \r
215     // Lock the maps.\r
216     ctx.m_lock->wrlock();\r
217     SharedLock wrapper(ctx.m_lock, false);\r
218     \r
219     // Check for a duplicate.\r
220     map<string,Record>::iterator i=ctx.m_dataMap.find(key);\r
221     if (i!=ctx.m_dataMap.end())\r
222         throw IOException("attempted to insert a record with duplicate key ($1)", params(1,key));\r
223     \r
224     ctx.m_dataMap[key]=Record(value,expiration);\r
225     ctx.m_expMap.insert(multimap<time_t,string>::value_type(expiration,key));\r
226     \r
227     m_log.debug("inserted record (%s) in context (%s)", key, context);\r
228 }\r
229 \r
230 bool MemoryStorageService::readString(const char* context, const char* key, string* pvalue, time_t* pexpiration)\r
231 {\r
232     Context& ctx = getContext(context);\r
233 \r
234     SharedLock wrapper(ctx.m_lock);\r
235     map<string,Record>::iterator i=ctx.m_dataMap.find(key);\r
236     if (i==ctx.m_dataMap.end())\r
237         return false;\r
238     else if (time(NULL) >= i->second.expiration)\r
239         return false;\r
240     if (pvalue)\r
241         *pvalue = i->second.data;\r
242     if (pexpiration)\r
243         *pexpiration = i->second.expiration;\r
244     return true;\r
245 }\r
246 \r
247 bool MemoryStorageService::updateString(const char* context, const char* key, const char* value, time_t expiration)\r
248 {\r
249     Context& ctx = getContext(context);\r
250 \r
251     // Lock the maps.\r
252     ctx.m_lock->wrlock();\r
253     SharedLock wrapper(ctx.m_lock, false);\r
254 \r
255     map<string,Record>::iterator i=ctx.m_dataMap.find(key);\r
256     if (i==ctx.m_dataMap.end())\r
257         return false;\r
258         \r
259     if (value)\r
260         i->second.data = value;\r
261         \r
262     if (expiration && expiration != i->second.expiration) {\r
263         // Update secondary map.\r
264         pair<multimap<time_t,string>::iterator,multimap<time_t,string>::iterator> range =\r
265             ctx.m_expMap.equal_range(i->second.expiration);\r
266         for (; range.first != range.second; ++range.first) {\r
267             if (range.first->second == i->first) {\r
268                 ctx.m_expMap.erase(range.first);\r
269                 break;\r
270             }\r
271         }\r
272         i->second.expiration = expiration;\r
273         ctx.m_expMap.insert(multimap<time_t,string>::value_type(expiration,key));\r
274     }\r
275 \r
276     m_log.debug("updated record (%s) in context (%s)", key, context);\r
277     return true;\r
278 }\r
279 \r
280 bool MemoryStorageService::deleteString(const char* context, const char* key)\r
281 {\r
282     Context& ctx = getContext(context);\r
283 \r
284     // Lock the maps.\r
285     ctx.m_lock->wrlock();\r
286     SharedLock wrapper(ctx.m_lock, false);\r
287     \r
288     // Find the record.\r
289     map<string,Record>::iterator i=ctx.m_dataMap.find(key);\r
290     if (i!=ctx.m_dataMap.end()) {\r
291         // Now find the reversed index of expiration to key, so we can clear it.\r
292         pair<multimap<time_t,string>::iterator,multimap<time_t,string>::iterator> range =\r
293             ctx.m_expMap.equal_range(i->second.expiration);\r
294         for (; range.first != range.second; ++range.first) {\r
295             if (range.first->second == i->first) {\r
296                 ctx.m_expMap.erase(range.first);\r
297                 break;\r
298             }\r
299         }\r
300         // And finally delete the record itself.\r
301         ctx.m_dataMap.erase(i);\r
302         m_log.debug("deleted record (%s) in context (%s)", key, context);\r
303         return true;\r
304     }\r
305 \r
306     m_log.debug("deleting record (%s) in context (%s)....not found", key, context);\r
307     return false;\r
308 }\r