Update copyright.
[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 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), version(1) {}
72             Record(const string& s, time_t t) : data(s), expiration(t), version(1) {}
73             string data;
74             time_t expiration;
75             int version;
76         };
77         
78         struct XMLTOOL_DLLLOCAL Context {
79             Context() : m_lock(RWLock::create()) {}
80             Context(const Context& src) {
81                 m_dataMap = src.m_dataMap;
82                 m_expMap = src.m_expMap;
83                 m_lock = RWLock::create();
84             }
85             ~Context() { delete m_lock; }
86             map<string,Record> m_dataMap;
87             multimap<time_t,string> m_expMap;
88             RWLock* m_lock;
89             unsigned long reap();
90         };
91
92         Context& getContext(const char* context) {
93             Lock wrapper(contextLock);
94             return m_contextMap[context];
95         }
96
97         map<string,Context> m_contextMap;
98         Mutex* contextLock;
99         CondWait* shutdown_wait;
100         Thread* cleanup_thread;
101         static void* cleanup_fn(void*);
102         bool shutdown;
103         int m_cleanupInterval;
104         Category& m_log;
105     };
106
107     StorageService* XMLTOOL_DLLLOCAL MemoryStorageServiceFactory(const DOMElement* const & e)
108     {
109         return new MemoryStorageService(e);
110     }
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=300;
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         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     Thread::exit(NULL);
186 }
187
188 void MemoryStorageService::reap(const char* context)
189 {
190     getContext(context).reap();
191 }
192
193 unsigned long MemoryStorageService::Context::reap()
194 {
195     // Lock the "database".
196     m_lock->wrlock();
197     SharedLock wrapper(m_lock, false);
198     
199     // Garbage collect any expired entries.
200     unsigned long count=0;
201     multimap<time_t,string>::iterator stop=m_expMap.upper_bound(time(NULL));
202     for (multimap<time_t,string>::iterator i=m_expMap.begin(); i!=stop; m_expMap.erase(i++)) {
203         m_dataMap.erase(i->second);
204         ++count;
205     }
206
207     return count;
208 }
209
210 void MemoryStorageService::createString(const char* context, const char* key, const char* value, time_t expiration)
211 {
212     Context& ctx = getContext(context);
213
214     // Lock the maps.
215     ctx.m_lock->wrlock();
216     SharedLock wrapper(ctx.m_lock, false);
217     
218     // Check for a duplicate.
219     map<string,Record>::iterator i=ctx.m_dataMap.find(key);
220     if (i!=ctx.m_dataMap.end()) {
221         // Not yet expired?
222         if (time(NULL) < i->second.expiration)
223             throw IOException("attempted to insert a record with duplicate key ($1)", params(1,key));
224         // It's dead, so we can just remove it now and create the new record.
225         // Now find the reversed index of expiration to key, so we can clear it.
226         pair<multimap<time_t,string>::iterator,multimap<time_t,string>::iterator> range =
227             ctx.m_expMap.equal_range(i->second.expiration);
228         for (; range.first != range.second; ++range.first) {
229             if (range.first->second == i->first) {
230                 ctx.m_expMap.erase(range.first);
231                 break;
232             }
233         }
234         // And finally delete the record itself.
235         ctx.m_dataMap.erase(i);
236     }
237     
238     ctx.m_dataMap[key]=Record(value,expiration);
239     ctx.m_expMap.insert(multimap<time_t,string>::value_type(expiration,key));
240     
241     m_log.debug("inserted record (%s) in context (%s)", key, context);
242 }
243
244 int MemoryStorageService::readString(const char* context, const char* key, string* pvalue, time_t* pexpiration, int version)
245 {
246     Context& ctx = getContext(context);
247
248     SharedLock wrapper(ctx.m_lock);
249     map<string,Record>::iterator i=ctx.m_dataMap.find(key);
250     if (i==ctx.m_dataMap.end())
251         return 0;
252     else if (time(NULL) >= i->second.expiration)
253         return 0;
254     if (i->second.version == version)
255         return version; // nothing's changed, so just echo back the version
256     if (pvalue)
257         *pvalue = i->second.data;
258     if (pexpiration)
259         *pexpiration = i->second.expiration;
260     return i->second.version;
261 }
262
263 int MemoryStorageService::updateString(const char* context, const char* key, const char* value, time_t expiration, int version)
264 {
265     Context& ctx = getContext(context);
266
267     // Lock the maps.
268     ctx.m_lock->wrlock();
269     SharedLock wrapper(ctx.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         // Update secondary map.
287         pair<multimap<time_t,string>::iterator,multimap<time_t,string>::iterator> range =
288             ctx.m_expMap.equal_range(i->second.expiration);
289         for (; range.first != range.second; ++range.first) {
290             if (range.first->second == i->first) {
291                 ctx.m_expMap.erase(range.first);
292                 break;
293             }
294         }
295         i->second.expiration = expiration;
296         ctx.m_expMap.insert(multimap<time_t,string>::value_type(expiration,key));
297     }
298
299     m_log.debug("updated record (%s) in context (%s)", key, context);
300     return i->second.version;
301 }
302
303 bool MemoryStorageService::deleteString(const char* context, const char* key)
304 {
305     Context& ctx = getContext(context);
306
307     // Lock the maps.
308     ctx.m_lock->wrlock();
309     SharedLock wrapper(ctx.m_lock, false);
310     
311     // Find the record.
312     map<string,Record>::iterator i=ctx.m_dataMap.find(key);
313     if (i!=ctx.m_dataMap.end()) {
314         // Now find the reversed index of expiration to key, so we can clear it.
315         pair<multimap<time_t,string>::iterator,multimap<time_t,string>::iterator> range =
316             ctx.m_expMap.equal_range(i->second.expiration);
317         for (; range.first != range.second; ++range.first) {
318             if (range.first->second == i->first) {
319                 ctx.m_expMap.erase(range.first);
320                 break;
321             }
322         }
323         // And finally delete the record itself.
324         ctx.m_dataMap.erase(i);
325         m_log.debug("deleted record (%s) in context (%s)", key, context);
326         return true;
327     }
328
329     m_log.debug("deleting record (%s) in context (%s)....not found", key, context);
330     return false;
331 }