Reducing header overuse, non-inlining selected methods (CPPOST-35).
[shibboleth/cpp-xmltooling.git] / xmltooling / util / ReloadableXMLFile.cpp
1 /*
2  *  Copyright 2001-2009 Internet2
3  *
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
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 /**
18  * @file ReloadableXMLFile.cpp
19  *
20  * Base class for file-based XML configuration.
21  */
22
23 #include "internal.h"
24 #include "util/NDC.h"
25 #include "util/PathResolver.h"
26 #include "util/ReloadableXMLFile.h"
27 #include "util/Threads.h"
28 #include "util/XMLConstants.h"
29 #include "util/XMLHelper.h"
30
31 #include <fstream>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34
35 #include <xercesc/framework/LocalFileInputSource.hpp>
36 #include <xercesc/framework/Wrapper4InputSource.hpp>
37 #include <xercesc/util/XMLUniDefs.hpp>
38
39 using namespace xmltooling::logging;
40 using namespace xmltooling;
41 using namespace xercesc;
42 using namespace std;
43
44 static const XMLCh uri[] =              UNICODE_LITERAL_3(u,r,i);
45 static const XMLCh url[] =              UNICODE_LITERAL_3(u,r,l);
46 static const XMLCh path[] =             UNICODE_LITERAL_4(p,a,t,h);
47 static const XMLCh pathname[] =         UNICODE_LITERAL_8(p,a,t,h,n,a,m,e);
48 static const XMLCh file[] =             UNICODE_LITERAL_4(f,i,l,e);
49 static const XMLCh filename[] =         UNICODE_LITERAL_8(f,i,l,e,n,a,m,e);
50 static const XMLCh validate[] =         UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
51 static const XMLCh reloadChanges[] =    UNICODE_LITERAL_13(r,e,l,o,a,d,C,h,a,n,g,e,s);
52 static const XMLCh reloadInterval[] =   UNICODE_LITERAL_14(r,e,l,o,a,d,I,n,t,e,r,v,a,l);
53 static const XMLCh backingFilePath[] =  UNICODE_LITERAL_15(b,a,c,k,i,n,g,F,i,l,e,P,a,t,h);
54
55
56 ReloadableXMLFile::~ReloadableXMLFile()
57 {
58     delete m_lock;
59 }
60
61 ReloadableXMLFile::ReloadableXMLFile(const DOMElement* e, Category& log)
62     : m_root(e), m_local(true), m_validate(false), m_filestamp(0), m_reloadInterval(0), m_lock(NULL), m_log(log)
63 {
64 #ifdef _DEBUG
65     NDC ndc("ReloadableXMLFile");
66 #endif
67
68     // Establish source of data...
69     const XMLCh* source=e->getAttributeNS(NULL,uri);
70     if (!source || !*source) {
71         source=e->getAttributeNS(NULL,url);
72         if (!source || !*source) {
73             source=e->getAttributeNS(NULL,path);
74             if (!source || !*source) {
75                 source=e->getAttributeNS(NULL,pathname);
76                 if (!source || !*source) {
77                     source=e->getAttributeNS(NULL,file);
78                     if (!source || !*source) {
79                         source=e->getAttributeNS(NULL,filename);
80                     }
81                 }
82             }
83         }
84         else
85             m_local=false;
86     }
87     else
88         m_local=false;
89
90     if (source && *source) {
91         const XMLCh* flag=e->getAttributeNS(NULL,validate);
92         m_validate=(XMLString::equals(flag,xmlconstants::XML_TRUE) || XMLString::equals(flag,xmlconstants::XML_ONE));
93
94         auto_ptr_char temp(source);
95         m_source=temp.get();
96
97         if (!m_local && !strstr(m_source.c_str(),"://")) {
98             log.warn("deprecated usage of uri/url attribute for a local resource, use path instead");
99             m_local=true;
100         }
101
102         if (m_local) {
103             XMLToolingConfig::getConfig().getPathResolver()->resolve(m_source, PathResolver::XMLTOOLING_CFG_FILE);
104
105             flag=e->getAttributeNS(NULL,reloadChanges);
106             if (!XMLString::equals(flag,xmlconstants::XML_FALSE) && !XMLString::equals(flag,xmlconstants::XML_ZERO)) {
107 #ifdef WIN32
108                 struct _stat stat_buf;
109                 if (_stat(m_source.c_str(), &stat_buf) == 0)
110 #else
111                 struct stat stat_buf;
112                 if (stat(m_source.c_str(), &stat_buf) == 0)
113 #endif
114                     m_filestamp=stat_buf.st_mtime;
115                 else
116                     throw IOException("Unable to access local file ($1)", params(1,m_source.c_str()));
117                 m_lock=RWLock::create();
118             }
119             log.debug("using local resource (%s), will %smonitor for changes", m_source.c_str(), m_lock ? "" : "not ");
120         }
121         else {
122             log.debug("using remote resource (%s)", m_source.c_str());
123             source = e->getAttributeNS(NULL,backingFilePath);
124             if (source && *source) {
125                 auto_ptr_char temp2(source);
126                 m_backing=temp2.get();
127                 XMLToolingConfig::getConfig().getPathResolver()->resolve(m_backing, PathResolver::XMLTOOLING_RUN_FILE);
128                 log.debug("backup remote resource with (%s)", m_backing.c_str());
129             }
130             source = e->getAttributeNS(NULL,reloadInterval);
131             if (source && *source) {
132                 m_reloadInterval = XMLString::parseInt(source);
133                 if (m_reloadInterval > 0) {
134                     m_log.debug("will reload remote resource at most every %d seconds", m_reloadInterval);
135                     m_lock=RWLock::create();
136                 }
137             }
138             m_filestamp = time(NULL);   // assume it gets loaded initially
139         }
140     }
141     else {
142         log.debug("no resource uri/path/name supplied, will load inline configuration");
143     }
144 }
145
146 pair<bool,DOMElement*> ReloadableXMLFile::load(bool backup)
147 {
148 #ifdef _DEBUG
149     NDC ndc("init");
150 #endif
151
152     try {
153         if (m_source.empty()) {
154             // Data comes from the DOM we were handed.
155             m_log.debug("loading inline configuration...");
156             return make_pair(false,XMLHelper::getFirstChildElement(m_root));
157         }
158         else {
159             // Data comes from a file we have to parse.
160             if (backup)
161                 m_log.warn("using local backup of remote resource");
162             else
163                 m_log.debug("loading configuration from external resource...");
164
165             DOMDocument* doc=NULL;
166             if (m_local || backup) {
167                 auto_ptr_XMLCh widenit(backup ? m_backing.c_str() : m_source.c_str());
168                 LocalFileInputSource src(widenit.get());
169                 Wrapper4InputSource dsrc(&src,false);
170                 if (m_validate)
171                     doc=XMLToolingConfig::getConfig().getValidatingParser().parse(dsrc);
172                 else
173                     doc=XMLToolingConfig::getConfig().getParser().parse(dsrc);
174             }
175             else {
176                 URLInputSource src(m_root);
177                 Wrapper4InputSource dsrc(&src,false);
178                 if (m_validate)
179                     doc=XMLToolingConfig::getConfig().getValidatingParser().parse(dsrc);
180                 else
181                     doc=XMLToolingConfig::getConfig().getParser().parse(dsrc);
182             }
183
184             m_log.infoStream() << "loaded XML resource (" << (backup ? m_backing : m_source) << ")" << logging::eol;
185
186             if (!backup && !m_backing.empty()) {
187                 m_log.debug("backing up remote resource to (%s)", m_backing.c_str());
188                 try {
189                     ofstream backer(m_backing.c_str());
190                     backer << *doc;
191                 }
192                 catch (exception& ex) {
193                     m_log.crit("exception while backing up resource: %s", ex.what());
194                 }
195             }
196
197             return make_pair(true,doc->getDocumentElement());
198         }
199     }
200     catch (XMLException& e) {
201         auto_ptr_char msg(e.getMessage());
202         m_log.errorStream() << "Xerces error while loading resource (" << (backup ? m_backing : m_source) << "): "
203             << msg.get() << logging::eol;
204         if (!backup && !m_backing.empty())
205             return load(true);
206         throw XMLParserException(msg.get());
207     }
208     catch (exception& e) {
209         m_log.errorStream() << "error while loading configuration from ("
210             << (m_source.empty() ? "inline" : (backup ? m_backing : m_source)) << "): " << e.what() << logging::eol;
211         if (!backup && !m_backing.empty())
212             return load(true);
213         throw;
214     }
215 }
216
217 Lockable* ReloadableXMLFile::lock()
218 {
219     if (!m_lock)
220         return this;
221
222     m_lock->rdlock();
223
224     // Check if we need to refresh.
225     if (m_local) {
226 #ifdef WIN32
227         struct _stat stat_buf;
228         if (_stat(m_source.c_str(), &stat_buf) != 0)
229             return this;
230 #else
231         struct stat stat_buf;
232         if (stat(m_source.c_str(), &stat_buf) != 0)
233             return this;
234 #endif
235         if (m_filestamp>=stat_buf.st_mtime)
236             return this;
237
238         // Elevate lock and recheck.
239         m_log.debug("timestamp of local resource changed, elevating to a write lock");
240         m_lock->unlock();
241         m_lock->wrlock();
242         if (m_filestamp>=stat_buf.st_mtime) {
243             // Somebody else handled it, just downgrade.
244             m_log.debug("update of local resource handled by another thread, downgrading lock");
245             m_lock->unlock();
246             m_lock->rdlock();
247             return this;
248         }
249
250         // Update the timestamp regardless. No point in repeatedly trying.
251         m_filestamp=stat_buf.st_mtime;
252         m_log.info("change detected, reloading local resource...");
253     }
254     else {
255         time_t now = time(NULL);
256
257         // Time to reload? If we have no data, filestamp is zero
258         // and there's no way current time is less than the interval.
259         if (now - m_filestamp < m_reloadInterval)
260             return this;
261
262         // Elevate lock and recheck.
263         m_log.debug("reload interval for remote resource elapsed, elevating to a write lock");
264         m_lock->unlock();
265         m_lock->wrlock();
266         if (now - m_filestamp < m_reloadInterval) {
267             // Somebody else handled it, just downgrade.
268             m_log.debug("update of remote resource handled by another thread, downgrading lock");
269             m_lock->unlock();
270             m_lock->rdlock();
271             return this;
272         }
273
274         m_filestamp = now;
275         m_log.info("reloading remote resource...");
276     }
277
278     // Do this once...
279     try {
280         // At this point we're holding the write lock, so make sure we pop it.
281         SharedLock lockwrap(m_lock,false);
282         pair<bool,DOMElement*> ret=load();
283         if (ret.first)
284             ret.second->getOwnerDocument()->release();
285     } catch (exception& ex) {
286         m_log.crit("maintaining existing configuration, error reloading resource (%s): %s", m_source.c_str(), ex.what());
287     }
288
289     // If we made it here, the swap may or may not have worked, but we need to relock.
290     m_log.debug("attempt to update resource complete, relocking");
291     m_lock->rdlock();
292     return this;
293 }
294
295 void ReloadableXMLFile::unlock()
296 {
297     if (m_lock)
298         m_lock->unlock();
299 }
300
301 pair<bool,DOMElement*> ReloadableXMLFile::load()
302 {
303     return load(false);
304 }