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