2 * Copyright 2001-2009 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"
33 #include <sys/types.h>
36 #include <xercesc/framework/LocalFileInputSource.hpp>
37 #include <xercesc/framework/Wrapper4InputSource.hpp>
38 #include <xercesc/util/XMLUniDefs.hpp>
40 using namespace xmltooling::logging;
41 using namespace xmltooling;
42 using namespace xercesc;
45 static const XMLCh uri[] = UNICODE_LITERAL_3(u,r,i);
46 static const XMLCh url[] = UNICODE_LITERAL_3(u,r,l);
47 static const XMLCh path[] = UNICODE_LITERAL_4(p,a,t,h);
48 static const XMLCh pathname[] = UNICODE_LITERAL_8(p,a,t,h,n,a,m,e);
49 static const XMLCh file[] = UNICODE_LITERAL_4(f,i,l,e);
50 static const XMLCh filename[] = UNICODE_LITERAL_8(f,i,l,e,n,a,m,e);
51 static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
52 static const XMLCh reloadChanges[] = UNICODE_LITERAL_13(r,e,l,o,a,d,C,h,a,n,g,e,s);
53 static const XMLCh reloadInterval[] = UNICODE_LITERAL_14(r,e,l,o,a,d,I,n,t,e,r,v,a,l);
54 static const XMLCh backingFilePath[] = UNICODE_LITERAL_15(b,a,c,k,i,n,g,F,i,l,e,P,a,t,h);
57 ReloadableXMLFile::~ReloadableXMLFile()
62 ReloadableXMLFile::ReloadableXMLFile(const DOMElement* e, Category& log)
63 : m_root(e), m_local(true), m_validate(false), m_filestamp(0), m_reloadInterval(0), m_lock(NULL), m_log(log)
66 NDC ndc("ReloadableXMLFile");
69 // Establish source of data...
70 const XMLCh* source=e->getAttributeNS(NULL,uri);
71 if (!source || !*source) {
72 source=e->getAttributeNS(NULL,url);
73 if (!source || !*source) {
74 source=e->getAttributeNS(NULL,path);
75 if (!source || !*source) {
76 source=e->getAttributeNS(NULL,pathname);
77 if (!source || !*source) {
78 source=e->getAttributeNS(NULL,file);
79 if (!source || !*source) {
80 source=e->getAttributeNS(NULL,filename);
91 if (source && *source) {
92 const XMLCh* flag=e->getAttributeNS(NULL,validate);
93 m_validate=(XMLString::equals(flag,xmlconstants::XML_TRUE) || XMLString::equals(flag,xmlconstants::XML_ONE));
95 auto_ptr_char temp(source);
98 if (!m_local && !strstr(m_source.c_str(),"://")) {
99 log.warn("deprecated usage of uri/url attribute for a local resource, use path instead");
104 XMLToolingConfig::getConfig().getPathResolver()->resolve(m_source, PathResolver::XMLTOOLING_CFG_FILE);
106 flag=e->getAttributeNS(NULL,reloadChanges);
107 if (!XMLString::equals(flag,xmlconstants::XML_FALSE) && !XMLString::equals(flag,xmlconstants::XML_ZERO)) {
109 struct _stat stat_buf;
110 if (_stat(m_source.c_str(), &stat_buf) == 0)
112 struct stat stat_buf;
113 if (stat(m_source.c_str(), &stat_buf) == 0)
115 m_filestamp=stat_buf.st_mtime;
117 throw IOException("Unable to access local file ($1)", params(1,m_source.c_str()));
118 m_lock=RWLock::create();
120 log.debug("using local resource (%s), will %smonitor for changes", m_source.c_str(), m_lock ? "" : "not ");
123 log.debug("using remote resource (%s)", m_source.c_str());
124 source = e->getAttributeNS(NULL,backingFilePath);
125 if (source && *source) {
126 auto_ptr_char temp2(source);
127 m_backing=temp2.get();
128 XMLToolingConfig::getConfig().getPathResolver()->resolve(m_backing, PathResolver::XMLTOOLING_RUN_FILE);
129 log.debug("backup remote resource with (%s)", m_backing.c_str());
131 source = e->getAttributeNS(NULL,reloadInterval);
132 if (source && *source) {
133 m_reloadInterval = XMLString::parseInt(source);
134 if (m_reloadInterval > 0) {
135 m_log.debug("will reload remote resource at most every %d seconds", m_reloadInterval);
136 m_lock=RWLock::create();
139 m_filestamp = time(NULL); // assume it gets loaded initially
143 log.debug("no resource uri/path/name supplied, will load inline configuration");
147 pair<bool,DOMElement*> ReloadableXMLFile::load(bool backup)
154 if (m_source.empty()) {
155 // Data comes from the DOM we were handed.
156 m_log.debug("loading inline configuration...");
157 return make_pair(false,XMLHelper::getFirstChildElement(m_root));
160 // Data comes from a file we have to parse.
162 m_log.warn("using local backup of remote resource");
164 m_log.debug("loading configuration from external resource...");
166 DOMDocument* doc=NULL;
167 if (m_local || backup) {
168 auto_ptr_XMLCh widenit(backup ? m_backing.c_str() : m_source.c_str());
169 LocalFileInputSource src(widenit.get());
170 Wrapper4InputSource dsrc(&src,false);
172 doc=XMLToolingConfig::getConfig().getValidatingParser().parse(dsrc);
174 doc=XMLToolingConfig::getConfig().getParser().parse(dsrc);
177 URLInputSource src(m_root, NULL, &m_cacheTag);
178 Wrapper4InputSource dsrc(&src,false);
180 doc=XMLToolingConfig::getConfig().getValidatingParser().parse(dsrc);
182 doc=XMLToolingConfig::getConfig().getParser().parse(dsrc);
184 // Check for a response code signal.
185 if (XMLHelper::isNodeNamed(doc->getDocumentElement(), xmlconstants::XMLTOOLING_NS, URLInputSource::utf16StatusCodeElementName)) {
186 int responseCode = XMLString::parseInt(doc->getDocumentElement()->getFirstChild()->getNodeValue());
188 if (responseCode == HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED) {
189 throw responseCode; // toss out as a "known" case to handle gracefully
192 m_log.warn("remote resource fetch returned atypical status code (%d)", responseCode);
193 throw IOException("remote resource fetch failed, check log for status code of response");
198 m_log.infoStream() << "loaded XML resource (" << (backup ? m_backing : m_source) << ")" << logging::eol;
200 if (!backup && !m_backing.empty()) {
201 m_log.debug("backing up remote resource to (%s)", m_backing.c_str());
203 ofstream backer(m_backing.c_str());
206 catch (exception& ex) {
207 m_log.crit("exception while backing up resource: %s", ex.what());
211 return make_pair(true,doc->getDocumentElement());
214 catch (XMLException& e) {
215 auto_ptr_char msg(e.getMessage());
216 m_log.errorStream() << "Xerces error while loading resource (" << (backup ? m_backing : m_source) << "): "
217 << msg.get() << logging::eol;
218 if (!backup && !m_backing.empty())
220 throw XMLParserException(msg.get());
222 catch (exception& e) {
223 m_log.errorStream() << "error while loading resource ("
224 << (m_source.empty() ? "inline" : (backup ? m_backing : m_source)) << "): " << e.what() << logging::eol;
225 if (!backup && !m_backing.empty())
231 Lockable* ReloadableXMLFile::lock()
238 // Check if we need to refresh.
241 struct _stat stat_buf;
242 if (_stat(m_source.c_str(), &stat_buf) != 0)
245 struct stat stat_buf;
246 if (stat(m_source.c_str(), &stat_buf) != 0)
249 if (m_filestamp>=stat_buf.st_mtime)
252 // Elevate lock and recheck.
253 m_log.debug("timestamp of local resource changed, elevating to a write lock");
256 if (m_filestamp>=stat_buf.st_mtime) {
257 // Somebody else handled it, just downgrade.
258 m_log.debug("update of local resource handled by another thread, downgrading lock");
264 // Update the timestamp regardless. No point in repeatedly trying.
265 m_filestamp=stat_buf.st_mtime;
266 m_log.info("change detected, reloading local resource...");
269 time_t now = time(NULL);
271 // Time to reload? If we have no data, filestamp is zero
272 // and there's no way current time is less than the interval.
273 if (now - m_filestamp < m_reloadInterval)
276 // Elevate lock and recheck.
277 m_log.debug("reload interval for remote resource elapsed, elevating to a write lock");
280 if (now - m_filestamp < m_reloadInterval) {
281 // Somebody else handled it, just downgrade.
282 m_log.debug("update of remote resource handled by another thread, downgrading lock");
289 m_log.info("reloading remote resource...");
294 // At this point we're holding the write lock, so make sure we pop it.
295 SharedLock lockwrap(m_lock,false);
296 pair<bool,DOMElement*> ret=load();
298 ret.second->getOwnerDocument()->release();
301 if (ex == HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED) {
302 m_log.info("remote resource (%s) unchanged from cached version", m_source.c_str());
305 // Shouldn't happen, we should only get codes intended to be gracefully handled.
306 m_log.crit("maintaining existing configuration, remote resource fetch returned atypical status code (%d)", ex);
309 catch (exception& ex) {
310 m_log.crit("maintaining existing configuration, error reloading resource (%s): %s", m_source.c_str(), ex.what());
313 // If we made it here, the swap may or may not have worked, but we need to relock.
314 m_log.debug("attempt to update resource complete, relocking");
319 void ReloadableXMLFile::unlock()
325 pair<bool,DOMElement*> ReloadableXMLFile::load()