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