2 * Licensed to the University Corporation for Advanced Internet
3 * Development, Inc. (UCAID) under one or more contributor license
4 * agreements. See the NOTICE file distributed with this work for
5 * additional information regarding copyright ownership.
7 * UCAID licenses this file to you under the Apache License,
8 * Version 2.0 (the "License"); you may not use this file except
9 * in compliance with the License. You may obtain a copy of the
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17 * either express or implied. See the License for the specific
18 * language governing permissions and limitations under the License.
24 * Storage Service using memcache (pre memcache tags).
27 #if defined (_MSC_VER) || defined(__BORLANDC__)
28 # include "config_win32.h"
34 # define _CRT_NONSTDC_NO_DEPRECATE 1
35 # define _CRT_SECURE_NO_DEPRECATE 1
36 # define MCEXT_EXPORTS __declspec(dllexport)
38 # define MCEXT_EXPORTS
41 #include <xmltooling/base.h>
45 #include <libmemcached/memcached.h>
46 #include <xercesc/util/XMLUniDefs.hpp>
48 #include <xmltooling/logging.h>
49 #include <xmltooling/unicode.h>
50 #include <xmltooling/XMLToolingConfig.h>
51 #include <xmltooling/util/NDC.h>
52 #include <xmltooling/util/StorageService.h>
53 #include <xmltooling/util/Threads.h>
54 #include <xmltooling/util/XMLHelper.h>
56 using namespace xmltooling::logging;
57 using namespace xmltooling;
58 using namespace xercesc;
59 using namespace boost;
63 static const XMLCh Hosts[] = UNICODE_LITERAL_5(H,o,s,t,s);
64 static const XMLCh prefix[] = UNICODE_LITERAL_6(p,r,e,f,i,x);
65 static const XMLCh buildMap[] = UNICODE_LITERAL_8(b,u,i,l,d,M,a,p);
66 static const XMLCh sendTimeout[] = UNICODE_LITERAL_11(s,e,n,d,T,i,m,e,o,u,t);
67 static const XMLCh recvTimeout[] = UNICODE_LITERAL_11(r,e,c,v,T,i,m,e,o,u,t);
68 static const XMLCh pollTimeout[] = UNICODE_LITERAL_11(p,o,l,l,T,i,m,e,o,u,t);
69 static const XMLCh failLimit[] = UNICODE_LITERAL_9(f,a,i,l,L,i,m,i,t);
70 static const XMLCh retryTimeout[] = UNICODE_LITERAL_12(r,e,t,r,y,T,i,m,e,o,u,t);
71 static const XMLCh nonBlocking[] = UNICODE_LITERAL_11(n,o,n,B,l,o,c,k,i,n,g);
78 mc_record(string _v, time_t _e) : value(_v), expiration(_e) {}
83 MemcacheBase(const DOMElement* e);
86 bool addMemcache(const char* key, string &value, time_t timeout, uint32_t flags, bool use_prefix = true);
87 bool setMemcache(const char* key, string &value, time_t timeout, uint32_t flags, bool use_prefix = true);
88 bool replaceMemcache(const char* key, string &value, time_t timeout, uint32_t flags, bool use_prefix = true);
89 bool getMemcache(const char* key, string &dest, uint32_t *flags, bool use_prefix = true);
90 bool deleteMemcache(const char* key, time_t timeout, bool use_prefix = true);
92 void serialize(mc_record &source, string &dest);
93 void serialize(list<string> &source, string &dest);
94 void deserialize(string &source, mc_record &dest);
95 void deserialize(string &source, list<string> &dest);
97 bool addLock(string what, bool use_prefix = true);
98 void deleteLock(string what, bool use_prefix = true);
104 scoped_ptr<Mutex> m_lock;
107 bool handleError(const char*, memcached_return) const;
110 class MemcacheStorageService : public StorageService, public MemcacheBase {
113 MemcacheStorageService(const DOMElement* e);
114 ~MemcacheStorageService() {}
116 const Capabilities& getCapabilities() const {
120 bool createString(const char* context, const char* key, const char* value, time_t expiration);
121 int readString(const char* context, const char* key, string* pvalue=nullptr, time_t* pexpiration=nullptr, int version=0);
122 int updateString(const char* context, const char* key, const char* value=nullptr, time_t expiration=0, int version=0);
123 bool deleteString(const char* context, const char* key);
125 bool createText(const char* context, const char* key, const char* value, time_t expiration) {
126 return createString(context, key, value, expiration);
128 int readText(const char* context, const char* key, string* pvalue=nullptr, time_t* pexpiration=nullptr, int version=0) {
129 return readString(context, key, pvalue, pexpiration, version);
131 int updateText(const char* context, const char* key, const char* value=nullptr, time_t expiration=0, int version=0) {
132 return updateString(context, key, value, expiration, version);
134 bool deleteText(const char* context, const char* key) {
135 return deleteString(context, key);
138 void reap(const char* context) {}
140 void updateContext(const char* context, time_t expiration);
141 void deleteContext(const char* context);
148 StorageService* MemcacheStorageServiceFactory(const DOMElement* const & e) {
149 return new MemcacheStorageService(e);
153 MemcacheBase::MemcacheBase(const DOMElement* e)
154 : m_log(Category::getInstance("XMLTooling.StorageService.MEMCACHE")), memc(nullptr),
155 m_prefix(XMLHelper::getAttrString(e, nullptr, prefix)), m_lock(Mutex::create())
157 memc = memcached_create(nullptr);
159 throw XMLToolingException("MemcacheBase::Memcache(): memcached_create() failed");
160 m_log.debug("Memcache created");
162 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_HASH, MEMCACHED_HASH_CRC);
163 m_log.debug("CRC hash set");
165 int prop = XMLHelper::getAttrInt(e, 999999, sendTimeout);
166 m_log.debug("MEMCACHED_BEHAVIOR_SND_TIMEOUT will be set to %d", prop);
167 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SND_TIMEOUT, prop);
169 prop = XMLHelper::getAttrInt(e, 999999, recvTimeout);
170 m_log.debug("MEMCACHED_BEHAVIOR_RCV_TIMEOUT will be set to %d", prop);
171 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_RCV_TIMEOUT, prop);
173 prop = XMLHelper::getAttrInt(e, 1000, pollTimeout);
174 m_log.debug("MEMCACHED_BEHAVIOR_POLL_TIMEOUT will be set to %d", prop);
175 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_POLL_TIMEOUT, prop);
177 prop = XMLHelper::getAttrInt(e, 5, failLimit);
178 m_log.debug("MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT will be set to %d", prop);
179 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT, prop);
181 prop = XMLHelper::getAttrInt(e, 30, retryTimeout);
182 m_log.debug("MEMCACHED_BEHAVIOR_RETRY_TIMEOUT will be set to %d", prop);
183 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_RETRY_TIMEOUT, prop);
185 prop = XMLHelper::getAttrInt(e, 1, nonBlocking);
186 m_log.debug("MEMCACHED_BEHAVIOR_NO_BLOCK will be set to %d", prop);
187 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NO_BLOCK, prop);
189 // Grab hosts from the configuration.
190 e = e ? XMLHelper::getFirstChildElement(e, Hosts) : nullptr;
191 if (!e || !e->hasChildNodes()) {
192 memcached_free(memc);
193 throw XMLToolingException("Memcache StorageService requires Hosts element in configuration.");
195 auto_ptr_char h(e->getTextContent());
196 m_log.debug("INIT: GOT Hosts: %s", h.get());
197 memcached_server_st* servers;
198 servers = memcached_servers_parse(const_cast<char*>(h.get()));
199 m_log.debug("Got %u hosts.", memcached_server_list_count(servers));
200 if (memcached_server_push(memc, servers) != MEMCACHED_SUCCESS) {
201 memcached_server_list_free(servers);
202 memcached_free(memc);
203 throw IOException("MemcacheBase: memcached_server_push() failed");
205 memcached_server_list_free(servers);
207 m_log.debug("Memcache object initialized");
210 MemcacheBase::~MemcacheBase()
212 memcached_free(memc);
213 m_log.debug("Base object destroyed");
217 bool MemcacheBase::handleError(const char* fn, memcached_return rv) const
219 #ifdef HAVE_MEMCACHED_LAST_ERROR_MESSAGE
220 string error = string("Memcache::") + fn + ": " + memcached_last_error_message(memc);
223 if (rv == MEMCACHED_ERRNO) {
225 error = string("Memcache::") + fn + "SYSTEM ERROR: " + strerror(memc->cached_errno);
228 error = string("Memcache::") + fn + " Problems: " + memcached_strerror(memc, rv);
232 throw IOException(error);
235 bool MemcacheBase::addLock(string what, bool use_prefix)
237 string lock_name = what + ":LOCK";
238 string set_val = "1";
240 while (!addMemcache(lock_name.c_str(), set_val, 5, 0, use_prefix)) {
242 m_log.debug("Unable to get lock %s... FAILED.", lock_name.c_str());
245 m_log.debug("Unable to get lock %s... Retrying.", lock_name.c_str());
251 struct timeval tv = { 0, 100000 };
252 select(0, 0, 0, 0, &tv);
258 void MemcacheBase::deleteLock(string what, bool use_prefix)
260 string lock_name = what + ":LOCK";
261 deleteMemcache(lock_name.c_str(), 0, use_prefix);
266 void MemcacheBase::deserialize(string& source, mc_record& dest)
268 istringstream is(source, stringstream::in | stringstream::out);
269 is >> dest.expiration;
270 is.ignore(1); // ignore delimiter
271 dest.value = is.str().c_str() + is.tellg();
274 void MemcacheBase::deserialize(string& source, list<string>& dest)
276 istringstream is(source, stringstream::in | stringstream::out);
284 void MemcacheBase::serialize(mc_record& source, string& dest)
286 ostringstream os(stringstream::in | stringstream::out);
287 os << source.expiration;
288 os << "-"; // delimiter
293 void MemcacheBase::serialize(list<string>& source, string& dest)
295 ostringstream os(stringstream::in | stringstream::out);
296 for(list<string>::iterator iter = source.begin(); iter != source.end(); iter++) {
297 if (iter != source.begin()) {
305 bool MemcacheBase::deleteMemcache(const char* key, time_t timeout, bool use_prefix)
309 final_key = m_prefix + key;
314 memcached_return rv = memcached_delete(memc, const_cast<char*>(final_key.c_str()), final_key.length(), timeout);
317 case MEMCACHED_SUCCESS:
319 case MEMCACHED_NOTFOUND:
320 // Key wasn't there... No biggie.
323 return handleError("deleteMemcache", rv);
327 bool MemcacheBase::getMemcache(const char* key, string& dest, uint32_t* flags, bool use_prefix)
331 final_key = m_prefix + key;
338 char* result = memcached_get(memc, const_cast<char*>(final_key.c_str()), final_key.length(), &len, flags, &rv);
341 case MEMCACHED_SUCCESS:
345 case MEMCACHED_NOTFOUND:
346 m_log.debug("Key %s not found in memcache...", key);
349 return handleError("getMemcache", rv);
353 bool MemcacheBase::addMemcache(const char* key, string& value, time_t timeout, uint32_t flags, bool use_prefix)
357 final_key = m_prefix + key;
362 memcached_return rv = memcached_add(
363 memc, const_cast<char*>(final_key.c_str()), final_key.length(), const_cast<char*>(value.c_str()), value.length(), timeout, flags
367 case MEMCACHED_SUCCESS:
369 case MEMCACHED_NOTSTORED:
372 return handleError("addMemcache", rv);
376 bool MemcacheBase::setMemcache(const char* key, string& value, time_t timeout, uint32_t flags, bool use_prefix)
380 final_key = m_prefix + key;
385 memcached_return rv = memcached_set(
386 memc, const_cast<char*>(final_key.c_str()), final_key.length(), const_cast<char*>(value.c_str()), value.length(), timeout, flags
389 if (rv == MEMCACHED_SUCCESS)
391 return handleError("setMemcache", rv);
394 bool MemcacheBase::replaceMemcache(const char* key, string& value, time_t timeout, uint32_t flags, bool use_prefix)
399 final_key = m_prefix + key;
404 memcached_return rv = memcached_replace(
405 memc, const_cast<char*>(final_key.c_str()), final_key.length(), const_cast<char*>(value.c_str()), value.length(), timeout, flags
409 case MEMCACHED_SUCCESS:
411 case MEMCACHED_NOTSTORED:
415 return handleError("replaceMemcache", rv);
420 MemcacheStorageService::MemcacheStorageService(const DOMElement* e)
421 : MemcacheBase(e), m_caps(80, 250 - m_prefix.length() - 1 - 80, 255),
422 m_buildMap(XMLHelper::getAttrBool(e, false, buildMap))
425 m_log.debug("Cache built with buildMap ON");
428 bool MemcacheStorageService::createString(const char* context, const char* key, const char* value, time_t expiration)
430 m_log.debug("createString ctx: %s - key: %s", context, key);
432 string final_key = string(context) + ":" + string(key);
434 mc_record rec(value, expiration);
436 serialize(rec, final_value);
438 bool result = addMemcache(final_key.c_str(), final_value, expiration, 1); // the flag will be the version
440 if (result && m_buildMap) {
441 m_log.debug("Got result, updating map");
443 string map_name = context;
444 // we need to update the context map
445 if (!addLock(map_name)) {
446 m_log.error("Unable to get lock for context %s!", context);
447 deleteMemcache(final_key.c_str(), 0);
453 bool result = getMemcache(map_name.c_str(), ser_arr, &flags);
455 list<string> contents;
457 m_log.debug("Match found. Parsing...");
458 deserialize(ser_arr, contents);
459 if (m_log.isDebugEnabled()) {
460 m_log.debug("Iterating retrieved session map...");
461 for(list<string>::const_iterator iter = contents.begin(); iter != contents.end(); ++iter)
462 m_log.debug("value = %s", iter->c_str());
466 m_log.debug("New context: %s", map_name.c_str());
469 contents.push_back(key);
470 serialize(contents, ser_arr);
471 setMemcache(map_name.c_str(), ser_arr, expiration, 0);
472 deleteLock(map_name);
477 int MemcacheStorageService::readString(const char* context, const char* key, string* pvalue, time_t* pexpiration, int version)
479 m_log.debug("readString ctx: %s - key: %s", context, key);
481 string final_key = string(context) + ":" + string(key);
482 uint32_t rec_version;
486 m_log.debug("Checking context");
487 string map_name = context;
490 bool ctx_found = getMemcache(map_name.c_str(), ser_arr, &flags);
495 bool found = getMemcache(final_key.c_str(), value, &rec_version);
499 if (version && rec_version <= (uint32_t)version)
502 if (pexpiration || pvalue) {
504 deserialize(value, rec);
507 *pexpiration = rec.expiration;
516 int MemcacheStorageService::updateString(const char* context, const char* key, const char* value, time_t expiration, int version)
518 m_log.debug("updateString ctx: %s - key: %s", context, key);
520 time_t final_exp = expiration;
521 time_t* want_expiration = nullptr;
523 want_expiration = &final_exp;
525 int read_res = readString(context, key, nullptr, want_expiration, version);
532 if (version && version != read_res) {
537 // Proceding with update
538 string final_key = string(context) + ":" + string(key);
539 mc_record rec(value, final_exp);
541 serialize(rec, final_value);
543 replaceMemcache(final_key.c_str(), final_value, final_exp, ++version);
547 bool MemcacheStorageService::deleteString(const char* context, const char* key)
549 m_log.debug("deleteString ctx: %s - key: %s", context, key);
551 string final_key = string(context) + ":" + string(key);
553 // Not updating context map, if there is one. There is no need.
554 return deleteMemcache(final_key.c_str(), 0);
557 void MemcacheStorageService::updateContext(const char* context, time_t expiration)
560 m_log.debug("updateContext ctx: %s", context);
563 m_log.error("updateContext invoked on a Storage with no context map built!");
567 string map_name = context;
570 bool result = getMemcache(map_name.c_str(), ser_arr, &flags);
572 list<string> contents;
574 m_log.debug("Match found. Parsing...");
575 deserialize(ser_arr, contents);
577 m_log.debug("Iterating retrieved session map...");
578 for(list<string>::const_iterator iter = contents.begin(); iter != contents.end(); ++iter) {
579 // Update expiration times
581 int read_res = readString(context, iter->c_str(), &value, nullptr, 0);
587 updateString(context, iter->c_str(), value.c_str(), expiration, read_res);
589 replaceMemcache(map_name.c_str(), ser_arr, expiration, flags);
593 void MemcacheStorageService::deleteContext(const char* context)
596 m_log.debug("deleteContext ctx: %s", context);
599 m_log.error("deleteContext invoked on a Storage with no context map built!");
603 string map_name = context;
606 bool result = getMemcache(map_name.c_str(), ser_arr, &flags);
608 list<string> contents;
610 m_log.debug("Match found. Parsing...");
611 deserialize(ser_arr, contents);
613 m_log.debug("Iterating retrieved session map...");
614 for (list<string>::const_iterator iter = contents.begin(); iter != contents.end(); ++iter) {
615 string final_key = map_name + *iter;
616 deleteMemcache(final_key.c_str(), 0);
619 deleteMemcache(map_name.c_str(), 0);
623 extern "C" int MCEXT_EXPORTS xmltooling_extension_init(void*) {
624 // Register this SS type
625 XMLToolingConfig::getConfig().StorageServiceManager.registerFactory("MEMCACHE", MemcacheStorageServiceFactory);
629 extern "C" void MCEXT_EXPORTS xmltooling_extension_term() {
630 XMLToolingConfig::getConfig().StorageServiceManager.deregisterFactory("MEMCACHE");