Xerces 3 revisions.
[shibboleth/cpp-xmltooling.git] / xmltooling / io / AbstractXMLObjectMarshaller.cpp
1 /*
2 *  Copyright 2001-2007 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 to see if the prefix is already declared properly above this node.
273         if (!ns.alwaysDeclare()) {
274             const XMLCh* declared=lookupNamespaceURI(domElement->getParentNode(),prefix);
275             if (declared && XMLString::equals(declared,uri))
276                 return;
277         }
278             
279         if (prefix && *prefix) {
280             XMLCh* xmlns=new XMLCh[XMLString::stringLen(XMLNS_PREFIX) + XMLString::stringLen(prefix) + 2*sizeof(XMLCh)];
281             *xmlns=chNull;
282             XMLString::catString(xmlns,XMLNS_PREFIX);
283             static const XMLCh colon[] = {chColon, chNull};
284             XMLString::catString(xmlns,colon);
285             XMLString::catString(xmlns,prefix);
286             domElement->setAttributeNS(XMLNS_NS, xmlns, uri);
287             XMLString::release(&xmlns);
288         }
289         else {
290             domElement->setAttributeNS(XMLNS_NS, XMLNS_PREFIX, uri);
291         }
292     }
293
294     const XMLCh* lookupNamespaceURI(const DOMNode* n, const XMLCh* prefix) const {
295         // Return NULL if no declaration in effect. The empty string signifies the null namespace.
296         if (!n || n->getNodeType()!=DOMNode::ELEMENT_NODE) {
297             // At the root, the default namespace is set to the null namespace.
298             if (!prefix || !*prefix)
299                 return &chNull;
300             return NULL;    // we're done
301         }
302         DOMNamedNodeMap* attributes = static_cast<const DOMElement*>(n)->getAttributes();
303         if (!attributes)
304             return lookupNamespaceURI(n->getParentNode(),prefix);   // defer to parent
305         DOMNode* childNode;
306         DOMAttr* attribute;
307         for (XMLSize_t i=0; i<attributes->getLength(); i++) {
308             childNode = attributes->item(i);
309             if (childNode->getNodeType() != DOMNode::ATTRIBUTE_NODE)    // not an attribute?
310                 continue;
311             attribute = static_cast<DOMAttr*>(childNode);
312             if (!XMLString::equals(attribute->getNamespaceURI(),XMLNS_NS))
313                 continue;   // not a namespace declaration
314             // Local name should be the prefix and the value would be the URI, except for the default namespace.
315             if ((!prefix || !*prefix) && XMLString::equals(attribute->getLocalName(),XMLNS_PREFIX))
316                 return attribute->getNodeValue();
317             else if (XMLString::equals(prefix,attribute->getLocalName()))
318                 return attribute->getNodeValue();
319         }
320         // Defer to parent.
321         return lookupNamespaceURI(n->getParentNode(),prefix);
322     }
323 };
324
325 void AbstractXMLObjectMarshaller::marshallNamespaces(DOMElement* domElement) const
326 {
327     m_log.debug("marshalling namespace attributes for XMLObject");
328     const set<Namespace>& namespaces = getNamespaces();
329     for_each(namespaces.begin(),namespaces.end(),bind1st(_addns(),domElement));
330 }
331
332 void AbstractXMLObjectMarshaller::marshallContent(
333     DOMElement* domElement
334 #ifndef XMLTOOLING_NO_XMLSEC
335     ,const Credential* credential
336 #endif
337     ) const
338 {
339     m_log.debug("marshalling text and child elements for XMLObject");
340     
341     unsigned int pos=0;
342     const XMLCh* val = getTextContent(pos);
343     if (val && *val)
344         domElement->appendChild(domElement->getOwnerDocument()->createTextNode(val));
345     
346     const list<XMLObject*>& children=getOrderedChildren();
347     for (list<XMLObject*>::const_iterator i=children.begin(); i!=children.end(); ++i) {
348         if (*i) {
349 #ifndef XMLTOOLING_NO_XMLSEC
350             (*i)->marshall(domElement,NULL,credential);
351 #else
352             (*i)->marshall(domElement);
353 #endif
354             val = getTextContent(++pos);
355             if (val && *val)
356                 domElement->appendChild(domElement->getOwnerDocument()->createTextNode(val));
357         }
358     }
359 }