Initial unit test plus 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(const XMLCh* targetNamespaceURI, const XMLCh* targetLocalName)\r
43         : m_targetQName(targetNamespaceURI, targetLocalName),\r
44         m_log(&Category::getInstance(XMLTOOLING_LOGCAT".Marshaller")) {\r
45     if (!targetLocalName || !*targetLocalName)\r
46         throw MarshallingException("targetLocalName cannot be null or empty");\r
47 }\r
48 \r
49 DOMElement* AbstractXMLObjectMarshaller::marshall(XMLObject* xmlObject, DOMDocument* document) const\r
50 {\r
51 #ifdef _DEBUG\r
52     xmltooling::NDC ndc("marshall");\r
53 #endif\r
54 \r
55     if (XT_log.isDebugEnabled()) {\r
56         XT_log.debug("starting to marshalling %s", xmlObject->getElementQName().toString().c_str());\r
57     }\r
58 \r
59     DOMCachingXMLObject* dc=dynamic_cast<DOMCachingXMLObject*>(xmlObject);\r
60     if (dc) {\r
61         DOMElement* cachedDOM=dc->getDOM();\r
62         if (cachedDOM) {\r
63             if (!document || document==cachedDOM->getOwnerDocument()) {\r
64                 XT_log.debug("XMLObject has a usable cached DOM, reusing it");\r
65                 if (document)\r
66                     setDocumentElement(cachedDOM->getOwnerDocument(),cachedDOM);\r
67                 dc->releaseParentDOM(true);\r
68                 return cachedDOM;\r
69             }\r
70             \r
71             // We have a DOM but it doesn't match the document we were given. This both sucks and blows.\r
72             // Without an adoptNode option to maintain the child pointers, we have to either import the\r
73             // DOM while somehow reassigning all the nested references (which amounts to a complete\r
74             // *unmarshall* operation), or we just release the existing DOM and hope that we can get\r
75             // it back. This depends on all objects being able to preserve their DOM at all costs.\r
76             dc->releaseChildrenDOM(true);\r
77             dc->releaseDOM();\r
78         }\r
79     }\r
80     \r
81     // If we get here, we didn't have a usable DOM (and/or we released the one we had).\r
82     // We may need to create our own document.\r
83     bool bindDocument=false;\r
84     if (!document) {\r
85         document=DOMImplementationRegistry::getDOMImplementation(NULL)->createDocument();\r
86         bindDocument=true;\r
87     }\r
88 \r
89     try {\r
90         XT_log.debug("creating root element to marshall");\r
91         DOMElement* domElement = document->createElementNS(\r
92             xmlObject->getElementQName().getNamespaceURI(), xmlObject->getElementQName().getLocalPart()\r
93             );\r
94         setDocumentElement(document, domElement);\r
95         marshallInto(xmlObject, domElement);\r
96 \r
97         //Recache the DOM.\r
98         if (dc) {\r
99             XT_log.debug("caching DOM for XMLObject (document is %sbound)", bindDocument ? "" : "not ");\r
100             dc->setDOM(domElement, bindDocument);\r
101             dc->releaseParentDOM(true);\r
102         }\r
103 \r
104         return domElement;\r
105     }\r
106     catch (...) {\r
107         // Delete the document if need be, and rethrow.\r
108         if (bindDocument) {\r
109             document->release();\r
110         }\r
111         throw;\r
112     }\r
113 }\r
114 \r
115 DOMElement* AbstractXMLObjectMarshaller::marshall(XMLObject* xmlObject, DOMElement* parentElement) const\r
116 {\r
117 #ifdef _DEBUG\r
118     xmltooling::NDC ndc("marshall");\r
119 #endif\r
120 \r
121     if (XT_log.isDebugEnabled()) {\r
122         XT_log.debug("starting to marshalling %s", xmlObject->getElementQName().toString().c_str());\r
123     }\r
124 \r
125     DOMCachingXMLObject* dc=dynamic_cast<DOMCachingXMLObject*>(xmlObject);\r
126     if (dc) {\r
127         DOMElement* cachedDOM=dc->getDOM();\r
128         if (cachedDOM) {\r
129             if (parentElement->getOwnerDocument()==cachedDOM->getOwnerDocument()) {\r
130                 XT_log.debug("XMLObject has a usable cached DOM, reusing it");\r
131                 if (parentElement!=cachedDOM->getParentNode()) {\r
132                     parentElement->appendChild(cachedDOM);\r
133                     dc->releaseParentDOM(true);\r
134                 }\r
135                 return cachedDOM;\r
136             }\r
137             \r
138             // We have a DOM but it doesn't match the document we were given. This both sucks and blows.\r
139             // Without an adoptNode option to maintain the child pointers, we have to either import the\r
140             // DOM while somehow reassigning all the nested references (which amounts to a complete\r
141             // *unmarshall* operation), or we just release the existing DOM and hope that we can get\r
142             // it back. This depends on all objects being able to preserve their DOM at all costs.\r
143             dc->releaseChildrenDOM(true);\r
144             dc->releaseDOM();\r
145         }\r
146     }\r
147     \r
148     // If we get here, we didn't have a usable DOM (and/or we released the one we had).\r
149     XT_log.debug("creating root element to marshall");\r
150     DOMElement* domElement = parentElement->getOwnerDocument()->createElementNS(\r
151         xmlObject->getElementQName().getNamespaceURI(), xmlObject->getElementQName().getLocalPart()\r
152         );\r
153     parentElement->appendChild(domElement);\r
154     marshallInto(xmlObject, domElement);\r
155 \r
156     //Recache the DOM.\r
157     if (dc) {\r
158         XT_log.debug("caching DOM for XMLObject");\r
159         dc->setDOM(domElement, false);\r
160         dc->releaseParentDOM(true);\r
161     }\r
162 \r
163     return domElement;\r
164 }\r
165         \r
166 void AbstractXMLObjectMarshaller::marshallInto(XMLObject* xmlObject, DOMElement* targetElement) const\r
167 {\r
168     targetElement->setPrefix(xmlObject->getElementQName().getPrefix());\r
169     marshallElementType(xmlObject, targetElement);\r
170     marshallNamespaces(xmlObject, targetElement);\r
171     marshallAttributes(xmlObject, targetElement);\r
172     marshallChildElements(xmlObject, targetElement);\r
173     marshallElementContent(xmlObject, targetElement);\r
174 \r
175     /* TODO Signing/Encryption\r
176     if (xmlObject instanceof SignableXMLObject) {\r
177         signElement(targetElement, xmlObject);\r
178     }\r
179 \r
180     if (xmlObject instanceof EncryptableXMLObject) {\r
181         encryptElement(targetElement, xmlObject);\r
182     }\r
183     */\r
184 }\r
185 \r
186 void AbstractXMLObjectMarshaller::marshallElementType(XMLObject* xmlObject, DOMElement* domElement) const\r
187 {\r
188     const QName* type = xmlObject->getSchemaType();\r
189     if (type) {\r
190         XT_log.debug("setting xsi:type attribute for XMLObject");\r
191         \r
192         const XMLCh* typeLocalName = type->getLocalPart();\r
193         if (!typeLocalName || !*typeLocalName) {\r
194             throw MarshallingException("Schema type of XMLObject may not have an empty local name.");\r
195         }\r
196 \r
197         static const XMLCh xsitype[] = {\r
198             chLatin_x, chLatin_s, chLatin_i, chColon, chLatin_t, chLatin_y, chLatin_p, chLatin_e, chNull\r
199             };\r
200         \r
201         XMLCh* xsivalue=const_cast<XMLCh*>(typeLocalName);\r
202         const XMLCh* prefix=type->getPrefix();\r
203         if (prefix && *prefix) {\r
204             xsivalue=new XMLCh[XMLString::stringLen(typeLocalName) + XMLString::stringLen(prefix) + 2*sizeof(XMLCh)];\r
205             *xsivalue=chNull;\r
206             XMLString::catString(xsivalue,prefix);\r
207             static const XMLCh colon[] = {chColon, chNull};\r
208             XMLString::catString(xsivalue,colon);\r
209             XMLString::catString(xsivalue,typeLocalName);\r
210         }   \r
211         domElement->setAttributeNS(XMLConstants::XSI_NS, xsitype, xsivalue);\r
212         if (xsivalue != typeLocalName)\r
213             XMLString::release(&xsivalue);\r
214 \r
215         XT_log.debug("Adding XSI namespace to list of namespaces used by XMLObject");\r
216         xmlObject->addNamespace(Namespace(XMLConstants::XSI_NS, XMLConstants::XSI_PREFIX));\r
217     }\r
218 }\r
219 \r
220 class _addns : public binary_function<DOMElement*,Namespace,void> {\r
221 public:\r
222     void operator()(DOMElement* domElement, const Namespace& ns) const {\r
223         const XMLCh* prefix=ns.getNamespacePrefix();\r
224         const XMLCh* uri=ns.getNamespaceURI();\r
225         \r
226         // Check to see if the prefix is already declared properly above this node.\r
227         if (!ns.alwaysDeclare() && domElement->getParentNode() &&\r
228                 XMLString::equals(domElement->getParentNode()->lookupNamespaceURI(prefix),uri))\r
229             return;\r
230             \r
231         if (prefix && *prefix) {\r
232             XMLCh* xmlns=new XMLCh[XMLString::stringLen(XMLConstants::XMLNS_PREFIX) + XMLString::stringLen(prefix) + 2*sizeof(XMLCh)];\r
233             *xmlns=chNull;\r
234             XMLString::catString(xmlns,XMLConstants::XMLNS_PREFIX);\r
235             static const XMLCh colon[] = {chColon, chNull};\r
236             XMLString::catString(xmlns,colon);\r
237             XMLString::catString(xmlns,prefix);\r
238             domElement->setAttributeNS(XMLConstants::XMLNS_NS, xmlns, uri);\r
239         }\r
240         else {\r
241             domElement->setAttributeNS(XMLConstants::XMLNS_NS, XMLConstants::XMLNS_PREFIX, uri);\r
242         }\r
243     }\r
244 };\r
245 \r
246 void AbstractXMLObjectMarshaller::marshallNamespaces(const XMLObject* xmlObject, DOMElement* domElement) const\r
247 {\r
248     XT_log.debug("marshalling namespace attributes for XMLObject");\r
249     const set<Namespace>& namespaces = xmlObject->getNamespaces();\r
250     for_each(namespaces.begin(),namespaces.end(),bind1st(_addns(),domElement));\r
251 }\r
252 \r
253 class _marshallchild : public binary_function<XMLObject*,DOMElement*,void> {\r
254     void* m_log;\r
255 public:\r
256     _marshallchild(void* log) : m_log(log) {}\r
257     void operator()(XMLObject* obj, DOMElement* element) const {\r
258         if (XT_log.isDebugEnabled()) {\r
259             XT_log.debug("getting marshaller for child XMLObject: %s", obj->getElementQName().toString().c_str());\r
260         }\r
261 \r
262         const Marshaller* marshaller = Marshaller::getMarshaller(obj);\r
263         if (!marshaller) {\r
264             XT_log.error(\r
265                 "no default unmarshaller installed, unknown child object: %s",\r
266                 obj->getElementQName().toString().c_str()\r
267                 );\r
268             throw MarshallingException("Marshaller found unknown child element, but no default marshaller was found.");\r
269         }\r
270         element->appendChild(marshaller->marshall(obj, element));\r
271     }\r
272 };\r
273 \r
274 void AbstractXMLObjectMarshaller::marshallChildElements(const XMLObject* xmlObject, DOMElement* domElement) const\r
275 {\r
276     XT_log.debug("marshalling child elements for XMLObject");\r
277 \r
278     vector<XMLObject*> children;\r
279     if (xmlObject->getOrderedChildren(children)) {\r
280         for_each(children.begin(),children.end(),bind2nd(_marshallchild(m_log),domElement));\r
281     }\r
282 }\r