Merge branch '1.x' of ssh://authdev.it.ohio-state.edu/~scantor/git/cpp-xmltooling...
[shibboleth/cpp-xmltooling.git] / xmltooling / io / AbstractXMLObjectUnmarshaller.cpp
1 /**
2  * Licensed to the University Corporation for Advanced Internet
3  * Development, Inc. (UCAID) under one or more contributor license
4  * agreements. See the NOTICE file distributed with this work for
5  * additional information regarding copyright ownership.
6  *
7  * UCAID licenses this file to you under the Apache License,
8  * Version 2.0 (the "License"); you may not use this file except
9  * in compliance with the License. You may obtain a copy of the
10  * License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17  * either express or implied. See the License for the specific
18  * language governing permissions and limitations under the License.
19  */
20
21 /**
22  * AbstractXMLObjectUnmarshaller.cpp
23  * 
24  * A thread-safe abstract unmarshaller.
25  */
26
27 #include "internal.h"
28 #include "exceptions.h"
29 #include "XMLObjectBuilder.h"
30 #include "io/AbstractXMLObjectUnmarshaller.h"
31 #include "util/NDC.h"
32 #include "util/XMLConstants.h"
33 #include "util/XMLHelper.h"
34
35 #include <xercesc/util/XMLUniDefs.hpp>
36
37 using namespace xmlconstants;
38 using namespace xmltooling;
39 using namespace xercesc;
40 using namespace std;
41
42 AbstractXMLObjectUnmarshaller::AbstractXMLObjectUnmarshaller()
43 {
44 }
45
46 AbstractXMLObjectUnmarshaller::~AbstractXMLObjectUnmarshaller()
47 {
48 }
49
50 XMLObject* AbstractXMLObjectUnmarshaller::unmarshall(DOMElement* element, bool bindDocument)
51 {
52 #ifdef _DEBUG
53     xmltooling::NDC ndc("unmarshall");
54 #endif
55
56     if (getDOM() || hasParent())
57         throw UnmarshallingException("Object already contains data, it cannot be unmarshalled at this stage.");
58
59     if (!XMLString::equals(element->getNamespaceURI(),getElementQName().getNamespaceURI()) ||
60         !XMLString::equals(element->getLocalName(),getElementQName().getLocalPart())) {
61         throw UnmarshallingException("Unrecognized element supplied to implementation for unmarshalling.");
62     }
63
64     if (m_log.isDebugEnabled()) {
65         auto_ptr_char dname(element->getNodeName());
66         m_log.debug("unmarshalling DOM element (%s)", dname.get());
67     }
68
69     if (element->hasAttributes()) {
70         unmarshallAttributes(element);
71     }
72
73     unmarshallContent(element);
74
75     setDOM(element,bindDocument);
76     return this;
77 }
78
79 void AbstractXMLObjectUnmarshaller::unmarshallAttributes(const DOMElement* domElement)
80 {
81 #ifdef _DEBUG
82     xmltooling::NDC ndc("unmarshallAttributes");
83 #endif
84
85     if (m_log.isDebugEnabled()) {
86         auto_ptr_char dname(domElement->getNodeName());
87         m_log.debug("unmarshalling attributes for DOM element (%s)", dname.get());
88     }
89
90     DOMNamedNodeMap* attributes = domElement->getAttributes();
91     if (!attributes) {
92         m_log.debug("no attributes to unmarshall");
93         return;
94     }
95
96     DOMNode* childNode;
97     DOMAttr* attribute;
98     for (XMLSize_t i=0; i<attributes->getLength(); i++) {
99         childNode = attributes->item(i);
100
101         // The child node should always be an attribute, but just in case
102         if (childNode->getNodeType() != DOMNode::ATTRIBUTE_NODE) {
103             m_log.debug("encountered child node of type %d in attribute list, ignoring it", childNode->getNodeType());
104             continue;
105         }
106
107         attribute = static_cast<DOMAttr*>(childNode);
108         
109         const XMLCh* nsuri=attribute->getNamespaceURI();
110         if (XMLString::equals(nsuri,XMLNS_NS)) {
111             if (XMLString::equals(attribute->getLocalName(),XMLNS_PREFIX)) {
112                 m_log.debug("found default namespace declaration, adding it to the list of namespaces on the XMLObject");
113                 addNamespace(Namespace(attribute->getValue(), nullptr, true));
114             }
115             else if (XMLString::equals(attribute->getLocalName(),XML_PREFIX) && XMLString::equals(attribute->getNodeValue(),XML_NS)) {
116                 m_log.debug("found standard xml prefix declaration, ignoring as superfluous");
117             }
118             else {
119                 m_log.debug("found namespace declaration, adding it to the list of namespaces on the XMLObject");
120                 addNamespace(Namespace(attribute->getValue(), attribute->getLocalName(), true));
121             }
122             continue;
123         }
124         else if (XMLString::equals(nsuri,XSI_NS)) {
125             static const XMLCh type[]= UNICODE_LITERAL_4(t,y,p,e);
126             static const XMLCh schemaLocation[]= UNICODE_LITERAL_14(s,c,h,e,m,a,L,o,c,a,t,i,o,n);
127             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);
128             static const XMLCh _nil[]= UNICODE_LITERAL_3(n,i,l);
129             if (XMLString::equals(attribute->getLocalName(),type)) {
130                 m_log.debug("skipping xsi:type declaration");
131                 continue;
132             }
133             else if (XMLString::equals(attribute->getLocalName(),schemaLocation)) {
134                 m_log.debug("storing off xsi:schemaLocation attribute");
135                 if (m_schemaLocation)
136                     XMLString::release(&m_schemaLocation);
137                 m_schemaLocation=XMLString::replicate(attribute->getValue());
138                 continue;
139             }
140             else if (XMLString::equals(attribute->getLocalName(),noNamespaceSchemaLocation)) {
141                 m_log.debug("storing off xsi:noNamespaceSchemaLocation attribute");
142                 if (m_noNamespaceSchemaLocation)
143                     XMLString::release(&m_noNamespaceSchemaLocation);
144                 m_schemaLocation=XMLString::replicate(attribute->getValue());
145                 m_noNamespaceSchemaLocation=XMLString::replicate(attribute->getValue());
146                 continue;
147             }
148             else if (XMLString::equals(attribute->getLocalName(), _nil)) {
149                 m_log.debug("processing xsi:nil attribute");
150                 setNil(attribute->getValue());
151                 continue;
152             }
153             // Note that the prefix is visibly used.
154             addNamespace(Namespace(nsuri, attribute->getPrefix(), false, Namespace::VisiblyUsed));
155         }
156         else if (nsuri && !XMLString::equals(nsuri,XML_NS)) {
157             m_log.debug("found namespace-qualified attribute, adding prefix to the list of visible namespaces on the XMLObject");
158             addNamespace(Namespace(nsuri, attribute->getPrefix(), false, Namespace::VisiblyUsed));
159         }
160
161         m_log.debug("processing generic attribute");
162         processAttribute(attribute);
163     }
164 }
165
166 void AbstractXMLObjectUnmarshaller::unmarshallContent(const DOMElement* domElement)
167 {
168 #ifdef _DEBUG
169     xmltooling::NDC ndc("unmarshallContent");
170 #endif
171
172     if (m_log.isDebugEnabled()) {
173         auto_ptr_char dname(domElement->getNodeName());
174         m_log.debug("unmarshalling child nodes of DOM element (%s)", dname.get());
175     }
176
177     DOMNode* childNode = domElement->getFirstChild();
178     if (!childNode) {
179         m_log.debug("element had no children");
180         return;
181     }
182
183     unsigned int position = 0;
184     while (childNode) {
185         if (childNode->getNodeType() == DOMNode::ELEMENT_NODE) {
186             const XMLObjectBuilder* builder = XMLObjectBuilder::getBuilder(static_cast<DOMElement*>(childNode));
187             if (!builder) {
188                 auto_ptr<QName> cname(XMLHelper::getNodeQName(childNode));
189                 m_log.error("no default builder installed, found unknown child element (%s)", cname->toString().c_str());
190                 throw UnmarshallingException("Unmarshaller found unknown child element, but no default builder was found.");
191             }
192
193             if (m_log.isDebugEnabled()) {
194                 auto_ptr<QName> cname(XMLHelper::getNodeQName(childNode));
195                 m_log.debug("unmarshalling child element (%s)", cname->toString().c_str());
196             }
197
198             // Retain ownership of the unmarshalled child until it's processed by the parent.
199             auto_ptr<XMLObject> childObject(builder->buildFromElement(static_cast<DOMElement*>(childNode)));
200             processChildElement(childObject.get(), static_cast<DOMElement*>(childNode));
201             childObject.release();
202             
203             // Advance the text node position marker.
204             ++position;
205         }
206         else if (childNode->getNodeType() == DOMNode::TEXT_NODE || childNode->getNodeType() == DOMNode::CDATA_SECTION_NODE) {
207             m_log.debug("processing text content at position (%d)", position);
208             setTextContent(childNode->getNodeValue(), position);
209         }
210         
211         childNode = childNode->getNextSibling();
212     }
213 }
214
215 void AbstractXMLObjectUnmarshaller::processChildElement(XMLObject* child, const DOMElement* childRoot)
216 {
217     throw UnmarshallingException("Invalid child element: $1",params(1,child->getElementQName().toString().c_str()));
218 }
219
220 void AbstractXMLObjectUnmarshaller::processAttribute(const DOMAttr* attribute)
221 {
222     auto_ptr<QName> q(XMLHelper::getNodeQName(attribute));
223     throw UnmarshallingException("Invalid attribute: $1",params(1,q->toString().c_str()));
224 }