2 * Copyright 2001-2007 Internet2
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * MemoryStorageService.cpp
20 * In-memory "persistent" storage, suitable for simple applications.
25 #include "util/StorageService.h"
26 #include "util/Threads.h"
27 #include "util/XMLHelper.h"
29 #include <log4cpp/Category.hh>
30 #include <xercesc/util/XMLUniDefs.hpp>
32 using namespace xmltooling;
33 using namespace log4cpp;
36 namespace xmltooling {
37 class XMLTOOL_DLLLOCAL MemoryStorageService : public StorageService
40 MemoryStorageService(const DOMElement* e);
41 virtual ~MemoryStorageService();
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);
48 void createText(const char* context, const char* key, const char* value, time_t expiration) {
49 return createString(context, key, value, expiration);
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);
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);
57 bool deleteText(const char* context, const char* key) {
58 return deleteString(context, key);
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);
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) {}
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();
85 ~Context() { delete m_lock; }
86 map<string,Record> m_dataMap;
88 unsigned long reap(time_t exp);
91 Context& getContext(const char* context) {
92 Lock wrapper(contextLock);
93 return m_contextMap[context];
96 map<string,Context> m_contextMap;
98 CondWait* shutdown_wait;
99 Thread* cleanup_thread;
100 static void* cleanup_fn(void*);
102 int m_cleanupInterval;
105 friend class _expcheck;
108 StorageService* XMLTOOL_DLLLOCAL MemoryStorageServiceFactory(const DOMElement* const & e)
110 return new MemoryStorageService(e);
114 static const XMLCh cleanupInterval[] = UNICODE_LITERAL_15(c,l,e,a,n,u,p,I,n,t,e,r,v,a,l);
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"))
120 const XMLCh* tag=e ? e->getAttributeNS(NULL,cleanupInterval) : NULL;
122 m_cleanupInterval = XMLString::parseInt(tag);
124 if (!m_cleanupInterval)
125 m_cleanupInterval=900;
127 contextLock = Mutex::create();
128 shutdown_wait = CondWait::create();
129 cleanup_thread = Thread::create(&cleanup_fn, (void*)this);
132 MemoryStorageService::~MemoryStorageService()
134 // Shut down the cleanup thread and let it know...
136 shutdown_wait->signal();
137 cleanup_thread->join(NULL);
139 delete shutdown_wait;
143 void* MemoryStorageService::cleanup_fn(void* cache_p)
145 MemoryStorageService* cache = reinterpret_cast<MemoryStorageService*>(cache_p);
148 // First, let's block all signals
149 Thread::mask_all_signals();
152 // Now run the cleanup process.
157 void MemoryStorageService::cleanup()
163 auto_ptr<Mutex> mutex(Mutex::create());
166 m_log.info("cleanup thread started...running every %d seconds", m_cleanupInterval);
169 shutdown_wait->timedwait(mutex.get(), m_cleanupInterval);
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);
180 m_log.info("purged %d expired record(s) from storage", count);
183 m_log.info("cleanup thread finished");
189 void MemoryStorageService::reap(const char* context)
191 getContext(context).reap(time(NULL));
194 unsigned long MemoryStorageService::Context::reap(time_t exp)
196 // Lock the "database".
198 SharedLock wrapper(m_lock, false);
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);
217 void MemoryStorageService::createString(const char* context, const char* key, const char* value, time_t expiration)
219 Context& ctx = getContext(context);
222 ctx.m_lock->wrlock();
223 SharedLock wrapper(ctx.m_lock, false);
225 // Check for a duplicate.
226 map<string,Record>::iterator i=ctx.m_dataMap.find(key);
227 if (i!=ctx.m_dataMap.end()) {
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);
235 ctx.m_dataMap[key]=Record(value,expiration);
237 m_log.debug("inserted record (%s) in context (%s)", key, context);
240 int MemoryStorageService::readString(const char* context, const char* key, string* pvalue, time_t* pexpiration, int version)
242 Context& ctx = getContext(context);
244 SharedLock wrapper(ctx.m_lock);
245 map<string,Record>::iterator i=ctx.m_dataMap.find(key);
246 if (i==ctx.m_dataMap.end())
248 else if (time(NULL) >= i->second.expiration)
251 *pexpiration = i->second.expiration;
252 if (i->second.version == version)
253 return version; // nothing's changed, so just echo back the version
255 *pvalue = i->second.data;
256 return i->second.version;
259 int MemoryStorageService::updateString(const char* context, const char* key, const char* value, time_t expiration, int version)
261 Context& ctx = getContext(context);
264 ctx.m_lock->wrlock();
265 SharedLock wrapper(ctx.m_lock, false);
267 map<string,Record>::iterator i=ctx.m_dataMap.find(key);
268 if (i==ctx.m_dataMap.end())
270 else if (time(NULL) >= i->second.expiration)
273 if (version > 0 && version != i->second.version)
274 return -1; // caller's out of sync
277 i->second.data = value;
278 ++(i->second.version);
281 if (expiration && expiration != i->second.expiration)
282 i->second.expiration = expiration;
284 m_log.debug("updated record (%s) in context (%s)", key, context);
285 return i->second.version;
288 bool MemoryStorageService::deleteString(const char* context, const char* key)
290 Context& ctx = getContext(context);
293 ctx.m_lock->wrlock();
294 SharedLock wrapper(ctx.m_lock, false);
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);
304 m_log.debug("deleting record (%s) in context (%s)....not found", key, context);
308 void MemoryStorageService::updateContext(const char* context, time_t expiration)
310 Context& ctx = getContext(context);
313 ctx.m_lock->wrlock();
314 SharedLock wrapper(ctx.m_lock, false);
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;
323 m_log.debug("updated expiration of valid records in context (%s)", context);