Implement metadata lookup by artifact, refactored metadata indexing.
[shibboleth/cpp-opensaml.git] / saml / saml2 / metadata / impl / FilesystemMetadataProvider.cpp
1 /*
2  *  Copyright 2001-2006 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  * FilesystemMetadataProvider.cpp
19  * 
20  * Supplies metadata from a local file, detecting and reloading changes.
21  */
22
23 #include "internal.h"
24 #include "saml2/metadata/MetadataProvider.h"
25
26 #include <ctime>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <log4cpp/Category.hh>
30 #include <xercesc/framework/LocalFileInputSource.hpp>
31 #include <xercesc/framework/Wrapper4InputSource.hpp>
32 #include <xmltooling/util/NDC.h>
33 #include <xmltooling/util/Threads.h>
34
35 using namespace opensaml::saml2md;
36 using namespace xmltooling;
37 using namespace log4cpp;
38 using namespace std;
39
40 namespace opensaml {
41     namespace saml2md {
42         
43         
44         class SAML_DLLLOCAL FilesystemMetadataProvider : public MetadataProvider
45         {
46         public:
47             FilesystemMetadataProvider(const DOMElement* e);
48             ~FilesystemMetadataProvider();
49
50             Lockable* lock();
51             void unlock() {
52                 if (m_lock)
53                     m_lock->unlock();
54             }
55
56             void init();
57
58             const XMLObject* getMetadata() const {
59                 return m_object;
60             }
61
62         private:
63             XMLObject* load() const;
64             void index();
65         
66             const DOMElement* m_root; // survives only until init() method is done
67             std::string m_source;
68             time_t m_filestamp;
69             bool m_validate;
70             RWLock* m_lock;
71             XMLObject* m_object;
72         }; 
73
74         MetadataProvider* SAML_DLLLOCAL FilesystemMetadataProviderFactory(const DOMElement* const & e)
75         {
76             return new FilesystemMetadataProvider(e);
77         }
78
79     };
80 };
81
82 static const XMLCh uri[] =      UNICODE_LITERAL_3(u,r,i);
83 static const XMLCh url[] =      UNICODE_LITERAL_3(u,r,l);
84 static const XMLCh path[] =     UNICODE_LITERAL_4(p,a,t,h);
85 static const XMLCh pathname[] = UNICODE_LITERAL_8(p,a,t,h,n,a,m,e);
86 static const XMLCh file[] =     UNICODE_LITERAL_4(f,i,l,e);
87 static const XMLCh filename[] = UNICODE_LITERAL_8(f,i,l,e,n,a,m,e);
88 static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
89
90 FilesystemMetadataProvider::FilesystemMetadataProvider(const DOMElement* e)
91     : MetadataProvider(e), m_root(e), m_filestamp(0), m_validate(false), m_lock(NULL), m_object(NULL)
92 {
93 #ifdef _DEBUG
94     NDC ndc("FilesystemMetadataProvider");
95 #endif
96     Category& log=Category::getInstance(SAML_LOGCAT".Metadata");
97
98     // Establish source of data...
99     const XMLCh* source=e->getAttributeNS(NULL,uri);
100     if (!source || !*source) {
101         source=e->getAttributeNS(NULL,url);
102         if (!source || !*source) {
103             source=e->getAttributeNS(NULL,path);
104             if (!source || !*source) {
105                 source=e->getAttributeNS(NULL,pathname);
106                 if (!source || !*source) {
107                     source=e->getAttributeNS(NULL,file);
108                     if (!source || !*source) {
109                         source=e->getAttributeNS(NULL,filename);
110                     }
111                 }
112             }
113         }
114     }
115     
116     if (source && *source) {
117         const XMLCh* valflag=e->getAttributeNS(NULL,validate);
118         m_validate=(XMLString::equals(valflag,XMLConstants::XML_TRUE) || XMLString::equals(valflag,XMLConstants::XML_ONE));
119         
120         auto_ptr_char temp(source);
121         m_source=temp.get();
122         log.debug("using external metadata file (%s)", temp.get());
123
124 #ifdef WIN32
125         struct _stat stat_buf;
126         if (_stat(m_source.c_str(), &stat_buf) == 0)
127 #else
128         struct stat stat_buf;
129         if (stat(m_source.c_str(), &stat_buf) == 0)
130 #endif
131             m_filestamp=stat_buf.st_mtime;
132         m_lock=RWLock::create();
133     }
134     else
135         log.debug("no file path/name supplied, will look for metadata inline");
136 }
137
138 FilesystemMetadataProvider::~FilesystemMetadataProvider()
139 {
140     delete m_lock;
141     delete m_object;
142 }
143
144 void FilesystemMetadataProvider::init()
145 {
146     m_object=load();
147     index();
148 }
149
150 XMLObject* FilesystemMetadataProvider::load() const
151 {
152 #ifdef _DEBUG
153     NDC ndc("load");
154 #endif
155     Category& log=Category::getInstance(SAML_LOGCAT".Metadata");
156     
157     try {
158         XMLObject* xmlObject=NULL;
159         
160         if (!m_source.empty()) {
161             // Data comes from a file we have to parse.
162             log.debug("loading metadata from file...");
163             auto_ptr_XMLCh widenit(m_source.c_str());
164             LocalFileInputSource src(widenit.get());
165             Wrapper4InputSource dsrc(&src,false);
166             DOMDocument* doc=NULL;
167             if (m_validate)
168                 doc=XMLToolingConfig::getConfig().getValidatingParser().parse(dsrc);
169             else
170                 doc=XMLToolingConfig::getConfig().getParser().parse(dsrc);
171             XercesJanitor<DOMDocument> docjanitor(doc);
172             log.infoStream() << "loaded and parsed XML file (" << m_source << ")" << CategoryStream::ENDLINE;
173             
174             // Unmarshall objects, binding the document.
175             xmlObject = XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true);
176             docjanitor.release();
177         }
178         else {
179             // Data comes from the DOM we were handed.
180             log.debug("loading inline metadata...");
181             DOMElement* child = XMLHelper::getFirstChildElement(m_root);
182             if (!child)
183                 throw XMLToolingException("No metadata was found inline.");
184             xmlObject = XMLObjectBuilder::buildOneFromElement(child);
185         }
186         
187         auto_ptr<XMLObject> xmlObjectPtr(xmlObject);
188         
189         doFilters(*xmlObject);
190         
191         xmlObjectPtr->releaseThisAndChildrenDOM();
192         xmlObjectPtr->setDocument(NULL);
193         return xmlObjectPtr.release();
194     }
195     catch (XMLException& e) {
196         auto_ptr_char msg(e.getMessage());
197         log.errorStream() << "Xerces parser error while loading metadata from ("
198             << (m_source.empty() ? "inline" : m_source) << "): " << msg.get() << CategoryStream::ENDLINE;
199         throw XMLParserException(msg.get());
200     }
201     catch (XMLToolingException& e) {
202         log.errorStream() << "error while loading metadata from ("
203             << (m_source.empty() ? "inline" : m_source) << "): " << e.what() << CategoryStream::ENDLINE;
204         throw;
205     }
206 }
207
208 Lockable* FilesystemMetadataProvider::lock()
209 {
210     if (!m_lock)
211         return this;
212         
213     m_lock->rdlock();
214
215     // Check if we need to refresh.
216 #ifdef WIN32
217     struct _stat stat_buf;
218     if (_stat(m_source.c_str(), &stat_buf) == 0)
219 #else
220     struct stat stat_buf;
221     if (stat(m_source.c_str(), &stat_buf) == 0)
222 #endif
223     {
224         if (m_filestamp>0 && m_filestamp<stat_buf.st_mtime) {
225             // Elevate lock and recheck.
226             m_lock->unlock();
227             m_lock->wrlock();
228             if (m_filestamp>0 && m_filestamp<stat_buf.st_mtime) {
229                 SharedLock lockwrap(m_lock,false);  // pops write lock
230                 try {
231                     // Update the timestamp regardless. No point in repeatedly trying.
232                     m_filestamp=stat_buf.st_mtime;
233                     XMLObject* newstuff = load();
234                     delete m_object;
235                     m_object = newstuff;
236                     index();
237                 }
238                 catch(XMLToolingException& e) {
239                     Category::getInstance(SAML_LOGCAT".Metadata").error("failed to reload metadata from file, sticking with what we have: %s", e.what());
240                 }
241             }
242             else {
243                 m_lock->unlock();
244             }
245             m_lock->rdlock();
246         }
247     }
248     return this;
249 }
250
251 void FilesystemMetadataProvider::index()
252 {
253     EntitiesDescriptor* group=dynamic_cast<EntitiesDescriptor*>(m_object);
254     if (group) {
255         MetadataProvider::index(group, SAMLTIME_MAX);
256         return;
257     }
258     EntityDescriptor* site=dynamic_cast<EntityDescriptor*>(m_object);
259     MetadataProvider::index(site, SAMLTIME_MAX);
260 }