e33a673007defc2443dafd7b159da29e7da065ee
[shibboleth/cpp-xmltooling.git] / xmltooling / io / AbstractXMLObjectUnmarshaller.cpp
1 /*
2 *  Copyright 2001-2010 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
33 using namespace xmlconstants;
34 using namespace xmltooling;
35 using namespace xercesc;
36 using namespace std;
37
38 AbstractXMLObjectUnmarshaller::AbstractXMLObjectUnmarshaller()
39 {
40 }
41
42 AbstractXMLObjectUnmarshaller::~AbstractXMLObjectUnmarshaller()
43 {
44 }
45
46 XMLObject* AbstractXMLObjectUnmarshaller::unmarshall(DOMElement* element, bool bindDocument)
47 {
48 #ifdef _DEBUG
49     xmltooling::NDC ndc("unmarshall");
50 #endif
51
52     if (getDOM() || hasParent())
53         throw UnmarshallingException("Object already contains data, it cannot be unmarshalled at this stage.");
54
55     if (!XMLString::equals(element->getNamespaceURI(),getElementQName().getNamespaceURI()) ||
56         !XMLString::equals(element->getLocalName(),getElementQName().getLocalPart())) {
57         throw UnmarshallingException("Unrecognized element supplied to implementation for unmarshalling.");
58     }
59
60     if (m_log.isDebugEnabled()) {
61         auto_ptr_char dname(element->getNodeName());
62         m_log.debug("unmarshalling DOM element (%s)", dname.get());
63     }
64
65     if (element->hasAttributes()) {
66         unmarshallAttributes(element);
67     }
68
69     unmarshallContent(element);
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
81     if (m_log.isDebugEnabled()) {
82         auto_ptr_char dname(domElement->getNodeName());
83         m_log.debug("unmarshalling attributes for DOM element (%s)", dname.get());
84     }
85
86     DOMNamedNodeMap* attributes = domElement->getAttributes();
87     if (!attributes) {
88         m_log.debug("no attributes to unmarshall");
89         return;
90     }
91
92     DOMNode* childNode;
93     DOMAttr* attribute;
94     for (XMLSize_t i=0; i<attributes->getLength(); i++) {
95         childNode = attributes->item(i);
96
97         // The child node should always be an attribute, but just in case
98         if (childNode->getNodeType() != DOMNode::ATTRIBUTE_NODE) {
99             m_log.debug("encountered child node of type %d in attribute list, ignoring it", childNode->getNodeType());
100             continue;
101         }
102
103         attribute = static_cast<DOMAttr*>(childNode);
104         
105         const XMLCh* nsuri=attribute->getNamespaceURI();
106         if (XMLString::equals(nsuri,XMLNS_NS)) {
107             if (XMLString::equals(attribute->getLocalName(),XMLNS_PREFIX)) {
108                 m_log.debug("found default namespace declaration, adding it to the list of namespaces on the XMLObject");
109                 addNamespace(Namespace(attribute->getValue(), nullptr, true));
110             }
111             else if (XMLString::equals(attribute->getLocalName(),XML_PREFIX) && XMLString::equals(attribute->getNodeValue(),XML_NS)) {
112                 m_log.debug("found standard xml prefix declaration, ignoring as superfluous");
113             }
114             else {
115                 m_log.debug("found namespace declaration, adding it to the list of namespaces on the XMLObject");
116                 addNamespace(Namespace(attribute->getValue(), attribute->getLocalName(), true));
117             }
118             continue;
119         }
120         else if (XMLString::equals(nsuri,XSI_NS)) {
121             static const XMLCh type[]= UNICODE_LITERAL_4(t,y,p,e);
122             static const XMLCh schemaLocation[]= UNICODE_LITERAL_14(s,c,h,e,m,a,L,o,c,a,t,i,o,n);
123             static const XMLCh noNamespaceSchemaLocation[]= UNICODE_LITERAL_25(n,o,N,a,m,e,s,p,a,c,e,S,c,h,e,m,a,L,o,c,a,t,i,o,n);
124             static const XMLCh _nil[]= UNICODE_LITERAL_3(n,i,l);
125             if (XMLString::equals(attribute->getLocalName(),type)) {
126                 m_log.debug("skipping xsi:type declaration");
127                 continue;
128             }
129             else if (XMLString::equals(attribute->getLocalName(),schemaLocation)) {
130                 m_log.debug("storing off xsi:schemaLocation attribute");
131                 if (m_schemaLocation)
132                     XMLString::release(&m_schemaLocation);
133                 m_schemaLocation=XMLString::replicate(attribute->getValue());
134                 continue;
135             }
136             else if (XMLString::equals(attribute->getLocalName(),noNamespaceSchemaLocation)) {
137                 m_log.debug("storing off xsi:noNamespaceSchemaLocation attribute");
138                 if (m_noNamespaceSchemaLocation)
139                     XMLString::release(&m_noNamespaceSchemaLocation);
140                 m_schemaLocation=XMLString::replicate(attribute->getValue());
141                 m_noNamespaceSchemaLocation=XMLString::replicate(attribute->getValue());
142                 continue;
143             }
144             else if (XMLString::equals(attribute->getLocalName(), _nil)) {
145                 m_log.debug("processing xsi:nil attribute");
146                 setNil(attribute->getValue());
147                 continue;
148             }
149             // Note that the prefix is visibly used.
150             addNamespace(Namespace(nsuri, attribute->getPrefix(), false, Namespace::VisiblyUsed));
151         }
152         else if (nsuri && !XMLString::equals(nsuri,XML_NS)) {
153             m_log.debug("found namespace-qualified attribute, adding prefix to the list of visible namespaces on the XMLObject");
154             addNamespace(Namespace(nsuri, attribute->getPrefix(), false, Namespace::VisiblyUsed));
155         }
156
157         m_log.debug("processing generic attribute");
158         processAttribute(attribute);
159     }
160 }
161
162 void AbstractXMLObjectUnmarshaller::unmarshallContent(const DOMElement* domElement)
163 {
164 #ifdef _DEBUG
165     xmltooling::NDC ndc("unmarshallContent");
166 #endif
167
168     if (m_log.isDebugEnabled()) {
169         auto_ptr_char dname(domElement->getNodeName());
170         m_log.debug("unmarshalling child nodes of DOM element (%s)", dname.get());
171     }
172
173     DOMNode* childNode = domElement->getFirstChild();
174     if (!childNode) {
175         m_log.debug("element had no children");
176         return;
177     }
178
179     unsigned int position = 0;
180     while (childNode) {
181         if (childNode->getNodeType() == DOMNode::ELEMENT_NODE) {
182             const XMLObjectBuilder* builder = XMLObjectBuilder::getBuilder(static_cast<DOMElement*>(childNode));
183             if (!builder) {
184                 auto_ptr<QName> cname(XMLHelper::getNodeQName(childNode));
185                 m_log.error("no default builder installed, found unknown child element (%s)", cname->toString().c_str());
186                 throw UnmarshallingException("Unmarshaller found unknown child element, but no default builder was found.");
187             }
188
189             if (m_log.isDebugEnabled()) {
190                 auto_ptr<QName> cname(XMLHelper::getNodeQName(childNode));
191                 m_log.debug("unmarshalling child element (%s)", cname->toString().c_str());
192             }
193
194             // Retain ownership of the unmarshalled child until it's processed by the parent.
195             auto_ptr<XMLObject> childObject(builder->buildFromElement(static_cast<DOMElement*>(childNode)));
196             processChildElement(childObject.get(), static_cast<DOMElement*>(childNode));
197             childObject.release();
198             
199             // Advance the text node position marker.
200             ++position;
201         }
202         else if (childNode->getNodeType() == DOMNode::TEXT_NODE || childNode->getNodeType() == DOMNode::CDATA_SECTION_NODE) {
203             m_log.debug("processing text content at position (%d)", position);
204             setTextContent(childNode->getNodeValue(), position);
205         }
206         
207         childNode = childNode->getNextSibling();
208     }
209 }
210
211 void AbstractXMLObjectUnmarshaller::processChildElement(XMLObject* child, const DOMElement* childRoot)
212 {
213     throw UnmarshallingException("Invalid child element: $1",params(1,child->getElementQName().toString().c_str()));
214 }
215
216 void AbstractXMLObjectUnmarshaller::processAttribute(const DOMAttr* attribute)
217 {
218     auto_ptr<QName> q(XMLHelper::getNodeQName(attribute));
219     throw UnmarshallingException("Invalid attribute: $1",params(1,q->toString().c_str()));
220 }