2 * Copyright 2001-2010 Internet2
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * @file ReloadableXMLFile.cpp
20 * Base class for file-based XML configuration.
24 #include "io/HTTPResponse.h"
26 #include "util/PathResolver.h"
27 #include "util/ReloadableXMLFile.h"
28 #include "util/Threads.h"
29 #include "util/XMLConstants.h"
30 #include "util/XMLHelper.h"
32 #if defined(XMLTOOLING_LOG4SHIB)
33 # include <log4shib/NDC.hh>
34 #elif defined(XMLTOOLING_LOG4CPP)
35 # include <log4cpp/NDC.hh>
39 #include <sys/types.h>
42 #include <xercesc/framework/LocalFileInputSource.hpp>
43 #include <xercesc/framework/Wrapper4InputSource.hpp>
44 #include <xercesc/util/XMLUniDefs.hpp>
46 using namespace xmltooling::logging;
47 using namespace xmltooling;
48 using namespace xercesc;
51 static const XMLCh id[] = UNICODE_LITERAL_2(i,d);
52 static const XMLCh uri[] = UNICODE_LITERAL_3(u,r,i);
53 static const XMLCh url[] = UNICODE_LITERAL_3(u,r,l);
54 static const XMLCh path[] = UNICODE_LITERAL_4(p,a,t,h);
55 static const XMLCh pathname[] = UNICODE_LITERAL_8(p,a,t,h,n,a,m,e);
56 static const XMLCh file[] = UNICODE_LITERAL_4(f,i,l,e);
57 static const XMLCh filename[] = UNICODE_LITERAL_8(f,i,l,e,n,a,m,e);
58 static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
59 static const XMLCh reloadChanges[] = UNICODE_LITERAL_13(r,e,l,o,a,d,C,h,a,n,g,e,s);
60 static const XMLCh reloadInterval[] = UNICODE_LITERAL_14(r,e,l,o,a,d,I,n,t,e,r,v,a,l);
61 static const XMLCh backingFilePath[] = UNICODE_LITERAL_15(b,a,c,k,i,n,g,F,i,l,e,P,a,t,h);
64 ReloadableXMLFile::ReloadableXMLFile(const DOMElement* e, Category& log)
65 : m_root(e), m_local(true), m_validate(false), m_backupIndicator(true), m_filestamp(0), m_reloadInterval(0), m_lock(NULL), m_log(log),
66 m_shutdown(false), m_reload_wait(NULL), m_reload_thread(NULL)
69 NDC ndc("ReloadableXMLFile");
72 // Establish source of data...
73 const XMLCh* source=e->getAttributeNS(NULL,uri);
74 if (!source || !*source) {
75 source=e->getAttributeNS(NULL,url);
76 if (!source || !*source) {
77 source=e->getAttributeNS(NULL,path);
78 if (!source || !*source) {
79 source=e->getAttributeNS(NULL,pathname);
80 if (!source || !*source) {
81 source=e->getAttributeNS(NULL,file);
82 if (!source || !*source) {
83 source=e->getAttributeNS(NULL,filename);
94 if (source && *source) {
95 const XMLCh* flag=e->getAttributeNS(NULL,validate);
96 m_validate=(XMLString::equals(flag,xmlconstants::XML_TRUE) || XMLString::equals(flag,xmlconstants::XML_ONE));
98 auto_ptr_char temp(source);
101 if (!m_local && !strstr(m_source.c_str(),"://")) {
102 log.warn("deprecated usage of uri/url attribute for a local resource, use path instead");
107 XMLToolingConfig::getConfig().getPathResolver()->resolve(m_source, PathResolver::XMLTOOLING_CFG_FILE);
109 flag=e->getAttributeNS(NULL,reloadChanges);
110 if (!XMLString::equals(flag,xmlconstants::XML_FALSE) && !XMLString::equals(flag,xmlconstants::XML_ZERO)) {
112 struct _stat stat_buf;
113 if (_stat(m_source.c_str(), &stat_buf) == 0)
115 struct stat stat_buf;
116 if (stat(m_source.c_str(), &stat_buf) == 0)
118 m_filestamp=stat_buf.st_mtime;
120 throw IOException("Unable to access local file ($1)", params(1,m_source.c_str()));
121 m_lock=RWLock::create();
123 log.debug("using local resource (%s), will %smonitor for changes", m_source.c_str(), m_lock ? "" : "not ");
126 log.debug("using remote resource (%s)", m_source.c_str());
127 source = e->getAttributeNS(NULL,backingFilePath);
128 if (source && *source) {
129 auto_ptr_char temp2(source);
130 m_backing=temp2.get();
131 XMLToolingConfig::getConfig().getPathResolver()->resolve(m_backing, PathResolver::XMLTOOLING_RUN_FILE);
132 log.debug("backup remote resource to (%s)", m_backing.c_str());
134 source = e->getAttributeNS(NULL,reloadInterval);
135 if (source && *source) {
136 m_reloadInterval = XMLString::parseInt(source);
137 if (m_reloadInterval > 0) {
138 m_log.debug("will reload remote resource at most every %d seconds", m_reloadInterval);
139 m_lock=RWLock::create();
142 m_filestamp = time(NULL); // assume it gets loaded initially
146 m_reload_wait = CondWait::create();
147 m_reload_thread = Thread::create(&reload_fn, this);
151 log.debug("no resource uri/path/name supplied, will load inline configuration");
154 source = e->getAttributeNS(NULL, id);
155 if (source && *source) {
156 auto_ptr_char tempid(source);
161 ReloadableXMLFile::~ReloadableXMLFile()
167 void ReloadableXMLFile::shutdown()
169 if (m_reload_thread) {
170 // Shut down the reload thread and let it know.
172 m_reload_wait->signal();
173 m_reload_thread->join(NULL);
174 delete m_reload_thread;
175 delete m_reload_wait;
176 m_reload_thread = NULL;
177 m_reload_wait = NULL;
181 void* ReloadableXMLFile::reload_fn(void* pv)
183 ReloadableXMLFile* r = reinterpret_cast<ReloadableXMLFile*>(pv);
186 // First, let's block all signals
187 Thread::mask_all_signals();
190 if (!r->m_id.empty()) {
191 string threadid("[");
192 threadid += r->m_id + ']';
193 logging::NDC::push(threadid);
200 auto_ptr<Mutex> mutex(Mutex::create());
204 r->m_log.info("reload thread started...running when signaled");
206 r->m_log.info("reload thread started...running every %d seconds", r->m_reloadInterval);
208 while (!r->m_shutdown) {
210 r->m_reload_wait->wait(mutex.get());
212 r->m_reload_wait->timedwait(mutex.get(), r->m_reloadInterval);
217 r->m_log.info("reloading %s resource...", r->m_local ? "local" : "remote");
218 pair<bool,DOMElement*> ret = r->background_load();
220 ret.second->getOwnerDocument()->release();
223 if (ex == HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED) {
224 r->m_log.info("remote resource (%s) unchanged from cached version", r->m_source.c_str());
227 // Shouldn't happen, we should only get codes intended to be gracefully handled.
228 r->m_log.crit("maintaining existing configuration, remote resource fetch returned atypical status code (%d)", ex);
231 catch (exception& ex) {
232 r->m_log.crit("maintaining existing configuration, error reloading resource (%s): %s", r->m_source.c_str(), ex.what());
236 r->m_log.info("reload thread finished");
240 if (!r->m_id.empty()) {
247 Lockable* ReloadableXMLFile::lock()
255 // Check if we need to refresh.
257 struct _stat stat_buf;
258 if (_stat(m_source.c_str(), &stat_buf) != 0)
261 struct stat stat_buf;
262 if (stat(m_source.c_str(), &stat_buf) != 0)
265 if (m_filestamp >= stat_buf.st_mtime)
268 // Elevate lock and recheck.
269 m_log.debug("timestamp of local resource changed, elevating to a write lock");
272 if (m_filestamp >= stat_buf.st_mtime) {
273 // Somebody else handled it, just downgrade.
274 m_log.debug("update of local resource handled by another thread, downgrading lock");
280 // Update the timestamp regardless.
281 m_filestamp = stat_buf.st_mtime;
282 m_log.info("change detected, signaling reload thread...");
283 m_reload_wait->signal();
289 void ReloadableXMLFile::unlock()
295 pair<bool,DOMElement*> ReloadableXMLFile::load(bool backup)
302 if (m_source.empty()) {
303 // Data comes from the DOM we were handed.
304 m_log.debug("loading inline configuration...");
305 return make_pair(false, XMLHelper::getFirstChildElement(m_root));
308 // Data comes from a file we have to parse.
310 m_log.warn("using local backup of remote resource");
312 m_log.debug("loading configuration from external resource...");
314 DOMDocument* doc=NULL;
315 if (m_local || backup) {
316 auto_ptr_XMLCh widenit(backup ? m_backing.c_str() : m_source.c_str());
317 // Use library-wide lock for now, nothing else is using it anyway.
318 Locker locker(backup ? getBackupLock() : NULL);
319 LocalFileInputSource src(widenit.get());
320 Wrapper4InputSource dsrc(&src, false);
322 doc=XMLToolingConfig::getConfig().getValidatingParser().parse(dsrc);
324 doc=XMLToolingConfig::getConfig().getParser().parse(dsrc);
327 URLInputSource src(m_root, NULL, &m_cacheTag);
328 Wrapper4InputSource dsrc(&src, false);
330 doc=XMLToolingConfig::getConfig().getValidatingParser().parse(dsrc);
332 doc=XMLToolingConfig::getConfig().getParser().parse(dsrc);
334 // Check for a response code signal.
335 if (XMLHelper::isNodeNamed(doc->getDocumentElement(), xmlconstants::XMLTOOLING_NS, URLInputSource::utf16StatusCodeElementName)) {
336 int responseCode = XMLString::parseInt(doc->getDocumentElement()->getFirstChild()->getNodeValue());
338 if (responseCode == HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED) {
339 throw (long)responseCode; // toss out as a "known" case to handle gracefully
342 m_log.warn("remote resource fetch returned atypical status code (%d)", responseCode);
343 throw IOException("remote resource fetch failed, check log for status code of response");
348 m_log.infoStream() << "loaded XML resource (" << (backup ? m_backing : m_source) << ")" << logging::eol;
350 if (!backup && !m_backing.empty()) {
351 // If the indicator is true, we're responsible for the backup.
352 if (m_backupIndicator) {
353 m_log.debug("backing up remote resource to (%s)", m_backing.c_str());
355 Locker locker(getBackupLock());
356 ofstream backer(m_backing.c_str());
359 catch (exception& ex) {
360 m_log.crit("exception while backing up resource: %s", ex.what());
364 // If the indicator was false, set true to signal that a backup is needed.
365 // The caller will presumably flip it back to false once that's done.
366 m_backupIndicator = true;
370 return make_pair(true, doc->getDocumentElement());
373 catch (XMLException& e) {
374 auto_ptr_char msg(e.getMessage());
375 m_log.errorStream() << "Xerces error while loading resource (" << (backup ? m_backing : m_source) << "): "
376 << msg.get() << logging::eol;
377 if (!backup && !m_backing.empty())
379 throw XMLParserException(msg.get());
381 catch (exception& e) {
382 m_log.errorStream() << "error while loading resource ("
383 << (m_source.empty() ? "inline" : (backup ? m_backing : m_source)) << "): " << e.what() << logging::eol;
384 if (!backup && !m_backing.empty())
390 pair<bool,DOMElement*> ReloadableXMLFile::load()
395 pair<bool,DOMElement*> ReloadableXMLFile::background_load()
397 // If this method isn't overridden, we acquire a write lock
398 // and just call the old override.
401 SharedLock locker(m_lock, false);
405 Lockable* ReloadableXMLFile::getBackupLock()
407 return &XMLToolingConfig::getConfig();