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