Fixed bug causing duplication of text content.
[shibboleth/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     marshallElementType(targetElement);
197     marshallNamespaces(targetElement);
198     marshallAttributes(targetElement);
199     
200 #ifndef XMLTOOLING_NO_XMLSEC
201     marshallContent(targetElement,credential);
202     if (sigs) {
203         for_each(sigs->begin(),sigs->end(),bind2nd(mem_fun1_t<void,Signature,const Credential*>(&Signature::sign),credential));
204     }
205 #else
206     marshallContent(targetElement);
207 #endif
208 }
209
210 void AbstractXMLObjectMarshaller::marshallElementType(DOMElement* domElement) const
211 {
212     const QName* type = getSchemaType();
213     if (type) {
214         m_log.debug("setting xsi:type attribute for XMLObject");
215         
216         const XMLCh* typeLocalName = type->getLocalPart();
217         if (!typeLocalName || !*typeLocalName) {
218             throw MarshallingException("Schema type of XMLObject may not have an empty local name.");
219         }
220
221         static const XMLCh xsitype[] = {
222             chLatin_x, chLatin_s, chLatin_i, chColon, chLatin_t, chLatin_y, chLatin_p, chLatin_e, chNull
223             };
224         
225         XMLCh* xsivalue=const_cast<XMLCh*>(typeLocalName);
226         const XMLCh* prefix=type->getPrefix();
227         if (prefix && *prefix) {
228             xsivalue=new XMLCh[XMLString::stringLen(typeLocalName) + XMLString::stringLen(prefix) + 2*sizeof(XMLCh)];
229             *xsivalue=chNull;
230             XMLString::catString(xsivalue,prefix);
231             static const XMLCh colon[] = {chColon, chNull};
232             XMLString::catString(xsivalue,colon);
233             XMLString::catString(xsivalue,typeLocalName);
234         }   
235         domElement->setAttributeNS(XSI_NS, xsitype, xsivalue);
236         if (xsivalue != typeLocalName)
237             XMLString::release(&xsivalue);
238
239         m_log.debug("adding XSI namespace to list of namespaces used by XMLObject");
240         addNamespace(Namespace(XSI_NS, XSI_PREFIX));
241     }
242 }
243
244 class _addns : public binary_function<DOMElement*,Namespace,void> {
245 public:
246     void operator()(DOMElement* domElement, const Namespace& ns) const {
247         const XMLCh* prefix=ns.getNamespacePrefix();
248         const XMLCh* uri=ns.getNamespaceURI();
249         
250         // Check to see if the prefix is already declared properly above this node.
251         if (!ns.alwaysDeclare()) {
252             const XMLCh* declared=lookupNamespaceURI(domElement->getParentNode(),prefix);
253             if (declared && XMLString::equals(declared,uri))
254                 return;
255         }
256             
257         if (prefix && *prefix) {
258             XMLCh* xmlns=new XMLCh[XMLString::stringLen(XMLNS_PREFIX) + XMLString::stringLen(prefix) + 2*sizeof(XMLCh)];
259             *xmlns=chNull;
260             XMLString::catString(xmlns,XMLNS_PREFIX);
261             static const XMLCh colon[] = {chColon, chNull};
262             XMLString::catString(xmlns,colon);
263             XMLString::catString(xmlns,prefix);
264             domElement->setAttributeNS(XMLNS_NS, xmlns, uri);
265             XMLString::release(&xmlns);
266         }
267         else {
268             domElement->setAttributeNS(XMLNS_NS, XMLNS_PREFIX, uri);
269         }
270     }
271
272     const XMLCh* lookupNamespaceURI(const DOMNode* n, const XMLCh* prefix) const {
273         // Return NULL if no declaration in effect. The empty string signifies the null namespace.
274         if (!n || n->getNodeType()!=DOMNode::ELEMENT_NODE) {
275             // At the root, the default namespace is set to the null namespace.
276             if (!prefix || !*prefix)
277                 return &chNull;
278             return NULL;    // we're done
279         }
280         DOMNamedNodeMap* attributes = static_cast<const DOMElement*>(n)->getAttributes();
281         if (!attributes)
282             return lookupNamespaceURI(n->getParentNode(),prefix);   // defer to parent
283         DOMNode* childNode;
284         DOMAttr* attribute;
285         for (XMLSize_t i=0; i<attributes->getLength(); i++) {
286             childNode = attributes->item(i);
287             if (childNode->getNodeType() != DOMNode::ATTRIBUTE_NODE)    // not an attribute?
288                 continue;
289             attribute = static_cast<DOMAttr*>(childNode);
290             if (!XMLString::equals(attribute->getNamespaceURI(),XMLNS_NS))
291                 continue;   // not a namespace declaration
292             // Local name should be the prefix and the value would be the URI, except for the default namespace.
293             if ((!prefix || !*prefix) && XMLString::equals(attribute->getLocalName(),XMLNS_PREFIX))
294                 return attribute->getNodeValue();
295             else if (XMLString::equals(prefix,attribute->getLocalName()))
296                 return attribute->getNodeValue();
297         }
298         // Defer to parent.
299         return lookupNamespaceURI(n->getParentNode(),prefix);
300     }
301 };
302
303 void AbstractXMLObjectMarshaller::marshallNamespaces(DOMElement* domElement) const
304 {
305     m_log.debug("marshalling namespace attributes for XMLObject");
306     const set<Namespace>& namespaces = getNamespaces();
307     for_each(namespaces.begin(),namespaces.end(),bind1st(_addns(),domElement));
308 }
309
310 void AbstractXMLObjectMarshaller::marshallContent(
311     DOMElement* domElement
312 #ifndef XMLTOOLING_NO_XMLSEC
313     ,const Credential* credential
314 #endif
315     ) const
316 {
317     m_log.debug("marshalling text and child elements for XMLObject");
318     
319     unsigned int pos=0;
320     const XMLCh* val = getTextContent(pos);
321     if (val && *val)
322         domElement->appendChild(domElement->getOwnerDocument()->createTextNode(val));
323     
324     const list<XMLObject*>& children=getOrderedChildren();
325     for (list<XMLObject*>::const_iterator i=children.begin(); i!=children.end(); ++i) {
326         if (*i) {
327 #ifndef XMLTOOLING_NO_XMLSEC
328             (*i)->marshall(domElement,NULL,credential);
329 #else
330             (*i)->marshall(domElement);
331 #endif
332             val = getTextContent(++pos);
333             if (val && *val)
334                 domElement->appendChild(domElement->getOwnerDocument()->createTextNode(val));
335         }
336     }
337 }