2 * Copyright 2001-2010 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.
26 #include "util/StorageService.h"
27 #include "util/Threads.h"
28 #include "util/XMLHelper.h"
31 #include <xercesc/util/XMLUniDefs.hpp>
33 using namespace xmltooling::logging;
34 using namespace xmltooling;
37 using xercesc::DOMElement;
39 namespace xmltooling {
40 class XMLTOOL_DLLLOCAL MemoryStorageService : public StorageService
43 MemoryStorageService(const DOMElement* e);
44 virtual ~MemoryStorageService();
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=nullptr, time_t* pexpiration=nullptr, int version=0);
48 int updateString(const char* context, const char* key, const char* value=nullptr, time_t expiration=0, int version=0);
49 bool deleteString(const char* context, const char* key);
51 bool createText(const char* context, const char* key, const char* value, time_t expiration) {
52 return createString(context, key, value, expiration);
54 int readText(const char* context, const char* key, string* pvalue=nullptr, time_t* pexpiration=nullptr, int version=0) {
55 return readString(context, key, pvalue, pexpiration, version);
57 int updateText(const char* context, const char* key, const char* value=nullptr, time_t expiration=0, int version=0) {
58 return updateString(context, key, value, expiration, version);
60 bool deleteText(const char* context, const char* key) {
61 return deleteString(context, key);
64 void reap(const char* context);
65 void updateContext(const char* context, time_t expiration);
66 void deleteContext(const char* context) {
68 m_contextMap.erase(context);
73 struct XMLTOOL_DLLLOCAL Record {
74 Record() : expiration(0), version(1) {}
75 Record(const string& s, time_t t) : data(s), expiration(t), version(1) {}
81 struct XMLTOOL_DLLLOCAL Context {
83 Context(const Context& src) {
84 m_dataMap = src.m_dataMap;
86 map<string,Record> m_dataMap;
87 unsigned long reap(time_t exp);
90 Context& readContext(const char* context) {
92 map<string,Context>::iterator i = m_contextMap.find(context);
93 if (i != m_contextMap.end())
97 return m_contextMap[context];
100 Context& writeContext(const char* context) {
102 return m_contextMap[context];
105 map<string,Context> m_contextMap;
107 CondWait* shutdown_wait;
108 Thread* cleanup_thread;
109 static void* cleanup_fn(void*);
111 int m_cleanupInterval;
115 StorageService* XMLTOOL_DLLLOCAL MemoryStorageServiceFactory(const DOMElement* const & e)
117 return new MemoryStorageService(e);
121 static const XMLCh cleanupInterval[] = UNICODE_LITERAL_15(c,l,e,a,n,u,p,I,n,t,e,r,v,a,l);
123 MemoryStorageService::MemoryStorageService(const DOMElement* e)
124 : m_lock(nullptr), shutdown_wait(nullptr), cleanup_thread(nullptr), shutdown(false),
125 m_cleanupInterval(XMLHelper::getAttrInt(e, 900, cleanupInterval)),
126 m_log(Category::getInstance(XMLTOOLING_LOGCAT".StorageService"))
128 m_lock = RWLock::create();
129 shutdown_wait = CondWait::create();
130 cleanup_thread = Thread::create(&cleanup_fn, (void*)this);
133 MemoryStorageService::~MemoryStorageService()
135 // Shut down the cleanup thread and let it know...
137 shutdown_wait->signal();
138 cleanup_thread->join(nullptr);
140 delete cleanup_thread;
141 delete shutdown_wait;
145 void* MemoryStorageService::cleanup_fn(void* pv)
147 MemoryStorageService* cache = reinterpret_cast<MemoryStorageService*>(pv);
150 // First, let's block all signals
151 Thread::mask_all_signals();
158 auto_ptr<Mutex> mutex(Mutex::create());
161 cache->m_log.info("cleanup thread started...running every %d seconds", cache->m_cleanupInterval);
163 while (!cache->shutdown) {
164 cache->shutdown_wait->timedwait(mutex.get(), cache->m_cleanupInterval);
168 unsigned long count=0;
169 time_t now = time(nullptr);
170 cache->m_lock->wrlock();
171 SharedLock locker(cache->m_lock, false);
172 for (map<string,Context>::iterator i=cache->m_contextMap.begin(); i!=cache->m_contextMap.end(); ++i)
173 count += i->second.reap(now);
176 cache->m_log.info("purged %d expired record(s) from storage", count);
179 cache->m_log.info("cleanup thread finished");
185 void MemoryStorageService::reap(const char* context)
187 Context& ctx = writeContext(context);
188 SharedLock locker(m_lock, false);
189 ctx.reap(time(nullptr));
192 unsigned long MemoryStorageService::Context::reap(time_t exp)
194 // Garbage collect any expired entries.
195 unsigned long count=0;
196 map<string,Record>::iterator cur = m_dataMap.begin();
197 map<string,Record>::iterator stop = m_dataMap.end();
198 while (cur != stop) {
199 if (cur->second.expiration <= exp) {
200 map<string,Record>::iterator tmp = cur++;
201 m_dataMap.erase(tmp);
211 bool MemoryStorageService::createString(const char* context, const char* key, const char* value, time_t expiration)
213 Context& ctx = writeContext(context);
214 SharedLock locker(m_lock, false);
216 // Check for a duplicate.
217 map<string,Record>::iterator i=ctx.m_dataMap.find(key);
218 if (i!=ctx.m_dataMap.end()) {
220 if (time(nullptr) < i->second.expiration)
222 // It's dead, so we can just remove it now and create the new record.
223 ctx.m_dataMap.erase(i);
226 ctx.m_dataMap[key]=Record(value,expiration);
228 m_log.debug("inserted record (%s) in context (%s) with expiration (%lu)", key, context, expiration);
232 int MemoryStorageService::readString(const char* context, const char* key, string* pvalue, time_t* pexpiration, int version)
234 Context& ctx = readContext(context);
235 SharedLock locker(m_lock, false);
237 map<string,Record>::iterator i=ctx.m_dataMap.find(key);
238 if (i==ctx.m_dataMap.end())
240 else if (time(nullptr) >= i->second.expiration)
243 *pexpiration = i->second.expiration;
244 if (i->second.version == version)
245 return version; // nothing's changed, so just echo back the version
247 *pvalue = i->second.data;
248 return i->second.version;
251 int MemoryStorageService::updateString(const char* context, const char* key, const char* value, time_t expiration, int version)
253 Context& ctx = writeContext(context);
254 SharedLock locker(m_lock, false);
256 map<string,Record>::iterator i=ctx.m_dataMap.find(key);
257 if (i==ctx.m_dataMap.end())
259 else if (time(nullptr) >= i->second.expiration)
262 if (version > 0 && version != i->second.version)
263 return -1; // caller's out of sync
266 i->second.data = value;
267 ++(i->second.version);
270 if (expiration && expiration != i->second.expiration)
271 i->second.expiration = expiration;
273 m_log.debug("updated record (%s) in context (%s) with expiration (%lu)", key, context, i->second.expiration);
274 return i->second.version;
277 bool MemoryStorageService::deleteString(const char* context, const char* key)
279 Context& ctx = writeContext(context);
280 SharedLock locker(m_lock, false);
283 map<string,Record>::iterator i=ctx.m_dataMap.find(key);
284 if (i!=ctx.m_dataMap.end()) {
285 ctx.m_dataMap.erase(i);
286 m_log.debug("deleted record (%s) in context (%s)", key, context);
290 m_log.debug("deleting record (%s) in context (%s)....not found", key, context);
294 void MemoryStorageService::updateContext(const char* context, time_t expiration)
296 Context& ctx = writeContext(context);
297 SharedLock locker(m_lock, false);
299 time_t now = time(nullptr);
300 map<string,Record>::iterator stop=ctx.m_dataMap.end();
301 for (map<string,Record>::iterator i = ctx.m_dataMap.begin(); i!=stop; ++i) {
302 if (now < i->second.expiration)
303 i->second.expiration = expiration;
306 m_log.debug("updated expiration of valid records in context (%s) to (%lu)", context, expiration);