fdef458d8ada2ee2c292504e98f037f3a719165d
[shibboleth/cpp-xmltooling.git] / xmltooling / impl / MemoryStorageService.cpp
1 /*\r
2  *  Copyright 2001-2007 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 "logging.h"\r
25 #include "util/NDC.h"\r
26 #include "util/StorageService.h"\r
27 #include "util/Threads.h"\r
28 #include "util/XMLHelper.h"\r
29 \r
30 #include <memory>\r
31 #include <xercesc/util/XMLUniDefs.hpp>\r
32 \r
33 using namespace xmltooling::logging;\r
34 using namespace xmltooling;\r
35 using namespace std;\r
36 \r
37 namespace xmltooling {\r
38     class XMLTOOL_DLLLOCAL MemoryStorageService : public StorageService\r
39     {\r
40     public:\r
41         MemoryStorageService(const DOMElement* e);\r
42         virtual ~MemoryStorageService();\r
43 \r
44         bool createString(const char* context, const char* key, const char* value, time_t expiration);\r
45         int readString(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL, int version=0);\r
46         int updateString(const char* context, const char* key, const char* value=NULL, time_t expiration=0, int version=0);\r
47         bool deleteString(const char* context, const char* key);\r
48 \r
49         bool createText(const char* context, const char* key, const char* value, time_t expiration) {\r
50             return createString(context, key, value, expiration);\r
51         }\r
52         int readText(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL, int version=0) {\r
53             return readString(context, key, pvalue, pexpiration, version);\r
54         }\r
55         int updateText(const char* context, const char* key, const char* value=NULL, time_t expiration=0, int version=0) {\r
56             return updateString(context, key, value, expiration, version);\r
57         }\r
58         bool deleteText(const char* context, const char* key) {\r
59             return deleteString(context, key);\r
60         }\r
61 \r
62         void reap(const char* context);\r
63         void updateContext(const char* context, time_t expiration);\r
64         void deleteContext(const char* context) {\r
65             m_lock->wrlock();\r
66             m_contextMap.erase(context);\r
67             m_lock->unlock();\r
68         }\r
69 \r
70     private:\r
71         void cleanup();\r
72 \r
73         struct XMLTOOL_DLLLOCAL Record {\r
74             Record() : expiration(0), version(1) {}\r
75             Record(const string& s, time_t t) : data(s), expiration(t), version(1) {}\r
76             string data;\r
77             time_t expiration;\r
78             int version;\r
79         };\r
80 \r
81         struct XMLTOOL_DLLLOCAL Context {\r
82             Context() {}\r
83             Context(const Context& src) {\r
84                 m_dataMap = src.m_dataMap;\r
85             }\r
86             map<string,Record> m_dataMap;\r
87             unsigned long reap(time_t exp);\r
88         };\r
89 \r
90         Context& readContext(const char* context) {\r
91             m_lock->rdlock();\r
92             map<string,Context>::iterator i = m_contextMap.find(context);\r
93             if (i != m_contextMap.end())\r
94                 return i->second;\r
95             m_lock->unlock();\r
96             m_lock->wrlock();\r
97             return m_contextMap[context];\r
98         }\r
99 \r
100         Context& writeContext(const char* context) {\r
101             m_lock->wrlock();\r
102             return m_contextMap[context];\r
103         }\r
104 \r
105         map<string,Context> m_contextMap;\r
106         RWLock* m_lock;\r
107         CondWait* shutdown_wait;\r
108         Thread* cleanup_thread;\r
109         static void* cleanup_fn(void*);\r
110         bool shutdown;\r
111         int m_cleanupInterval;\r
112         Category& m_log;\r
113     };\r
114 \r
115     StorageService* XMLTOOL_DLLLOCAL MemoryStorageServiceFactory(const DOMElement* const & e)\r
116     {\r
117         return new MemoryStorageService(e);\r
118     }\r
119 };\r
120 \r
121 static const XMLCh cleanupInterval[] = UNICODE_LITERAL_15(c,l,e,a,n,u,p,I,n,t,e,r,v,a,l);\r
122 \r
123 MemoryStorageService::MemoryStorageService(const DOMElement* e)\r
124     : m_lock(NULL), shutdown_wait(NULL), cleanup_thread(NULL), shutdown(false), m_cleanupInterval(0),\r
125         m_log(Category::getInstance(XMLTOOLING_LOGCAT".StorageService"))\r
126 {\r
127     const XMLCh* tag=e ? e->getAttributeNS(NULL,cleanupInterval) : NULL;\r
128     if (tag && *tag) {\r
129         m_cleanupInterval = XMLString::parseInt(tag);\r
130     }\r
131     if (!m_cleanupInterval)\r
132         m_cleanupInterval=900;\r
133 \r
134     m_lock = RWLock::create();\r
135     shutdown_wait = CondWait::create();\r
136     cleanup_thread = Thread::create(&cleanup_fn, (void*)this);\r
137 }\r
138 \r
139 MemoryStorageService::~MemoryStorageService()\r
140 {\r
141     // Shut down the cleanup thread and let it know...\r
142     shutdown = true;\r
143     shutdown_wait->signal();\r
144     cleanup_thread->join(NULL);\r
145 \r
146     delete cleanup_thread;\r
147     delete shutdown_wait;\r
148     delete m_lock;\r
149 }\r
150 \r
151 void* MemoryStorageService::cleanup_fn(void* cache_p)\r
152 {\r
153     MemoryStorageService* cache = reinterpret_cast<MemoryStorageService*>(cache_p);\r
154 \r
155 #ifndef WIN32\r
156     // First, let's block all signals\r
157     Thread::mask_all_signals();\r
158 #endif\r
159 \r
160     // Now run the cleanup process.\r
161     cache->cleanup();\r
162     return NULL;\r
163 }\r
164 \r
165 void MemoryStorageService::cleanup()\r
166 {\r
167 #ifdef _DEBUG\r
168     NDC ndc("cleanup");\r
169 #endif\r
170 \r
171     auto_ptr<Mutex> mutex(Mutex::create());\r
172     mutex->lock();\r
173 \r
174     m_log.info("cleanup thread started...running every %d seconds", m_cleanupInterval);\r
175 \r
176     while (!shutdown) {\r
177         shutdown_wait->timedwait(mutex.get(), m_cleanupInterval);\r
178         if (shutdown)\r
179             break;\r
180 \r
181         unsigned long count=0;\r
182         time_t now = time(NULL);\r
183         m_lock->wrlock();\r
184         SharedLock locker(m_lock, false);\r
185         for (map<string,Context>::iterator i=m_contextMap.begin(); i!=m_contextMap.end(); ++i)\r
186             count += i->second.reap(now);\r
187 \r
188         if (count)\r
189             m_log.info("purged %d expired record(s) from storage", count);\r
190     }\r
191 \r
192     m_log.info("cleanup thread finished");\r
193 \r
194     mutex->unlock();\r
195     Thread::exit(NULL);\r
196 }\r
197 \r
198 void MemoryStorageService::reap(const char* context)\r
199 {\r
200     Context& ctx = writeContext(context);\r
201     SharedLock locker(m_lock, false);\r
202     ctx.reap(time(NULL));\r
203 }\r
204 \r
205 unsigned long MemoryStorageService::Context::reap(time_t exp)\r
206 {\r
207     // Garbage collect any expired entries.\r
208     unsigned long count=0;\r
209     map<string,Record>::iterator cur = m_dataMap.begin();\r
210     map<string,Record>::iterator stop = m_dataMap.end();\r
211     while (cur != stop) {\r
212         if (cur->second.expiration <= exp) {\r
213             map<string,Record>::iterator tmp = cur++;\r
214             m_dataMap.erase(tmp);\r
215             ++count;\r
216         }\r
217         else {\r
218             cur++;\r
219         }\r
220     }\r
221     return count;\r
222 }\r
223 \r
224 bool MemoryStorageService::createString(const char* context, const char* key, const char* value, time_t expiration)\r
225 {\r
226     Context& ctx = writeContext(context);\r
227     SharedLock locker(m_lock, false);\r
228 \r
229     // Check for a duplicate.\r
230     map<string,Record>::iterator i=ctx.m_dataMap.find(key);\r
231     if (i!=ctx.m_dataMap.end()) {\r
232         // Not yet expired?\r
233         if (time(NULL) < i->second.expiration)\r
234             return false;\r
235         // It's dead, so we can just remove it now and create the new record.\r
236         ctx.m_dataMap.erase(i);\r
237     }\r
238 \r
239     ctx.m_dataMap[key]=Record(value,expiration);\r
240 \r
241     m_log.debug("inserted record (%s) in context (%s)", key, context);\r
242     return true;\r
243 }\r
244 \r
245 int MemoryStorageService::readString(const char* context, const char* key, string* pvalue, time_t* pexpiration, int version)\r
246 {\r
247     Context& ctx = readContext(context);\r
248     SharedLock locker(m_lock, false);\r
249 \r
250     map<string,Record>::iterator i=ctx.m_dataMap.find(key);\r
251     if (i==ctx.m_dataMap.end())\r
252         return 0;\r
253     else if (time(NULL) >= i->second.expiration)\r
254         return 0;\r
255     if (pexpiration)\r
256         *pexpiration = i->second.expiration;\r
257     if (i->second.version == version)\r
258         return version; // nothing's changed, so just echo back the version\r
259     if (pvalue)\r
260         *pvalue = i->second.data;\r
261     return i->second.version;\r
262 }\r
263 \r
264 int MemoryStorageService::updateString(const char* context, const char* key, const char* value, time_t expiration, int version)\r
265 {\r
266     Context& ctx = writeContext(context);\r
267     SharedLock locker(m_lock, false);\r
268 \r
269     map<string,Record>::iterator i=ctx.m_dataMap.find(key);\r
270     if (i==ctx.m_dataMap.end())\r
271         return 0;\r
272     else if (time(NULL) >= i->second.expiration)\r
273         return 0;\r
274 \r
275     if (version > 0 && version != i->second.version)\r
276         return -1;  // caller's out of sync\r
277 \r
278     if (value) {\r
279         i->second.data = value;\r
280         ++(i->second.version);\r
281     }\r
282 \r
283     if (expiration && expiration != i->second.expiration)\r
284         i->second.expiration = expiration;\r
285 \r
286     m_log.debug("updated record (%s) in context (%s)", key, context);\r
287     return i->second.version;\r
288 }\r
289 \r
290 bool MemoryStorageService::deleteString(const char* context, const char* key)\r
291 {\r
292     Context& ctx = writeContext(context);\r
293     SharedLock locker(m_lock, false);\r
294 \r
295     // Find the record.\r
296     map<string,Record>::iterator i=ctx.m_dataMap.find(key);\r
297     if (i!=ctx.m_dataMap.end()) {\r
298         ctx.m_dataMap.erase(i);\r
299         m_log.debug("deleted record (%s) in context (%s)", key, context);\r
300         return true;\r
301     }\r
302 \r
303     m_log.debug("deleting record (%s) in context (%s)....not found", key, context);\r
304     return false;\r
305 }\r
306 \r
307 void MemoryStorageService::updateContext(const char* context, time_t expiration)\r
308 {\r
309     Context& ctx = writeContext(context);\r
310     SharedLock locker(m_lock, false);\r
311 \r
312     time_t now = time(NULL);\r
313     map<string,Record>::iterator stop=ctx.m_dataMap.end();\r
314     for (map<string,Record>::iterator i = ctx.m_dataMap.begin(); i!=stop; ++i) {\r
315         if (now < i->second.expiration)\r
316             i->second.expiration = expiration;\r
317     }\r
318 \r
319     m_log.debug("updated expiration of valid records in context (%s)", context);\r
320 }\r