First cut at signing support.
[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()\r
43     : m_log(&Category::getInstance(XMLTOOLING_LOGCAT".Marshaller")) {}\r
44 \r
45 DOMElement* AbstractXMLObjectMarshaller::marshall(XMLObject* xmlObject, DOMDocument* document, MarshallingContext* ctx) 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, ctx);\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, MarshallingContext* ctx) 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, ctx);\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 #ifndef XMLTOOLING_NO_XMLSEC\r
163     class _signit : public unary_function<const pair<Signature*,const SigningContext*>&, void> {\r
164     public:\r
165         void operator()(const pair<Signature*,const SigningContext*>& p) const {\r
166             p.first->sign(p.second);\r
167         }\r
168     };\r
169 #endif\r
170 \r
171 void AbstractXMLObjectMarshaller::marshallInto(\r
172     XMLObject& xmlObject, DOMElement* targetElement, MarshallingContext* ctx\r
173     ) const\r
174 {\r
175     if (xmlObject.getElementQName().hasPrefix())\r
176         targetElement->setPrefix(xmlObject.getElementQName().getPrefix());\r
177     marshallElementType(xmlObject, targetElement);\r
178     marshallNamespaces(xmlObject, targetElement);\r
179     marshallAttributes(xmlObject, targetElement);\r
180     marshallChildElements(xmlObject, targetElement);\r
181     marshallElementContent(xmlObject, targetElement);\r
182 \r
183 #ifndef XMLTOOLING_NO_XMLSEC\r
184     if (ctx) {\r
185         for_each(ctx->m_signingContexts.begin(),ctx->m_signingContexts.end(),_signit());\r
186     }\r
187 #endif\r
188 }\r
189 \r
190 void AbstractXMLObjectMarshaller::marshallElementType(XMLObject& xmlObject, DOMElement* domElement) const\r
191 {\r
192     const QName* type = xmlObject.getSchemaType();\r
193     if (type) {\r
194         XT_log.debug("setting xsi:type attribute for XMLObject");\r
195         \r
196         const XMLCh* typeLocalName = type->getLocalPart();\r
197         if (!typeLocalName || !*typeLocalName) {\r
198             throw MarshallingException("Schema type of XMLObject may not have an empty local name.");\r
199         }\r
200 \r
201         static const XMLCh xsitype[] = {\r
202             chLatin_x, chLatin_s, chLatin_i, chColon, chLatin_t, chLatin_y, chLatin_p, chLatin_e, chNull\r
203             };\r
204         \r
205         XMLCh* xsivalue=const_cast<XMLCh*>(typeLocalName);\r
206         const XMLCh* prefix=type->getPrefix();\r
207         if (prefix && *prefix) {\r
208             xsivalue=new XMLCh[XMLString::stringLen(typeLocalName) + XMLString::stringLen(prefix) + 2*sizeof(XMLCh)];\r
209             *xsivalue=chNull;\r
210             XMLString::catString(xsivalue,prefix);\r
211             static const XMLCh colon[] = {chColon, chNull};\r
212             XMLString::catString(xsivalue,colon);\r
213             XMLString::catString(xsivalue,typeLocalName);\r
214         }   \r
215         domElement->setAttributeNS(XMLConstants::XSI_NS, xsitype, xsivalue);\r
216         if (xsivalue != typeLocalName)\r
217             XMLString::release(&xsivalue);\r
218 \r
219         XT_log.debug("Adding XSI namespace to list of namespaces used by XMLObject");\r
220         xmlObject.addNamespace(Namespace(XMLConstants::XSI_NS, XMLConstants::XSI_PREFIX));\r
221     }\r
222 }\r
223 \r
224 class _addns : public binary_function<DOMElement*,Namespace,void> {\r
225 public:\r
226     void operator()(DOMElement* domElement, const Namespace& ns) const {\r
227         const XMLCh* prefix=ns.getNamespacePrefix();\r
228         const XMLCh* uri=ns.getNamespaceURI();\r
229         \r
230         // Check to see if the prefix is already declared properly above this node.\r
231         if (!ns.alwaysDeclare()) {\r
232             const XMLCh* declared=lookupNamespaceURI(domElement->getParentNode(),prefix);\r
233             if (declared && XMLString::equals(declared,uri))\r
234                 return;\r
235         }\r
236             \r
237         if (prefix && *prefix) {\r
238             XMLCh* xmlns=new XMLCh[XMLString::stringLen(XMLConstants::XMLNS_PREFIX) + XMLString::stringLen(prefix) + 2*sizeof(XMLCh)];\r
239             *xmlns=chNull;\r
240             XMLString::catString(xmlns,XMLConstants::XMLNS_PREFIX);\r
241             static const XMLCh colon[] = {chColon, chNull};\r
242             XMLString::catString(xmlns,colon);\r
243             XMLString::catString(xmlns,prefix);\r
244             domElement->setAttributeNS(XMLConstants::XMLNS_NS, xmlns, uri);\r
245         }\r
246         else {\r
247             domElement->setAttributeNS(XMLConstants::XMLNS_NS, XMLConstants::XMLNS_PREFIX, uri);\r
248         }\r
249     }\r
250 \r
251     const XMLCh* lookupNamespaceURI(const DOMNode* n, const XMLCh* prefix) const {\r
252         // Return NULL if no declaration in effect. The empty string signifies the null namespace.\r
253         if (!n || n->getNodeType()!=DOMNode::ELEMENT_NODE) {\r
254             // At the root, the default namespace is set to the null namespace.\r
255             if (!prefix || !*prefix)\r
256                 return &chNull;\r
257             return NULL;    // we're done\r
258         }\r
259         DOMNamedNodeMap* attributes = static_cast<const DOMElement*>(n)->getAttributes();\r
260         if (!attributes)\r
261             return lookupNamespaceURI(n->getParentNode(),prefix);   // defer to parent\r
262         DOMNode* childNode;\r
263         DOMAttr* attribute;\r
264         for (XMLSize_t i=0; i<attributes->getLength(); i++) {\r
265             childNode = attributes->item(i);\r
266             if (childNode->getNodeType() != DOMNode::ATTRIBUTE_NODE)    // not an attribute?\r
267                 continue;\r
268             attribute = static_cast<DOMAttr*>(childNode);\r
269             if (!XMLString::equals(attribute->getNamespaceURI(),XMLConstants::XMLNS_NS))\r
270                 continue;   // not a namespace declaration\r
271             // Local name should be the prefix and the value would be the URI, except for the default namespace.\r
272             if ((!prefix || !*prefix) && XMLString::equals(attribute->getLocalName(),XMLConstants::XMLNS_PREFIX))\r
273                 return attribute->getNodeValue();\r
274             else if (XMLString::equals(prefix,attribute->getLocalName()))\r
275                 return attribute->getNodeValue();\r
276         }\r
277         // Defer to parent.\r
278         return lookupNamespaceURI(n->getParentNode(),prefix);\r
279     }\r
280 };\r
281 \r
282 void AbstractXMLObjectMarshaller::marshallNamespaces(const XMLObject& xmlObject, DOMElement* domElement) const\r
283 {\r
284     XT_log.debug("marshalling namespace attributes for XMLObject");\r
285     const set<Namespace>& namespaces = xmlObject.getNamespaces();\r
286     for_each(namespaces.begin(),namespaces.end(),bind1st(_addns(),domElement));\r
287 }\r
288 \r
289 class _marshallchild : public binary_function<XMLObject*,DOMElement*,void> {\r
290     void* m_log;\r
291 public:\r
292     _marshallchild(void* log) : m_log(log) {}\r
293     void operator()(XMLObject* obj, DOMElement* element) const {\r
294         if (!obj)\r
295             return;\r
296         if (XT_log.isDebugEnabled()) {\r
297             XT_log.debug("getting marshaller for child XMLObject: %s", obj->getElementQName().toString().c_str());\r
298         }\r
299 \r
300         const Marshaller* marshaller = Marshaller::getMarshaller(obj);\r
301         if (!marshaller) {\r
302             XT_log.error(\r
303                 "no default unmarshaller installed, unknown child object: %s",\r
304                 obj->getElementQName().toString().c_str()\r
305                 );\r
306             throw MarshallingException("Marshaller found unknown child element, but no default marshaller was found.");\r
307         }\r
308         marshaller->marshall(obj, element);\r
309     }\r
310 };\r
311 \r
312 void AbstractXMLObjectMarshaller::marshallChildElements(const XMLObject& xmlObject, DOMElement* domElement) const\r
313 {\r
314     XT_log.debug("marshalling child elements for XMLObject");\r
315 \r
316     const list<XMLObject*>& children=xmlObject.getOrderedChildren();\r
317     for_each(children.begin(),children.end(),bind2nd(_marshallchild(m_log),domElement));\r
318 }\r