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