-/*\r
- * Copyright 2001-2009 Internet2\r
- * \r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- * http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-\r
-/**\r
- * memcache-store.cpp\r
- *\r
- * Storage Service using memcache (pre memcache tags)\r
- */\r
-\r
-#if defined (_MSC_VER) || defined(__BORLANDC__)\r
-# include "config_win32.h"\r
-#else\r
-# include "config.h"\r
-#endif\r
-\r
-#ifdef WIN32\r
-# define _CRT_NONSTDC_NO_DEPRECATE 1\r
-# define _CRT_SECURE_NO_DEPRECATE 1\r
-# define MCEXT_EXPORTS __declspec(dllexport)\r
-#else\r
-# define MCEXT_EXPORTS\r
-#endif\r
-\r
-#include <xmltooling/base.h>\r
-\r
-#include <list>\r
-#include <iostream> \r
-#include <libmemcached/memcached.h>\r
-#include <xercesc/util/XMLUniDefs.hpp>\r
-\r
-#include <xmltooling/logging.h>\r
-#include <xmltooling/XMLToolingConfig.h>\r
-#include <xmltooling/util/NDC.h>\r
-#include <xmltooling/util/StorageService.h>\r
-#include <xmltooling/util/Threads.h>\r
-#include <xmltooling/util/XMLHelper.h>\r
-\r
-using namespace xmltooling::logging;\r
-using namespace xmltooling;\r
-using namespace xercesc;\r
-using namespace std;\r
-\r
-namespace xmltooling {\r
- static const XMLCh Hosts[] = UNICODE_LITERAL_5(H,o,s,t,s);\r
- static const XMLCh prefix[] = UNICODE_LITERAL_6(p,r,e,f,i,x);\r
- static const XMLCh buildMap[] = UNICODE_LITERAL_8(b,u,i,l,d,M,a,p);\r
- \r
- class mc_record {\r
- public:\r
- string value;\r
- time_t expiration;\r
- mc_record(){};\r
- mc_record(string _v, time_t _e) :\r
- value(_v), expiration(_e)\r
- {}\r
- };\r
-\r
- class MemcacheBase {\r
- public:\r
- MemcacheBase(const DOMElement* e);\r
- ~MemcacheBase();\r
- \r
- bool addMemcache(const char *key,\r
- string &value,\r
- time_t timeout,\r
- uint32_t flags,\r
- bool use_prefix = true);\r
- bool setMemcache(const char *key,\r
- string &value,\r
- time_t timeout,\r
- uint32_t flags,\r
- bool use_prefix = true);\r
- bool replaceMemcache(const char *key,\r
- string &value,\r
- time_t timeout,\r
- uint32_t flags,\r
- bool use_prefix = true);\r
- bool getMemcache(const char *key,\r
- string &dest,\r
- uint32_t *flags,\r
- bool use_prefix = true);\r
- bool deleteMemcache(const char *key,\r
- time_t timeout,\r
- bool use_prefix = true);\r
-\r
- void serialize(mc_record &source, string &dest);\r
- void serialize(list<string> &source, string &dest);\r
- void deserialize(string &source, mc_record &dest);\r
- void deserialize(string &source, list<string> &dest);\r
-\r
- bool addSessionToUser(string &key, string &user);\r
- bool addLock(string what, bool use_prefix = true);\r
- void deleteLock(string what, bool use_prefix = true);\r
-\r
- protected:\r
- const DOMElement* m_root; // can only use this during initialization\r
- Category& log;\r
- memcached_st *memc;\r
- string m_memcacheHosts;\r
- string m_prefix;\r
- Mutex* m_lock;\r
- };\r
- \r
- class MemcacheStorageService : public StorageService, public MemcacheBase {\r
-\r
- public:\r
- MemcacheStorageService(const DOMElement* e);\r
- ~MemcacheStorageService();\r
- \r
- bool createString(const char* context, const char* key, const char* value, time_t expiration);\r
- int readString(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL, int version=0);\r
- int updateString(const char* context, const char* key, const char* value=NULL, time_t expiration=0, int version=0);\r
- bool deleteString(const char* context, const char* key);\r
- \r
- bool createText(const char* context, const char* key, const char* value, time_t expiration) {\r
- return createString(context, key, value, expiration);\r
- }\r
- int readText(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL, int version=0) {\r
- return readString(context, key, pvalue, pexpiration, version);\r
- }\r
- int updateText(const char* context, const char* key, const char* value=NULL, time_t expiration=0, int version=0) {\r
- return updateString(context, key, value, expiration, version);\r
- }\r
- bool deleteText(const char* context, const char* key) {\r
- return deleteString(context, key);\r
- }\r
- \r
- void reap(const char* context) {}\r
-\r
- void updateContext(const char* context, time_t expiration);\r
- void deleteContext(const char* context);\r
-\r
- private:\r
-\r
- Category& m_log;\r
- bool m_buildMap;\r
-\r
-\r
- };\r
-\r
- StorageService* MemcacheStorageServiceFactory(const DOMElement* const & e) {\r
- return new MemcacheStorageService(e);\r
- }\r
-\r
-};\r
-\r
-bool MemcacheBase::addLock(string what, bool use_prefix) {\r
- string lock_name = what + ":LOCK";\r
- string set_val = "1";\r
- unsigned tries = 5;\r
- while (!addMemcache(lock_name.c_str(), set_val, 5, 0, use_prefix)) {\r
- if (tries-- == 0) {\r
- log.debug("Unable to get lock %s... FAILED.", lock_name.c_str());\r
- return false;\r
- }\r
- log.debug("Unable to get lock %s... Retrying.", lock_name.c_str());\r
- \r
- // sleep 100ms\r
-#ifdef WIN32\r
- Sleep(100);\r
-#else\r
- struct timeval tv = { 0, 100000 };\r
- select(0, 0, 0, 0, &tv);\r
-#endif\r
- }\r
- return true;\r
-}\r
-\r
-void MemcacheBase::deleteLock(string what, bool use_prefix) {\r
-\r
- string lock_name = what + ":LOCK";\r
- deleteMemcache(lock_name.c_str(), 0, use_prefix);\r
- return;\r
-\r
-} \r
-\r
-void MemcacheBase::deserialize(string &source, mc_record &dest) {\r
- istringstream is(source, stringstream::in | stringstream::out);\r
- is >> dest.expiration;\r
- is.ignore(1); // ignore delimiter\r
- dest.value = is.str().c_str() + is.tellg();\r
-}\r
-\r
-void MemcacheBase::deserialize(string &source, list<string> &dest) {\r
- istringstream is(source, stringstream::in | stringstream::out);\r
- while (!is.eof()) {\r
- string s;\r
- is >> s;\r
- dest.push_back(s);\r
- } \r
-}\r
-\r
-void MemcacheBase::serialize(mc_record &source, string &dest) {\r
- ostringstream os(stringstream::in | stringstream::out);\r
- os << source.expiration;\r
- os << "-"; // delimiter\r
- os << source.value;\r
- dest = os.str();\r
-}\r
-\r
-void MemcacheBase::serialize(list<string> &source, string &dest) { \r
- ostringstream os(stringstream::in | stringstream::out);\r
- for(list<string>::iterator iter = source.begin(); iter != source.end(); iter++) {\r
- if (iter != source.begin()) {\r
- os << endl;\r
- }\r
- os << *iter;\r
- }\r
- dest = os.str();\r
-}\r
-\r
-bool MemcacheBase::addSessionToUser(string &key, string &user) {\r
-\r
- if (! addLock(user, false)) {\r
- return false;\r
- }\r
-\r
- // Aquired lock\r
-\r
- string sessid = m_prefix + key; // add specific prefix to session\r
- string delimiter = ";";\r
- string user_key = "UDATA:";\r
- user_key += user;\r
- string user_val;\r
- uint32_t flags;\r
- bool result = getMemcache(user_key.c_str(), user_val, &flags, false);\r
-\r
- if (result) {\r
- bool already_there = false;\r
- // skip delimiters at beginning.\r
- string::size_type lastPos = user_val.find_first_not_of(delimiter, 0);\r
- \r
- // find first "non-delimiter".\r
- string::size_type pos = user_val.find_first_of(delimiter, lastPos);\r
- \r
- while (string::npos != pos || string::npos != lastPos) {\r
- // found a token, add it to the vector.\r
- string session = user_val.substr(lastPos, pos - lastPos);\r
- if (strcmp(session.c_str(), sessid.c_str()) == 0) {\r
- already_there = true;\r
- break;\r
- }\r
- \r
- // skip delimiters. Note the "not_of"\r
- lastPos = user_val.find_first_not_of(delimiter, pos);\r
- \r
- // find next "non-delimiter"\r
- pos = user_val.find_first_of(delimiter, lastPos);\r
- }\r
- \r
- if (!already_there) {\r
- user_val += delimiter + sessid;\r
- replaceMemcache(user_key.c_str(), user_val, 0, 0, false);\r
- }\r
- } else {\r
- addMemcache(user_key.c_str(), sessid, 0, 0, false);\r
- }\r
-\r
- deleteLock(user, false);\r
- return true;\r
- \r
-}\r
-\r
-bool MemcacheBase::deleteMemcache(const char *key,\r
- time_t timeout,\r
- bool use_prefix) {\r
- memcached_return rv;\r
- string final_key;\r
- bool success;\r
-\r
- if (use_prefix) {\r
- final_key = m_prefix + key;\r
- } else {\r
- final_key = key;\r
- }\r
-\r
- m_lock->lock();\r
- rv = memcached_delete(memc, (char *)final_key.c_str(), final_key.length(), timeout);\r
- m_lock->unlock();\r
-\r
- if (rv == MEMCACHED_SUCCESS) {\r
- success = true;\r
- } else if (rv == MEMCACHED_NOTFOUND) {\r
- // Key wasn't there... No biggie.\r
- success = false;\r
- } else if (rv == MEMCACHED_ERRNO) {\r
- // System error\r
- log.error(string("Memcache::deleteMemcache() SYSTEM ERROR: ") + string(strerror(memc->cached_errno)));\r
- success = false;\r
- } else {\r
- log.error(string("Memcache::deleteMemcache() Problems: ") + memcached_strerror(memc, rv));\r
- // shouldn't be here\r
- success = false;\r
- }\r
-\r
- return success;\r
-}\r
-\r
-bool MemcacheBase::getMemcache(const char *key,\r
- string &dest,\r
- uint32_t *flags,\r
- bool use_prefix) {\r
- memcached_return rv;\r
- size_t len;\r
- char *result;\r
- string final_key;\r
- bool success;\r
- \r
- if (use_prefix) {\r
- final_key = m_prefix + key;\r
- } else {\r
- final_key = key;\r
- }\r
-\r
- m_lock->lock();\r
- result = memcached_get(memc, (char *)final_key.c_str(), final_key.length(), &len, flags, &rv);\r
- m_lock->unlock();\r
-\r
- if (rv == MEMCACHED_SUCCESS) {\r
- dest = result;\r
- free(result);\r
- success = true;\r
- } else if (rv == MEMCACHED_NOTFOUND) {\r
- log.debug("Key %s not found in memcache...", key);\r
- success = false;\r
- } else if (rv == MEMCACHED_ERRNO) {\r
- // System error\r
- log.error(string("Memcache::getMemcache() SYSTEM ERROR: ") + string(strerror(memc->cached_errno)));\r
- success = false;\r
- } else {\r
- log.error(string("Memcache::getMemcache() Problems: ") + memcached_strerror(memc, rv));\r
- success = false;\r
- }\r
-\r
- return success;\r
-}\r
-\r
-bool MemcacheBase::addMemcache(const char *key,\r
- string &value,\r
- time_t timeout,\r
- uint32_t flags,\r
- bool use_prefix) {\r
-\r
- memcached_return rv;\r
- string final_key;\r
- bool success;\r
-\r
- if (use_prefix) {\r
- final_key = m_prefix + key;\r
- } else {\r
- final_key = key;\r
- }\r
-\r
- m_lock->lock();\r
- rv = memcached_add(memc, (char *)final_key.c_str(), final_key.length(), (char *)value.c_str(), value.length(), timeout, flags);\r
- m_lock->unlock();\r
-\r
- if (rv == MEMCACHED_SUCCESS) {\r
- success = true;\r
- } else if (rv == MEMCACHED_NOTSTORED) {\r
- // already there\r
- success = false;\r
- } else if (rv == MEMCACHED_ERRNO) {\r
- // System error\r
- log.error(string("Memcache::addMemcache() SYSTEM ERROR: ") + string(strerror(memc->cached_errno)));\r
- success = false;\r
- } else {\r
- // shouldn't be here\r
- log.error(string("Memcache::addMemcache() Problems: ") + memcached_strerror(memc, rv));\r
- success = false;\r
- }\r
-\r
- return success;\r
-}\r
-\r
-bool MemcacheBase::setMemcache(const char *key,\r
- string &value,\r
- time_t timeout,\r
- uint32_t flags,\r
- bool use_prefix) {\r
-\r
- memcached_return rv;\r
- string final_key;\r
- bool success;\r
-\r
- if (use_prefix) {\r
- final_key = m_prefix + key;\r
- } else {\r
- final_key = key;\r
- }\r
-\r
- m_lock->lock();\r
- rv = memcached_set(memc, (char *)final_key.c_str(), final_key.length(), (char *)value.c_str(), value.length(), timeout, flags);\r
- m_lock->unlock();\r
-\r
- if (rv == MEMCACHED_SUCCESS) {\r
- success = true;\r
- } else if (rv == MEMCACHED_ERRNO) {\r
- // System error\r
- log.error(string("Memcache::setMemcache() SYSTEM ERROR: ") + string(strerror(memc->cached_errno)));\r
- success = false;\r
- } else {\r
- // shouldn't be here\r
- log.error(string("Memcache::setMemcache() Problems: ") + memcached_strerror(memc, rv));\r
- success = false;\r
- }\r
-\r
- return success;\r
-}\r
-\r
-bool MemcacheBase::replaceMemcache(const char *key,\r
- string &value,\r
- time_t timeout,\r
- uint32_t flags,\r
- bool use_prefix) {\r
- \r
- memcached_return rv;\r
- string final_key;\r
- bool success;\r
-\r
- if (use_prefix) {\r
- final_key = m_prefix + key;\r
- } else {\r
- final_key = key;\r
- }\r
-\r
- m_lock->lock();\r
- rv = memcached_replace(memc, (char *)final_key.c_str(), final_key.length(), (char *)value.c_str(), value.length(), timeout, flags);\r
- m_lock->unlock();\r
-\r
- if (rv == MEMCACHED_SUCCESS) {\r
- success = true;\r
- } else if (rv == MEMCACHED_NOTSTORED) {\r
- // not there\r
- success = false;\r
- } else if (rv == MEMCACHED_ERRNO) {\r
- // System error\r
- log.error(string("Memcache::replaceMemcache() SYSTEM ERROR: ") + string(strerror(memc->cached_errno)));\r
- success = false;\r
- } else {\r
- // shouldn't be here\r
- log.error(string("Memcache::replaceMemcache() Problems: ") + memcached_strerror(memc, rv));\r
- success = false;\r
- }\r
-\r
- return success;\r
-}\r
-\r
-MemcacheBase::MemcacheBase(const DOMElement* e) : m_root(e), log(Category::getInstance("XMLTooling.MemcacheBase")), m_memcacheHosts(""), m_prefix("") {\r
-\r
- auto_ptr_char p(e ? e->getAttributeNS(NULL,prefix) : NULL);\r
- if (p.get() && *p.get()) {\r
- log.debug("INIT: GOT key prefix: %s", p.get());\r
- m_prefix = p.get();\r
- }\r
-\r
- // Grab hosts from the configuration.\r
- e = e ? XMLHelper::getFirstChildElement(e,Hosts) : NULL;\r
- if (!e || !e->hasChildNodes()) {\r
- throw XMLToolingException("Memcache StorageService requires Hosts element in configuration.");\r
- }\r
- auto_ptr_char h(e->getFirstChild()->getNodeValue());\r
- log.debug("INIT: GOT Hosts: %s", h.get());\r
- m_memcacheHosts = h.get();\r
-\r
- m_lock = Mutex::create();\r
- log.debug("Lock created");\r
-\r
- memc = memcached_create(NULL);\r
- if (memc == NULL) {\r
- throw XMLToolingException("MemcacheBase::Memcache(): memcached_create() failed");\r
- }\r
-\r
- log.debug("Memcache created");\r
-\r
- unsigned int hash = MEMCACHED_HASH_CRC;\r
- memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_HASH, hash);\r
- log.debug("CRC hash set");\r
-\r
- int32_t timeout = 1000000;\r
- memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SND_TIMEOUT, timeout);\r
- memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_RCV_TIMEOUT, timeout);\r
-\r
- int32_t poll_timeout = 1000;\r
- memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_POLL_TIMEOUT, poll_timeout);\r
-\r
- int32_t fail_limit = 5;\r
- memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT, fail_limit);\r
-\r
- int32_t retry_timeout = 30;\r
- memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_RETRY_TIMEOUT, retry_timeout);\r
-\r
- memcached_server_st *servers;\r
- servers = memcached_servers_parse((char *)m_memcacheHosts.c_str());\r
- log.debug("Got %u hosts.", memcached_server_list_count(servers));\r
- if (memcached_server_push(memc, servers) != MEMCACHED_SUCCESS) {\r
- throw IOException("MemcacheBase::Memcache(): memcached_server_push() failed"); \r
- }\r
- memcached_server_list_free(servers);\r
-\r
- log.debug("Memcache object initialized");\r
-}\r
-\r
-MemcacheBase::~MemcacheBase() {\r
- memcached_free(memc);\r
- delete m_lock;\r
- log.debug("Base object destroyed");\r
-}\r
-\r
-MemcacheStorageService::MemcacheStorageService(const DOMElement* e)\r
- : MemcacheBase(e), m_log(Category::getInstance("XMLTooling.MemcacheStorageService")), m_buildMap(false) {\r
-\r
- const XMLCh* tag=e ? e->getAttributeNS(NULL,buildMap) : NULL;\r
- if (tag && *tag && XMLString::parseInt(tag) != 0) {\r
- m_buildMap = true;\r
- m_log.debug("Cache built with buildMap ON");\r
- }\r
-\r
-}\r
-\r
-MemcacheStorageService::~MemcacheStorageService() {\r
-\r
- \r
-}\r
-\r
-bool MemcacheStorageService::createString(const char* context, const char* key, const char* value, time_t expiration) {\r
-\r
- log.debug("createString ctx: %s - key: %s", context, key);\r
-\r
- string final_key = string(context) + ":" + string(key);\r
-\r
- mc_record rec(value, expiration);\r
- string final_value;\r
- serialize(rec, final_value);\r
-\r
- bool result = addMemcache(final_key.c_str(), final_value, expiration, 1); // the flag will be the version\r
-\r
- if (result && m_buildMap) {\r
- log.debug("Got result, updating map");\r
-\r
- string map_name = context;\r
- // we need to update the context map\r
- if (! addLock(map_name)) {\r
- log.error("Unable to get lock for context %s!", context);\r
- deleteMemcache(final_key.c_str(), 0);\r
- return false;\r
- }\r
-\r
- string ser_arr;\r
- uint32_t flags;\r
- bool result = getMemcache(map_name.c_str(), ser_arr, &flags);\r
- \r
- list<string> contents;\r
- if (result) {\r
- log.debug("Match found. Parsing...");\r
-\r
- deserialize(ser_arr, contents);\r
- \r
- log.debug("Iterating retrieved session map...");\r
- list<string>::iterator iter;\r
- for(iter = contents.begin(); \r
- iter != contents.end();\r
- iter++) {\r
- log.debug("value = " + *iter);\r
- }\r
-\r
- } else {\r
- log.debug("New context: %s", map_name.c_str());\r
-\r
- }\r
-\r
- contents.push_back(key);\r
- serialize(contents, ser_arr); \r
- setMemcache(map_name.c_str(), ser_arr, expiration, 0); \r
- \r
- deleteLock(map_name);\r
- }\r
-\r
- return result; \r
-\r
-}\r
-\r
-int MemcacheStorageService::readString(const char* context, const char* key, string* pvalue, time_t* pexpiration, int version) {\r
-\r
- log.debug("readString ctx: %s - key: %s", context, key);\r
-\r
- string final_key = string(context) + ":" + string(key);\r
- uint32_t rec_version;\r
- string value;\r
-\r
- if (m_buildMap) {\r
- log.debug("Checking context");\r
-\r
- string map_name = context;\r
- string ser_arr;\r
- uint32_t flags;\r
- bool ctx_found = getMemcache(map_name.c_str(), ser_arr, &flags);\r
-\r
- if (!ctx_found) {\r
- return 0;\r
- }\r
- }\r
-\r
- bool found = getMemcache(final_key.c_str(), value, &rec_version);\r
- if (!found) {\r
- return 0;\r
- }\r
-\r
- if (version && rec_version <= (uint32_t)version) {\r
- return version;\r
- }\r
-\r
- if (pexpiration || pvalue) {\r
- mc_record rec;\r
- deserialize(value, rec);\r
- \r
- if (pexpiration) {\r
- *pexpiration = rec.expiration;\r
- }\r
- \r
- if (pvalue) {\r
- *pvalue = rec.value;\r
- }\r
- }\r
- \r
- return rec_version;\r
-\r
-}\r
-\r
-int MemcacheStorageService::updateString(const char* context, const char* key, const char* value, time_t expiration, int version) {\r
-\r
- log.debug("updateString ctx: %s - key: %s", context, key);\r
-\r
- time_t final_exp = expiration;\r
- time_t *want_expiration = NULL;\r
- if (! final_exp) {\r
- want_expiration = &final_exp;\r
- }\r
-\r
- int read_res = readString(context, key, NULL, want_expiration, version);\r
-\r
- if (!read_res) {\r
- // not found\r
- return read_res;\r
- }\r
-\r
- if (version && version != read_res) {\r
- // version incorrect\r
- return -1;\r
- }\r
-\r
- // Proceding with update\r
- string final_key = string(context) + ":" + string(key);\r
- mc_record rec(value, final_exp);\r
- string final_value;\r
- serialize(rec, final_value);\r
-\r
- replaceMemcache(final_key.c_str(), final_value, final_exp, ++version);\r
- return version;\r
-\r
-}\r
-\r
-bool MemcacheStorageService::deleteString(const char* context, const char* key) {\r
-\r
- log.debug("deleteString ctx: %s - key: %s", context, key);\r
- \r
- string final_key = string(context) + ":" + string(key);\r
-\r
- // Not updating context map, if there is one. There is no need.\r
-\r
- return deleteMemcache(final_key.c_str(), 0);\r
-\r
-}\r
-\r
-void MemcacheStorageService::updateContext(const char* context, time_t expiration) {\r
-\r
- log.debug("updateContext ctx: %s", context);\r
-\r
- if (!m_buildMap) {\r
- log.error("updateContext invoked on a Storage with no context map built!");\r
- return;\r
- }\r
-\r
- string map_name = context;\r
- string ser_arr;\r
- uint32_t flags;\r
- bool result = getMemcache(map_name.c_str(), ser_arr, &flags);\r
- \r
- list<string> contents;\r
- if (result) {\r
- log.debug("Match found. Parsing...");\r
- \r
- deserialize(ser_arr, contents);\r
- \r
- log.debug("Iterating retrieved session map...");\r
- list<string>::iterator iter;\r
- for(iter = contents.begin(); \r
- iter != contents.end();\r
- iter++) {\r
-\r
- // Update expiration times\r
- string value; \r
- int read_res = readString(context, iter->c_str(), &value, NULL, 0);\r
- \r
- if (!read_res) {\r
- // not found\r
- continue;\r
- }\r
-\r
- updateString(context, iter->c_str(), value.c_str(), expiration, read_res);\r
- }\r
- replaceMemcache(map_name.c_str(), ser_arr, expiration, flags);\r
- }\r
- \r
-}\r
-\r
-void MemcacheStorageService::deleteContext(const char* context) {\r
-\r
- log.debug("deleteContext ctx: %s", context);\r
-\r
- if (!m_buildMap) {\r
- log.error("deleteContext invoked on a Storage with no context map built!");\r
- return;\r
- }\r
-\r
- string map_name = context;\r
- string ser_arr;\r
- uint32_t flags;\r
- bool result = getMemcache(map_name.c_str(), ser_arr, &flags);\r
- \r
- list<string> contents;\r
- if (result) {\r
- log.debug("Match found. Parsing...");\r
- \r
- deserialize(ser_arr, contents);\r
- \r
- log.debug("Iterating retrieved session map...");\r
- list<string>::iterator iter;\r
- for(iter = contents.begin(); \r
- iter != contents.end();\r
- iter++) {\r
- string final_key = map_name + *iter;\r
- deleteMemcache(final_key.c_str(), 0);\r
- }\r
- \r
- deleteMemcache(map_name.c_str(), 0);\r
- }\r
- \r
-}\r
-\r
-extern "C" int MCEXT_EXPORTS xmltooling_extension_init(void*) {\r
- // Register this SS type\r
- XMLToolingConfig::getConfig().StorageServiceManager.registerFactory("MEMCACHE", MemcacheStorageServiceFactory);\r
- return 0;\r
-}\r
-\r
-extern "C" void MCEXT_EXPORTS xmltooling_extension_term() {\r
- XMLToolingConfig::getConfig().StorageServiceManager.deregisterFactory("MEMCACHE");\r
-}\r
+/**
+ * Licensed to the University Corporation for Advanced Internet
+ * Development, Inc. (UCAID) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ *
+ * UCAID licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the
+ * License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific
+ * language governing permissions and limitations under the License.
+ */
+
+/**
+ * memcache-store.cpp
+ *
+ * Storage Service using memcache (pre memcache tags).
+ */
+
+#if defined (_MSC_VER) || defined(__BORLANDC__)
+# include "config_win32.h"
+#else
+# include "config.h"
+#endif
+
+#ifdef WIN32
+# define _CRT_NONSTDC_NO_DEPRECATE 1
+# define _CRT_SECURE_NO_DEPRECATE 1
+# define MCEXT_EXPORTS __declspec(dllexport)
+#else
+# define MCEXT_EXPORTS
+#endif
+
+#include <xmltooling/base.h>
+
+#include <list>
+#include <iostream>
+#include <libmemcached/memcached.h>
+#include <xercesc/util/XMLUniDefs.hpp>
+
+#include <xmltooling/logging.h>
+#include <xmltooling/unicode.h>
+#include <xmltooling/XMLToolingConfig.h>
+#include <xmltooling/util/NDC.h>
+#include <xmltooling/util/StorageService.h>
+#include <xmltooling/util/Threads.h>
+#include <xmltooling/util/XMLHelper.h>
+
+using namespace xmltooling::logging;
+using namespace xmltooling;
+using namespace xercesc;
+using namespace boost;
+using namespace std;
+
+namespace {
+ static const XMLCh Hosts[] = UNICODE_LITERAL_5(H,o,s,t,s);
+ static const XMLCh prefix[] = UNICODE_LITERAL_6(p,r,e,f,i,x);
+ static const XMLCh buildMap[] = UNICODE_LITERAL_8(b,u,i,l,d,M,a,p);
+ static const XMLCh sendTimeout[] = UNICODE_LITERAL_11(s,e,n,d,T,i,m,e,o,u,t);
+ static const XMLCh recvTimeout[] = UNICODE_LITERAL_11(r,e,c,v,T,i,m,e,o,u,t);
+ static const XMLCh pollTimeout[] = UNICODE_LITERAL_11(p,o,l,l,T,i,m,e,o,u,t);
+ static const XMLCh failLimit[] = UNICODE_LITERAL_9(f,a,i,l,L,i,m,i,t);
+ static const XMLCh retryTimeout[] = UNICODE_LITERAL_12(r,e,t,r,y,T,i,m,e,o,u,t);
+ static const XMLCh nonBlocking[] = UNICODE_LITERAL_11(n,o,n,B,l,o,c,k,i,n,g);
+
+ class mc_record {
+ public:
+ string value;
+ time_t expiration;
+ mc_record() {};
+ mc_record(string _v, time_t _e) : value(_v), expiration(_e) {}
+ };
+
+ class MemcacheBase {
+ public:
+ MemcacheBase(const DOMElement* e);
+ ~MemcacheBase();
+
+ bool addMemcache(const char* key, string &value, time_t timeout, uint32_t flags, bool use_prefix = true);
+ bool setMemcache(const char* key, string &value, time_t timeout, uint32_t flags, bool use_prefix = true);
+ bool replaceMemcache(const char* key, string &value, time_t timeout, uint32_t flags, bool use_prefix = true);
+ bool getMemcache(const char* key, string &dest, uint32_t *flags, bool use_prefix = true);
+ bool deleteMemcache(const char* key, time_t timeout, bool use_prefix = true);
+
+ void serialize(mc_record &source, string &dest);
+ void serialize(list<string> &source, string &dest);
+ void deserialize(string &source, mc_record &dest);
+ void deserialize(string &source, list<string> &dest);
+
+ bool addLock(string what, bool use_prefix = true);
+ void deleteLock(string what, bool use_prefix = true);
+
+ protected:
+ Category& m_log;
+ memcached_st* memc;
+ string m_prefix;
+ scoped_ptr<Mutex> m_lock;
+
+ private:
+ bool handleError(const char*, memcached_return) const;
+ };
+
+ class MemcacheStorageService : public StorageService, public MemcacheBase {
+
+ public:
+ MemcacheStorageService(const DOMElement* e);
+ ~MemcacheStorageService() {}
+
+ const Capabilities& getCapabilities() const {
+ return m_caps;
+ }
+
+ bool createString(const char* context, const char* key, const char* value, time_t expiration);
+ int readString(const char* context, const char* key, string* pvalue=nullptr, time_t* pexpiration=nullptr, int version=0);
+ int updateString(const char* context, const char* key, const char* value=nullptr, time_t expiration=0, int version=0);
+ bool deleteString(const char* context, const char* key);
+
+ bool createText(const char* context, const char* key, const char* value, time_t expiration) {
+ return createString(context, key, value, expiration);
+ }
+ int readText(const char* context, const char* key, string* pvalue=nullptr, time_t* pexpiration=nullptr, int version=0) {
+ return readString(context, key, pvalue, pexpiration, version);
+ }
+ int updateText(const char* context, const char* key, const char* value=nullptr, time_t expiration=0, int version=0) {
+ return updateString(context, key, value, expiration, version);
+ }
+ bool deleteText(const char* context, const char* key) {
+ return deleteString(context, key);
+ }
+
+ void reap(const char* context) {}
+
+ void updateContext(const char* context, time_t expiration);
+ void deleteContext(const char* context);
+
+ private:
+ Capabilities m_caps;
+ bool m_buildMap;
+ };
+
+ StorageService* MemcacheStorageServiceFactory(const DOMElement* const & e) {
+ return new MemcacheStorageService(e);
+ }
+};
+
+MemcacheBase::MemcacheBase(const DOMElement* e)
+ : m_log(Category::getInstance("XMLTooling.StorageService.MEMCACHE")), memc(nullptr),
+ m_prefix(XMLHelper::getAttrString(e, nullptr, prefix)), m_lock(Mutex::create())
+{
+ memc = memcached_create(nullptr);
+ if (!memc)
+ throw XMLToolingException("MemcacheBase::Memcache(): memcached_create() failed");
+ m_log.debug("Memcache created");
+
+ memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_HASH, MEMCACHED_HASH_CRC);
+ m_log.debug("CRC hash set");
+
+ int prop = XMLHelper::getAttrInt(e, 999999, sendTimeout);
+ m_log.debug("MEMCACHED_BEHAVIOR_SND_TIMEOUT will be set to %d", prop);
+ memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SND_TIMEOUT, prop);
+
+ prop = XMLHelper::getAttrInt(e, 999999, recvTimeout);
+ m_log.debug("MEMCACHED_BEHAVIOR_RCV_TIMEOUT will be set to %d", prop);
+ memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_RCV_TIMEOUT, prop);
+
+ prop = XMLHelper::getAttrInt(e, 1000, pollTimeout);
+ m_log.debug("MEMCACHED_BEHAVIOR_POLL_TIMEOUT will be set to %d", prop);
+ memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_POLL_TIMEOUT, prop);
+
+ prop = XMLHelper::getAttrInt(e, 5, failLimit);
+ m_log.debug("MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT will be set to %d", prop);
+ memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT, prop);
+
+ prop = XMLHelper::getAttrInt(e, 30, retryTimeout);
+ m_log.debug("MEMCACHED_BEHAVIOR_RETRY_TIMEOUT will be set to %d", prop);
+ memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_RETRY_TIMEOUT, prop);
+
+ prop = XMLHelper::getAttrInt(e, 1, nonBlocking);
+ m_log.debug("MEMCACHED_BEHAVIOR_NO_BLOCK will be set to %d", prop);
+ memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NO_BLOCK, prop);
+
+ // Grab hosts from the configuration.
+ e = e ? XMLHelper::getFirstChildElement(e, Hosts) : nullptr;
+ if (!e || !e->hasChildNodes()) {
+ memcached_free(memc);
+ throw XMLToolingException("Memcache StorageService requires Hosts element in configuration.");
+ }
+ auto_ptr_char h(e->getTextContent());
+ m_log.debug("INIT: GOT Hosts: %s", h.get());
+ memcached_server_st* servers;
+ servers = memcached_servers_parse(const_cast<char*>(h.get()));
+ m_log.debug("Got %u hosts.", memcached_server_list_count(servers));
+ if (memcached_server_push(memc, servers) != MEMCACHED_SUCCESS) {
+ memcached_server_list_free(servers);
+ memcached_free(memc);
+ throw IOException("MemcacheBase: memcached_server_push() failed");
+ }
+ memcached_server_list_free(servers);
+
+ m_log.debug("Memcache object initialized");
+}
+
+MemcacheBase::~MemcacheBase()
+{
+ memcached_free(memc);
+ m_log.debug("Base object destroyed");
+}
+
+
+bool MemcacheBase::handleError(const char* fn, memcached_return rv) const
+{
+#ifdef HAVE_MEMCACHED_LAST_ERROR_MESSAGE
+ string error = string("Memcache::") + fn + ": " + memcached_last_error_message(memc);
+#else
+ string error;
+ if (rv == MEMCACHED_ERRNO) {
+ // System error
+ error = string("Memcache::") + fn + "SYSTEM ERROR: " + strerror(memc->cached_errno);
+ }
+ else {
+ error = string("Memcache::") + fn + " Problems: " + memcached_strerror(memc, rv);
+ }
+#endif
+ m_log.error(error);
+ throw IOException(error);
+}
+
+bool MemcacheBase::addLock(string what, bool use_prefix)
+{
+ string lock_name = what + ":LOCK";
+ string set_val = "1";
+ unsigned tries = 5;
+ while (!addMemcache(lock_name.c_str(), set_val, 5, 0, use_prefix)) {
+ if (tries-- == 0) {
+ m_log.debug("Unable to get lock %s... FAILED.", lock_name.c_str());
+ return false;
+ }
+ m_log.debug("Unable to get lock %s... Retrying.", lock_name.c_str());
+
+ // sleep 100ms
+#ifdef WIN32
+ Sleep(100);
+#else
+ struct timeval tv = { 0, 100000 };
+ select(0, 0, 0, 0, &tv);
+#endif
+ }
+ return true;
+}
+
+void MemcacheBase::deleteLock(string what, bool use_prefix)
+{
+ string lock_name = what + ":LOCK";
+ deleteMemcache(lock_name.c_str(), 0, use_prefix);
+ return;
+
+}
+
+void MemcacheBase::deserialize(string& source, mc_record& dest)
+{
+ istringstream is(source, stringstream::in | stringstream::out);
+ is >> dest.expiration;
+ is.ignore(1); // ignore delimiter
+ dest.value = is.str().c_str() + is.tellg();
+}
+
+void MemcacheBase::deserialize(string& source, list<string>& dest)
+{
+ istringstream is(source, stringstream::in | stringstream::out);
+ while (!is.eof()) {
+ string s;
+ is >> s;
+ dest.push_back(s);
+ }
+}
+
+void MemcacheBase::serialize(mc_record& source, string& dest)
+{
+ ostringstream os(stringstream::in | stringstream::out);
+ os << source.expiration;
+ os << "-"; // delimiter
+ os << source.value;
+ dest = os.str();
+}
+
+void MemcacheBase::serialize(list<string>& source, string& dest)
+{
+ ostringstream os(stringstream::in | stringstream::out);
+ for(list<string>::iterator iter = source.begin(); iter != source.end(); iter++) {
+ if (iter != source.begin()) {
+ os << endl;
+ }
+ os << *iter;
+ }
+ dest = os.str();
+}
+
+bool MemcacheBase::deleteMemcache(const char* key, time_t timeout, bool use_prefix)
+{
+ string final_key;
+ if (use_prefix)
+ final_key = m_prefix + key;
+ else
+ final_key = key;
+
+ Lock lock(m_lock);
+ memcached_return rv = memcached_delete(memc, const_cast<char*>(final_key.c_str()), final_key.length(), timeout);
+
+ switch (rv) {
+ case MEMCACHED_SUCCESS:
+ return true;
+ case MEMCACHED_NOTFOUND:
+ // Key wasn't there... No biggie.
+ return false;
+ default:
+ return handleError("deleteMemcache", rv);
+ }
+}
+
+bool MemcacheBase::getMemcache(const char* key, string& dest, uint32_t* flags, bool use_prefix)
+{
+ string final_key;
+ if (use_prefix)
+ final_key = m_prefix + key;
+ else
+ final_key = key;
+
+ Lock lock(m_lock);
+ size_t len;
+ memcached_return rv;
+ char* result = memcached_get(memc, const_cast<char*>(final_key.c_str()), final_key.length(), &len, flags, &rv);
+
+ switch (rv) {
+ case MEMCACHED_SUCCESS:
+ dest = result;
+ free(result);
+ return true;
+ case MEMCACHED_NOTFOUND:
+ m_log.debug("Key %s not found in memcache...", key);
+ return false;
+ default:
+ return handleError("getMemcache", rv);
+ }
+}
+
+bool MemcacheBase::addMemcache(const char* key, string& value, time_t timeout, uint32_t flags, bool use_prefix)
+{
+ string final_key;
+ if (use_prefix)
+ final_key = m_prefix + key;
+ else
+ final_key = key;
+
+ Lock lock(m_lock);
+ memcached_return rv = memcached_add(
+ memc, const_cast<char*>(final_key.c_str()), final_key.length(), const_cast<char*>(value.c_str()), value.length(), timeout, flags
+ );
+
+ switch (rv) {
+ case MEMCACHED_SUCCESS:
+ return true;
+ case MEMCACHED_NOTSTORED:
+ return false;
+ default:
+ return handleError("addMemcache", rv);
+ }
+}
+
+bool MemcacheBase::setMemcache(const char* key, string& value, time_t timeout, uint32_t flags, bool use_prefix)
+{
+ string final_key;
+ if (use_prefix)
+ final_key = m_prefix + key;
+ else
+ final_key = key;
+
+ Lock lock(m_lock);
+ memcached_return rv = memcached_set(
+ memc, const_cast<char*>(final_key.c_str()), final_key.length(), const_cast<char*>(value.c_str()), value.length(), timeout, flags
+ );
+
+ if (rv == MEMCACHED_SUCCESS)
+ return true;
+ return handleError("setMemcache", rv);
+}
+
+bool MemcacheBase::replaceMemcache(const char* key, string& value, time_t timeout, uint32_t flags, bool use_prefix)
+{
+
+ string final_key;
+ if (use_prefix)
+ final_key = m_prefix + key;
+ else
+ final_key = key;
+
+ Lock lock(m_lock);
+ memcached_return rv = memcached_replace(
+ memc, const_cast<char*>(final_key.c_str()), final_key.length(), const_cast<char*>(value.c_str()), value.length(), timeout, flags
+ );
+
+ switch (rv) {
+ case MEMCACHED_SUCCESS:
+ return true;
+ case MEMCACHED_NOTSTORED:
+ // not there
+ return false;
+ default:
+ return handleError("replaceMemcache", rv);
+ }
+}
+
+
+MemcacheStorageService::MemcacheStorageService(const DOMElement* e)
+ : MemcacheBase(e), m_caps(80, 250 - m_prefix.length() - 1 - 80, 255),
+ m_buildMap(XMLHelper::getAttrBool(e, false, buildMap))
+{
+ if (m_buildMap)
+ m_log.debug("Cache built with buildMap ON");
+}
+
+bool MemcacheStorageService::createString(const char* context, const char* key, const char* value, time_t expiration)
+{
+ m_log.debug("createString ctx: %s - key: %s", context, key);
+
+ string final_key = string(context) + ":" + string(key);
+
+ mc_record rec(value, expiration);
+ string final_value;
+ serialize(rec, final_value);
+
+ bool result = addMemcache(final_key.c_str(), final_value, expiration, 1); // the flag will be the version
+
+ if (result && m_buildMap) {
+ m_log.debug("Got result, updating map");
+
+ string map_name = context;
+ // we need to update the context map
+ if (!addLock(map_name)) {
+ m_log.error("Unable to get lock for context %s!", context);
+ deleteMemcache(final_key.c_str(), 0);
+ return false;
+ }
+
+ string ser_arr;
+ uint32_t flags;
+ bool result = getMemcache(map_name.c_str(), ser_arr, &flags);
+
+ list<string> contents;
+ if (result) {
+ m_log.debug("Match found. Parsing...");
+ deserialize(ser_arr, contents);
+ if (m_log.isDebugEnabled()) {
+ m_log.debug("Iterating retrieved session map...");
+ for(list<string>::const_iterator iter = contents.begin(); iter != contents.end(); ++iter)
+ m_log.debug("value = %s", iter->c_str());
+ }
+ }
+ else {
+ m_log.debug("New context: %s", map_name.c_str());
+ }
+
+ contents.push_back(key);
+ serialize(contents, ser_arr);
+ setMemcache(map_name.c_str(), ser_arr, expiration, 0);
+ deleteLock(map_name);
+ }
+ return result;
+}
+
+int MemcacheStorageService::readString(const char* context, const char* key, string* pvalue, time_t* pexpiration, int version)
+{
+ m_log.debug("readString ctx: %s - key: %s", context, key);
+
+ string final_key = string(context) + ":" + string(key);
+ uint32_t rec_version;
+ string value;
+
+ if (m_buildMap) {
+ m_log.debug("Checking context");
+ string map_name = context;
+ string ser_arr;
+ uint32_t flags;
+ bool ctx_found = getMemcache(map_name.c_str(), ser_arr, &flags);
+ if (!ctx_found)
+ return 0;
+ }
+
+ bool found = getMemcache(final_key.c_str(), value, &rec_version);
+ if (!found)
+ return 0;
+
+ if (version && rec_version <= (uint32_t)version)
+ return version;
+
+ if (pexpiration || pvalue) {
+ mc_record rec;
+ deserialize(value, rec);
+
+ if (pexpiration)
+ *pexpiration = rec.expiration;
+
+ if (pvalue)
+ *pvalue = rec.value;
+ }
+
+ return rec_version;
+}
+
+int MemcacheStorageService::updateString(const char* context, const char* key, const char* value, time_t expiration, int version)
+{
+ m_log.debug("updateString ctx: %s - key: %s", context, key);
+
+ time_t final_exp = expiration;
+ time_t* want_expiration = nullptr;
+ if (!final_exp)
+ want_expiration = &final_exp;
+
+ int read_res = readString(context, key, nullptr, want_expiration, version);
+
+ if (!read_res) {
+ // not found
+ return read_res;
+ }
+
+ if (version && version != read_res) {
+ // version incorrect
+ return -1;
+ }
+
+ // Proceding with update
+ string final_key = string(context) + ":" + string(key);
+ mc_record rec(value, final_exp);
+ string final_value;
+ serialize(rec, final_value);
+
+ replaceMemcache(final_key.c_str(), final_value, final_exp, ++version);
+ return version;
+}
+
+bool MemcacheStorageService::deleteString(const char* context, const char* key)
+{
+ m_log.debug("deleteString ctx: %s - key: %s", context, key);
+
+ string final_key = string(context) + ":" + string(key);
+
+ // Not updating context map, if there is one. There is no need.
+ return deleteMemcache(final_key.c_str(), 0);
+}
+
+void MemcacheStorageService::updateContext(const char* context, time_t expiration)
+{
+
+ m_log.debug("updateContext ctx: %s", context);
+
+ if (!m_buildMap) {
+ m_log.error("updateContext invoked on a Storage with no context map built!");
+ return;
+ }
+
+ string map_name = context;
+ string ser_arr;
+ uint32_t flags;
+ bool result = getMemcache(map_name.c_str(), ser_arr, &flags);
+
+ list<string> contents;
+ if (result) {
+ m_log.debug("Match found. Parsing...");
+ deserialize(ser_arr, contents);
+
+ m_log.debug("Iterating retrieved session map...");
+ for(list<string>::const_iterator iter = contents.begin(); iter != contents.end(); ++iter) {
+ // Update expiration times
+ string value;
+ int read_res = readString(context, iter->c_str(), &value, nullptr, 0);
+ if (!read_res) {
+ // not found
+ continue;
+ }
+
+ updateString(context, iter->c_str(), value.c_str(), expiration, read_res);
+ }
+ replaceMemcache(map_name.c_str(), ser_arr, expiration, flags);
+ }
+}
+
+void MemcacheStorageService::deleteContext(const char* context)
+{
+
+ m_log.debug("deleteContext ctx: %s", context);
+
+ if (!m_buildMap) {
+ m_log.error("deleteContext invoked on a Storage with no context map built!");
+ return;
+ }
+
+ string map_name = context;
+ string ser_arr;
+ uint32_t flags;
+ bool result = getMemcache(map_name.c_str(), ser_arr, &flags);
+
+ list<string> contents;
+ if (result) {
+ m_log.debug("Match found. Parsing...");
+ deserialize(ser_arr, contents);
+
+ m_log.debug("Iterating retrieved session map...");
+ for (list<string>::const_iterator iter = contents.begin(); iter != contents.end(); ++iter) {
+ string final_key = map_name + *iter;
+ deleteMemcache(final_key.c_str(), 0);
+ }
+
+ deleteMemcache(map_name.c_str(), 0);
+ }
+}
+
+extern "C" int MCEXT_EXPORTS xmltooling_extension_init(void*) {
+ // Register this SS type
+ XMLToolingConfig::getConfig().StorageServiceManager.registerFactory("MEMCACHE", MemcacheStorageServiceFactory);
+ return 0;
+}
+
+extern "C" void MCEXT_EXPORTS xmltooling_extension_term() {
+ XMLToolingConfig::getConfig().StorageServiceManager.deregisterFactory("MEMCACHE");
+}