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