39b515c19df7190033f64f471bdc53badc3ad59a
[shibboleth/cpp-xmltooling.git] / xmltooling / io / AbstractXMLObjectMarshaller.cpp
1 /*
2 *  Copyright 2001-2009 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  * AbstractXMLObjectMarshaller.cpp
19  * 
20  * A thread-safe abstract marshaller.
21  */
22
23 #include "internal.h"
24 #include "exceptions.h"
25 #include "io/AbstractXMLObjectMarshaller.h"
26 #ifndef XMLTOOLING_NO_XMLSEC
27     #include "security/Credential.h"
28     #include "signature/Signature.h"
29 #endif
30 #include "util/NDC.h"
31 #include "util/XMLConstants.h"
32 #include "util/XMLHelper.h"
33
34 #include <algorithm>
35 #include <functional>
36 #include <xercesc/util/XMLUniDefs.hpp>
37
38 #ifndef XMLTOOLING_NO_XMLSEC
39     using namespace xmlsignature;
40 #endif
41 using namespace xmlconstants;
42 using namespace xmltooling;
43 using namespace xercesc;
44 using namespace std;
45
46 AbstractXMLObjectMarshaller::AbstractXMLObjectMarshaller()
47 {
48 }
49
50 AbstractXMLObjectMarshaller::~AbstractXMLObjectMarshaller()
51 {
52 }
53
54 void AbstractXMLObjectMarshaller::setDocumentElement(DOMDocument* document, DOMElement* element) const
55 {
56     DOMElement* documentRoot = document->getDocumentElement();
57     if (documentRoot)
58         document->replaceChild(element, documentRoot);
59     else
60         document->appendChild(element);
61 }
62
63 DOMElement* AbstractXMLObjectMarshaller::marshall(
64     DOMDocument* document
65 #ifndef XMLTOOLING_NO_XMLSEC
66     ,const vector<Signature*>* sigs
67     ,const Credential* credential
68 #endif
69     ) const
70 {
71 #ifdef _DEBUG
72     xmltooling::NDC ndc("marshall");
73 #endif
74
75     if (m_log.isDebugEnabled()) {
76         m_log.debug("starting to marshal %s", getElementQName().toString().c_str());
77     }
78
79     DOMElement* cachedDOM=getDOM();
80     if (cachedDOM) {
81         if (!document || document==cachedDOM->getOwnerDocument()) {
82             m_log.debug("XMLObject has a usable cached DOM, reusing it");
83             if (document)
84                 setDocumentElement(cachedDOM->getOwnerDocument(),cachedDOM);
85             releaseParentDOM(true);
86             return cachedDOM;
87         }
88         
89         // We have a DOM but it doesn't match the document we were given. This both sucks and blows.
90         // Without an adoptNode option to maintain the child pointers, we have to either import the
91         // DOM while somehow reassigning all the nested references (which amounts to a complete
92         // *unmarshall* operation), or we just release the existing DOM and hope that we can get
93         // it back. This depends on all objects being able to preserve their DOM at all costs.
94         releaseChildrenDOM(true);
95         releaseDOM();
96     }
97     
98     // If we get here, we didn't have a usable DOM (and/or we released the one we had).
99     // We may need to create our own document.
100     bool bindDocument=false;
101     if (!document) {
102         document=DOMImplementationRegistry::getDOMImplementation(NULL)->createDocument();
103         bindDocument=true;
104     }
105     
106     XercesJanitor<DOMDocument> janitor(bindDocument ? document : NULL);
107
108     m_log.debug("creating root element to marshall");
109     DOMElement* domElement = document->createElementNS(
110         getElementQName().getNamespaceURI(), getElementQName().getLocalPart()
111         );
112     setDocumentElement(document, domElement);
113 #ifndef XMLTOOLING_NO_XMLSEC
114     marshallInto(domElement, sigs, credential);
115 #else
116     marshallInto(domElement);
117 #endif
118     //Recache the DOM.
119     m_log.debug("caching DOM for XMLObject (document is %sbound)", bindDocument ? "" : "not ");
120     setDOM(domElement, bindDocument);
121     janitor.release();  // safely transferred
122     releaseParentDOM(true);
123
124     return domElement;
125 }
126
127 DOMElement* AbstractXMLObjectMarshaller::marshall(
128     DOMElement* parentElement
129 #ifndef XMLTOOLING_NO_XMLSEC
130     ,const vector<Signature*>* sigs
131     ,const Credential* credential
132 #endif
133     ) const
134 {
135 #ifdef _DEBUG
136     xmltooling::NDC ndc("marshall");
137 #endif
138
139     if (m_log.isDebugEnabled()) {
140         m_log.debug("starting to marshalling %s", getElementQName().toString().c_str());
141     }
142
143     DOMElement* cachedDOM=getDOM();
144     if (cachedDOM) {
145         if (parentElement->getOwnerDocument()==cachedDOM->getOwnerDocument()) {
146             m_log.debug("XMLObject has a usable cached DOM, reusing it");
147             if (parentElement!=cachedDOM->getParentNode()) {
148                 parentElement->appendChild(cachedDOM);
149                 releaseParentDOM(true);
150             }
151             return cachedDOM;
152         }
153         
154         // We have a DOM but it doesn't match the document we were given. This both sucks and blows.
155         // Without an adoptNode option to maintain the child pointers, we have to either import the
156         // DOM while somehow reassigning all the nested references (which amounts to a complete
157         // *unmarshall* operation), or we just release the existing DOM and hope that we can get
158         // it back. This depends on all objects being able to preserve their DOM at all costs.
159         releaseChildrenDOM(true);
160         releaseDOM();
161     }
162     
163     // If we get here, we didn't have a usable DOM (and/or we released the one we had).
164     m_log.debug("creating root element to marshall");
165     DOMElement* domElement = parentElement->getOwnerDocument()->createElementNS(
166         getElementQName().getNamespaceURI(), getElementQName().getLocalPart()
167         );
168     parentElement->appendChild(domElement);
169 #ifndef XMLTOOLING_NO_XMLSEC
170     marshallInto(domElement, sigs, credential);
171 #else
172     marshallInto(domElement);
173 #endif
174
175     //Recache the DOM.
176     m_log.debug("caching DOM for XMLObject");
177     setDOM(domElement, false);
178     releaseParentDOM(true);
179
180     return domElement;
181 }
182
183 void AbstractXMLObjectMarshaller::marshallInto(
184     DOMElement* targetElement
185 #ifndef XMLTOOLING_NO_XMLSEC
186     ,const vector<Signature*>* sigs
187     ,const Credential* credential
188 #endif
189     ) const
190 {
191     if (getElementQName().hasPrefix())
192         targetElement->setPrefix(getElementQName().getPrefix());
193
194     if (m_schemaLocation || m_noNamespaceSchemaLocation) {
195         static const XMLCh schemaLocation[] = {
196             chLatin_x, chLatin_s, chLatin_i, chColon,
197             chLatin_s, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_a,
198             chLatin_L, chLatin_o, chLatin_c, chLatin_a, chLatin_t, chLatin_i, chLatin_o, chLatin_n, chNull
199             };
200         static const XMLCh noNamespaceSchemaLocation[] = {
201             chLatin_x, chLatin_s, chLatin_i, chColon,
202             chLatin_n, chLatin_o, chLatin_N, chLatin_a, chLatin_m, chLatin_e, chLatin_s, chLatin_p, chLatin_a, chLatin_c, chLatin_e,
203             chLatin_S, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_a,
204             chLatin_L, chLatin_o, chLatin_c, chLatin_a, chLatin_t, chLatin_i, chLatin_o, chLatin_n, chNull
205             };
206         if (targetElement->getParentNode()==NULL || targetElement->getParentNode()->getNodeType()==DOMNode::DOCUMENT_NODE) {
207             if (m_schemaLocation)
208                 targetElement->setAttributeNS(XSI_NS,schemaLocation,m_schemaLocation); 
209             if (m_noNamespaceSchemaLocation)
210                 targetElement->setAttributeNS(XSI_NS,noNamespaceSchemaLocation,m_noNamespaceSchemaLocation); 
211         }
212     }
213     
214     static const XMLCh _nil[] = { chLatin_x, chLatin_s, chLatin_i, chColon, chLatin_n, chLatin_i, chLatin_l, chNull };
215     
216     if (m_nil != xmlconstants::XML_BOOL_NULL) {
217         switch (m_nil) {
218             case xmlconstants::XML_BOOL_TRUE:
219                 targetElement->setAttributeNS(XSI_NS, _nil, xmlconstants::XML_TRUE);
220                 break;
221             case xmlconstants::XML_BOOL_ONE:
222                 targetElement->setAttributeNS(XSI_NS, _nil, xmlconstants::XML_ONE);
223                 break;
224             case xmlconstants::XML_BOOL_FALSE:
225                 targetElement->setAttributeNS(XSI_NS, _nil, xmlconstants::XML_FALSE);
226                 break;
227             case xmlconstants::XML_BOOL_ZERO:
228                 targetElement->setAttributeNS(XSI_NS, _nil, xmlconstants::XML_ZERO);
229                 break;
230         }
231         m_log.debug("adding XSI namespace to list of namespaces visibly used by XMLObject");
232         addNamespace(Namespace(XSI_NS, XSI_PREFIX, false, Namespace::VisiblyUsed));
233     }
234
235     marshallElementType(targetElement);
236     marshallNamespaces(targetElement);
237     marshallAttributes(targetElement);
238     
239 #ifndef XMLTOOLING_NO_XMLSEC
240     marshallContent(targetElement,credential);
241     if (sigs) {
242         for_each(sigs->begin(),sigs->end(),bind2nd(mem_fun1_t<void,Signature,const Credential*>(&Signature::sign),credential));
243     }
244 #else
245     marshallContent(targetElement);
246 #endif
247 }
248
249 void AbstractXMLObjectMarshaller::marshallElementType(DOMElement* domElement) const
250 {
251     const QName* type = getSchemaType();
252     if (type) {
253         m_log.debug("setting xsi:type attribute for XMLObject");
254         
255         const XMLCh* typeLocalName = type->getLocalPart();
256         if (!typeLocalName || !*typeLocalName) {
257             throw MarshallingException("Schema type of XMLObject may not have an empty local name.");
258         }
259
260         static const XMLCh xsitype[] = {
261             chLatin_x, chLatin_s, chLatin_i, chColon, chLatin_t, chLatin_y, chLatin_p, chLatin_e, chNull
262             };
263         
264         XMLCh* xsivalue=const_cast<XMLCh*>(typeLocalName);
265         const XMLCh* prefix=type->getPrefix();
266         if (prefix && *prefix) {
267             xsivalue=new XMLCh[XMLString::stringLen(typeLocalName) + XMLString::stringLen(prefix) + 2*sizeof(XMLCh)];
268             *xsivalue=chNull;
269             XMLString::catString(xsivalue,prefix);
270             static const XMLCh colon[] = {chColon, chNull};
271             XMLString::catString(xsivalue,colon);
272             XMLString::catString(xsivalue,typeLocalName);
273         }   
274         domElement->setAttributeNS(XSI_NS, xsitype, xsivalue);
275         if (xsivalue != typeLocalName)
276             XMLString::release(&xsivalue);
277
278         m_log.debug("adding XSI namespace to list of namespaces visibly used by XMLObject");
279         addNamespace(Namespace(XSI_NS, XSI_PREFIX, false, Namespace::VisiblyUsed));
280     }
281 }
282
283 class _addns : public binary_function<DOMElement*,Namespace,void> {
284 public:
285     void operator()(DOMElement* domElement, const Namespace& ns) const {
286         const XMLCh* prefix=ns.getNamespacePrefix();
287         const XMLCh* uri=ns.getNamespaceURI();
288         
289         // Check for xmlns:xml.
290         if (XMLString::equals(prefix, XML_PREFIX) && XMLString::equals(uri, XML_NS))
291             return;
292
293         // Check to see if the prefix is already declared properly above this node.
294         if (!ns.alwaysDeclare()) {
295             const XMLCh* declared=lookupNamespaceURI(domElement->getParentNode(),prefix);
296             if (declared && XMLString::equals(declared,uri))
297                 return;
298         }
299             
300         if (prefix && *prefix) {
301             XMLCh* xmlns=new XMLCh[XMLString::stringLen(XMLNS_PREFIX) + XMLString::stringLen(prefix) + 2*sizeof(XMLCh)];
302             *xmlns=chNull;
303             XMLString::catString(xmlns,XMLNS_PREFIX);
304             static const XMLCh colon[] = {chColon, chNull};
305             XMLString::catString(xmlns,colon);
306             XMLString::catString(xmlns,prefix);
307             domElement->setAttributeNS(XMLNS_NS, xmlns, uri);
308             delete[] xmlns;
309         }
310         else {
311             domElement->setAttributeNS(XMLNS_NS, XMLNS_PREFIX, uri);
312         }
313     }
314
315     const XMLCh* lookupNamespaceURI(const DOMNode* n, const XMLCh* prefix) const {
316         // Return NULL if no declaration in effect. The empty string signifies the null namespace.
317         if (!n || n->getNodeType()!=DOMNode::ELEMENT_NODE) {
318             // At the root, the default namespace is set to the null namespace.
319             if (!prefix || !*prefix)
320                 return &chNull;
321             return NULL;    // we're done
322         }
323         DOMNamedNodeMap* attributes = static_cast<const DOMElement*>(n)->getAttributes();
324         if (!attributes)
325             return lookupNamespaceURI(n->getParentNode(),prefix);   // defer to parent
326         DOMNode* childNode;
327         DOMAttr* attribute;
328         for (XMLSize_t i=0; i<attributes->getLength(); i++) {
329             childNode = attributes->item(i);
330             if (childNode->getNodeType() != DOMNode::ATTRIBUTE_NODE)    // not an attribute?
331                 continue;
332             attribute = static_cast<DOMAttr*>(childNode);
333             if (!XMLString::equals(attribute->getNamespaceURI(),XMLNS_NS))
334                 continue;   // not a namespace declaration
335             // Local name should be the prefix and the value would be the URI, except for the default namespace.
336             if ((!prefix || !*prefix) && XMLString::equals(attribute->getLocalName(),XMLNS_PREFIX))
337                 return attribute->getNodeValue();
338             else if (XMLString::equals(prefix,attribute->getLocalName()))
339                 return attribute->getNodeValue();
340         }
341         // Defer to parent.
342         return lookupNamespaceURI(n->getParentNode(),prefix);
343     }
344 };
345
346 void AbstractXMLObjectMarshaller::marshallNamespaces(DOMElement* domElement) const
347 {
348     m_log.debug("marshalling namespace attributes for XMLObject");
349     const set<Namespace>& namespaces = getNamespaces();
350     for_each(namespaces.begin(),namespaces.end(),bind1st(_addns(),domElement));
351 }
352
353 void AbstractXMLObjectMarshaller::marshallContent(
354     DOMElement* domElement
355 #ifndef XMLTOOLING_NO_XMLSEC
356     ,const Credential* credential
357 #endif
358     ) const
359 {
360     m_log.debug("marshalling text and child elements for XMLObject");
361     
362     unsigned int pos=0;
363     const XMLCh* val = getTextContent(pos);
364     if (val && *val)
365         domElement->appendChild(domElement->getOwnerDocument()->createTextNode(val));
366     
367     const list<XMLObject*>& children=getOrderedChildren();
368     for (list<XMLObject*>::const_iterator i=children.begin(); i!=children.end(); ++i) {
369         if (*i) {
370 #ifndef XMLTOOLING_NO_XMLSEC
371             (*i)->marshall(domElement,NULL,credential);
372 #else
373             (*i)->marshall(domElement);
374 #endif
375             val = getTextContent(++pos);
376             if (val && *val)
377                 domElement->appendChild(domElement->getOwnerDocument()->createTextNode(val));
378         }
379     }
380 }
381
382 void AbstractXMLObjectMarshaller::marshallAttributes(DOMElement* domElement) const
383 {
384 }