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