gcc const fix, converted linefeeds
[shibboleth/cpp-xmltooling.git] / xmltooling / io / AbstractXMLObjectMarshaller.cpp
1 /*
2 *  Copyright 2001-2006 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 #include <log4cpp/Category.hh>
37
38 #ifndef XMLTOOLING_NO_XMLSEC
39     using namespace xmlsignature;
40 #endif
41 using namespace xmltooling;
42 using namespace log4cpp;
43 using namespace std;
44
45 #define XT_log (*static_cast<Category*>(m_log))
46
47 DOMElement* AbstractXMLObjectMarshaller::marshall(
48     DOMDocument* document
49 #ifndef XMLTOOLING_NO_XMLSEC
50     ,const std::vector<xmlsignature::Signature*>* sigs
51 #endif
52     ) const
53 {
54 #ifdef _DEBUG
55     xmltooling::NDC ndc("marshall");
56 #endif
57
58     if (XT_log.isDebugEnabled()) {
59         XT_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             XT_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     XT_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);
98 #else
99     marshallInto(domElement);
100 #endif
101     //Recache the DOM.
102     XT_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 std::vector<xmlsignature::Signature*>* sigs
114 #endif
115     ) const
116 {
117 #ifdef _DEBUG
118     xmltooling::NDC ndc("marshall");
119 #endif
120
121     if (XT_log.isDebugEnabled()) {
122         XT_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             XT_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     XT_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);
153 #else
154     marshallInto(domElement);
155 #endif
156
157     //Recache the DOM.
158     XT_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 std::vector<xmlsignature::Signature*>* sigs
169 #endif
170     ) const
171 {
172     if (getElementQName().hasPrefix())
173         targetElement->setPrefix(getElementQName().getPrefix());
174
175     if (m_schemaLocation) {
176         static const XMLCh schemaLocation[]= UNICODE_LITERAL_14(s,c,h,e,m,a,L,o,c,a,t,i,o,n);
177         if (targetElement->getParentNode()==NULL || targetElement->getParentNode()->getNodeType()==DOMNode::DOCUMENT_NODE)
178             targetElement->setAttributeNS(XMLConstants::XSI_NS,schemaLocation,m_schemaLocation); 
179     }
180
181     marshallElementType(targetElement);
182     marshallNamespaces(targetElement);
183     marshallAttributes(targetElement);
184     marshallContent(targetElement);
185     
186 #ifndef XMLTOOLING_NO_XMLSEC
187     if (sigs) {
188         for_each(sigs->begin(),sigs->end(),mem_fun<void,Signature>(&Signature::sign));
189     }
190 #endif
191 }
192
193 void AbstractXMLObjectMarshaller::marshallElementType(DOMElement* domElement) const
194 {
195     const QName* type = getSchemaType();
196     if (type) {
197         XT_log.debug("setting xsi:type attribute for XMLObject");
198         
199         const XMLCh* typeLocalName = type->getLocalPart();
200         if (!typeLocalName || !*typeLocalName) {
201             throw MarshallingException("Schema type of XMLObject may not have an empty local name.");
202         }
203
204         static const XMLCh xsitype[] = {
205             chLatin_x, chLatin_s, chLatin_i, chColon, chLatin_t, chLatin_y, chLatin_p, chLatin_e, chNull
206             };
207         
208         XMLCh* xsivalue=const_cast<XMLCh*>(typeLocalName);
209         const XMLCh* prefix=type->getPrefix();
210         if (prefix && *prefix) {
211             xsivalue=new XMLCh[XMLString::stringLen(typeLocalName) + XMLString::stringLen(prefix) + 2*sizeof(XMLCh)];
212             *xsivalue=chNull;
213             XMLString::catString(xsivalue,prefix);
214             static const XMLCh colon[] = {chColon, chNull};
215             XMLString::catString(xsivalue,colon);
216             XMLString::catString(xsivalue,typeLocalName);
217         }   
218         domElement->setAttributeNS(XMLConstants::XSI_NS, xsitype, xsivalue);
219         if (xsivalue != typeLocalName)
220             XMLString::release(&xsivalue);
221
222         XT_log.debug("Adding XSI namespace to list of namespaces used by XMLObject");
223         addNamespace(Namespace(XMLConstants::XSI_NS, XMLConstants::XSI_PREFIX));
224     }
225 }
226
227 class _addns : public binary_function<DOMElement*,Namespace,void> {
228 public:
229     void operator()(DOMElement* domElement, const Namespace& ns) const {
230         const XMLCh* prefix=ns.getNamespacePrefix();
231         const XMLCh* uri=ns.getNamespaceURI();
232         
233         // Check to see if the prefix is already declared properly above this node.
234         if (!ns.alwaysDeclare()) {
235             const XMLCh* declared=lookupNamespaceURI(domElement->getParentNode(),prefix);
236             if (declared && XMLString::equals(declared,uri))
237                 return;
238         }
239             
240         if (prefix && *prefix) {
241             XMLCh* xmlns=new XMLCh[XMLString::stringLen(XMLConstants::XMLNS_PREFIX) + XMLString::stringLen(prefix) + 2*sizeof(XMLCh)];
242             *xmlns=chNull;
243             XMLString::catString(xmlns,XMLConstants::XMLNS_PREFIX);
244             static const XMLCh colon[] = {chColon, chNull};
245             XMLString::catString(xmlns,colon);
246             XMLString::catString(xmlns,prefix);
247             domElement->setAttributeNS(XMLConstants::XMLNS_NS, xmlns, uri);
248         }
249         else {
250             domElement->setAttributeNS(XMLConstants::XMLNS_NS, XMLConstants::XMLNS_PREFIX, uri);
251         }
252     }
253
254     const XMLCh* lookupNamespaceURI(const DOMNode* n, const XMLCh* prefix) const {
255         // Return NULL if no declaration in effect. The empty string signifies the null namespace.
256         if (!n || n->getNodeType()!=DOMNode::ELEMENT_NODE) {
257             // At the root, the default namespace is set to the null namespace.
258             if (!prefix || !*prefix)
259                 return &chNull;
260             return NULL;    // we're done
261         }
262         DOMNamedNodeMap* attributes = static_cast<const DOMElement*>(n)->getAttributes();
263         if (!attributes)
264             return lookupNamespaceURI(n->getParentNode(),prefix);   // defer to parent
265         DOMNode* childNode;
266         DOMAttr* attribute;
267         for (XMLSize_t i=0; i<attributes->getLength(); i++) {
268             childNode = attributes->item(i);
269             if (childNode->getNodeType() != DOMNode::ATTRIBUTE_NODE)    // not an attribute?
270                 continue;
271             attribute = static_cast<DOMAttr*>(childNode);
272             if (!XMLString::equals(attribute->getNamespaceURI(),XMLConstants::XMLNS_NS))
273                 continue;   // not a namespace declaration
274             // Local name should be the prefix and the value would be the URI, except for the default namespace.
275             if ((!prefix || !*prefix) && XMLString::equals(attribute->getLocalName(),XMLConstants::XMLNS_PREFIX))
276                 return attribute->getNodeValue();
277             else if (XMLString::equals(prefix,attribute->getLocalName()))
278                 return attribute->getNodeValue();
279         }
280         // Defer to parent.
281         return lookupNamespaceURI(n->getParentNode(),prefix);
282     }
283 };
284
285 void AbstractXMLObjectMarshaller::marshallNamespaces(DOMElement* domElement) const
286 {
287     XT_log.debug("marshalling namespace attributes for XMLObject");
288     const set<Namespace>& namespaces = getNamespaces();
289     for_each(namespaces.begin(),namespaces.end(),bind1st(_addns(),domElement));
290 }
291
292 void AbstractXMLObjectMarshaller::marshallContent(DOMElement* domElement) const
293 {
294     XT_log.debug("marshalling text and child elements for XMLObject");
295     
296     const XMLCh* val;
297     unsigned int pos=0;
298     const list<XMLObject*>& children=getOrderedChildren();
299     for (list<XMLObject*>::const_iterator i=children.begin(); i!=children.end(); ++i, ++pos) {
300         val = getTextContent(pos);
301         if (val && *val)
302             domElement->appendChild(domElement->getOwnerDocument()->createTextNode(val));
303         if (*i)
304             (*i)->marshall(domElement);
305     }
306     val = getTextContent(pos);
307     if (val && *val)
308         domElement->appendChild(domElement->getOwnerDocument()->createTextNode(val));
309 }