Added a simple storage abstraction, and an in-memory sample.
[shibboleth/cpp-xmltooling.git] / xmltooling / impl / MemoryStorageService.cpp
1 /*\r
2  *  Copyright 2001-2005 Internet2\r
3  * \r
4  * Licensed under the Apache License, Version 2.0 (the "License");\r
5  * you may not use this file except in compliance with the License.\r
6  * You may obtain a copy of the License at\r
7  *\r
8  *     http://www.apache.org/licenses/LICENSE-2.0\r
9  *\r
10  * Unless required by applicable law or agreed to in writing, software\r
11  * distributed under the License is distributed on an "AS IS" BASIS,\r
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
13  * See the License for the specific language governing permissions and\r
14  * limitations under the License.\r
15  */\r
16 \r
17 /**\r
18  * MemoryStorageService.cpp\r
19  * \r
20  * In-memory "persistent" storage, suitable for simple applications.\r
21  */\r
22 \r
23 #include "internal.h"\r
24 #include "util/NDC.h"\r
25 #include "util/StorageService.h"\r
26 #include "util/Threads.h"\r
27 #include "util/XMLHelper.h"\r
28 \r
29 #include <log4cpp/Category.hh>\r
30 #include <xercesc/util/XMLUniDefs.hpp>\r
31 \r
32 using namespace xmltooling;\r
33 using namespace log4cpp;\r
34 using namespace std;\r
35 \r
36 namespace xmltooling {\r
37     class XMLTOOL_DLLLOCAL MemoryStorageService : public StorageService\r
38     {\r
39     public:\r
40         MemoryStorageService(const DOMElement* e);\r
41         virtual ~MemoryStorageService();\r
42         \r
43         void createString(const char* key, const char* value, time_t expiration);\r
44         bool readString(const char* key, string& value, time_t modifiedSince=0);\r
45         bool updateString(const char* key, const char* value=NULL, time_t expiration=0);\r
46         bool deleteString(const char* key);\r
47         \r
48         void createText(const char* key, const char* value, time_t expiration) {\r
49             return createString(key, value, expiration);\r
50         }\r
51         bool readText(const char* key, string& value, time_t modifiedSince=0) {\r
52             return readString(key, value, modifiedSince);\r
53         }\r
54         bool updateText(const char* key, const char* value=NULL, time_t expiration=0) {\r
55             return updateString(key, value, expiration);\r
56         }\r
57         bool deleteText(const char* key) {\r
58             return deleteString(key);\r
59         }\r
60         \r
61         void reap() {\r
62             shutdown_wait->signal();\r
63         }\r
64 \r
65     private:\r
66         void cleanup();\r
67     \r
68         struct XMLTOOL_DLLLOCAL Record {\r
69             Record() : modified(0), expiration(0) {}\r
70             Record(string s, time_t t1, time_t t2) : data(s), modified(t1), expiration(t2) {}\r
71             string data;\r
72             time_t modified, expiration;\r
73         };\r
74         \r
75         map<string,Record> m_dataMap;\r
76         multimap<time_t,string> m_expMap;\r
77         RWLock* m_lock;\r
78         CondWait* shutdown_wait;\r
79         Thread* cleanup_thread;\r
80         static void* cleanup_fn(void*);\r
81         bool shutdown;\r
82         int m_cleanupInterval;\r
83         Category& m_log;\r
84     };\r
85 \r
86     StorageService* XMLTOOL_DLLLOCAL MemoryStorageServiceFactory(const DOMElement* const & e)\r
87     {\r
88         return new MemoryStorageService(e);\r
89     }\r
90 \r
91 };\r
92 \r
93 static const XMLCh cleanupInterval[] = UNICODE_LITERAL_15(c,l,e,a,n,u,p,I,n,t,e,r,v,a,l);\r
94 \r
95 MemoryStorageService::MemoryStorageService(const DOMElement* e)\r
96     : m_lock(NULL), shutdown_wait(NULL), cleanup_thread(NULL), shutdown(false), m_cleanupInterval(0),\r
97         m_log(Category::getInstance(XMLTOOLING_LOGCAT".StorageService"))\r
98 {\r
99     m_lock = RWLock::create();\r
100     shutdown_wait = CondWait::create();\r
101     cleanup_thread = Thread::create(&cleanup_fn, (void*)this);\r
102 \r
103     const XMLCh* tag=e ? e->getAttributeNS(NULL,cleanupInterval) : NULL;\r
104     if (tag && *tag) {\r
105         m_cleanupInterval = XMLString::parseInt(tag);\r
106     }\r
107     if (!m_cleanupInterval)\r
108         m_cleanupInterval=300;\r
109 }\r
110 \r
111 MemoryStorageService::~MemoryStorageService()\r
112 {\r
113     // Shut down the cleanup thread and let it know...\r
114     shutdown = true;\r
115     shutdown_wait->signal();\r
116     cleanup_thread->join(NULL);\r
117 \r
118     delete m_lock;\r
119     delete shutdown_wait;\r
120 }\r
121 \r
122 void* MemoryStorageService::cleanup_fn(void* cache_p)\r
123 {\r
124     MemoryStorageService* cache = reinterpret_cast<MemoryStorageService*>(cache_p);\r
125 \r
126 #ifndef WIN32\r
127     // First, let's block all signals \r
128     Thread::mask_all_signals();\r
129 #endif\r
130 \r
131     // Now run the cleanup process.\r
132     cache->cleanup();\r
133     return NULL;\r
134 }\r
135 \r
136 void MemoryStorageService::cleanup()\r
137 {\r
138 #ifdef _DEBUG\r
139     NDC ndc("cleanup");\r
140 #endif\r
141     \r
142 \r
143     Mutex* mutex = Mutex::create();\r
144     mutex->lock();\r
145 \r
146     m_log.info("cleanup thread started...running every %d seconds", m_cleanupInterval);\r
147 \r
148     while (!shutdown) {\r
149         shutdown_wait->timedwait(mutex, m_cleanupInterval);\r
150         if (shutdown)\r
151             break;\r
152 \r
153         // Lock the "database".\r
154         m_lock->wrlock();\r
155         \r
156         // Garbage collect any expired entries.\r
157         unsigned int count=0;\r
158         time_t now=time(NULL)-XMLToolingConfig::getConfig().clock_skew_secs;\r
159         multimap<time_t,string>::iterator stop=m_expMap.upper_bound(now);\r
160         for (multimap<time_t,string>::iterator i=m_expMap.begin(); i!=stop; m_expMap.erase(i++)) {\r
161             m_dataMap.erase(i->second);\r
162             ++count;\r
163         }\r
164         \r
165         m_lock->unlock();\r
166         \r
167         if (count)\r
168             m_log.info("purged %d record(s) from storage", count);\r
169     }\r
170 \r
171     m_log.info("cleanup thread finished");\r
172 \r
173     mutex->unlock();\r
174     delete mutex;\r
175     Thread::exit(NULL);\r
176 }\r
177 \r
178 void MemoryStorageService::createString(const char* key, const char* value, time_t expiration)\r
179 {\r
180     // Lock the maps.\r
181     m_lock->wrlock();\r
182     SharedLock wrapper(m_lock, false);\r
183     \r
184     // Check for a duplicate.\r
185     map<string,Record>::iterator i=m_dataMap.find(key);\r
186     if (i!=m_dataMap.end())\r
187         throw IOException("attempted to insert a record with duplicate key ($1)", params(1,key));\r
188     \r
189     m_dataMap[key]=Record(value,time(NULL),expiration);\r
190     m_expMap.insert(multimap<time_t,string>::value_type(expiration,key));\r
191     \r
192     m_log.debug("inserted record (%s)", key);\r
193 }\r
194 \r
195 bool MemoryStorageService::readString(const char* key, string& value, time_t modifiedSince)\r
196 {\r
197     SharedLock wrapper(m_lock);\r
198     map<string,Record>::iterator i=m_dataMap.find(key);\r
199     if (i==m_dataMap.end())\r
200         return false;\r
201     else if (modifiedSince >= i->second.modified)\r
202         return false;\r
203     value = i->second.data;\r
204     return true;\r
205 }\r
206 \r
207 bool MemoryStorageService::updateString(const char* key, const char* value, time_t expiration)\r
208 {\r
209     // Lock the maps.\r
210     m_lock->wrlock();\r
211     SharedLock wrapper(m_lock, false);\r
212 \r
213     map<string,Record>::iterator i=m_dataMap.find(key);\r
214     if (i==m_dataMap.end())\r
215         return false;\r
216         \r
217     if (value)\r
218         i->second.data = value;\r
219         \r
220     if (expiration && expiration != i->second.expiration) {\r
221         // Update secondary map.\r
222         pair<multimap<time_t,string>::iterator,multimap<time_t,string>::iterator> range=m_expMap.equal_range(i->second.expiration);\r
223         for (; range.first != range.second; ++range.first) {\r
224             if (range.first->second == i->first) {\r
225                 m_expMap.erase(range.first);\r
226                 break;\r
227             }\r
228         }\r
229         i->second.expiration = expiration;\r
230         m_expMap.insert(multimap<time_t,string>::value_type(expiration,key));\r
231     }\r
232 \r
233     i->second.modified = time(NULL);\r
234     m_log.debug("updated record (%s)", key);\r
235     return true;\r
236 }\r
237 \r
238 bool MemoryStorageService::deleteString(const char* key)\r
239 {\r
240     // Lock the maps.\r
241     m_lock->wrlock();\r
242     SharedLock wrapper(m_lock, false);\r
243     \r
244     // Find the record.\r
245     map<string,Record>::iterator i=m_dataMap.find(key);\r
246     if (i!=m_dataMap.end()) {\r
247         // Now find the reversed index of expiration to key, so we can clear it.\r
248         pair<multimap<time_t,string>::iterator,multimap<time_t,string>::iterator> range=m_expMap.equal_range(i->second.expiration);\r
249         for (; range.first != range.second; ++range.first) {\r
250             if (range.first->second == i->first) {\r
251                 m_expMap.erase(range.first);\r
252                 break;\r
253             }\r
254         }\r
255         // And finally delete the record itself.\r
256         m_dataMap.erase(i);\r
257         m_log.debug("deleted record (%s)", key);\r
258         return true;\r
259     }\r
260 \r
261     m_log.debug("deleting record (%s)....not found", key);\r
262     return false;\r
263 }\r