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