+#endif
+
+Session* SSCache::find(const char* key, const Application& application, const char* client_addr, time_t* timeout)
+{
+#ifdef _DEBUG
+ xmltooling::NDC ndc("find");
+#endif
+ StoredSession* session=NULL;
+
+ if (inproc) {
+ m_log.debug("searching local cache for session (%s)", key);
+ m_lock->rdlock();
+ map<string,StoredSession*>::const_iterator i=m_hashtable.find(key);
+ if (i!=m_hashtable.end()) {
+ // Save off and lock the session.
+ session = i->second;
+ session->lock();
+ m_lock->unlock();
+ m_log.debug("session found locally, validating it for use");
+ }
+ else {
+ m_lock->unlock();
+ }
+ }
+
+ if (!session) {
+ if (!SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
+ m_log.debug("session not found locally, remoting the search");
+ // Remote the request.
+ DDF in("find::"STORAGESERVICE_SESSION_CACHE"::SessionCache"), out;
+ DDFJanitor jin(in);
+ in.structure();
+ in.addmember("key").string(key);
+ in.addmember("application_id").string(application.getId());
+ if (timeout && *timeout) {
+ // On 64-bit Windows, time_t doesn't fit in a long, so I'm using ISO timestamps.
+#ifndef HAVE_GMTIME_R
+ struct tm* ptime=gmtime(timeout);
+#else
+ struct tm res;
+ struct tm* ptime=gmtime_r(timeout,&res);
+#endif
+ char timebuf[32];
+ strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
+ in.addmember("timeout").string(timebuf);
+ }
+
+ try {
+ out=application.getServiceProvider().getListenerService()->send(in);
+ if (!out.isstruct()) {
+ out.destroy();
+ m_log.debug("session not found in remote cache");
+ return NULL;
+ }
+
+ // Wrap the results in a local entry and save it.
+ session = new StoredSession(this, out);
+ // The remote end has handled timeout issues, we handle address and expiration checks.
+ timeout = NULL;
+ }
+ catch (...) {
+ out.destroy();
+ throw;
+ }
+ }
+ else {
+ // We're out of process, so we can search the storage service directly.
+#ifndef SHIBSP_LITE
+ if (!m_storage)
+ throw ConfigurationException("SessionCache lookup requires a StorageService.");
+
+ m_log.debug("searching for session (%s)", key);
+
+ DDF obj;
+ time_t lastAccess;
+ string record;
+ int ver = m_storage->readText(key, "session", &record, &lastAccess);
+ if (!ver)
+ return NULL;
+
+ m_log.debug("reconstituting session and checking validity");
+
+ istringstream in(record);
+ in >> obj;
+
+ lastAccess -= m_cacheTimeout; // adjusts it back to the last time the record's timestamp was touched
+ time_t now=time(NULL);
+
+ if (timeout && *timeout > 0 && now - lastAccess >= *timeout) {
+ m_log.info("session timed out (ID: %s)", key);
+ remove(key, application);
+ RetryableProfileException ex("Your session has expired, and you must re-authenticate.");
+ const char* eid = obj["entity_id"].string();
+ if (!eid) {
+ obj.destroy();
+ throw ex;
+ }
+ string eid2(eid);
+ obj.destroy();
+ MetadataProvider* m=application.getMetadataProvider();
+ Locker locker(m);
+ annotateException(&ex,m->getEntityDescriptor(MetadataProvider::Criteria(eid2.c_str(),NULL,NULL,false)).first); // throws it
+ }
+
+ if (timeout) {
+ // Update storage expiration, if possible.
+ try {
+ m_storage->updateContext(key, now + m_cacheTimeout);
+ }
+ catch (exception& ex) {
+ m_log.error("failed to update session expiration: %s", ex.what());
+ }
+ }
+
+ // Wrap the results in a local entry and save it.
+ session = new StoredSession(this, obj);
+ // We handled timeout issues, still need to handle address and expiration checks.
+ timeout = NULL;
+#else
+ throw ConfigurationException("SessionCache search requires a StorageService.");
+#endif
+ }
+
+ if (inproc) {
+ // Lock for writing and repeat the search to avoid duplication.
+ m_lock->wrlock();
+ SharedLock shared(m_lock, false);
+ if (m_hashtable.count(key)) {
+ // We're using an existing session entry.
+ delete session;
+ session = m_hashtable[key];
+ session->lock();
+ }
+ else {
+ m_hashtable[key]=session;
+ session->lock();
+ }
+ }
+ }
+
+ if (!XMLString::equals(session->getApplicationID(), application.getId())) {
+ m_log.error("an application (%s) tried to access another application's session", application.getId());
+ session->unlock();
+ return NULL;
+ }
+
+ // Verify currency and update the timestamp if indicated by caller.
+ try {
+ session->validate(application, client_addr, timeout);
+ }
+ catch (...) {
+ session->unlock();
+ remove(key, application);
+ throw;
+ }
+
+ return session;
+}
+
+void SSCache::remove(const char* key, const Application& application)
+{
+#ifdef _DEBUG
+ xmltooling::NDC ndc("remove");
+#endif
+ // Take care of local copy.
+ if (inproc)
+ dormant(key);
+
+ if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
+ // Remove the session from storage directly.
+#ifndef SHIBSP_LITE
+ m_storage->deleteContext(key);
+ m_log.info("removed session (%s)", key);
+
+ TransactionLog* xlog = application.getServiceProvider().getTransactionLog();
+ Locker locker(xlog);
+ xlog->log.info("Destroyed session (applicationId: %s) (ID: %s)", application.getId(), key);
+#else
+ throw ConfigurationException("SessionCache removal requires a StorageService.");
+#endif
+ }
+ else {
+ // Remote the request.
+ DDF in("remove::"STORAGESERVICE_SESSION_CACHE"::SessionCache");
+ DDFJanitor jin(in);
+ in.structure();
+ in.addmember("key").string(key);
+ in.addmember("application_id").string(application.getId());
+
+ DDF out = application.getServiceProvider().getListenerService()->send(in);
+ out.destroy();
+ }
+}
+
+void SSCache::dormant(const char* key)
+{
+#ifdef _DEBUG
+ xmltooling::NDC ndc("dormant");
+#endif
+
+ m_log.debug("deleting local copy of session (%s)", key);
+
+ // lock the cache for writing, which means we know nobody is sitting in find()
+ m_lock->wrlock();
+
+ // grab the entry from the table
+ map<string,StoredSession*>::const_iterator i=m_hashtable.find(key);
+ if (i==m_hashtable.end()) {
+ m_lock->unlock();
+ return;
+ }
+
+ // ok, remove the entry and lock it
+ StoredSession* entry=i->second;
+ m_hashtable.erase(key);
+ entry->lock();
+
+ // unlock the cache
+ m_lock->unlock();
+
+ // we can release the cache entry lock because we know we're not in the cache anymore
+ entry->unlock();
+
+ delete entry;
+}
+
+void SSCache::cleanup()
+{
+#ifdef _DEBUG
+ xmltooling::NDC ndc("cleanup");
+#endif
+
+ Mutex* mutex = Mutex::create();
+
+ // Load our configuration details...
+ static const XMLCh cleanupInterval[] = UNICODE_LITERAL_15(c,l,e,a,n,u,p,I,n,t,e,r,v,a,l);
+ const XMLCh* tag=m_root ? m_root->getAttributeNS(NULL,cleanupInterval) : NULL;
+ int rerun_timer = 900;
+ if (tag && *tag)
+ rerun_timer = XMLString::parseInt(tag);
+ if (rerun_timer <= 0)
+ rerun_timer = 900;
+
+ mutex->lock();
+
+ m_log.info("cleanup thread started...run every %d secs; timeout after %d secs", rerun_timer, m_inprocTimeout);
+
+ while (!shutdown) {
+ shutdown_wait->timedwait(mutex,rerun_timer);
+ if (shutdown)
+ break;
+
+ // Ok, let's run through the cleanup process and clean out
+ // really old sessions. This is a two-pass process. The
+ // first pass is done holding a read-lock while we iterate over
+ // the cache. The second pass doesn't need a lock because
+ // the 'deletes' will lock the cache.
+
+ // Pass 1: iterate over the map and find all entries that have not been
+ // used in the allotted timeout.
+ vector<string> stale_keys;
+ time_t stale = time(NULL) - m_inprocTimeout;
+
+ m_log.debug("cleanup thread running");
+
+ m_lock->rdlock();
+ for (map<string,StoredSession*>::const_iterator i=m_hashtable.begin(); i!=m_hashtable.end(); ++i) {
+ // If the last access was BEFORE the stale timeout...
+ i->second->lock();
+ time_t last=i->second->getLastAccess();
+ i->second->unlock();
+ if (last < stale)
+ stale_keys.push_back(i->first);
+ }
+ m_lock->unlock();
+
+ if (!stale_keys.empty()) {
+ m_log.info("purging %d old sessions", stale_keys.size());
+
+ // Pass 2: walk through the list of stale entries and remove them from the cache
+ for (vector<string>::const_iterator j = stale_keys.begin(); j != stale_keys.end(); ++j)
+ dormant(j->c_str());
+ }
+
+ m_log.debug("cleanup thread completed");
+ }
+
+ m_log.info("cleanup thread exiting");
+
+ mutex->unlock();
+ delete mutex;
+ Thread::exit(NULL);
+}
+
+void* SSCache::cleanup_fn(void* cache_p)
+{
+#ifndef WIN32
+ // First, let's block all signals
+ Thread::mask_all_signals();
+#endif
+
+ // Now run the cleanup process.
+ reinterpret_cast<SSCache*>(cache_p)->cleanup();
+ return NULL;
+}
+
+#ifndef SHIBSP_LITE
+