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