gcc const fix, converted linefeeds
[shibboleth/cpp-xmltooling.git] / xmltooling / impl / MemoryStorageService.cpp
1 /*
2  *  Copyright 2001-2005 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         bool readString(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL);
45         bool updateString(const char* context, const char* key, const char* value=NULL, time_t expiration=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         bool readText(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL) {
52             return readString(context, key, pvalue, pexpiration);
53         }
54         bool updateText(const char* context, const char* key, const char* value=NULL, time_t expiration=0) {
55             return updateString(context, key, value, expiration);
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 deleteContext(const char* context) {
63             Lock wrapper(contextLock);
64             m_contextMap.erase(context);
65         }
66
67     private:
68         void cleanup();
69     
70         struct XMLTOOL_DLLLOCAL Record {
71             Record() : expiration(0) {}
72             Record(string s, time_t t) : data(s), expiration(t) {}
73             string data;
74             time_t expiration;
75         };
76         
77         struct XMLTOOL_DLLLOCAL Context {
78             Context() : m_lock(RWLock::create()) {}
79             Context(const Context& src) {
80                 m_dataMap = src.m_dataMap;
81                 m_expMap = src.m_expMap;
82                 m_lock = RWLock::create();
83             }
84             ~Context() { delete m_lock; }
85             map<string,Record> m_dataMap;
86             multimap<time_t,string> m_expMap;
87             RWLock* m_lock;
88             unsigned long reap();
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
106     StorageService* XMLTOOL_DLLLOCAL MemoryStorageServiceFactory(const DOMElement* const & e)
107     {
108         return new MemoryStorageService(e);
109     }
110
111 };
112
113 static const XMLCh cleanupInterval[] = UNICODE_LITERAL_15(c,l,e,a,n,u,p,I,n,t,e,r,v,a,l);
114
115 MemoryStorageService::MemoryStorageService(const DOMElement* e)
116     : contextLock(NULL), shutdown_wait(NULL), cleanup_thread(NULL), shutdown(false), m_cleanupInterval(0),
117         m_log(Category::getInstance(XMLTOOLING_LOGCAT".StorageService"))
118 {
119     const XMLCh* tag=e ? e->getAttributeNS(NULL,cleanupInterval) : NULL;
120     if (tag && *tag) {
121         m_cleanupInterval = XMLString::parseInt(tag);
122     }
123     if (!m_cleanupInterval)
124         m_cleanupInterval=300;
125
126     contextLock = Mutex::create();
127     shutdown_wait = CondWait::create();
128     cleanup_thread = Thread::create(&cleanup_fn, (void*)this);
129 }
130
131 MemoryStorageService::~MemoryStorageService()
132 {
133     // Shut down the cleanup thread and let it know...
134     shutdown = true;
135     shutdown_wait->signal();
136     cleanup_thread->join(NULL);
137
138     delete shutdown_wait;
139     delete contextLock;
140 }
141
142 void* MemoryStorageService::cleanup_fn(void* cache_p)
143 {
144     MemoryStorageService* cache = reinterpret_cast<MemoryStorageService*>(cache_p);
145
146 #ifndef WIN32
147     // First, let's block all signals 
148     Thread::mask_all_signals();
149 #endif
150
151     // Now run the cleanup process.
152     cache->cleanup();
153     return NULL;
154 }
155
156 void MemoryStorageService::cleanup()
157 {
158 #ifdef _DEBUG
159     NDC ndc("cleanup");
160 #endif
161     
162
163     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, m_cleanupInterval);
170         if (shutdown)
171             break;
172         
173         unsigned long count=0;
174         Lock wrapper(contextLock);
175         for (map<string,Context>::iterator i=m_contextMap.begin(); i!=m_contextMap.end(); ++i)
176             count += i->second.reap();
177         
178         if (count)
179             m_log.info("purged %d record(s) from storage", count);
180     }
181
182     m_log.info("cleanup thread finished");
183
184     mutex->unlock();
185     delete mutex;
186     Thread::exit(NULL);
187 }
188
189 void MemoryStorageService::reap(const char* context)
190 {
191     getContext(context).reap();
192 }
193
194 unsigned long MemoryStorageService::Context::reap()
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     multimap<time_t,string>::iterator stop=m_expMap.upper_bound(time(NULL));
203     for (multimap<time_t,string>::iterator i=m_expMap.begin(); i!=stop; m_expMap.erase(i++)) {
204         m_dataMap.erase(i->second);
205         ++count;
206     }
207
208     return count;
209 }
210
211 void MemoryStorageService::createString(const char* context, const char* key, const char* value, time_t expiration)
212 {
213     Context& ctx = getContext(context);
214
215     // Lock the maps.
216     ctx.m_lock->wrlock();
217     SharedLock wrapper(ctx.m_lock, false);
218     
219     // Check for a duplicate.
220     map<string,Record>::iterator i=ctx.m_dataMap.find(key);
221     if (i!=ctx.m_dataMap.end()) {
222         // Not yet expired?
223         if (time(NULL) < i->second.expiration)
224             throw IOException("attempted to insert a record with duplicate key ($1)", params(1,key));
225         // It's dead, so we can just remove it now and create the new record.
226         // Now find the reversed index of expiration to key, so we can clear it.
227         pair<multimap<time_t,string>::iterator,multimap<time_t,string>::iterator> range =
228             ctx.m_expMap.equal_range(i->second.expiration);
229         for (; range.first != range.second; ++range.first) {
230             if (range.first->second == i->first) {
231                 ctx.m_expMap.erase(range.first);
232                 break;
233             }
234         }
235         // And finally delete the record itself.
236         ctx.m_dataMap.erase(i);
237     }
238     
239     ctx.m_dataMap[key]=Record(value,expiration);
240     ctx.m_expMap.insert(multimap<time_t,string>::value_type(expiration,key));
241     
242     m_log.debug("inserted record (%s) in context (%s)", key, context);
243 }
244
245 bool MemoryStorageService::readString(const char* context, const char* key, string* pvalue, time_t* pexpiration)
246 {
247     Context& ctx = getContext(context);
248
249     SharedLock wrapper(ctx.m_lock);
250     map<string,Record>::iterator i=ctx.m_dataMap.find(key);
251     if (i==ctx.m_dataMap.end())
252         return false;
253     else if (time(NULL) >= i->second.expiration)
254         return false;
255     if (pvalue)
256         *pvalue = i->second.data;
257     if (pexpiration)
258         *pexpiration = i->second.expiration;
259     return true;
260 }
261
262 bool MemoryStorageService::updateString(const char* context, const char* key, const char* value, time_t expiration)
263 {
264     Context& ctx = getContext(context);
265
266     // Lock the maps.
267     ctx.m_lock->wrlock();
268     SharedLock wrapper(ctx.m_lock, false);
269
270     map<string,Record>::iterator i=ctx.m_dataMap.find(key);
271     if (i==ctx.m_dataMap.end())
272         return false;
273     else if (time(NULL) >= i->second.expiration)
274         return false;
275         
276     if (value)
277         i->second.data = value;
278         
279     if (expiration && expiration != i->second.expiration) {
280         // Update secondary map.
281         pair<multimap<time_t,string>::iterator,multimap<time_t,string>::iterator> range =
282             ctx.m_expMap.equal_range(i->second.expiration);
283         for (; range.first != range.second; ++range.first) {
284             if (range.first->second == i->first) {
285                 ctx.m_expMap.erase(range.first);
286                 break;
287             }
288         }
289         i->second.expiration = expiration;
290         ctx.m_expMap.insert(multimap<time_t,string>::value_type(expiration,key));
291     }
292
293     m_log.debug("updated record (%s) in context (%s)", key, context);
294     return true;
295 }
296
297 bool MemoryStorageService::deleteString(const char* context, const char* key)
298 {
299     Context& ctx = getContext(context);
300
301     // Lock the maps.
302     ctx.m_lock->wrlock();
303     SharedLock wrapper(ctx.m_lock, false);
304     
305     // Find the record.
306     map<string,Record>::iterator i=ctx.m_dataMap.find(key);
307     if (i!=ctx.m_dataMap.end()) {
308         // Now find the reversed index of expiration to key, so we can clear it.
309         pair<multimap<time_t,string>::iterator,multimap<time_t,string>::iterator> range =
310             ctx.m_expMap.equal_range(i->second.expiration);
311         for (; range.first != range.second; ++range.first) {
312             if (range.first->second == i->first) {
313                 ctx.m_expMap.erase(range.first);
314                 break;
315             }
316         }
317         // And finally delete the record itself.
318         ctx.m_dataMap.erase(i);
319         m_log.debug("deleted record (%s) in context (%s)", key, context);
320         return true;
321     }
322
323     m_log.debug("deleting record (%s) in context (%s)....not found", key, context);
324     return false;
325 }