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