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