Tighten up validator namespace checking, rework registration.
[shibboleth/cpp-xmltooling.git] / xmltooling / io / AbstractXMLObjectUnmarshaller.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  * AbstractXMLObjectUnmarshaller.cpp
19  * 
20  * A thread-safe abstract unmarshaller.
21  */
22
23 #include "internal.h"
24 #include "exceptions.h"
25 #include "XMLObjectBuilder.h"
26 #include "io/AbstractXMLObjectUnmarshaller.h"
27 #include "util/NDC.h"
28 #include "util/XMLConstants.h"
29 #include "util/XMLHelper.h"
30
31 #include <xercesc/util/XMLUniDefs.hpp>
32 #include <log4cpp/Category.hh>
33
34 using namespace xmltooling;
35 using namespace log4cpp;
36 using namespace std;
37
38 #define XT_log (*static_cast<Category*>(m_log))
39
40 XMLObject* AbstractXMLObjectUnmarshaller::unmarshall(DOMElement* element, bool bindDocument)
41 {
42 #ifdef _DEBUG
43     xmltooling::NDC ndc("unmarshall");
44 #endif
45
46     if (getDOM() || hasParent())
47         throw UnmarshallingException("Object already contains data, it cannot be unmarshalled at this stage.");
48
49     if (!XMLString::equals(element->getNamespaceURI(),getElementQName().getNamespaceURI()) ||
50         !XMLString::equals(element->getLocalName(),getElementQName().getLocalPart())) {
51         throw UnmarshallingException("Unrecognized element supplied to implementation for unmarshalling.");
52     }
53
54     if (XT_log.isDebugEnabled()) {
55         auto_ptr_char dname(element->getNodeName());
56         XT_log.debug("unmarshalling DOM element (%s)", dname.get());
57     }
58
59     if (element->hasAttributes()) {
60         unmarshallAttributes(element);
61     }
62
63     unmarshallChildElements(element);
64
65     /* TODO: Signing
66     if (xmlObject instanceof SignableXMLObject) {
67         verifySignature(domElement, xmlObject);
68     }
69     */
70
71     setDOM(element,bindDocument);
72     return this;
73 }
74
75 void AbstractXMLObjectUnmarshaller::unmarshallAttributes(const DOMElement* domElement)
76 {
77 #ifdef _DEBUG
78     xmltooling::NDC ndc("unmarshallAttributes");
79 #endif
80     static const XMLCh type[]={chLatin_t, chLatin_y, chLatin_p, chLatin_e, chNull};
81
82     if (XT_log.isDebugEnabled()) {
83         auto_ptr_char dname(domElement->getNodeName());
84         XT_log.debug("unmarshalling attributes for DOM element (%s)", dname.get());
85     }
86
87     DOMNamedNodeMap* attributes = domElement->getAttributes();
88     if (!attributes) {
89         XT_log.debug("no attributes to unmarshall");
90         return;
91     }
92
93     DOMNode* childNode;
94     DOMAttr* attribute;
95     for (XMLSize_t i=0; i<attributes->getLength(); i++) {
96         childNode = attributes->item(i);
97
98         // The child node should always be an attribute, but just in case
99         if (childNode->getNodeType() != DOMNode::ATTRIBUTE_NODE) {
100             XT_log.debug("encountered child node of type %d in attribute list, ignoring it", childNode->getNodeType());
101             continue;
102         }
103
104         attribute = static_cast<DOMAttr*>(childNode);
105         
106         const XMLCh* nsuri=attribute->getNamespaceURI();
107         if (XMLString::equals(nsuri,XMLConstants::XMLNS_NS)) {
108             if (XMLString::equals(attribute->getLocalName(),XMLConstants::XMLNS_PREFIX)) {
109                 XT_log.debug("found default namespace declaration, adding it to the list of namespaces on the XMLObject");
110                 addNamespace(Namespace(attribute->getValue(), NULL, true));
111                 continue;
112             }
113             else {
114                 XT_log.debug("found namespace declaration, adding it to the list of namespaces on the XMLObject");
115                 addNamespace(Namespace(attribute->getValue(), attribute->getLocalName(), true));
116                 continue;
117             }
118         }
119         else if (XMLString::equals(nsuri,XMLConstants::XSI_NS) && XMLString::equals(attribute->getLocalName(),type)) {
120             XT_log.debug("skipping xsi:type declaration");
121             continue;
122         }
123         else if (nsuri && !XMLString::equals(nsuri,XMLConstants::XML_NS)) {
124             XT_log.debug("found namespace-qualified attribute, adding prefix to the list of namespaces on the XMLObject");
125             addNamespace(Namespace(nsuri, attribute->getPrefix()));
126         }
127
128         XT_log.debug("processing generic attribute");
129         processAttribute(attribute);
130     }
131 }
132
133 void AbstractXMLObjectUnmarshaller::unmarshallChildElements(const DOMElement* domElement)
134 {
135 #ifdef _DEBUG
136     xmltooling::NDC ndc("unmarshallChildElements");
137 #endif
138
139     if (XT_log.isDebugEnabled()) {
140         auto_ptr_char dname(domElement->getNodeName());
141         XT_log.debug("unmarshalling child elements of DOM element (%s)", dname.get());
142     }
143
144     DOMNodeList* childNodes = domElement->getChildNodes();
145     DOMNode* childNode;
146     if (!childNodes || childNodes->getLength()==0) {
147         XT_log.debug("element had no children");
148         return;
149     }
150
151     for (XMLSize_t i = 0; i < childNodes->getLength(); i++) {
152         childNode = childNodes->item(i);
153         if (childNode->getNodeType() == DOMNode::ELEMENT_NODE) {
154             const XMLObjectBuilder* builder = XMLObjectBuilder::getBuilder(static_cast<DOMElement*>(childNode));
155             if (!builder) {
156                 auto_ptr<QName> cname(XMLHelper::getNodeQName(childNode));
157                 XT_log.error("no default builder installed, found unknown child element (%s)", cname->toString().c_str());
158                 throw UnmarshallingException("Unmarshaller found unknown child element, but no default builder was found.");
159             }
160
161             if (XT_log.isDebugEnabled()) {
162                 auto_ptr<QName> cname(XMLHelper::getNodeQName(childNode));
163                 XT_log.debug("unmarshalling child element (%s)", cname->toString().c_str());
164             }
165
166             // Retain ownership of the unmarshalled child until it's processed by the parent.
167             auto_ptr<XMLObject> childObject(builder->buildFromElement(static_cast<DOMElement*>(childNode)));
168             processChildElement(childObject.get(), static_cast<DOMElement*>(childNode));
169             childObject.release();
170         }
171         else if (childNode->getNodeType() == DOMNode::TEXT_NODE) {
172             XT_log.debug("processing element content");
173             processElementContent(childNode->getNodeValue());
174         }
175     }
176 }
177
178 void AbstractXMLObjectUnmarshaller::processChildElement(XMLObject* child, const DOMElement* childRoot)
179 {
180     throw UnmarshallingException("Child elements are not permitted on this object.");
181 }