a17476e8a3cbe07429bfee939107e6b9d5819548
[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 "exceptions.h"\r
25 #include "io/AbstractXMLObjectMarshaller.h"\r
26 #ifndef XMLTOOLING_NO_XMLSEC\r
27     #include "signature/Signature.h"\r
28 #endif\r
29 #include "util/NDC.h"\r
30 #include "util/XMLConstants.h"\r
31 #include "util/XMLHelper.h"\r
32 \r
33 #include <algorithm>\r
34 #include <functional>\r
35 #include <xercesc/util/XMLUniDefs.hpp>\r
36 #include <log4cpp/Category.hh>\r
37 \r
38 using namespace xmlsignature;\r
39 using namespace xmltooling;\r
40 using namespace log4cpp;\r
41 using namespace std;\r
42 \r
43 #define XT_log (*static_cast<Category*>(m_log))\r
44 \r
45 DOMElement* AbstractXMLObjectMarshaller::marshall(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 marshal %s", getElementQName().toString().c_str());\r
53     }\r
54 \r
55     DOMElement* cachedDOM=getDOM();\r
56     if (cachedDOM) {\r
57         if (!document || document==cachedDOM->getOwnerDocument()) {\r
58             XT_log.debug("XMLObject has a usable cached DOM, reusing it");\r
59             if (document)\r
60                 setDocumentElement(cachedDOM->getOwnerDocument(),cachedDOM);\r
61             releaseParentDOM(true);\r
62             return cachedDOM;\r
63         }\r
64         \r
65         // We have a DOM but it doesn't match the document we were given. This both sucks and blows.\r
66         // Without an adoptNode option to maintain the child pointers, we have to either import the\r
67         // DOM while somehow reassigning all the nested references (which amounts to a complete\r
68         // *unmarshall* operation), or we just release the existing DOM and hope that we can get\r
69         // it back. This depends on all objects being able to preserve their DOM at all costs.\r
70         releaseChildrenDOM(true);\r
71         releaseDOM();\r
72     }\r
73     \r
74     // If we get here, we didn't have a usable DOM (and/or we released the one we had).\r
75     // We may need to create our own document.\r
76     bool bindDocument=false;\r
77     if (!document) {\r
78         document=DOMImplementationRegistry::getDOMImplementation(NULL)->createDocument();\r
79         bindDocument=true;\r
80     }\r
81 \r
82     try {\r
83         XT_log.debug("creating root element to marshall");\r
84         DOMElement* domElement = document->createElementNS(\r
85             getElementQName().getNamespaceURI(), getElementQName().getLocalPart()\r
86             );\r
87         setDocumentElement(document, domElement);\r
88         marshallInto(domElement, ctx);\r
89 \r
90         //Recache the DOM.\r
91         XT_log.debug("caching DOM for XMLObject (document is %sbound)", bindDocument ? "" : "not ");\r
92         setDOM(domElement, bindDocument);\r
93         releaseParentDOM(true);\r
94 \r
95         return domElement;\r
96     }\r
97     catch (...) {\r
98         // Delete the document if need be, and rethrow.\r
99         if (bindDocument) {\r
100             document->release();\r
101         }\r
102         throw;\r
103     }\r
104 }\r
105 \r
106 DOMElement* AbstractXMLObjectMarshaller::marshall(DOMElement* parentElement, MarshallingContext* ctx) const\r
107 {\r
108 #ifdef _DEBUG\r
109     xmltooling::NDC ndc("marshall");\r
110 #endif\r
111 \r
112     if (XT_log.isDebugEnabled()) {\r
113         XT_log.debug("starting to marshalling %s", getElementQName().toString().c_str());\r
114     }\r
115 \r
116     DOMElement* cachedDOM=getDOM();\r
117     if (cachedDOM) {\r
118         if (parentElement->getOwnerDocument()==cachedDOM->getOwnerDocument()) {\r
119             XT_log.debug("XMLObject has a usable cached DOM, reusing it");\r
120             if (parentElement!=cachedDOM->getParentNode()) {\r
121                 parentElement->appendChild(cachedDOM);\r
122                 releaseParentDOM(true);\r
123             }\r
124             return cachedDOM;\r
125         }\r
126         \r
127         // We have a DOM but it doesn't match the document we were given. This both sucks and blows.\r
128         // Without an adoptNode option to maintain the child pointers, we have to either import the\r
129         // DOM while somehow reassigning all the nested references (which amounts to a complete\r
130         // *unmarshall* operation), or we just release the existing DOM and hope that we can get\r
131         // it back. This depends on all objects being able to preserve their DOM at all costs.\r
132         releaseChildrenDOM(true);\r
133         releaseDOM();\r
134     }\r
135     \r
136     // If we get here, we didn't have a usable DOM (and/or we released the one we had).\r
137     XT_log.debug("creating root element to marshall");\r
138     DOMElement* domElement = parentElement->getOwnerDocument()->createElementNS(\r
139         getElementQName().getNamespaceURI(), getElementQName().getLocalPart()\r
140         );\r
141     parentElement->appendChild(domElement);\r
142     marshallInto(domElement, ctx);\r
143 \r
144     //Recache the DOM.\r
145     XT_log.debug("caching DOM for XMLObject");\r
146     setDOM(domElement, false);\r
147     releaseParentDOM(true);\r
148 \r
149     return domElement;\r
150 }\r
151 \r
152 #ifndef XMLTOOLING_NO_XMLSEC\r
153     class _signit : public unary_function<const pair<Signature*,const SigningContext*>&, void> {\r
154     public:\r
155         void operator()(const pair<Signature*,const SigningContext*>& p) const {\r
156             p.first->sign(*(p.second));\r
157         }\r
158     };\r
159 #endif\r
160 \r
161 void AbstractXMLObjectMarshaller::marshallInto(DOMElement* targetElement, MarshallingContext* ctx) const\r
162 {\r
163     if (getElementQName().hasPrefix())\r
164         targetElement->setPrefix(getElementQName().getPrefix());\r
165     marshallElementType(targetElement);\r
166     marshallNamespaces(targetElement);\r
167     marshallAttributes(targetElement);\r
168     marshallChildElements(targetElement);\r
169     marshallElementContent(targetElement);\r
170 \r
171 #ifndef XMLTOOLING_NO_XMLSEC\r
172     if (ctx) {\r
173         for_each(ctx->m_signingContexts.begin(),ctx->m_signingContexts.end(),_signit());\r
174     }\r
175 #endif\r
176 }\r
177 \r
178 void AbstractXMLObjectMarshaller::marshallElementType(DOMElement* domElement) const\r
179 {\r
180     const QName* type = getSchemaType();\r
181     if (type) {\r
182         XT_log.debug("setting xsi:type attribute for XMLObject");\r
183         \r
184         const XMLCh* typeLocalName = type->getLocalPart();\r
185         if (!typeLocalName || !*typeLocalName) {\r
186             throw MarshallingException("Schema type of XMLObject may not have an empty local name.");\r
187         }\r
188 \r
189         static const XMLCh xsitype[] = {\r
190             chLatin_x, chLatin_s, chLatin_i, chColon, chLatin_t, chLatin_y, chLatin_p, chLatin_e, chNull\r
191             };\r
192         \r
193         XMLCh* xsivalue=const_cast<XMLCh*>(typeLocalName);\r
194         const XMLCh* prefix=type->getPrefix();\r
195         if (prefix && *prefix) {\r
196             xsivalue=new XMLCh[XMLString::stringLen(typeLocalName) + XMLString::stringLen(prefix) + 2*sizeof(XMLCh)];\r
197             *xsivalue=chNull;\r
198             XMLString::catString(xsivalue,prefix);\r
199             static const XMLCh colon[] = {chColon, chNull};\r
200             XMLString::catString(xsivalue,colon);\r
201             XMLString::catString(xsivalue,typeLocalName);\r
202         }   \r
203         domElement->setAttributeNS(XMLConstants::XSI_NS, xsitype, xsivalue);\r
204         if (xsivalue != typeLocalName)\r
205             XMLString::release(&xsivalue);\r
206 \r
207         XT_log.debug("Adding XSI namespace to list of namespaces used by XMLObject");\r
208         addNamespace(Namespace(XMLConstants::XSI_NS, XMLConstants::XSI_PREFIX));\r
209     }\r
210 }\r
211 \r
212 class _addns : public binary_function<DOMElement*,Namespace,void> {\r
213 public:\r
214     void operator()(DOMElement* domElement, const Namespace& ns) const {\r
215         const XMLCh* prefix=ns.getNamespacePrefix();\r
216         const XMLCh* uri=ns.getNamespaceURI();\r
217         \r
218         // Check to see if the prefix is already declared properly above this node.\r
219         if (!ns.alwaysDeclare()) {\r
220             const XMLCh* declared=lookupNamespaceURI(domElement->getParentNode(),prefix);\r
221             if (declared && XMLString::equals(declared,uri))\r
222                 return;\r
223         }\r
224             \r
225         if (prefix && *prefix) {\r
226             XMLCh* xmlns=new XMLCh[XMLString::stringLen(XMLConstants::XMLNS_PREFIX) + XMLString::stringLen(prefix) + 2*sizeof(XMLCh)];\r
227             *xmlns=chNull;\r
228             XMLString::catString(xmlns,XMLConstants::XMLNS_PREFIX);\r
229             static const XMLCh colon[] = {chColon, chNull};\r
230             XMLString::catString(xmlns,colon);\r
231             XMLString::catString(xmlns,prefix);\r
232             domElement->setAttributeNS(XMLConstants::XMLNS_NS, xmlns, uri);\r
233         }\r
234         else {\r
235             domElement->setAttributeNS(XMLConstants::XMLNS_NS, XMLConstants::XMLNS_PREFIX, uri);\r
236         }\r
237     }\r
238 \r
239     const XMLCh* lookupNamespaceURI(const DOMNode* n, const XMLCh* prefix) const {\r
240         // Return NULL if no declaration in effect. The empty string signifies the null namespace.\r
241         if (!n || n->getNodeType()!=DOMNode::ELEMENT_NODE) {\r
242             // At the root, the default namespace is set to the null namespace.\r
243             if (!prefix || !*prefix)\r
244                 return &chNull;\r
245             return NULL;    // we're done\r
246         }\r
247         DOMNamedNodeMap* attributes = static_cast<const DOMElement*>(n)->getAttributes();\r
248         if (!attributes)\r
249             return lookupNamespaceURI(n->getParentNode(),prefix);   // defer to parent\r
250         DOMNode* childNode;\r
251         DOMAttr* attribute;\r
252         for (XMLSize_t i=0; i<attributes->getLength(); i++) {\r
253             childNode = attributes->item(i);\r
254             if (childNode->getNodeType() != DOMNode::ATTRIBUTE_NODE)    // not an attribute?\r
255                 continue;\r
256             attribute = static_cast<DOMAttr*>(childNode);\r
257             if (!XMLString::equals(attribute->getNamespaceURI(),XMLConstants::XMLNS_NS))\r
258                 continue;   // not a namespace declaration\r
259             // Local name should be the prefix and the value would be the URI, except for the default namespace.\r
260             if ((!prefix || !*prefix) && XMLString::equals(attribute->getLocalName(),XMLConstants::XMLNS_PREFIX))\r
261                 return attribute->getNodeValue();\r
262             else if (XMLString::equals(prefix,attribute->getLocalName()))\r
263                 return attribute->getNodeValue();\r
264         }\r
265         // Defer to parent.\r
266         return lookupNamespaceURI(n->getParentNode(),prefix);\r
267     }\r
268 };\r
269 \r
270 void AbstractXMLObjectMarshaller::marshallNamespaces(DOMElement* domElement) const\r
271 {\r
272     XT_log.debug("marshalling namespace attributes for XMLObject");\r
273     const set<Namespace>& namespaces = getNamespaces();\r
274     for_each(namespaces.begin(),namespaces.end(),bind1st(_addns(),domElement));\r
275 }\r
276 \r
277 class _marshallit : public binary_function<const XMLObject*,DOMElement*,void> {\r
278 public:\r
279     void operator()(const XMLObject* xo, DOMElement* e) const {\r
280         if (xo) xo->marshall(e);\r
281     }\r
282 };\r
283 \r
284 void AbstractXMLObjectMarshaller::marshallChildElements(DOMElement* domElement) const\r
285 {\r
286     XT_log.debug("marshalling child elements for XMLObject");\r
287 \r
288     const list<XMLObject*>& children=getOrderedChildren();\r
289     for_each(children.begin(),children.end(),bind2nd(_marshallit(),domElement));\r
290 }\r