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