Set fourth file version digit to signify rebuild.
[shibboleth/cpp-xmltooling.git] / xmltooling / impl / MemoryStorageService.cpp
1 /**
2  * Licensed to the University Corporation for Advanced Internet
3  * Development, Inc. (UCAID) under one or more contributor license
4  * agreements. See the NOTICE file distributed with this work for
5  * additional information regarding copyright ownership.
6  *
7  * UCAID licenses this file to you under the Apache License,
8  * Version 2.0 (the "License"); you may not use this file except
9  * in compliance with the License. You may obtain a copy of the
10  * License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17  * either express or implied. See the License for the specific
18  * language governing permissions and limitations under the License.
19  */
20
21 /**
22  * MemoryStorageService.cpp
23  *
24  * In-memory "persistent" storage, suitable for simple applications.
25  */
26
27 #include "internal.h"
28 #include "logging.h"
29 #include "util/NDC.h"
30 #include "util/StorageService.h"
31 #include "util/Threads.h"
32 #include "util/XMLHelper.h"
33
34 #include <memory>
35 #include <xercesc/util/XMLUniDefs.hpp>
36
37 using namespace xmltooling::logging;
38 using namespace xmltooling;
39 using namespace std;
40
41 using xercesc::DOMElement;
42
43 namespace {
44     // Reasonably extended sizes to avoid callers needing to shrink unduly.
45     static const XMLTOOL_DLLLOCAL StorageService::Capabilities g_memCaps(0x4000, 0x4000, 0x4000);
46 };
47
48 namespace xmltooling {
49     class XMLTOOL_DLLLOCAL MemoryStorageService : public StorageService
50     {
51     public:
52         MemoryStorageService(const DOMElement* e);
53         virtual ~MemoryStorageService();
54
55         const Capabilities& getCapabilities() const {
56             return g_memCaps;
57         }
58
59         bool createString(const char* context, const char* key, const char* value, time_t expiration);
60         int readString(const char* context, const char* key, string* pvalue=nullptr, time_t* pexpiration=nullptr, int version=0);
61         int updateString(const char* context, const char* key, const char* value=nullptr, time_t expiration=0, int version=0);
62         bool deleteString(const char* context, const char* key);
63
64         bool createText(const char* context, const char* key, const char* value, time_t expiration) {
65             return createString(context, key, value, expiration);
66         }
67         int readText(const char* context, const char* key, string* pvalue=nullptr, time_t* pexpiration=nullptr, int version=0) {
68             return readString(context, key, pvalue, pexpiration, version);
69         }
70         int updateText(const char* context, const char* key, const char* value=nullptr, time_t expiration=0, int version=0) {
71             return updateString(context, key, value, expiration, version);
72         }
73         bool deleteText(const char* context, const char* key) {
74             return deleteString(context, key);
75         }
76
77         void reap(const char* context);
78         void updateContext(const char* context, time_t expiration);
79         void deleteContext(const char* context) {
80             m_lock->wrlock();
81             m_contextMap.erase(context);
82             m_lock->unlock();
83         }
84
85     private:
86         struct XMLTOOL_DLLLOCAL Record {
87             Record() : expiration(0), version(1) {}
88             Record(const string& s, time_t t) : data(s), expiration(t), version(1) {}
89             string data;
90             time_t expiration;
91             int version;
92         };
93
94         struct XMLTOOL_DLLLOCAL Context {
95             Context() {}
96             Context(const Context& src) {
97                 m_dataMap = src.m_dataMap;
98             }
99             map<string,Record> m_dataMap;
100             unsigned long reap(time_t exp);
101         };
102
103         Context& readContext(const char* context) {
104             m_lock->rdlock();
105             map<string,Context>::iterator i = m_contextMap.find(context);
106             if (i != m_contextMap.end())
107                 return i->second;
108             m_lock->unlock();
109             m_lock->wrlock();
110             return m_contextMap[context];
111         }
112
113         Context& writeContext(const char* context) {
114             m_lock->wrlock();
115             return m_contextMap[context];
116         }
117
118         map<string,Context> m_contextMap;
119         auto_ptr<RWLock> m_lock;
120         auto_ptr<CondWait> shutdown_wait;
121         auto_ptr<Thread> cleanup_thread;
122         static void* cleanup_fn(void*);
123         bool shutdown;
124         int m_cleanupInterval;
125         Category& m_log;
126     };
127
128     StorageService* XMLTOOL_DLLLOCAL MemoryStorageServiceFactory(const DOMElement* const & e)
129     {
130         return new MemoryStorageService(e);
131     }
132 };
133
134 static const XMLCh cleanupInterval[] = UNICODE_LITERAL_15(c,l,e,a,n,u,p,I,n,t,e,r,v,a,l);
135
136 MemoryStorageService::MemoryStorageService(const DOMElement* e)
137     : m_lock(RWLock::create()), shutdown_wait(CondWait::create()), shutdown(false),
138         m_cleanupInterval(XMLHelper::getAttrInt(e, 900, cleanupInterval)),
139         m_log(Category::getInstance(XMLTOOLING_LOGCAT ".StorageService"))
140 {
141     cleanup_thread.reset(Thread::create(&cleanup_fn, (void*)this));
142 }
143
144 MemoryStorageService::~MemoryStorageService()
145 {
146     // Shut down the cleanup thread and let it know...
147     shutdown = true;
148     shutdown_wait->signal();
149     cleanup_thread->join(nullptr);
150 }
151
152 void* MemoryStorageService::cleanup_fn(void* pv)
153 {
154     MemoryStorageService* cache = reinterpret_cast<MemoryStorageService*>(pv);
155
156 #ifndef WIN32
157     // First, let's block all signals
158     Thread::mask_all_signals();
159 #endif
160
161 #ifdef _DEBUG
162     NDC ndc("cleanup");
163 #endif
164
165     auto_ptr<Mutex> mutex(Mutex::create());
166     mutex->lock();
167
168     cache->m_log.info("cleanup thread started...running every %d seconds", cache->m_cleanupInterval);
169
170     while (!cache->shutdown) {
171         cache->shutdown_wait->timedwait(mutex.get(), cache->m_cleanupInterval);
172         if (cache->shutdown)
173             break;
174
175         unsigned long count=0;
176         time_t now = time(nullptr);
177         cache->m_lock->wrlock();
178         SharedLock locker(cache->m_lock.get(), false);
179         for (map<string,Context>::iterator i=cache->m_contextMap.begin(); i!=cache->m_contextMap.end(); ++i)
180             count += i->second.reap(now);
181
182         if (count)
183             cache->m_log.info("purged %d expired record(s) from storage", count);
184     }
185
186     cache->m_log.info("cleanup thread finished");
187
188     mutex->unlock();
189     return nullptr;
190 }
191
192 void MemoryStorageService::reap(const char* context)
193 {
194     Context& ctx = writeContext(context);
195     SharedLock locker(m_lock.get(), false);
196     ctx.reap(time(nullptr));
197 }
198
199 unsigned long MemoryStorageService::Context::reap(time_t exp)
200 {
201     // Garbage collect any expired entries.
202     unsigned long count=0;
203     map<string,Record>::iterator cur = m_dataMap.begin();
204     map<string,Record>::iterator stop = m_dataMap.end();
205     while (cur != stop) {
206         if (cur->second.expiration <= exp) {
207             map<string,Record>::iterator tmp = cur++;
208             m_dataMap.erase(tmp);
209             ++count;
210         }
211         else {
212             cur++;
213         }
214     }
215     return count;
216 }
217
218 bool MemoryStorageService::createString(const char* context, const char* key, const char* value, time_t expiration)
219 {
220     Context& ctx = writeContext(context);
221     SharedLock locker(m_lock.get(), false);
222
223     // Check for a duplicate.
224     map<string,Record>::iterator i=ctx.m_dataMap.find(key);
225     if (i!=ctx.m_dataMap.end()) {
226         // Not yet expired?
227         if (time(nullptr) < i->second.expiration)
228             return false;
229         // It's dead, so we can just remove it now and create the new record.
230         ctx.m_dataMap.erase(i);
231     }
232
233     ctx.m_dataMap[key]=Record(value,expiration);
234
235     m_log.debug("inserted record (%s) in context (%s) with expiration (%lu)", key, context, expiration);
236     return true;
237 }
238
239 int MemoryStorageService::readString(const char* context, const char* key, string* pvalue, time_t* pexpiration, int version)
240 {
241     Context& ctx = readContext(context);
242     SharedLock locker(m_lock.get(), false);
243
244     map<string,Record>::iterator i=ctx.m_dataMap.find(key);
245     if (i==ctx.m_dataMap.end())
246         return 0;
247     else if (time(nullptr) >= i->second.expiration)
248         return 0;
249     if (pexpiration)
250         *pexpiration = i->second.expiration;
251     if (i->second.version == version)
252         return version; // nothing's changed, so just echo back the version
253     if (pvalue)
254         *pvalue = i->second.data;
255     return i->second.version;
256 }
257
258 int MemoryStorageService::updateString(const char* context, const char* key, const char* value, time_t expiration, int version)
259 {
260     Context& ctx = writeContext(context);
261     SharedLock locker(m_lock.get(), false);
262
263     map<string,Record>::iterator i=ctx.m_dataMap.find(key);
264     if (i==ctx.m_dataMap.end())
265         return 0;
266     else if (time(nullptr) >= i->second.expiration)
267         return 0;
268
269     if (version > 0 && version != i->second.version)
270         return -1;  // caller's out of sync
271
272     if (value) {
273         i->second.data = value;
274         ++(i->second.version);
275     }
276
277     if (expiration && expiration != i->second.expiration)
278         i->second.expiration = expiration;
279
280     m_log.debug("updated record (%s) in context (%s) with expiration (%lu)", key, context, i->second.expiration);
281     return i->second.version;
282 }
283
284 bool MemoryStorageService::deleteString(const char* context, const char* key)
285 {
286     Context& ctx = writeContext(context);
287     SharedLock locker(m_lock.get(), false);
288
289     // Find the record.
290     map<string,Record>::iterator i=ctx.m_dataMap.find(key);
291     if (i!=ctx.m_dataMap.end()) {
292         ctx.m_dataMap.erase(i);
293         m_log.debug("deleted record (%s) in context (%s)", key, context);
294         return true;
295     }
296
297     m_log.debug("deleting record (%s) in context (%s)....not found", key, context);
298     return false;
299 }
300
301 void MemoryStorageService::updateContext(const char* context, time_t expiration)
302 {
303     Context& ctx = writeContext(context);
304     SharedLock locker(m_lock.get(), false);
305
306     time_t now = time(nullptr);
307     map<string,Record>::iterator stop=ctx.m_dataMap.end();
308     for (map<string,Record>::iterator i = ctx.m_dataMap.begin(); i!=stop; ++i) {
309         if (now < i->second.expiration)
310             i->second.expiration = expiration;
311     }
312
313     m_log.debug("updated expiration of valid records in context (%s) to (%lu)", context, expiration);
314 }