Default support for arbitrary DOM objects.
[shibboleth/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("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, using it.");\r
65                 return cachedDOM;\r
66             }\r
67             \r
68             // We have a DOM but it doesn't match the document we were given. This both sucks and blows.\r
69             // Without an adoptNode option to maintain the child pointers, we have to either import the\r
70             // DOM while somehow reassigning all the nested references (which amounts to a complete\r
71             // *unmarshall* operation), or we just release the existing DOM and hope that we can get\r
72             // it back. This depends on all objects being able to preserve their DOM at all costs.\r
73             dc->releaseChildrenDOM(true);\r
74             dc->releaseDOM();\r
75         }\r
76     }\r
77     \r
78     // If we get here, we didn't have a usable DOM (and/or we flushed the one we had).\r
79     // We may need to create our own document.\r
80     bool bindDocument=false;\r
81     if (!document) {\r
82         document=DOMImplementationRegistry::getDOMImplementation(NULL)->createDocument();\r
83         bindDocument=true;\r
84     }\r
85 \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     domElement->setPrefix(xmlObject->getElementQName().getPrefix());\r
91 \r
92     try {\r
93         marshallNamespaces(xmlObject, domElement);\r
94         marshallAttributes(xmlObject, domElement);\r
95         marshallChildElements(xmlObject, domElement);\r
96         marshallElementContent(xmlObject, domElement);\r
97         marshallElementType(xmlObject, domElement);\r
98     \r
99         /* TODO: signing\r
100         if (xmlObject instanceof SignableXMLObject) {\r
101             signElement(domElement, xmlObject);\r
102         }\r
103         */\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             document=NULL;\r
110         }\r
111         throw;\r
112     }\r
113 \r
114     //Recache the DOM.\r
115     if (dc) {\r
116         XT_log.debug("caching DOM for XMLObject");\r
117         dc->setDOM(domElement, bindDocument);\r
118     }\r
119 \r
120     return domElement;\r
121 }\r
122     \r
123 void AbstractXMLObjectMarshaller::marshallElementType(XMLObject* xmlObject, DOMElement* domElement) const\r
124 {\r
125     const QName* type = xmlObject->getSchemaType();\r
126     if (type) {\r
127         XT_log.debug("setting xsi:type attribute for XMLObject");\r
128         \r
129         const XMLCh* typeLocalName = type->getLocalPart();\r
130         if (!typeLocalName || !*typeLocalName) {\r
131             throw MarshallingException("Schema type of XMLObject may not have an empty local name.");\r
132         }\r
133 \r
134         static const XMLCh xsitype[] = {\r
135             chLatin_x, chLatin_s, chLatin_i, chColon, chLatin_t, chLatin_y, chLatin_p, chLatin_e, chNull\r
136             };\r
137         \r
138         XMLCh* xsivalue=const_cast<XMLCh*>(typeLocalName);\r
139         const XMLCh* prefix=type->getPrefix();\r
140         if (prefix && *prefix) {\r
141             xsivalue=new XMLCh[XMLString::stringLen(typeLocalName) + XMLString::stringLen(prefix) + 2*sizeof(XMLCh)];\r
142             *xsivalue=chNull;\r
143             XMLString::catString(xsivalue,prefix);\r
144             static const XMLCh colon[] = {chColon, chNull};\r
145             XMLString::catString(xsivalue,colon);\r
146             XMLString::catString(xsivalue,typeLocalName);\r
147         }   \r
148         domElement->setAttributeNS(XMLConstants::XSI_NS, xsitype, xsivalue);\r
149         if (xsivalue != typeLocalName)\r
150             XMLString::release(&xsivalue);\r
151 \r
152         XT_log.debug("Adding XSI namespace to list of namespaces used by XMLObject");\r
153         xmlObject->addNamespace(Namespace(XMLConstants::XSI_NS, XMLConstants::XSI_PREFIX));\r
154     }\r
155 }\r
156 \r
157 class _addns : public binary_function<DOMElement*,Namespace,void> {\r
158 public:\r
159     void operator()(DOMElement* domElement, const Namespace& ns) const {\r
160         const XMLCh* prefix=ns.getNamespacePrefix();\r
161         const XMLCh* uri=ns.getNamespaceURI();\r
162         if (prefix && *prefix) {\r
163             XMLCh* xmlns=new XMLCh[XMLString::stringLen(XMLConstants::XMLNS_PREFIX) + XMLString::stringLen(prefix) + 2*sizeof(XMLCh)];\r
164             *xmlns=chNull;\r
165             XMLString::catString(xmlns,XMLConstants::XMLNS_PREFIX);\r
166             static const XMLCh colon[] = {chColon, chNull};\r
167             XMLString::catString(xmlns,colon);\r
168             XMLString::catString(xmlns,prefix);\r
169             domElement->setAttributeNS(XMLConstants::XMLNS_NS, xmlns, uri);\r
170         }\r
171         else {\r
172             domElement->setAttributeNS(XMLConstants::XMLNS_NS, XMLConstants::XMLNS_PREFIX, uri);\r
173         }\r
174     }\r
175 };\r
176 \r
177 void AbstractXMLObjectMarshaller::marshallNamespaces(const XMLObject* xmlObject, DOMElement* domElement) const\r
178 {\r
179     XT_log.debug("marshalling namespace attributes for XMLObject");\r
180     const set<Namespace>& namespaces = xmlObject->getNamespaces();\r
181     for_each(namespaces.begin(),namespaces.end(),bind1st(_addns(),domElement));\r
182 }\r
183 \r
184 class _marshallchild : public binary_function<XMLObject*,DOMElement*,void> {\r
185     void* m_log;\r
186 public:\r
187     _marshallchild(void* log) : m_log(log) {}\r
188     void operator()(XMLObject* obj, DOMElement* element) const {\r
189         if (XT_log.isDebugEnabled()) {\r
190             XT_log.debug("getting marshaller for child XMLObject: %s", obj->getElementQName().toString().c_str());\r
191         }\r
192 \r
193         const Marshaller* marshaller = Marshaller::getMarshaller(obj);\r
194         if (!marshaller) {\r
195             XT_log.error(\r
196                 "no default unmarshaller installed, unknown child object: %s",\r
197                 obj->getElementQName().toString().c_str()\r
198                 );\r
199             throw MarshallingException("Marshaller found unknown child element, but no default marshaller was found.");\r
200         }\r
201         element->appendChild(marshaller->marshall(obj, element->getOwnerDocument()));\r
202     }\r
203 };\r
204 \r
205 void AbstractXMLObjectMarshaller::marshallChildElements(const XMLObject* xmlObject, DOMElement* domElement) const\r
206 {\r
207     XT_log.debug("marshalling child elements for XMLObject");\r
208 \r
209     vector<XMLObject*> children;\r
210     if (xmlObject->getOrderedChildren(children)) {\r
211         for_each(children.begin(),children.end(),bind2nd(_marshallchild(m_log),domElement));\r
212     }\r
213 }\r