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