https://bugs.internet2.edu/jira/browse/CPPXT-10
[shibboleth/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 shutdown_wait;\r
147     delete m_lock;\r
148 }\r
149 \r
150 void* MemoryStorageService::cleanup_fn(void* cache_p)\r
151 {\r
152     MemoryStorageService* cache = reinterpret_cast<MemoryStorageService*>(cache_p);\r
153 \r
154 #ifndef WIN32\r
155     // First, let's block all signals \r
156     Thread::mask_all_signals();\r
157 #endif\r
158 \r
159     // Now run the cleanup process.\r
160     cache->cleanup();\r
161     return NULL;\r
162 }\r
163 \r
164 void MemoryStorageService::cleanup()\r
165 {\r
166 #ifdef _DEBUG\r
167     NDC ndc("cleanup");\r
168 #endif\r
169 \r
170     auto_ptr<Mutex> mutex(Mutex::create());\r
171     mutex->lock();\r
172 \r
173     m_log.info("cleanup thread started...running every %d seconds", m_cleanupInterval);\r
174 \r
175     while (!shutdown) {\r
176         shutdown_wait->timedwait(mutex.get(), m_cleanupInterval);\r
177         if (shutdown)\r
178             break;\r
179         \r
180         unsigned long count=0;\r
181         time_t now = time(NULL);\r
182         m_lock->wrlock();\r
183         SharedLock locker(m_lock, false);\r
184         for (map<string,Context>::iterator i=m_contextMap.begin(); i!=m_contextMap.end(); ++i)\r
185             count += i->second.reap(now);\r
186         \r
187         if (count)\r
188             m_log.info("purged %d expired record(s) from storage", count);\r
189     }\r
190 \r
191     m_log.info("cleanup thread finished");\r
192 \r
193     mutex->unlock();\r
194     Thread::exit(NULL);\r
195 }\r
196 \r
197 void MemoryStorageService::reap(const char* context)\r
198 {\r
199     Context& ctx = writeContext(context);\r
200     SharedLock locker(m_lock, false);\r
201     ctx.reap(time(NULL));\r
202 }\r
203 \r
204 unsigned long MemoryStorageService::Context::reap(time_t exp)\r
205 {\r
206     // Garbage collect any expired entries.\r
207     unsigned long count=0;\r
208     map<string,Record>::iterator cur = m_dataMap.begin();\r
209     map<string,Record>::iterator stop = m_dataMap.end();\r
210     while (cur != stop) {\r
211         if (cur->second.expiration <= exp) {\r
212             map<string,Record>::iterator tmp = cur++;\r
213             m_dataMap.erase(tmp);\r
214             ++count;\r
215         }\r
216         else {\r
217             cur++;\r
218         }\r
219     }\r
220     return count;\r
221 }\r
222 \r
223 bool MemoryStorageService::createString(const char* context, const char* key, const char* value, time_t expiration)\r
224 {\r
225     Context& ctx = writeContext(context);\r
226     SharedLock locker(m_lock, false);\r
227 \r
228     // Check for a duplicate.\r
229     map<string,Record>::iterator i=ctx.m_dataMap.find(key);\r
230     if (i!=ctx.m_dataMap.end()) {\r
231         // Not yet expired?\r
232         if (time(NULL) < i->second.expiration)\r
233             return false;\r
234         // It's dead, so we can just remove it now and create the new record.\r
235         ctx.m_dataMap.erase(i);\r
236     }\r
237     \r
238     ctx.m_dataMap[key]=Record(value,expiration);\r
239     \r
240     m_log.debug("inserted record (%s) in context (%s)", key, context);\r
241     return true;\r
242 }\r
243 \r
244 int MemoryStorageService::readString(const char* context, const char* key, string* pvalue, time_t* pexpiration, int version)\r
245 {\r
246     Context& ctx = readContext(context);\r
247     SharedLock locker(m_lock, false);\r
248 \r
249     map<string,Record>::iterator i=ctx.m_dataMap.find(key);\r
250     if (i==ctx.m_dataMap.end())\r
251         return 0;\r
252     else if (time(NULL) >= i->second.expiration)\r
253         return 0;\r
254     if (pexpiration)\r
255         *pexpiration = i->second.expiration;\r
256     if (i->second.version == version)\r
257         return version; // nothing's changed, so just echo back the version\r
258     if (pvalue)\r
259         *pvalue = i->second.data;\r
260     return i->second.version;\r
261 }\r
262 \r
263 int MemoryStorageService::updateString(const char* context, const char* key, const char* value, time_t expiration, int version)\r
264 {\r
265     Context& ctx = writeContext(context);\r
266     SharedLock locker(m_lock, false);\r
267 \r
268     map<string,Record>::iterator i=ctx.m_dataMap.find(key);\r
269     if (i==ctx.m_dataMap.end())\r
270         return 0;\r
271     else if (time(NULL) >= i->second.expiration)\r
272         return 0;\r
273     \r
274     if (version > 0 && version != i->second.version)\r
275         return -1;  // caller's out of sync\r
276 \r
277     if (value) {\r
278         i->second.data = value;\r
279         ++(i->second.version);\r
280     }\r
281         \r
282     if (expiration && expiration != i->second.expiration)\r
283         i->second.expiration = expiration;\r
284 \r
285     m_log.debug("updated record (%s) in context (%s)", key, context);\r
286     return i->second.version;\r
287 }\r
288 \r
289 bool MemoryStorageService::deleteString(const char* context, const char* key)\r
290 {\r
291     Context& ctx = writeContext(context);\r
292     SharedLock locker(m_lock, false);\r
293     \r
294     // Find the record.\r
295     map<string,Record>::iterator i=ctx.m_dataMap.find(key);\r
296     if (i!=ctx.m_dataMap.end()) {\r
297         ctx.m_dataMap.erase(i);\r
298         m_log.debug("deleted record (%s) in context (%s)", key, context);\r
299         return true;\r
300     }\r
301 \r
302     m_log.debug("deleting record (%s) in context (%s)....not found", key, context);\r
303     return false;\r
304 }\r
305 \r
306 void MemoryStorageService::updateContext(const char* context, time_t expiration)\r
307 {\r
308     Context& ctx = writeContext(context);\r
309     SharedLock locker(m_lock, false);\r
310 \r
311     time_t now = time(NULL);\r
312     map<string,Record>::iterator stop=ctx.m_dataMap.end();\r
313     for (map<string,Record>::iterator i = ctx.m_dataMap.begin(); i!=stop; ++i) {\r
314         if (now < i->second.expiration)\r
315             i->second.expiration = expiration;\r
316     }\r
317 \r
318     m_log.debug("updated expiration of valid records in context (%s)", context);\r
319 }\r