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