Namespace handling fixes
[shibboleth/cpp-xmltooling.git] / xmltooling / io / AbstractXMLObjectMarshaller.cpp
1 /*\r
2 *  Copyright 2001-2006 Internet2\r
3  * \r
4 * Licensed under the Apache License, Version 2.0 (the "License");\r
5  * you may not use this file except in compliance with the License.\r
6  * You may obtain a copy of the License at\r
7  *\r
8  *     http://www.apache.org/licenses/LICENSE-2.0\r
9  *\r
10  * Unless required by applicable law or agreed to in writing, software\r
11  * distributed under the License is distributed on an "AS IS" BASIS,\r
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
13  * See the License for the specific language governing permissions and\r
14  * limitations under the License.\r
15  */\r
16 \r
17 /**\r
18  * AbstractXMLObjectMarshaller.cpp\r
19  * \r
20  * A thread-safe abstract marshaller.\r
21  */\r
22 \r
23 #include "internal.h"\r
24 #include "DOMCachingXMLObject.h"\r
25 #include "exceptions.h"\r
26 #include "io/AbstractXMLObjectMarshaller.h"\r
27 #include "util/NDC.h"\r
28 #include "util/XMLConstants.h"\r
29 #include "util/XMLHelper.h"\r
30 \r
31 #include <algorithm>\r
32 #include <functional>\r
33 #include <xercesc/util/XMLUniDefs.hpp>\r
34 #include <log4cpp/Category.hh>\r
35 \r
36 using namespace xmltooling;\r
37 using namespace log4cpp;\r
38 using namespace std;\r
39 \r
40 #define XT_log (*static_cast<Category*>(m_log))\r
41 \r
42 AbstractXMLObjectMarshaller::AbstractXMLObjectMarshaller()\r
43     : m_log(&Category::getInstance(XMLTOOLING_LOGCAT".Marshaller")) {}\r
44 \r
45 DOMElement* AbstractXMLObjectMarshaller::marshall(XMLObject* xmlObject, DOMDocument* document) const\r
46 {\r
47 #ifdef _DEBUG\r
48     xmltooling::NDC ndc("marshall");\r
49 #endif\r
50 \r
51     if (XT_log.isDebugEnabled()) {\r
52         XT_log.debug("starting to marshalling %s", xmlObject->getElementQName().toString().c_str());\r
53     }\r
54 \r
55     DOMCachingXMLObject* dc=dynamic_cast<DOMCachingXMLObject*>(xmlObject);\r
56     if (dc) {\r
57         DOMElement* cachedDOM=dc->getDOM();\r
58         if (cachedDOM) {\r
59             if (!document || document==cachedDOM->getOwnerDocument()) {\r
60                 XT_log.debug("XMLObject has a usable cached DOM, reusing it");\r
61                 if (document)\r
62                     setDocumentElement(cachedDOM->getOwnerDocument(),cachedDOM);\r
63                 dc->releaseParentDOM(true);\r
64                 return cachedDOM;\r
65             }\r
66             \r
67             // We have a DOM but it doesn't match the document we were given. This both sucks and blows.\r
68             // Without an adoptNode option to maintain the child pointers, we have to either import the\r
69             // DOM while somehow reassigning all the nested references (which amounts to a complete\r
70             // *unmarshall* operation), or we just release the existing DOM and hope that we can get\r
71             // it back. This depends on all objects being able to preserve their DOM at all costs.\r
72             dc->releaseChildrenDOM(true);\r
73             dc->releaseDOM();\r
74         }\r
75     }\r
76     \r
77     // If we get here, we didn't have a usable DOM (and/or we released the one we had).\r
78     // We may need to create our own document.\r
79     bool bindDocument=false;\r
80     if (!document) {\r
81         document=DOMImplementationRegistry::getDOMImplementation(NULL)->createDocument();\r
82         bindDocument=true;\r
83     }\r
84 \r
85     try {\r
86         XT_log.debug("creating root element to marshall");\r
87         DOMElement* domElement = document->createElementNS(\r
88             xmlObject->getElementQName().getNamespaceURI(), xmlObject->getElementQName().getLocalPart()\r
89             );\r
90         setDocumentElement(document, domElement);\r
91         marshallInto(*xmlObject, domElement);\r
92 \r
93         //Recache the DOM.\r
94         if (dc) {\r
95             XT_log.debug("caching DOM for XMLObject (document is %sbound)", bindDocument ? "" : "not ");\r
96             dc->setDOM(domElement, bindDocument);\r
97             dc->releaseParentDOM(true);\r
98         }\r
99 \r
100         return domElement;\r
101     }\r
102     catch (...) {\r
103         // Delete the document if need be, and rethrow.\r
104         if (bindDocument) {\r
105             document->release();\r
106         }\r
107         throw;\r
108     }\r
109 }\r
110 \r
111 DOMElement* AbstractXMLObjectMarshaller::marshall(XMLObject* xmlObject, DOMElement* parentElement) const\r
112 {\r
113 #ifdef _DEBUG\r
114     xmltooling::NDC ndc("marshall");\r
115 #endif\r
116 \r
117     if (XT_log.isDebugEnabled()) {\r
118         XT_log.debug("starting to marshalling %s", xmlObject->getElementQName().toString().c_str());\r
119     }\r
120 \r
121     DOMCachingXMLObject* dc=dynamic_cast<DOMCachingXMLObject*>(xmlObject);\r
122     if (dc) {\r
123         DOMElement* cachedDOM=dc->getDOM();\r
124         if (cachedDOM) {\r
125             if (parentElement->getOwnerDocument()==cachedDOM->getOwnerDocument()) {\r
126                 XT_log.debug("XMLObject has a usable cached DOM, reusing it");\r
127                 if (parentElement!=cachedDOM->getParentNode()) {\r
128                     parentElement->appendChild(cachedDOM);\r
129                     dc->releaseParentDOM(true);\r
130                 }\r
131                 return cachedDOM;\r
132             }\r
133             \r
134             // We have a DOM but it doesn't match the document we were given. This both sucks and blows.\r
135             // Without an adoptNode option to maintain the child pointers, we have to either import the\r
136             // DOM while somehow reassigning all the nested references (which amounts to a complete\r
137             // *unmarshall* operation), or we just release the existing DOM and hope that we can get\r
138             // it back. This depends on all objects being able to preserve their DOM at all costs.\r
139             dc->releaseChildrenDOM(true);\r
140             dc->releaseDOM();\r
141         }\r
142     }\r
143     \r
144     // If we get here, we didn't have a usable DOM (and/or we released the one we had).\r
145     XT_log.debug("creating root element to marshall");\r
146     DOMElement* domElement = parentElement->getOwnerDocument()->createElementNS(\r
147         xmlObject->getElementQName().getNamespaceURI(), xmlObject->getElementQName().getLocalPart()\r
148         );\r
149     parentElement->appendChild(domElement);\r
150     marshallInto(*xmlObject, domElement);\r
151 \r
152     //Recache the DOM.\r
153     if (dc) {\r
154         XT_log.debug("caching DOM for XMLObject");\r
155         dc->setDOM(domElement, false);\r
156         dc->releaseParentDOM(true);\r
157     }\r
158 \r
159     return domElement;\r
160 }\r
161         \r
162 void AbstractXMLObjectMarshaller::marshallInto(XMLObject& xmlObject, DOMElement* targetElement) const\r
163 {\r
164     if (xmlObject.getElementQName().hasPrefix())\r
165         targetElement->setPrefix(xmlObject.getElementQName().getPrefix());\r
166     marshallElementType(xmlObject, targetElement);\r
167     marshallNamespaces(xmlObject, targetElement);\r
168     marshallAttributes(xmlObject, targetElement);\r
169     marshallChildElements(xmlObject, targetElement);\r
170     marshallElementContent(xmlObject, targetElement);\r
171 \r
172     /* TODO Signing/Encryption\r
173     if (xmlObject instanceof SignableXMLObject) {\r
174         signElement(targetElement, xmlObject);\r
175     }\r
176 \r
177     if (xmlObject instanceof EncryptableXMLObject) {\r
178         encryptElement(targetElement, xmlObject);\r
179     }\r
180     */\r
181 }\r
182 \r
183 void AbstractXMLObjectMarshaller::marshallElementType(XMLObject& xmlObject, DOMElement* domElement) const\r
184 {\r
185     const QName* type = xmlObject.getSchemaType();\r
186     if (type) {\r
187         XT_log.debug("setting xsi:type attribute for XMLObject");\r
188         \r
189         const XMLCh* typeLocalName = type->getLocalPart();\r
190         if (!typeLocalName || !*typeLocalName) {\r
191             throw MarshallingException("Schema type of XMLObject may not have an empty local name.");\r
192         }\r
193 \r
194         static const XMLCh xsitype[] = {\r
195             chLatin_x, chLatin_s, chLatin_i, chColon, chLatin_t, chLatin_y, chLatin_p, chLatin_e, chNull\r
196             };\r
197         \r
198         XMLCh* xsivalue=const_cast<XMLCh*>(typeLocalName);\r
199         const XMLCh* prefix=type->getPrefix();\r
200         if (prefix && *prefix) {\r
201             xsivalue=new XMLCh[XMLString::stringLen(typeLocalName) + XMLString::stringLen(prefix) + 2*sizeof(XMLCh)];\r
202             *xsivalue=chNull;\r
203             XMLString::catString(xsivalue,prefix);\r
204             static const XMLCh colon[] = {chColon, chNull};\r
205             XMLString::catString(xsivalue,colon);\r
206             XMLString::catString(xsivalue,typeLocalName);\r
207         }   \r
208         domElement->setAttributeNS(XMLConstants::XSI_NS, xsitype, xsivalue);\r
209         if (xsivalue != typeLocalName)\r
210             XMLString::release(&xsivalue);\r
211 \r
212         XT_log.debug("Adding XSI namespace to list of namespaces used by XMLObject");\r
213         xmlObject.addNamespace(Namespace(XMLConstants::XSI_NS, XMLConstants::XSI_PREFIX));\r
214     }\r
215 }\r
216 \r
217 class _addns : public binary_function<DOMElement*,Namespace,void> {\r
218 public:\r
219     void operator()(DOMElement* domElement, const Namespace& ns) const {\r
220         const XMLCh* prefix=ns.getNamespacePrefix();\r
221         const XMLCh* uri=ns.getNamespaceURI();\r
222         \r
223         // Check to see if the prefix is already declared properly above this node.\r
224         if (!ns.alwaysDeclare()) {\r
225             const XMLCh* declared=lookupNamespaceURI(domElement->getParentNode(),prefix);\r
226             if (declared && XMLString::equals(declared,uri))\r
227                 return;\r
228         }\r
229             \r
230         if (prefix && *prefix) {\r
231             XMLCh* xmlns=new XMLCh[XMLString::stringLen(XMLConstants::XMLNS_PREFIX) + XMLString::stringLen(prefix) + 2*sizeof(XMLCh)];\r
232             *xmlns=chNull;\r
233             XMLString::catString(xmlns,XMLConstants::XMLNS_PREFIX);\r
234             static const XMLCh colon[] = {chColon, chNull};\r
235             XMLString::catString(xmlns,colon);\r
236             XMLString::catString(xmlns,prefix);\r
237             domElement->setAttributeNS(XMLConstants::XMLNS_NS, xmlns, uri);\r
238         }\r
239         else {\r
240             domElement->setAttributeNS(XMLConstants::XMLNS_NS, XMLConstants::XMLNS_PREFIX, uri);\r
241         }\r
242     }\r
243 \r
244     const XMLCh* lookupNamespaceURI(const DOMNode* n, const XMLCh* prefix) const {\r
245         // Return NULL if no declaration in effect. The empty string signifies the null namespace.\r
246         if (!n || n->getNodeType()!=DOMNode::ELEMENT_NODE) {\r
247             // At the root, the default namespace is set to the null namespace.\r
248             if (!prefix || !*prefix)\r
249                 return &chNull;\r
250             return NULL;    // we're done\r
251         }\r
252         DOMNamedNodeMap* attributes = static_cast<const DOMElement*>(n)->getAttributes();\r
253         if (!attributes)\r
254             return lookupNamespaceURI(n->getParentNode(),prefix);   // defer to parent\r
255         DOMNode* childNode;\r
256         DOMAttr* attribute;\r
257         for (XMLSize_t i=0; i<attributes->getLength(); i++) {\r
258             childNode = attributes->item(i);\r
259             if (childNode->getNodeType() != DOMNode::ATTRIBUTE_NODE)    // not an attribute?\r
260                 continue;\r
261             attribute = static_cast<DOMAttr*>(childNode);\r
262             if (!XMLString::equals(attribute->getNamespaceURI(),XMLConstants::XMLNS_NS))\r
263                 continue;   // not a namespace declaration\r
264             // Local name should be the prefix and the value would be the URI, except for the default namespace.\r
265             if ((!prefix || !*prefix) && XMLString::equals(attribute->getLocalName(),XMLConstants::XMLNS_PREFIX))\r
266                 return attribute->getNodeValue();\r
267             else if (XMLString::equals(prefix,attribute->getLocalName()))\r
268                 return attribute->getNodeValue();\r
269         }\r
270         // Defer to parent.\r
271         return lookupNamespaceURI(n->getParentNode(),prefix);\r
272     }\r
273 };\r
274 \r
275 void AbstractXMLObjectMarshaller::marshallNamespaces(const XMLObject& xmlObject, DOMElement* domElement) const\r
276 {\r
277     XT_log.debug("marshalling namespace attributes for XMLObject");\r
278     const set<Namespace>& namespaces = xmlObject.getNamespaces();\r
279     for_each(namespaces.begin(),namespaces.end(),bind1st(_addns(),domElement));\r
280 }\r
281 \r
282 class _marshallchild : public binary_function<XMLObject*,DOMElement*,void> {\r
283     void* m_log;\r
284 public:\r
285     _marshallchild(void* log) : m_log(log) {}\r
286     void operator()(XMLObject* obj, DOMElement* element) const {\r
287         if (!obj)\r
288             return;\r
289         if (XT_log.isDebugEnabled()) {\r
290             XT_log.debug("getting marshaller for child XMLObject: %s", obj->getElementQName().toString().c_str());\r
291         }\r
292 \r
293         const Marshaller* marshaller = Marshaller::getMarshaller(obj);\r
294         if (!marshaller) {\r
295             XT_log.error(\r
296                 "no default unmarshaller installed, unknown child object: %s",\r
297                 obj->getElementQName().toString().c_str()\r
298                 );\r
299             throw MarshallingException("Marshaller found unknown child element, but no default marshaller was found.");\r
300         }\r
301         element->appendChild(marshaller->marshall(obj, element));\r
302     }\r
303 };\r
304 \r
305 void AbstractXMLObjectMarshaller::marshallChildElements(const XMLObject& xmlObject, DOMElement* domElement) const\r
306 {\r
307     XT_log.debug("marshalling child elements for XMLObject");\r
308 \r
309     const list<XMLObject*>& children=xmlObject.getOrderedChildren();\r
310     for_each(children.begin(),children.end(),bind2nd(_marshallchild(m_log),domElement));\r
311 }\r