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