2 * Copyright 2001-2010 Internet2
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * AbstractXMLObjectMarshaller.cpp
20 * A thread-safe abstract marshaller.
24 #include "exceptions.h"
25 #include "io/AbstractXMLObjectMarshaller.h"
26 #ifndef XMLTOOLING_NO_XMLSEC
27 #include "security/Credential.h"
28 #include "signature/Signature.h"
31 #include "util/XMLConstants.h"
32 #include "util/XMLHelper.h"
36 #include <xercesc/util/XMLUniDefs.hpp>
38 #ifndef XMLTOOLING_NO_XMLSEC
39 using namespace xmlsignature;
41 using namespace xmlconstants;
42 using namespace xmltooling;
43 using namespace xercesc;
46 AbstractXMLObjectMarshaller::AbstractXMLObjectMarshaller()
50 AbstractXMLObjectMarshaller::~AbstractXMLObjectMarshaller()
54 void AbstractXMLObjectMarshaller::setDocumentElement(DOMDocument* document, DOMElement* element) const
56 DOMElement* documentRoot = document->getDocumentElement();
58 document->replaceChild(element, documentRoot);
60 document->appendChild(element);
63 DOMElement* AbstractXMLObjectMarshaller::marshall(
65 #ifndef XMLTOOLING_NO_XMLSEC
66 ,const vector<Signature*>* sigs
67 ,const Credential* credential
72 xmltooling::NDC ndc("marshall");
75 if (m_log.isDebugEnabled()) {
76 m_log.debug("starting to marshal %s", getElementQName().toString().c_str());
79 DOMElement* cachedDOM=getDOM();
81 if (!document || document==cachedDOM->getOwnerDocument()) {
82 m_log.debug("XMLObject has a usable cached DOM, reusing it");
84 setDocumentElement(cachedDOM->getOwnerDocument(),cachedDOM);
85 releaseParentDOM(true);
89 // We have a DOM but it doesn't match the document we were given. This both sucks and blows.
90 // Without an adoptNode option to maintain the child pointers, we have to either import the
91 // DOM while somehow reassigning all the nested references (which amounts to a complete
92 // *unmarshall* operation), or we just release the existing DOM and hope that we can get
93 // it back. This depends on all objects being able to preserve their DOM at all costs.
94 releaseChildrenDOM(true);
98 // If we get here, we didn't have a usable DOM (and/or we released the one we had).
99 prepareForMarshalling();
101 // We may need to create our own document.
102 bool bindDocument=false;
104 document=DOMImplementationRegistry::getDOMImplementation(nullptr)->createDocument();
108 XercesJanitor<DOMDocument> janitor(bindDocument ? document : nullptr);
110 m_log.debug("creating root element to marshall");
111 DOMElement* domElement = document->createElementNS(
112 getElementQName().getNamespaceURI(), getElementQName().getLocalPart()
114 setDocumentElement(document, domElement);
115 #ifndef XMLTOOLING_NO_XMLSEC
116 marshallInto(domElement, sigs, credential);
118 marshallInto(domElement);
121 m_log.debug("caching DOM for XMLObject (document is %sbound)", bindDocument ? "" : "not ");
122 setDOM(domElement, bindDocument);
123 janitor.release(); // safely transferred
124 releaseParentDOM(true);
129 DOMElement* AbstractXMLObjectMarshaller::marshall(
130 DOMElement* parentElement
131 #ifndef XMLTOOLING_NO_XMLSEC
132 ,const vector<Signature*>* sigs
133 ,const Credential* credential
138 xmltooling::NDC ndc("marshall");
141 if (m_log.isDebugEnabled()) {
142 m_log.debug("starting to marshalling %s", getElementQName().toString().c_str());
145 DOMElement* cachedDOM=getDOM();
147 if (parentElement->getOwnerDocument()==cachedDOM->getOwnerDocument()) {
148 m_log.debug("XMLObject has a usable cached DOM, reusing it");
149 if (parentElement!=cachedDOM->getParentNode()) {
150 parentElement->appendChild(cachedDOM);
151 releaseParentDOM(true);
156 // We have a DOM but it doesn't match the document we were given. This both sucks and blows.
157 // Without an adoptNode option to maintain the child pointers, we have to either import the
158 // DOM while somehow reassigning all the nested references (which amounts to a complete
159 // *unmarshall* operation), or we just release the existing DOM and hope that we can get
160 // it back. This depends on all objects being able to preserve their DOM at all costs.
161 releaseChildrenDOM(true);
165 // If we get here, we didn't have a usable DOM (and/or we released the one we had).
166 prepareForMarshalling();
168 m_log.debug("creating root element to marshall");
169 DOMElement* domElement = parentElement->getOwnerDocument()->createElementNS(
170 getElementQName().getNamespaceURI(), getElementQName().getLocalPart()
172 parentElement->appendChild(domElement);
173 #ifndef XMLTOOLING_NO_XMLSEC
174 marshallInto(domElement, sigs, credential);
176 marshallInto(domElement);
180 m_log.debug("caching DOM for XMLObject");
181 setDOM(domElement, false);
182 releaseParentDOM(true);
187 void AbstractXMLObjectMarshaller::marshallInto(
188 DOMElement* targetElement
189 #ifndef XMLTOOLING_NO_XMLSEC
190 ,const vector<Signature*>* sigs
191 ,const Credential* credential
195 if (getElementQName().hasPrefix())
196 targetElement->setPrefix(getElementQName().getPrefix());
198 if (m_schemaLocation || m_noNamespaceSchemaLocation) {
199 static const XMLCh schemaLocation[] = {
200 chLatin_x, chLatin_s, chLatin_i, chColon,
201 chLatin_s, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_a,
202 chLatin_L, chLatin_o, chLatin_c, chLatin_a, chLatin_t, chLatin_i, chLatin_o, chLatin_n, chNull
204 static const XMLCh noNamespaceSchemaLocation[] = {
205 chLatin_x, chLatin_s, chLatin_i, chColon,
206 chLatin_n, chLatin_o, chLatin_N, chLatin_a, chLatin_m, chLatin_e, chLatin_s, chLatin_p, chLatin_a, chLatin_c, chLatin_e,
207 chLatin_S, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_a,
208 chLatin_L, chLatin_o, chLatin_c, chLatin_a, chLatin_t, chLatin_i, chLatin_o, chLatin_n, chNull
210 if (targetElement->getParentNode()==nullptr || targetElement->getParentNode()->getNodeType()==DOMNode::DOCUMENT_NODE) {
211 if (m_schemaLocation)
212 targetElement->setAttributeNS(XSI_NS,schemaLocation,m_schemaLocation);
213 if (m_noNamespaceSchemaLocation)
214 targetElement->setAttributeNS(XSI_NS,noNamespaceSchemaLocation,m_noNamespaceSchemaLocation);
218 static const XMLCh _nil[] = { chLatin_x, chLatin_s, chLatin_i, chColon, chLatin_n, chLatin_i, chLatin_l, chNull };
220 if (m_nil != xmlconstants::XML_BOOL_NULL) {
222 case xmlconstants::XML_BOOL_TRUE:
223 targetElement->setAttributeNS(XSI_NS, _nil, xmlconstants::XML_TRUE);
225 case xmlconstants::XML_BOOL_ONE:
226 targetElement->setAttributeNS(XSI_NS, _nil, xmlconstants::XML_ONE);
228 case xmlconstants::XML_BOOL_FALSE:
229 targetElement->setAttributeNS(XSI_NS, _nil, xmlconstants::XML_FALSE);
231 case xmlconstants::XML_BOOL_ZERO:
232 targetElement->setAttributeNS(XSI_NS, _nil, xmlconstants::XML_ZERO);
235 m_log.debug("adding XSI namespace to list of namespaces visibly used by XMLObject");
236 addNamespace(Namespace(XSI_NS, XSI_PREFIX, false, Namespace::VisiblyUsed));
239 marshallElementType(targetElement);
240 marshallNamespaces(targetElement);
241 marshallAttributes(targetElement);
243 #ifndef XMLTOOLING_NO_XMLSEC
244 marshallContent(targetElement,credential);
246 for_each(sigs->begin(),sigs->end(),bind2nd(mem_fun1_t<void,Signature,const Credential*>(&Signature::sign),credential));
249 marshallContent(targetElement);
253 void AbstractXMLObjectMarshaller::marshallElementType(DOMElement* domElement) const
255 const QName* type = getSchemaType();
257 m_log.debug("setting xsi:type attribute for XMLObject");
259 const XMLCh* typeLocalName = type->getLocalPart();
260 if (!typeLocalName || !*typeLocalName) {
261 throw MarshallingException("Schema type of XMLObject may not have an empty local name.");
264 static const XMLCh xsitype[] = {
265 chLatin_x, chLatin_s, chLatin_i, chColon, chLatin_t, chLatin_y, chLatin_p, chLatin_e, chNull
268 XMLCh* xsivalue=const_cast<XMLCh*>(typeLocalName);
269 const XMLCh* prefix=type->getPrefix();
270 if (prefix && *prefix) {
271 xsivalue=new XMLCh[XMLString::stringLen(typeLocalName) + XMLString::stringLen(prefix) + 2*sizeof(XMLCh)];
273 XMLString::catString(xsivalue,prefix);
274 static const XMLCh colon[] = {chColon, chNull};
275 XMLString::catString(xsivalue,colon);
276 XMLString::catString(xsivalue,typeLocalName);
278 domElement->setAttributeNS(XSI_NS, xsitype, xsivalue);
279 if (xsivalue != typeLocalName)
280 XMLString::release(&xsivalue);
282 m_log.debug("adding XSI namespace to list of namespaces visibly used by XMLObject");
283 addNamespace(Namespace(XSI_NS, XSI_PREFIX, false, Namespace::VisiblyUsed));
287 class _addns : public binary_function<DOMElement*,Namespace,void> {
289 void operator()(DOMElement* domElement, const Namespace& ns) const {
290 const XMLCh* prefix=ns.getNamespacePrefix();
291 const XMLCh* uri=ns.getNamespaceURI();
293 // Check for xmlns:xml.
294 if (XMLString::equals(prefix, XML_PREFIX) && XMLString::equals(uri, XML_NS))
297 // Check to see if the prefix is already declared properly above this node.
298 if (!ns.alwaysDeclare()) {
299 const XMLCh* declared=lookupNamespaceURI(domElement->getParentNode(),prefix);
300 if (declared && XMLString::equals(declared,uri))
304 if (prefix && *prefix) {
305 XMLCh* xmlns=new XMLCh[XMLString::stringLen(XMLNS_PREFIX) + XMLString::stringLen(prefix) + 2*sizeof(XMLCh)];
307 XMLString::catString(xmlns,XMLNS_PREFIX);
308 static const XMLCh colon[] = {chColon, chNull};
309 XMLString::catString(xmlns,colon);
310 XMLString::catString(xmlns,prefix);
311 domElement->setAttributeNS(XMLNS_NS, xmlns, uri);
315 domElement->setAttributeNS(XMLNS_NS, XMLNS_PREFIX, uri);
319 const XMLCh* lookupNamespaceURI(const DOMNode* n, const XMLCh* prefix) const {
320 // Return nullptr if no declaration in effect. The empty string signifies the null namespace.
321 if (!n || n->getNodeType()!=DOMNode::ELEMENT_NODE) {
322 // At the root, the default namespace is set to the null namespace.
323 if (!prefix || !*prefix)
325 return nullptr; // we're done
327 DOMNamedNodeMap* attributes = static_cast<const DOMElement*>(n)->getAttributes();
329 return lookupNamespaceURI(n->getParentNode(),prefix); // defer to parent
332 for (XMLSize_t i=0; i<attributes->getLength(); i++) {
333 childNode = attributes->item(i);
334 if (childNode->getNodeType() != DOMNode::ATTRIBUTE_NODE) // not an attribute?
336 attribute = static_cast<DOMAttr*>(childNode);
337 if (!XMLString::equals(attribute->getNamespaceURI(),XMLNS_NS))
338 continue; // not a namespace declaration
339 // Local name should be the prefix and the value would be the URI, except for the default namespace.
340 if ((!prefix || !*prefix) && XMLString::equals(attribute->getLocalName(),XMLNS_PREFIX))
341 return attribute->getNodeValue();
342 else if (XMLString::equals(prefix,attribute->getLocalName()))
343 return attribute->getNodeValue();
346 return lookupNamespaceURI(n->getParentNode(),prefix);
350 void AbstractXMLObjectMarshaller::marshallNamespaces(DOMElement* domElement) const
352 m_log.debug("marshalling namespace attributes for XMLObject");
353 const set<Namespace>& namespaces = getNamespaces();
354 for_each(namespaces.begin(),namespaces.end(),bind1st(_addns(),domElement));
357 void AbstractXMLObjectMarshaller::marshallContent(
358 DOMElement* domElement
359 #ifndef XMLTOOLING_NO_XMLSEC
360 ,const Credential* credential
364 m_log.debug("marshalling text and child elements for XMLObject");
367 const XMLCh* val = getTextContent(pos);
369 domElement->appendChild(domElement->getOwnerDocument()->createTextNode(val));
371 const list<XMLObject*>& children=getOrderedChildren();
372 for (list<XMLObject*>::const_iterator i=children.begin(); i!=children.end(); ++i) {
374 #ifndef XMLTOOLING_NO_XMLSEC
375 (*i)->marshall(domElement,nullptr,credential);
377 (*i)->marshall(domElement);
379 val = getTextContent(++pos);
381 domElement->appendChild(domElement->getOwnerDocument()->createTextNode(val));
386 void AbstractXMLObjectMarshaller::marshallAttributes(DOMElement* domElement) const
390 void AbstractXMLObjectMarshaller::prepareForMarshalling() const