1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.opensaml.xml.io;
18
19 import java.util.List;
20 import java.util.Set;
21
22 import javax.xml.namespace.QName;
23 import javax.xml.parsers.DocumentBuilderFactory;
24 import javax.xml.parsers.ParserConfigurationException;
25
26 import org.opensaml.xml.Configuration;
27 import org.opensaml.xml.Namespace;
28 import org.opensaml.xml.XMLObject;
29 import org.opensaml.xml.parse.XMLParserException;
30 import org.opensaml.xml.util.DatatypeHelper;
31 import org.opensaml.xml.util.XMLConstants;
32 import org.opensaml.xml.util.XMLHelper;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35 import org.w3c.dom.Document;
36 import org.w3c.dom.Element;
37
38
39
40
41
42
43
44
45
46
47
48
49 public abstract class AbstractXMLObjectMarshaller implements Marshaller {
50
51
52 private final Logger log = LoggerFactory.getLogger(AbstractXMLObjectMarshaller.class);
53
54
55 private QName targetQName;
56
57
58 private MarshallerFactory marshallerFactory;
59
60
61
62
63
64 protected AbstractXMLObjectMarshaller() {
65 marshallerFactory = Configuration.getMarshallerFactory();
66 }
67
68
69
70
71
72
73
74
75
76
77
78
79 protected AbstractXMLObjectMarshaller(String targetNamespaceURI, String targetLocalName) {
80 targetQName = XMLHelper.constructQName(targetNamespaceURI, targetLocalName, null);
81
82 marshallerFactory = Configuration.getMarshallerFactory();
83 }
84
85
86 public Element marshall(XMLObject xmlObject) throws MarshallingException {
87 try {
88 Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
89 return marshall(xmlObject, document);
90 } catch (ParserConfigurationException e) {
91 throw new MarshallingException("Unable to create Document to place marshalled elements in", e);
92 }
93 }
94
95
96 public Element marshall(XMLObject xmlObject, Document document) throws MarshallingException {
97 Element domElement;
98
99 log.trace("Starting to marshall {}", xmlObject.getElementQName());
100
101 if (document == null) {
102 throw new MarshallingException("Given document may not be null");
103 }
104
105 checkXMLObjectIsTarget(xmlObject);
106
107 log.trace("Checking if {} contains a cached DOM representation", xmlObject.getElementQName());
108 domElement = xmlObject.getDOM();
109 if (domElement != null) {
110
111 prepareForAdoption(xmlObject);
112
113 if (domElement.getOwnerDocument() != document) {
114 log.trace("Adopting DOM of XMLObject into given Document");
115 XMLHelper.adoptElement(domElement, document);
116 }
117
118 log.trace("Setting DOM of XMLObject as document element of given Document");
119 setDocumentElement(document, domElement);
120
121 return domElement;
122 }
123
124 log.trace("{} does not contain a cached DOM representation. Creating Element to marshall into.", xmlObject
125 .getElementQName());
126 domElement = XMLHelper.constructElement(document, xmlObject.getElementQName());
127
128 log.trace("Setting created element as document root");
129
130
131 setDocumentElement(document, domElement);
132
133 domElement = marshallInto(xmlObject, domElement);
134
135 log.trace("Setting created element to DOM cache for XMLObject {}", xmlObject.getElementQName());
136 xmlObject.setDOM(domElement);
137 xmlObject.releaseParentDOM(true);
138
139 return domElement;
140 }
141
142
143 public Element marshall(XMLObject xmlObject, Element parentElement) throws MarshallingException {
144 Element domElement;
145
146 log.trace("Starting to marshall {} as child of {}", xmlObject.getElementQName(), XMLHelper
147 .getNodeQName(parentElement));
148
149 if (parentElement == null) {
150 throw new MarshallingException("Given parent element is null");
151 }
152
153 checkXMLObjectIsTarget(xmlObject);
154
155 log.trace("Checking if {} contains a cached DOM representation", xmlObject.getElementQName());
156 domElement = xmlObject.getDOM();
157 if (domElement != null) {
158 log.trace("{} contains a cached DOM representation", xmlObject.getElementQName());
159
160 prepareForAdoption(xmlObject);
161
162 log.trace("Appending DOM of XMLObject {} as child of parent element {}", xmlObject.getElementQName(),
163 XMLHelper.getNodeQName(parentElement));
164 XMLHelper.appendChildElement(parentElement, domElement);
165
166 return domElement;
167 }
168
169 log.trace("{} does not contain a cached DOM representation. Creating Element to marshall into.", xmlObject
170 .getElementQName());
171 Document owningDocument = parentElement.getOwnerDocument();
172 domElement = XMLHelper.constructElement(owningDocument, xmlObject.getElementQName());
173
174 log.trace("Appending newly created element to given parent element");
175
176
177 XMLHelper.appendChildElement(parentElement, domElement);
178 domElement = marshallInto(xmlObject, domElement);
179
180 log.trace("Setting created element to DOM cache for XMLObject {}", xmlObject.getElementQName());
181 xmlObject.setDOM(domElement);
182 xmlObject.releaseParentDOM(true);
183
184 return domElement;
185
186 }
187
188
189
190
191
192
193
194
195 protected void setDocumentElement(Document document, Element element) {
196 Element documentRoot = document.getDocumentElement();
197 if (documentRoot != null) {
198 document.replaceChild(element, documentRoot);
199 } else {
200 document.appendChild(element);
201 }
202 }
203
204
205
206
207
208
209
210
211
212
213
214
215 protected Element marshallInto(XMLObject xmlObject, Element targetElement) throws MarshallingException {
216 log.trace("Setting namespace prefix for {} for XMLObject {}", xmlObject.getElementQName().getPrefix(),
217 xmlObject.getElementQName());
218
219 marshallNamespacePrefix(xmlObject, targetElement);
220
221 marshallSchemaInstanceAttributes(xmlObject, targetElement);
222
223 marshallNamespaces(xmlObject, targetElement);
224
225 marshallAttributes(xmlObject, targetElement);
226
227 marshallChildElements(xmlObject, targetElement);
228
229 marshallElementContent(xmlObject, targetElement);
230
231 return targetElement;
232 }
233
234
235
236
237
238
239
240
241
242 protected void checkXMLObjectIsTarget(XMLObject xmlObject) throws MarshallingException {
243 if (targetQName == null) {
244 log.trace("Targeted QName checking is not available for this marshaller, XMLObject {} was not verified",
245 xmlObject.getElementQName());
246 return;
247 }
248
249 log.trace("Checking that {} meets target criteria", xmlObject.getElementQName());
250 QName type = xmlObject.getSchemaType();
251 if (type != null && type.equals(targetQName)) {
252 log.trace("{} schema type matches target", xmlObject.getElementQName());
253 return;
254 } else {
255 QName elementQName = xmlObject.getElementQName();
256 if (elementQName.equals(targetQName)) {
257 log.trace("{} element QName matches target", xmlObject.getElementQName());
258 return;
259 }
260 }
261
262 String errorMsg = "This marshaller only operations on " + targetQName + " elements not "
263 + xmlObject.getElementQName();
264 log.error(errorMsg);
265 throw new MarshallingException(errorMsg);
266 }
267
268
269
270
271
272
273
274 protected void marshallNamespacePrefix(XMLObject xmlObject, Element domElement) {
275 String prefix = xmlObject.getElementQName().getPrefix();
276 prefix = DatatypeHelper.safeTrimOrNullString(prefix);
277
278 if (prefix != null) {
279 domElement.setPrefix(prefix);
280 }
281 }
282
283
284
285
286
287
288
289
290
291 protected void marshallChildElements(XMLObject xmlObject, Element domElement) throws MarshallingException {
292 log.trace("Marshalling child elements for XMLObject {}", xmlObject.getElementQName());
293
294 List<XMLObject> childXMLObjects = xmlObject.getOrderedChildren();
295 if (childXMLObjects != null && childXMLObjects.size() > 0) {
296 for (XMLObject childXMLObject : childXMLObjects) {
297 if (childXMLObject == null) {
298 continue;
299 }
300
301 log.trace("Getting marshaller for child XMLObject {}", childXMLObject.getElementQName());
302 Marshaller marshaller = marshallerFactory.getMarshaller(childXMLObject);
303
304 if (marshaller == null) {
305 marshaller = marshallerFactory.getMarshaller(Configuration.getDefaultProviderQName());
306
307 if (marshaller == null) {
308 String errorMsg = "No marshaller available for " + childXMLObject.getElementQName()
309 + ", child of " + xmlObject.getElementQName();
310 log.error(errorMsg);
311 throw new MarshallingException(errorMsg);
312 } else {
313 log.trace("No marshaller was registered for {}, child of {}. Using default marshaller",
314 childXMLObject.getElementQName(), xmlObject.getElementQName());
315 }
316 }
317
318 log.trace("Marshalling {} and adding it to DOM", childXMLObject.getElementQName());
319 marshaller.marshall(childXMLObject, domElement);
320 }
321 } else {
322 log.trace("No child elements to marshall for XMLObject {}", xmlObject.getElementQName());
323 }
324 }
325
326
327
328
329
330
331
332 protected void marshallNamespaces(XMLObject xmlObject, Element domElement) {
333 log.trace("Marshalling namespace attributes for XMLObject {}", xmlObject.getElementQName());
334 Set<Namespace> namespaces = xmlObject.getNamespaces();
335
336 for (Namespace namespace : namespaces) {
337 if (!namespace.alwaysDeclare()
338 && XMLHelper.lookupNamespaceURI(domElement, namespace.getNamespacePrefix()) != null) {
339 log.trace("Namespace {} has already been declared on an ancestor of no need to add it here", namespace,
340 xmlObject.getElementQName());
341 } else {
342 log.trace("Adding namespace decleration {} to {}", namespace, xmlObject.getElementQName());
343 String nsURI = DatatypeHelper.safeTrimOrNullString(namespace.getNamespaceURI());
344 String nsPrefix = DatatypeHelper.safeTrimOrNullString(namespace.getNamespacePrefix());
345
346 XMLHelper.appendNamespaceDeclaration(domElement, nsURI, nsPrefix);
347 }
348 }
349 }
350
351
352
353
354
355
356
357
358
359 protected void marshallSchemaInstanceAttributes(XMLObject xmlObject, Element domElement)
360 throws MarshallingException {
361
362 if (!DatatypeHelper.isEmpty(xmlObject.getSchemaLocation())) {
363 log.trace("Setting xsi:schemaLocation for XMLObject {} to {}", xmlObject.getElementQName(), xmlObject
364 .getSchemaLocation());
365 domElement.setAttributeNS(XMLConstants.XSI_NS, XMLConstants.XSI_PREFIX + ":schemaLocation", xmlObject
366 .getSchemaLocation());
367 }
368
369 if (!DatatypeHelper.isEmpty(xmlObject.getNoNamespaceSchemaLocation())) {
370 log.trace("Setting xsi:noNamespaceSchemaLocation for XMLObject {} to {}", xmlObject.getElementQName(),
371 xmlObject.getNoNamespaceSchemaLocation());
372 domElement.setAttributeNS(XMLConstants.XSI_NS, XMLConstants.XSI_PREFIX + ":noNamespaceSchemaLocation",
373 xmlObject.getNoNamespaceSchemaLocation());
374 }
375
376 QName type = xmlObject.getSchemaType();
377 if (type == null) {
378 return;
379 }
380
381 log.trace("Setting xsi:type attribute with for XMLObject {}", xmlObject.getElementQName());
382 String typeLocalName = DatatypeHelper.safeTrimOrNullString(type.getLocalPart());
383 String typePrefix = DatatypeHelper.safeTrimOrNullString(type.getPrefix());
384
385 if (typeLocalName == null) {
386 throw new MarshallingException("The type QName on XMLObject " + xmlObject.getElementQName()
387 + " may not have a null local name");
388 }
389
390 if (type.getNamespaceURI() == null) {
391 throw new MarshallingException("The type URI QName on XMLObject " + xmlObject.getElementQName()
392 + " may not have a null namespace URI");
393 }
394
395 String attributeValue;
396 if (typePrefix == null) {
397 attributeValue = typeLocalName;
398 } else {
399 attributeValue = typePrefix + ":" + typeLocalName;
400 }
401
402 domElement.setAttributeNS(XMLConstants.XSI_NS, XMLConstants.XSI_PREFIX + ":type", attributeValue);
403
404 log.trace("Adding XSI namespace to list of namespaces used by XMLObject {}", xmlObject.getElementQName());
405 xmlObject.addNamespace(new Namespace(XMLConstants.XSI_NS, XMLConstants.XSI_PREFIX));
406 }
407
408
409
410
411
412
413
414
415
416
417
418 protected abstract void marshallAttributes(XMLObject xmlObject, Element domElement) throws MarshallingException;
419
420
421
422
423
424
425
426
427
428 protected abstract void marshallElementContent(XMLObject xmlObject, Element domElement) throws MarshallingException;
429
430
431
432
433
434
435
436
437
438
439 private void prepareForAdoption(XMLObject domCachingObject) throws MarshallingException {
440 if (domCachingObject.getParent() != null) {
441 log.trace("Rooting all visible namespaces of XMLObject {} before adding it to new parent Element",
442 domCachingObject.getElementQName());
443 try {
444 XMLHelper.rootNamespaces(domCachingObject.getDOM());
445 } catch (XMLParserException e) {
446 String errorMsg = "Unable to root namespaces of cached DOM element, "
447 + domCachingObject.getElementQName();
448 log.error(errorMsg, e);
449 throw new MarshallingException(errorMsg, e);
450 }
451
452 log.trace("Release DOM of XMLObject parent");
453 domCachingObject.releaseParentDOM(true);
454 }
455 }
456 }