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;
61 namespace xmltooling {
62 static const XMLCh Hosts[] = UNICODE_LITERAL_5(H,o,s,t,s);
63 static const XMLCh prefix[] = UNICODE_LITERAL_6(p,r,e,f,i,x);
64 static const XMLCh buildMap[] = UNICODE_LITERAL_8(b,u,i,l,d,M,a,p);
65 static const XMLCh sendTimeout[] = UNICODE_LITERAL_11(s,e,n,d,T,i,m,e,o,u,t);
66 static const XMLCh recvTimeout[] = UNICODE_LITERAL_11(r,e,c,v,T,i,m,e,o,u,t);
67 static const XMLCh pollTimeout[] = UNICODE_LITERAL_11(p,o,l,l,T,i,m,e,o,u,t);
68 static const XMLCh failLimit[] = UNICODE_LITERAL_9(f,a,i,l,L,i,m,i,t);
69 static const XMLCh retryTimeout[] = UNICODE_LITERAL_12(r,e,t,r,y,T,i,m,e,o,u,t);
70 static const XMLCh nonBlocking[] = UNICODE_LITERAL_11(n,o,n,B,l,o,c,k,i,n,g);
77 mc_record(string _v, time_t _e) :
78 value(_v), expiration(_e)
84 MemcacheBase(const DOMElement* e);
87 bool addMemcache(const char *key,
91 bool use_prefix = true);
92 bool setMemcache(const char *key,
96 bool use_prefix = true);
97 bool replaceMemcache(const char *key,
101 bool use_prefix = true);
102 bool getMemcache(const char *key,
105 bool use_prefix = true);
106 bool deleteMemcache(const char *key,
108 bool use_prefix = true);
110 void serialize(mc_record &source, string &dest);
111 void serialize(list<string> &source, string &dest);
112 void deserialize(string &source, mc_record &dest);
113 void deserialize(string &source, list<string> &dest);
115 bool addSessionToUser(string &key, string &user);
116 bool addLock(string what, bool use_prefix = true);
117 void deleteLock(string what, bool use_prefix = true);
120 const DOMElement* m_root; // can only use this during initialization
127 class MemcacheStorageService : public StorageService, public MemcacheBase {
130 MemcacheStorageService(const DOMElement* e);
131 ~MemcacheStorageService();
133 bool createString(const char* context, const char* key, const char* value, time_t expiration);
134 int readString(const char* context, const char* key, string* pvalue=nullptr, time_t* pexpiration=nullptr, int version=0);
135 int updateString(const char* context, const char* key, const char* value=nullptr, time_t expiration=0, int version=0);
136 bool deleteString(const char* context, const char* key);
138 bool createText(const char* context, const char* key, const char* value, time_t expiration) {
139 return createString(context, key, value, expiration);
141 int readText(const char* context, const char* key, string* pvalue=nullptr, time_t* pexpiration=nullptr, int version=0) {
142 return readString(context, key, pvalue, pexpiration, version);
144 int updateText(const char* context, const char* key, const char* value=nullptr, time_t expiration=0, int version=0) {
145 return updateString(context, key, value, expiration, version);
147 bool deleteText(const char* context, const char* key) {
148 return deleteString(context, key);
151 void reap(const char* context) {}
153 void updateContext(const char* context, time_t expiration);
154 void deleteContext(const char* context);
164 StorageService* MemcacheStorageServiceFactory(const DOMElement* const & e) {
165 return new MemcacheStorageService(e);
170 bool MemcacheBase::addLock(string what, bool use_prefix) {
171 string lock_name = what + ":LOCK";
172 string set_val = "1";
174 while (!addMemcache(lock_name.c_str(), set_val, 5, 0, use_prefix)) {
176 log.debug("Unable to get lock %s... FAILED.", lock_name.c_str());
179 log.debug("Unable to get lock %s... Retrying.", lock_name.c_str());
185 struct timeval tv = { 0, 100000 };
186 select(0, 0, 0, 0, &tv);
192 void MemcacheBase::deleteLock(string what, bool use_prefix) {
194 string lock_name = what + ":LOCK";
195 deleteMemcache(lock_name.c_str(), 0, use_prefix);
200 void MemcacheBase::deserialize(string &source, mc_record &dest) {
201 istringstream is(source, stringstream::in | stringstream::out);
202 is >> dest.expiration;
203 is.ignore(1); // ignore delimiter
204 dest.value = is.str().c_str() + is.tellg();
207 void MemcacheBase::deserialize(string &source, list<string> &dest) {
208 istringstream is(source, stringstream::in | stringstream::out);
216 void MemcacheBase::serialize(mc_record &source, string &dest) {
217 ostringstream os(stringstream::in | stringstream::out);
218 os << source.expiration;
219 os << "-"; // delimiter
224 void MemcacheBase::serialize(list<string> &source, string &dest) {
225 ostringstream os(stringstream::in | stringstream::out);
226 for(list<string>::iterator iter = source.begin(); iter != source.end(); iter++) {
227 if (iter != source.begin()) {
235 bool MemcacheBase::addSessionToUser(string &key, string &user) {
237 if (! addLock(user, false)) {
243 string sessid = m_prefix + key; // add specific prefix to session
244 string delimiter = ";";
245 string user_key = "UDATA:";
249 bool result = getMemcache(user_key.c_str(), user_val, &flags, false);
252 bool already_there = false;
253 // skip delimiters at beginning.
254 string::size_type lastPos = user_val.find_first_not_of(delimiter, 0);
256 // find first "non-delimiter".
257 string::size_type pos = user_val.find_first_of(delimiter, lastPos);
259 while (string::npos != pos || string::npos != lastPos) {
260 // found a token, add it to the vector.
261 string session = user_val.substr(lastPos, pos - lastPos);
262 if (strcmp(session.c_str(), sessid.c_str()) == 0) {
263 already_there = true;
267 // skip delimiters. Note the "not_of"
268 lastPos = user_val.find_first_not_of(delimiter, pos);
270 // find next "non-delimiter"
271 pos = user_val.find_first_of(delimiter, lastPos);
274 if (!already_there) {
275 user_val += delimiter + sessid;
276 replaceMemcache(user_key.c_str(), user_val, 0, 0, false);
279 addMemcache(user_key.c_str(), sessid, 0, 0, false);
282 deleteLock(user, false);
287 bool MemcacheBase::deleteMemcache(const char *key,
295 final_key = m_prefix + key;
301 rv = memcached_delete(memc, (char *)final_key.c_str(), final_key.length(), timeout);
304 if (rv == MEMCACHED_SUCCESS) {
306 } else if (rv == MEMCACHED_NOTFOUND) {
307 // Key wasn't there... No biggie.
309 } else if (rv == MEMCACHED_ERRNO) {
311 string error = string("Memcache::deleteMemcache() SYSTEM ERROR: ") + string(strerror(memc->cached_errno));
313 throw IOException(error);
315 string error = string("Memcache::deleteMemcache() Problems: ") + memcached_strerror(memc, rv);
317 throw IOException(error);
323 bool MemcacheBase::getMemcache(const char *key,
334 final_key = m_prefix + key;
340 result = memcached_get(memc, (char *)final_key.c_str(), final_key.length(), &len, flags, &rv);
343 if (rv == MEMCACHED_SUCCESS) {
347 } else if (rv == MEMCACHED_NOTFOUND) {
348 log.debug("Key %s not found in memcache...", key);
350 } else if (rv == MEMCACHED_ERRNO) {
352 string error = string("Memcache::getMemcache() SYSTEM ERROR: ") + string(strerror(memc->cached_errno));
354 throw IOException(error);
356 string error = string("Memcache::getMemcache() Problems: ") + memcached_strerror(memc, rv);
358 throw IOException(error);
364 bool MemcacheBase::addMemcache(const char *key,
375 final_key = m_prefix + key;
381 rv = memcached_add(memc, (char *)final_key.c_str(), final_key.length(), (char *)value.c_str(), value.length(), timeout, flags);
384 if (rv == MEMCACHED_SUCCESS) {
386 } else if (rv == MEMCACHED_NOTSTORED) {
389 } else if (rv == MEMCACHED_ERRNO) {
391 string error = string("Memcache::addMemcache() SYSTEM ERROR: ") + string(strerror(memc->cached_errno));
393 throw IOException(error);
395 string error = string("Memcache::addMemcache() Problems: ") + memcached_strerror(memc, rv);
397 throw IOException(error);
403 bool MemcacheBase::setMemcache(const char *key,
414 final_key = m_prefix + key;
420 rv = memcached_set(memc, (char *)final_key.c_str(), final_key.length(), (char *)value.c_str(), value.length(), timeout, flags);
423 if (rv == MEMCACHED_SUCCESS) {
425 } else if (rv == MEMCACHED_ERRNO) {
427 string error = string("Memcache::setMemcache() SYSTEM ERROR: ") + string(strerror(memc->cached_errno));
429 throw IOException(error);
431 string error = string("Memcache::setMemcache() Problems: ") + memcached_strerror(memc, rv);
433 throw IOException(error);
439 bool MemcacheBase::replaceMemcache(const char *key,
450 final_key = m_prefix + key;
456 rv = memcached_replace(memc, (char *)final_key.c_str(), final_key.length(), (char *)value.c_str(), value.length(), timeout, flags);
459 if (rv == MEMCACHED_SUCCESS) {
461 } else if (rv == MEMCACHED_NOTSTORED) {
464 } else if (rv == MEMCACHED_ERRNO) {
466 string error = string("Memcache::replaceMemcache() SYSTEM ERROR: ") + string(strerror(memc->cached_errno));
468 throw IOException(error);
470 string error = string("Memcache::replaceMemcache() Problems: ") + memcached_strerror(memc, rv);
472 throw IOException(error);
478 MemcacheBase::MemcacheBase(const DOMElement* e) : m_root(e), log(Category::getInstance("XMLTooling.MemcacheBase")), m_prefix("") {
480 auto_ptr_char p(e ? e->getAttributeNS(nullptr,prefix) : nullptr);
481 if (p.get() && *p.get()) {
482 log.debug("INIT: GOT key prefix: %s", p.get());
486 m_lock = Mutex::create();
487 log.debug("Lock created");
489 memc = memcached_create(nullptr);
490 if (memc == nullptr) {
491 throw XMLToolingException("MemcacheBase::Memcache(): memcached_create() failed");
494 log.debug("Memcache created");
496 unsigned int hash = MEMCACHED_HASH_CRC;
497 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_HASH, hash);
498 log.debug("CRC hash set");
500 int32_t send_timeout = 999999;
501 const XMLCh* tag = e ? e->getAttributeNS(nullptr, sendTimeout) : nullptr;
503 send_timeout = XMLString::parseInt(tag);
505 log.debug("MEMCACHED_BEHAVIOR_SND_TIMEOUT will be set to %d", send_timeout);
506 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SND_TIMEOUT, send_timeout);
508 int32_t recv_timeout = 999999;
509 tag = e ? e->getAttributeNS(nullptr, sendTimeout) : nullptr;
511 recv_timeout = XMLString::parseInt(tag);
513 log.debug("MEMCACHED_BEHAVIOR_RCV_TIMEOUT will be set to %d", recv_timeout);
514 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_RCV_TIMEOUT, recv_timeout);
516 int32_t poll_timeout = 1000;
517 tag = e ? e->getAttributeNS(nullptr, pollTimeout) : nullptr;
519 poll_timeout = XMLString::parseInt(tag);
521 log.debug("MEMCACHED_BEHAVIOR_POLL_TIMEOUT will be set to %d", poll_timeout);
522 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_POLL_TIMEOUT, poll_timeout);
524 int32_t fail_limit = 5;
525 tag = e ? e->getAttributeNS(nullptr, failLimit) : nullptr;
527 fail_limit = XMLString::parseInt(tag);
529 log.debug("MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT will be set to %d", fail_limit);
530 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT, fail_limit);
532 int32_t retry_timeout = 30;
533 tag = e ? e->getAttributeNS(nullptr, retryTimeout) : nullptr;
535 retry_timeout = XMLString::parseInt(tag);
537 log.debug("MEMCACHED_BEHAVIOR_RETRY_TIMEOUT will be set to %d", retry_timeout);
538 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_RETRY_TIMEOUT, retry_timeout);
540 int32_t nonblock_set = 1;
541 tag = e ? e->getAttributeNS(nullptr, nonBlocking) : nullptr;
543 nonblock_set = XMLString::parseInt(tag);
545 log.debug("MEMCACHED_BEHAVIOR_NO_BLOCK will be set to %d", nonblock_set);
546 memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NO_BLOCK, nonblock_set);
548 // Grab hosts from the configuration.
549 e = e ? XMLHelper::getFirstChildElement(e,Hosts) : nullptr;
550 if (!e || !e->hasChildNodes()) {
551 throw XMLToolingException("Memcache StorageService requires Hosts element in configuration.");
553 auto_ptr_char h(e->getFirstChild()->getNodeValue());
554 log.debug("INIT: GOT Hosts: %s", h.get());
555 memcached_server_st *servers;
556 servers = memcached_servers_parse(const_cast<char*>(h.get()));
557 log.debug("Got %u hosts.", memcached_server_list_count(servers));
558 if (memcached_server_push(memc, servers) != MEMCACHED_SUCCESS) {
559 throw IOException("MemcacheBase::Memcache(): memcached_server_push() failed");
561 memcached_server_list_free(servers);
563 log.debug("Memcache object initialized");
566 MemcacheBase::~MemcacheBase() {
567 memcached_free(memc);
569 log.debug("Base object destroyed");
572 MemcacheStorageService::MemcacheStorageService(const DOMElement* e)
573 : MemcacheBase(e), m_log(Category::getInstance("XMLTooling.MemcacheStorageService")), m_buildMap(false) {
575 const XMLCh* tag=e ? e->getAttributeNS(nullptr,buildMap) : nullptr;
576 if (tag && *tag && XMLString::parseInt(tag) != 0) {
578 m_log.debug("Cache built with buildMap ON");
583 MemcacheStorageService::~MemcacheStorageService() {
588 bool MemcacheStorageService::createString(const char* context, const char* key, const char* value, time_t expiration) {
590 log.debug("createString ctx: %s - key: %s", context, key);
592 string final_key = string(context) + ":" + string(key);
594 mc_record rec(value, expiration);
596 serialize(rec, final_value);
598 bool result = addMemcache(final_key.c_str(), final_value, expiration, 1); // the flag will be the version
600 if (result && m_buildMap) {
601 log.debug("Got result, updating map");
603 string map_name = context;
604 // we need to update the context map
605 if (! addLock(map_name)) {
606 log.error("Unable to get lock for context %s!", context);
607 deleteMemcache(final_key.c_str(), 0);
613 bool result = getMemcache(map_name.c_str(), ser_arr, &flags);
615 list<string> contents;
617 log.debug("Match found. Parsing...");
619 deserialize(ser_arr, contents);
621 log.debug("Iterating retrieved session map...");
622 list<string>::iterator iter;
623 for(iter = contents.begin();
624 iter != contents.end();
626 log.debug("value = " + *iter);
630 log.debug("New context: %s", map_name.c_str());
634 contents.push_back(key);
635 serialize(contents, ser_arr);
636 setMemcache(map_name.c_str(), ser_arr, expiration, 0);
638 deleteLock(map_name);
645 int MemcacheStorageService::readString(const char* context, const char* key, string* pvalue, time_t* pexpiration, int version) {
647 log.debug("readString ctx: %s - key: %s", context, key);
649 string final_key = string(context) + ":" + string(key);
650 uint32_t rec_version;
654 log.debug("Checking context");
656 string map_name = context;
659 bool ctx_found = getMemcache(map_name.c_str(), ser_arr, &flags);
666 bool found = getMemcache(final_key.c_str(), value, &rec_version);
671 if (version && rec_version <= (uint32_t)version) {
675 if (pexpiration || pvalue) {
677 deserialize(value, rec);
680 *pexpiration = rec.expiration;
692 int MemcacheStorageService::updateString(const char* context, const char* key, const char* value, time_t expiration, int version) {
694 log.debug("updateString ctx: %s - key: %s", context, key);
696 time_t final_exp = expiration;
697 time_t *want_expiration = nullptr;
699 want_expiration = &final_exp;
702 int read_res = readString(context, key, nullptr, want_expiration, version);
709 if (version && version != read_res) {
714 // Proceding with update
715 string final_key = string(context) + ":" + string(key);
716 mc_record rec(value, final_exp);
718 serialize(rec, final_value);
720 replaceMemcache(final_key.c_str(), final_value, final_exp, ++version);
725 bool MemcacheStorageService::deleteString(const char* context, const char* key) {
727 log.debug("deleteString ctx: %s - key: %s", context, key);
729 string final_key = string(context) + ":" + string(key);
731 // Not updating context map, if there is one. There is no need.
733 return deleteMemcache(final_key.c_str(), 0);
737 void MemcacheStorageService::updateContext(const char* context, time_t expiration) {
739 log.debug("updateContext ctx: %s", context);
742 log.error("updateContext invoked on a Storage with no context map built!");
746 string map_name = context;
749 bool result = getMemcache(map_name.c_str(), ser_arr, &flags);
751 list<string> contents;
753 log.debug("Match found. Parsing...");
755 deserialize(ser_arr, contents);
757 log.debug("Iterating retrieved session map...");
758 list<string>::iterator iter;
759 for(iter = contents.begin();
760 iter != contents.end();
763 // Update expiration times
765 int read_res = readString(context, iter->c_str(), &value, nullptr, 0);
772 updateString(context, iter->c_str(), value.c_str(), expiration, read_res);
774 replaceMemcache(map_name.c_str(), ser_arr, expiration, flags);
779 void MemcacheStorageService::deleteContext(const char* context) {
781 log.debug("deleteContext ctx: %s", context);
784 log.error("deleteContext invoked on a Storage with no context map built!");
788 string map_name = context;
791 bool result = getMemcache(map_name.c_str(), ser_arr, &flags);
793 list<string> contents;
795 log.debug("Match found. Parsing...");
797 deserialize(ser_arr, contents);
799 log.debug("Iterating retrieved session map...");
800 list<string>::iterator iter;
801 for(iter = contents.begin();
802 iter != contents.end();
804 string final_key = map_name + *iter;
805 deleteMemcache(final_key.c_str(), 0);
808 deleteMemcache(map_name.c_str(), 0);
813 extern "C" int MCEXT_EXPORTS xmltooling_extension_init(void*) {
814 // Register this SS type
815 XMLToolingConfig::getConfig().StorageServiceManager.registerFactory("MEMCACHE", MemcacheStorageServiceFactory);
819 extern "C" void MCEXT_EXPORTS xmltooling_extension_term() {
820 XMLToolingConfig::getConfig().StorageServiceManager.deregisterFactory("MEMCACHE");