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