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
337 continue;
338 }
339
340 String declared = XMLHelper.lookupNamespaceURI(domElement, namespace.getNamespacePrefix());
341 if (declared != null && namespace.getNamespaceURI().equals(declared)) {
342 log.trace("Namespace {} has already been declared on an ancestor of {} no need to add it here", namespace,
343 xmlObject.getElementQName());
344 continue;
345 }
346 }
347 log.trace("Adding namespace declaration {} to {}", namespace, xmlObject.getElementQName());
348 String nsURI = DatatypeHelper.safeTrimOrNullString(namespace.getNamespaceURI());
349 String nsPrefix = DatatypeHelper.safeTrimOrNullString(namespace.getNamespacePrefix());
350
351 XMLHelper.appendNamespaceDeclaration(domElement, nsURI, nsPrefix);
352 }
353 }
354
355
356
357
358
359
360
361
362
363 protected void marshallSchemaInstanceAttributes(XMLObject xmlObject, Element domElement)
364 throws MarshallingException {
365
366 if (!DatatypeHelper.isEmpty(xmlObject.getSchemaLocation())) {
367 log.trace("Setting xsi:schemaLocation for XMLObject {} to {}", xmlObject.getElementQName(), xmlObject
368 .getSchemaLocation());
369 domElement.setAttributeNS(XMLConstants.XSI_NS, XMLConstants.XSI_PREFIX + ":schemaLocation", xmlObject
370 .getSchemaLocation());
371 }
372
373 if (!DatatypeHelper.isEmpty(xmlObject.getNoNamespaceSchemaLocation())) {
374 log.trace("Setting xsi:noNamespaceSchemaLocation for XMLObject {} to {}", xmlObject.getElementQName(),
375 xmlObject.getNoNamespaceSchemaLocation());
376 domElement.setAttributeNS(XMLConstants.XSI_NS, XMLConstants.XSI_PREFIX + ":noNamespaceSchemaLocation",
377 xmlObject.getNoNamespaceSchemaLocation());
378 }
379
380 QName type = xmlObject.getSchemaType();
381 if (type == null) {
382 return;
383 }
384
385 log.trace("Setting xsi:type attribute with for XMLObject {}", xmlObject.getElementQName());
386 String typeLocalName = DatatypeHelper.safeTrimOrNullString(type.getLocalPart());
387 String typePrefix = DatatypeHelper.safeTrimOrNullString(type.getPrefix());
388
389 if (typeLocalName == null) {
390 throw new MarshallingException("The type QName on XMLObject " + xmlObject.getElementQName()
391 + " may not have a null local name");
392 }
393
394 if (type.getNamespaceURI() == null) {
395 throw new MarshallingException("The type URI QName on XMLObject " + xmlObject.getElementQName()
396 + " may not have a null namespace URI");
397 }
398
399 String attributeValue;
400 if (typePrefix == null) {
401 attributeValue = typeLocalName;
402 } else {
403 attributeValue = typePrefix + ":" + typeLocalName;
404 }
405
406 domElement.setAttributeNS(XMLConstants.XSI_NS, XMLConstants.XSI_PREFIX + ":type", attributeValue);
407
408 log.trace("Adding XSI namespace to list of namespaces used by XMLObject {}", xmlObject.getElementQName());
409 xmlObject.addNamespace(new Namespace(XMLConstants.XSI_NS, XMLConstants.XSI_PREFIX));
410 }
411
412
413
414
415
416
417
418
419
420
421
422 protected abstract void marshallAttributes(XMLObject xmlObject, Element domElement) throws MarshallingException;
423
424
425
426
427
428
429
430
431
432 protected abstract void marshallElementContent(XMLObject xmlObject, Element domElement) throws MarshallingException;
433
434
435
436
437
438
439
440
441
442
443 private void prepareForAdoption(XMLObject domCachingObject) throws MarshallingException {
444 if (domCachingObject.getParent() != null) {
445 log.trace("Rooting all visible namespaces of XMLObject {} before adding it to new parent Element",
446 domCachingObject.getElementQName());
447 try {
448 XMLHelper.rootNamespaces(domCachingObject.getDOM());
449 } catch (XMLParserException e) {
450 String errorMsg = "Unable to root namespaces of cached DOM element, "
451 + domCachingObject.getElementQName();
452 log.error(errorMsg, e);
453 throw new MarshallingException(errorMsg, e);
454 }
455
456 log.trace("Release DOM of XMLObject parent");
457 domCachingObject.releaseParentDOM(true);
458 }
459 }
460 }