Convert from NULL macro to nullptr.
[shibboleth/cpp-xmltooling.git] / xmltooling / util / ReloadableXMLFile.cpp
index db20c57..dda1982 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2001-2009 Internet2
+ *  Copyright 2001-2010 Internet2
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include "util/XMLConstants.h"
 #include "util/XMLHelper.h"
 
+#if defined(XMLTOOLING_LOG4SHIB)
+# include <log4shib/NDC.hh>
+#elif defined(XMLTOOLING_LOG4CPP)
+# include <log4cpp/NDC.hh>
+#endif
+
+#include <memory>
 #include <fstream>
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -42,6 +49,7 @@ using namespace xmltooling;
 using namespace xercesc;
 using namespace std;
 
+static const XMLCh id[] =               UNICODE_LITERAL_2(i,d);
 static const XMLCh uri[] =              UNICODE_LITERAL_3(u,r,i);
 static const XMLCh url[] =              UNICODE_LITERAL_3(u,r,l);
 static const XMLCh path[] =             UNICODE_LITERAL_4(p,a,t,h);
@@ -54,30 +62,26 @@ static const XMLCh reloadInterval[] =   UNICODE_LITERAL_14(r,e,l,o,a,d,I,n,t,e,r
 static const XMLCh backingFilePath[] =  UNICODE_LITERAL_15(b,a,c,k,i,n,g,F,i,l,e,P,a,t,h);
 
 
-ReloadableXMLFile::~ReloadableXMLFile()
-{
-    delete m_lock;
-}
-
 ReloadableXMLFile::ReloadableXMLFile(const DOMElement* e, Category& log)
-    : m_root(e), m_local(true), m_validate(false), m_filestamp(0), m_reloadInterval(0), m_lock(NULL), m_log(log)
+    : m_root(e), m_local(true), m_validate(false), m_backupIndicator(true), m_filestamp(0), m_reloadInterval(0), m_lock(nullptr), m_log(log),
+        m_shutdown(false), m_reload_wait(nullptr), m_reload_thread(nullptr)
 {
 #ifdef _DEBUG
     NDC ndc("ReloadableXMLFile");
 #endif
 
     // Establish source of data...
-    const XMLCh* source=e->getAttributeNS(NULL,uri);
+    const XMLCh* source=e->getAttributeNS(nullptr,uri);
     if (!source || !*source) {
-        source=e->getAttributeNS(NULL,url);
+        source=e->getAttributeNS(nullptr,url);
         if (!source || !*source) {
-            source=e->getAttributeNS(NULL,path);
+            source=e->getAttributeNS(nullptr,path);
             if (!source || !*source) {
-                source=e->getAttributeNS(NULL,pathname);
+                source=e->getAttributeNS(nullptr,pathname);
                 if (!source || !*source) {
-                    source=e->getAttributeNS(NULL,file);
+                    source=e->getAttributeNS(nullptr,file);
                     if (!source || !*source) {
-                        source=e->getAttributeNS(NULL,filename);
+                        source=e->getAttributeNS(nullptr,filename);
                     }
                 }
             }
@@ -89,7 +93,7 @@ ReloadableXMLFile::ReloadableXMLFile(const DOMElement* e, Category& log)
         m_local=false;
 
     if (source && *source) {
-        const XMLCh* flag=e->getAttributeNS(NULL,validate);
+        const XMLCh* flag=e->getAttributeNS(nullptr,validate);
         m_validate=(XMLString::equals(flag,xmlconstants::XML_TRUE) || XMLString::equals(flag,xmlconstants::XML_ONE));
 
         auto_ptr_char temp(source);
@@ -103,7 +107,7 @@ ReloadableXMLFile::ReloadableXMLFile(const DOMElement* e, Category& log)
         if (m_local) {
             XMLToolingConfig::getConfig().getPathResolver()->resolve(m_source, PathResolver::XMLTOOLING_CFG_FILE);
 
-            flag=e->getAttributeNS(NULL,reloadChanges);
+            flag=e->getAttributeNS(nullptr,reloadChanges);
             if (!XMLString::equals(flag,xmlconstants::XML_FALSE) && !XMLString::equals(flag,xmlconstants::XML_ZERO)) {
 #ifdef WIN32
                 struct _stat stat_buf;
@@ -121,14 +125,14 @@ ReloadableXMLFile::ReloadableXMLFile(const DOMElement* e, Category& log)
         }
         else {
             log.debug("using remote resource (%s)", m_source.c_str());
-            source = e->getAttributeNS(NULL,backingFilePath);
+            source = e->getAttributeNS(nullptr,backingFilePath);
             if (source && *source) {
                 auto_ptr_char temp2(source);
                 m_backing=temp2.get();
                 XMLToolingConfig::getConfig().getPathResolver()->resolve(m_backing, PathResolver::XMLTOOLING_RUN_FILE);
-                log.debug("backup remote resource with (%s)", m_backing.c_str());
+                log.debug("backup remote resource to (%s)", m_backing.c_str());
             }
-            source = e->getAttributeNS(NULL,reloadInterval);
+            source = e->getAttributeNS(nullptr,reloadInterval);
             if (source && *source) {
                 m_reloadInterval = XMLString::parseInt(source);
                 if (m_reloadInterval > 0) {
@@ -136,25 +140,170 @@ ReloadableXMLFile::ReloadableXMLFile(const DOMElement* e, Category& log)
                     m_lock=RWLock::create();
                 }
             }
-            m_filestamp = time(NULL);   // assume it gets loaded initially
+            m_filestamp = time(nullptr);   // assume it gets loaded initially
+        }
+
+        if (m_lock) {
+            m_reload_wait = CondWait::create();
+            m_reload_thread = Thread::create(&reload_fn, this);
         }
     }
     else {
         log.debug("no resource uri/path/name supplied, will load inline configuration");
     }
+
+    source = e->getAttributeNS(nullptr, id);
+    if (source && *source) {
+        auto_ptr_char tempid(source);
+        m_id = tempid.get();
+    }
+}
+
+ReloadableXMLFile::~ReloadableXMLFile()
+{
+    shutdown();
+    delete m_lock;
+}
+
+void ReloadableXMLFile::shutdown()
+{
+    if (m_reload_thread) {
+        // Shut down the reload thread and let it know.
+        m_shutdown = true;
+        m_reload_wait->signal();
+        m_reload_thread->join(nullptr);
+        delete m_reload_thread;
+        delete m_reload_wait;
+        m_reload_thread = nullptr;
+        m_reload_wait = nullptr;
+    }
+}
+
+void* ReloadableXMLFile::reload_fn(void* pv)
+{
+    ReloadableXMLFile* r = reinterpret_cast<ReloadableXMLFile*>(pv);
+
+#ifndef WIN32
+    // First, let's block all signals
+    Thread::mask_all_signals();
+#endif
+
+    if (!r->m_id.empty()) {
+        string threadid("[");
+        threadid += r->m_id + ']';
+        logging::NDC::push(threadid);
+    }
+
+#ifdef _DEBUG
+    NDC ndc("reload");
+#endif
+
+    auto_ptr<Mutex> mutex(Mutex::create());
+    mutex->lock();
+
+    if (r->m_local)
+        r->m_log.info("reload thread started...running when signaled");
+    else
+        r->m_log.info("reload thread started...running every %d seconds", r->m_reloadInterval);
+
+    while (!r->m_shutdown) {
+        if (r->m_local)
+            r->m_reload_wait->wait(mutex.get());
+        else
+            r->m_reload_wait->timedwait(mutex.get(), r->m_reloadInterval);
+        if (r->m_shutdown)
+            break;
+
+        try {
+            r->m_log.info("reloading %s resource...", r->m_local ? "local" : "remote");
+            pair<bool,DOMElement*> ret = r->background_load();
+            if (ret.first)
+                ret.second->getOwnerDocument()->release();
+        }
+        catch (long& ex) {
+            if (ex == HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED) {
+                r->m_log.info("remote resource (%s) unchanged from cached version", r->m_source.c_str());
+            }
+            else {
+                // Shouldn't happen, we should only get codes intended to be gracefully handled.
+                r->m_log.crit("maintaining existing configuration, remote resource fetch returned atypical status code (%d)", ex);
+            }
+        }
+        catch (exception& ex) {
+            r->m_log.crit("maintaining existing configuration, error reloading resource (%s): %s", r->m_source.c_str(), ex.what());
+        }
+    }
+
+    r->m_log.info("reload thread finished");
+
+    mutex->unlock();
+
+    if (!r->m_id.empty()) {
+        logging::NDC::pop();
+    }
+
+    return nullptr;
+}
+
+Lockable* ReloadableXMLFile::lock()
+{
+    if (!m_lock)
+        return this;
+
+    m_lock->rdlock();
+
+    if (m_local) {
+    // Check if we need to refresh.
+#ifdef WIN32
+        struct _stat stat_buf;
+        if (_stat(m_source.c_str(), &stat_buf) != 0)
+            return this;
+#else
+        struct stat stat_buf;
+        if (stat(m_source.c_str(), &stat_buf) != 0)
+            return this;
+#endif
+        if (m_filestamp >= stat_buf.st_mtime)
+            return this;
+
+        // Elevate lock and recheck.
+        m_log.debug("timestamp of local resource changed, elevating to a write lock");
+        m_lock->unlock();
+        m_lock->wrlock();
+        if (m_filestamp >= stat_buf.st_mtime) {
+            // Somebody else handled it, just downgrade.
+            m_log.debug("update of local resource handled by another thread, downgrading lock");
+            m_lock->unlock();
+            m_lock->rdlock();
+            return this;
+        }
+
+        // Update the timestamp regardless.
+        m_filestamp = stat_buf.st_mtime;
+        m_log.info("change detected, signaling reload thread...");
+        m_reload_wait->signal();
+    }
+
+    return this;
+}
+
+void ReloadableXMLFile::unlock()
+{
+    if (m_lock)
+        m_lock->unlock();
 }
 
 pair<bool,DOMElement*> ReloadableXMLFile::load(bool backup)
 {
 #ifdef _DEBUG
-    NDC ndc("init");
+    NDC ndc("load");
 #endif
 
     try {
         if (m_source.empty()) {
             // Data comes from the DOM we were handed.
             m_log.debug("loading inline configuration...");
-            return make_pair(false,XMLHelper::getFirstChildElement(m_root));
+            return make_pair(false, XMLHelper::getFirstChildElement(m_root));
         }
         else {
             // Data comes from a file we have to parse.
@@ -163,19 +312,21 @@ pair<bool,DOMElement*> ReloadableXMLFile::load(bool backup)
             else
                 m_log.debug("loading configuration from external resource...");
 
-            DOMDocument* doc=NULL;
+            DOMDocument* doc=nullptr;
             if (m_local || backup) {
                 auto_ptr_XMLCh widenit(backup ? m_backing.c_str() : m_source.c_str());
+                // Use library-wide lock for now, nothing else is using it anyway.
+                Locker locker(backup ? getBackupLock() : nullptr);
                 LocalFileInputSource src(widenit.get());
-                Wrapper4InputSource dsrc(&src,false);
+                Wrapper4InputSource dsrc(&src, false);
                 if (m_validate)
                     doc=XMLToolingConfig::getConfig().getValidatingParser().parse(dsrc);
                 else
                     doc=XMLToolingConfig::getConfig().getParser().parse(dsrc);
             }
             else {
-                URLInputSource src(m_root, NULL, &m_cacheTag);
-                Wrapper4InputSource dsrc(&src,false);
+                URLInputSource src(m_root, nullptr, &m_cacheTag);
+                Wrapper4InputSource dsrc(&src, false);
                 if (m_validate)
                     doc=XMLToolingConfig::getConfig().getValidatingParser().parse(dsrc);
                 else
@@ -198,17 +349,26 @@ pair<bool,DOMElement*> ReloadableXMLFile::load(bool backup)
             m_log.infoStream() << "loaded XML resource (" << (backup ? m_backing : m_source) << ")" << logging::eol;
 
             if (!backup && !m_backing.empty()) {
-                m_log.debug("backing up remote resource to (%s)", m_backing.c_str());
-                try {
-                    ofstream backer(m_backing.c_str());
-                    backer << *doc;
+                // If the indicator is true, we're responsible for the backup.
+                if (m_backupIndicator) {
+                    m_log.debug("backing up remote resource to (%s)", m_backing.c_str());
+                    try {
+                        Locker locker(getBackupLock());
+                        ofstream backer(m_backing.c_str());
+                        backer << *doc;
+                    }
+                    catch (exception& ex) {
+                        m_log.crit("exception while backing up resource: %s", ex.what());
+                    }
                 }
-                catch (exception& ex) {
-                    m_log.crit("exception while backing up resource: %s", ex.what());
+                else {
+                    // If the indicator was false, set true to signal that a backup is needed.
+                    // The caller will presumably flip it back to false once that's done.
+                    m_backupIndicator = true;
                 }
             }
 
-            return make_pair(true,doc->getDocumentElement());
+            return make_pair(true, doc->getDocumentElement());
         }
     }
     catch (XMLException& e) {
@@ -228,101 +388,22 @@ pair<bool,DOMElement*> ReloadableXMLFile::load(bool backup)
     }
 }
 
-Lockable* ReloadableXMLFile::lock()
+pair<bool,DOMElement*> ReloadableXMLFile::load()
 {
-    if (!m_lock)
-        return this;
-
-    m_lock->rdlock();
-
-    // Check if we need to refresh.
-    if (m_local) {
-#ifdef WIN32
-        struct _stat stat_buf;
-        if (_stat(m_source.c_str(), &stat_buf) != 0)
-            return this;
-#else
-        struct stat stat_buf;
-        if (stat(m_source.c_str(), &stat_buf) != 0)
-            return this;
-#endif
-        if (m_filestamp>=stat_buf.st_mtime)
-            return this;
-
-        // Elevate lock and recheck.
-        m_log.debug("timestamp of local resource changed, elevating to a write lock");
-        m_lock->unlock();
-        m_lock->wrlock();
-        if (m_filestamp>=stat_buf.st_mtime) {
-            // Somebody else handled it, just downgrade.
-            m_log.debug("update of local resource handled by another thread, downgrading lock");
-            m_lock->unlock();
-            m_lock->rdlock();
-            return this;
-        }
-
-        // Update the timestamp regardless. No point in repeatedly trying.
-        m_filestamp=stat_buf.st_mtime;
-        m_log.info("change detected, reloading local resource...");
-    }
-    else {
-        time_t now = time(NULL);
-
-        // Time to reload? If we have no data, filestamp is zero
-        // and there's no way current time is less than the interval.
-        if (now - m_filestamp < m_reloadInterval)
-            return this;
-
-        // Elevate lock and recheck.
-        m_log.debug("reload interval for remote resource elapsed, elevating to a write lock");
-        m_lock->unlock();
-        m_lock->wrlock();
-        if (now - m_filestamp < m_reloadInterval) {
-            // Somebody else handled it, just downgrade.
-            m_log.debug("update of remote resource handled by another thread, downgrading lock");
-            m_lock->unlock();
-            m_lock->rdlock();
-            return this;
-        }
-
-        m_filestamp = now;
-        m_log.info("reloading remote resource...");
-    }
-
-    // Do this once...
-    try {
-        // At this point we're holding the write lock, so make sure we pop it.
-        SharedLock lockwrap(m_lock,false);
-        pair<bool,DOMElement*> ret=load();
-        if (ret.first)
-            ret.second->getOwnerDocument()->release();
-    }
-    catch (long& ex) {
-        if (ex == HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED) {
-            m_log.info("remote resource (%s) unchanged from cached version", m_source.c_str());
-        }
-        else {
-            // Shouldn't happen, we should only get codes intended to be gracefully handled.
-            m_log.crit("maintaining existing configuration, remote resource fetch returned atypical status code (%d)", ex);
-        }
-    }
-    catch (exception& ex) {
-        m_log.crit("maintaining existing configuration, error reloading resource (%s): %s", m_source.c_str(), ex.what());
-    }
-
-    // If we made it here, the swap may or may not have worked, but we need to relock.
-    m_log.debug("attempt to update resource complete, relocking");
-    m_lock->rdlock();
-    return this;
+    return load(false);
 }
 
-void ReloadableXMLFile::unlock()
+pair<bool,DOMElement*> ReloadableXMLFile::background_load()
 {
+    // If this method isn't overridden, we acquire a write lock
+    // and just call the old override.
     if (m_lock)
-        m_lock->unlock();
+        m_lock->wrlock();
+    SharedLock locker(m_lock, false);
+    return load();
 }
 
-pair<bool,DOMElement*> ReloadableXMLFile::load()
+Lockable* ReloadableXMLFile::getBackupLock()
 {
-    return load(false);
+    return &XMLToolingConfig::getConfig();
 }