Update copyright.
[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 <sys/types.h>\r
30 #include <sys/stat.h>\r
31 \r
32 #include <log4cpp/Category.hh>\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 \r
51 ReloadableXMLFile::ReloadableXMLFile(const DOMElement* e)\r
52     : m_root(e), m_local(true), m_validate(false), m_filestamp(0), m_lock(NULL)\r
53 {\r
54 #ifdef _DEBUG\r
55     NDC ndc("ReloadableXMLFile");\r
56 #endif\r
57     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".ReloadableXMLFile");\r
58 \r
59     // Establish source of data...\r
60     const XMLCh* source=e->getAttributeNS(NULL,uri);\r
61     if (!source || !*source) {\r
62         source=e->getAttributeNS(NULL,url);\r
63         if (!source || !*source) {\r
64             source=e->getAttributeNS(NULL,path);\r
65             if (!source || !*source) {\r
66                 source=e->getAttributeNS(NULL,pathname);\r
67                 if (!source || !*source) {\r
68                     source=e->getAttributeNS(NULL,file);\r
69                     if (!source || !*source) {\r
70                         source=e->getAttributeNS(NULL,filename);\r
71                     }\r
72                 }\r
73             }\r
74         }\r
75         else\r
76             m_local=false;\r
77     }\r
78     else\r
79         m_local=false;\r
80     \r
81     if (source && *source) {\r
82         const XMLCh* flag=e->getAttributeNS(NULL,validate);\r
83         m_validate=(XMLString::equals(flag,xmlconstants::XML_TRUE) || XMLString::equals(flag,xmlconstants::XML_ONE));\r
84 \r
85         auto_ptr_char temp(source);\r
86         m_source=temp.get();\r
87         \r
88         if (!m_local && !strstr(m_source.c_str(),"://")) {\r
89             log.warn("deprecated usage of uri/url attribute for a local resource, use path instead");\r
90             m_local=true;\r
91         }\r
92 \r
93         flag=e->getAttributeNS(NULL,reloadChanges);\r
94         if (!XMLString::equals(flag,xmlconstants::XML_FALSE) && !XMLString::equals(flag,xmlconstants::XML_ZERO)) {\r
95             if (m_local) {\r
96 #ifdef WIN32\r
97                 struct _stat stat_buf;\r
98                 if (_stat(m_source.c_str(), &stat_buf) == 0)\r
99                     m_filestamp=stat_buf.st_mtime;\r
100                 else\r
101                     m_local=false;\r
102 #else\r
103                 struct stat stat_buf;\r
104                 if (stat(m_source.c_str(), &stat_buf) == 0)\r
105                     m_filestamp=stat_buf.st_mtime;\r
106                 else\r
107                     m_local=false;\r
108 #endif\r
109             }\r
110             m_lock=RWLock::create();\r
111         }\r
112 \r
113         log.debug("using external resource (%s), will %smonitor for changes", m_source.c_str(), m_lock ? "" : "not ");\r
114     }\r
115     else\r
116         log.debug("no resource uri/path/name supplied, will load inline configuration");\r
117 }\r
118 \r
119 pair<bool,DOMElement*> ReloadableXMLFile::load()\r
120 {\r
121 #ifdef _DEBUG\r
122     NDC ndc("init");\r
123 #endif\r
124     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".ReloadableXMLFile");\r
125 \r
126     try {\r
127         if (m_source.empty()) {\r
128             // Data comes from the DOM we were handed.\r
129             log.debug("loading inline configuration...");\r
130             return make_pair(false,XMLHelper::getFirstChildElement(m_root));\r
131         }\r
132         else {\r
133             // Data comes from a file we have to parse.\r
134             log.debug("loading configuration from external resource...");\r
135 \r
136             DOMDocument* doc=NULL;\r
137             auto_ptr_XMLCh widenit(m_source.c_str());\r
138             if (m_local) {\r
139                 LocalFileInputSource src(widenit.get());\r
140                 Wrapper4InputSource dsrc(&src,false);\r
141                 if (m_validate)\r
142                     doc=XMLToolingConfig::getConfig().getValidatingParser().parse(dsrc);\r
143                 else\r
144                     doc=XMLToolingConfig::getConfig().getParser().parse(dsrc);\r
145             }\r
146             else {\r
147                 URLInputSource src(widenit.get());\r
148                 Wrapper4InputSource dsrc(&src,false);\r
149                 if (m_validate)\r
150                     doc=XMLToolingConfig::getConfig().getValidatingParser().parse(dsrc);\r
151                 else\r
152                     doc=XMLToolingConfig::getConfig().getParser().parse(dsrc);\r
153             }\r
154 \r
155             log.infoStream() << "loaded XML resource (" << m_source << ")" << CategoryStream::ENDLINE;\r
156             return make_pair(true,doc->getDocumentElement());\r
157         }\r
158     }\r
159     catch (XMLException& e) {\r
160         auto_ptr_char msg(e.getMessage());\r
161         log.critStream() << "Xerces error while loading resource (" << m_source << "): "\r
162             << msg.get() << CategoryStream::ENDLINE;\r
163         throw XMLParserException(msg.get());\r
164     }\r
165     catch (exception& e) {\r
166         log.critStream() << "error while loading configuration from ("\r
167             << (m_source.empty() ? "inline" : m_source) << "): " << e.what() << CategoryStream::ENDLINE;\r
168         throw;\r
169     }\r
170 }\r
171 \r
172 Lockable* ReloadableXMLFile::lock()\r
173 {\r
174     if (!m_lock)\r
175         return this;\r
176         \r
177     m_lock->rdlock();\r
178 \r
179     // Check if we need to refresh.\r
180     if (m_local) {\r
181 #ifdef WIN32\r
182         struct _stat stat_buf;\r
183         if (_stat(m_source.c_str(), &stat_buf) != 0)\r
184             return this;\r
185 #else\r
186         struct stat stat_buf;\r
187         if (stat(m_source.c_str(), &stat_buf) != 0)\r
188             return this;\r
189 #endif\r
190         if (m_filestamp>=stat_buf.st_mtime)\r
191             return this;\r
192         \r
193         // Elevate lock and recheck.\r
194         m_lock->unlock();\r
195         m_lock->wrlock();\r
196         if (m_filestamp>=stat_buf.st_mtime) {\r
197             // Somebody else handled it, just downgrade.\r
198             m_lock->unlock();\r
199             m_lock->rdlock();\r
200             return this;\r
201         }\r
202 \r
203         // Update the timestamp regardless. No point in repeatedly trying.\r
204         m_filestamp=stat_buf.st_mtime;\r
205         Category::getInstance(XMLTOOLING_LOGCAT".ReloadableXMLFile").info("change detected, reloading local resource...");\r
206     }\r
207     else {\r
208         if (isValid())\r
209             return this;\r
210 \r
211         // Elevate lock and recheck.\r
212         m_lock->unlock();\r
213         m_lock->wrlock();\r
214         if (isValid()) {\r
215             // Somebody else handled it, just downgrade.\r
216             m_lock->unlock();\r
217             m_lock->rdlock();\r
218             return this;\r
219         }\r
220         Category::getInstance(XMLTOOLING_LOGCAT".ReloadableXMLFile").info("local copy invalid, reloading remote resource...");\r
221     }\r
222     \r
223     // Do this once...\r
224     try {\r
225         // At this point we're holding the write lock, so make sure we pop it.\r
226         SharedLock lockwrap(m_lock,false);\r
227         pair<bool,DOMElement*> ret=load();\r
228         if (ret.first)\r
229             ret.second->getOwnerDocument()->release();\r
230     } catch (exception& ex) {\r
231         Category::getInstance(XMLTOOLING_LOGCAT".ReloadableXMLFile").crit(\r
232             "maintaining existing configuration, error reloading resource (%s): %s", m_source.c_str(), ex.what()\r
233             );\r
234     }\r
235     \r
236     // If we made it here, the swap may or may not have worked, but we need to relock.\r
237     m_lock->rdlock();\r
238     return this;\r
239 }\r