org.opensaml.xml.io.AbstractXMLObjectMarshaller Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xmltooling Show documentation
Show all versions of xmltooling Show documentation
XMLTooling-J is a low-level library that may be used to construct libraries that allow developers to work with
XML in a Java beans manner.
/*
* Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.opensaml.xml.io;
import java.util.List;
import java.util.Set;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.opensaml.xml.Configuration;
import org.opensaml.xml.Namespace;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.parse.XMLParserException;
import org.opensaml.xml.util.DatatypeHelper;
import org.opensaml.xml.util.XMLConstants;
import org.opensaml.xml.util.XMLHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* A thread safe, abstract implementation of the {@link org.opensaml.xml.io.Marshaller} interface. This class handles
* most of the boilerplate code:
*
* - Ensuring elements to be marshalled are of either the correct xsi:type or element QName
* - Setting the appropriate namespace and prefix for the marshalled element
* - Setting the xsi:type for the element if the element has an explicit type
* - Setting namespaces attributes declared for the element
* - Marshalling of child elements
*
*/
public abstract class AbstractXMLObjectMarshaller implements Marshaller {
/** Class logger. */
private final Logger log = LoggerFactory.getLogger(AbstractXMLObjectMarshaller.class);
/** The target name and namespace for this marshaller. */
private QName targetQName;
/** Factory for XMLObject Marshallers. */
private MarshallerFactory marshallerFactory;
/** Constructor. */
protected AbstractXMLObjectMarshaller() {
marshallerFactory = Configuration.getMarshallerFactory();
}
/**
* This constructor supports checking an XMLObject to be marshalled, either element name or schema type, against a
* given namespace/local name pair.
*
* @deprecated no replacement
*
* @param targetNamespaceURI the namespace URI of either the schema type QName or element QName of the elements this
* unmarshaller operates on
* @param targetLocalName the local name of either the schema type QName or element QName of the elements this
* unmarshaller operates on
*/
protected AbstractXMLObjectMarshaller(String targetNamespaceURI, String targetLocalName) {
targetQName = XMLHelper.constructQName(targetNamespaceURI, targetLocalName, null);
marshallerFactory = Configuration.getMarshallerFactory();
}
/** {@inheritDoc} */
public Element marshall(XMLObject xmlObject) throws MarshallingException {
try {
Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
return marshall(xmlObject, document);
} catch (ParserConfigurationException e) {
throw new MarshallingException("Unable to create Document to place marshalled elements in", e);
}
}
/** {@inheritDoc} */
public Element marshall(XMLObject xmlObject, Document document) throws MarshallingException {
Element domElement;
log.trace("Starting to marshall {}", xmlObject.getElementQName());
if (document == null) {
throw new MarshallingException("Given document may not be null");
}
checkXMLObjectIsTarget(xmlObject);
log.trace("Checking if {} contains a cached DOM representation", xmlObject.getElementQName());
domElement = xmlObject.getDOM();
if (domElement != null) {
prepareForAdoption(xmlObject);
if (domElement.getOwnerDocument() != document) {
log.trace("Adopting DOM of XMLObject into given Document");
XMLHelper.adoptElement(domElement, document);
}
log.trace("Setting DOM of XMLObject as document element of given Document");
setDocumentElement(document, domElement);
return domElement;
}
log.trace("{} does not contain a cached DOM representation. Creating Element to marshall into.", xmlObject
.getElementQName());
domElement = XMLHelper.constructElement(document, xmlObject.getElementQName());
log.trace("Setting created element as document root");
// we need to do this before the rest of the marshalling so that signing and other ID dependent operations have
// a path to the document root
setDocumentElement(document, domElement);
domElement = marshallInto(xmlObject, domElement);
log.trace("Setting created element to DOM cache for XMLObject {}", xmlObject.getElementQName());
xmlObject.setDOM(domElement);
xmlObject.releaseParentDOM(true);
return domElement;
}
/** {@inheritDoc} */
public Element marshall(XMLObject xmlObject, Element parentElement) throws MarshallingException {
Element domElement;
log.trace("Starting to marshall {} as child of {}", xmlObject.getElementQName(), XMLHelper
.getNodeQName(parentElement));
if (parentElement == null) {
throw new MarshallingException("Given parent element is null");
}
checkXMLObjectIsTarget(xmlObject);
log.trace("Checking if {} contains a cached DOM representation", xmlObject.getElementQName());
domElement = xmlObject.getDOM();
if (domElement != null) {
log.trace("{} contains a cached DOM representation", xmlObject.getElementQName());
prepareForAdoption(xmlObject);
log.trace("Appending DOM of XMLObject {} as child of parent element {}", xmlObject.getElementQName(),
XMLHelper.getNodeQName(parentElement));
XMLHelper.appendChildElement(parentElement, domElement);
return domElement;
}
log.trace("{} does not contain a cached DOM representation. Creating Element to marshall into.", xmlObject
.getElementQName());
Document owningDocument = parentElement.getOwnerDocument();
domElement = XMLHelper.constructElement(owningDocument, xmlObject.getElementQName());
log.trace("Appending newly created element to given parent element");
// we need to do this before the rest of the marshalling so that signing and other ID dependent operations have
// a path to the document root
XMLHelper.appendChildElement(parentElement, domElement);
domElement = marshallInto(xmlObject, domElement);
log.trace("Setting created element to DOM cache for XMLObject {}", xmlObject.getElementQName());
xmlObject.setDOM(domElement);
xmlObject.releaseParentDOM(true);
return domElement;
}
/**
* Sets the given element as the Document Element of the given Document. If the document already has a Document
* Element it is replaced by the given element.
*
* @param document the document
* @param element the Element that will serve as the Document Element
*/
protected void setDocumentElement(Document document, Element element) {
Element documentRoot = document.getDocumentElement();
if (documentRoot != null) {
document.replaceChild(element, documentRoot);
} else {
document.appendChild(element);
}
}
/**
* Marshalls the given XMLObject into the given DOM Element. The DOM Element must be within a DOM tree whose root is
* the Document Element of the Document that owns the given DOM Element.
*
* @param xmlObject the XMLObject to marshall
* @param targetElement the Element into which the XMLObject is marshalled into
*
* @return the DOM element the {@link XMLObject} is marshalled into
*
* @throws MarshallingException thrown if there is a problem marshalling the object
*/
protected Element marshallInto(XMLObject xmlObject, Element targetElement) throws MarshallingException {
log.trace("Setting namespace prefix for {} for XMLObject {}", xmlObject.getElementQName().getPrefix(),
xmlObject.getElementQName());
marshallNamespacePrefix(xmlObject, targetElement);
marshallSchemaInstanceAttributes(xmlObject, targetElement);
marshallNamespaces(xmlObject, targetElement);
marshallAttributes(xmlObject, targetElement);
marshallChildElements(xmlObject, targetElement);
marshallElementContent(xmlObject, targetElement);
return targetElement;
}
/**
* Checks to make sure the given XMLObject's schema type or element QName matches the target parameters given at
* marshaller construction time.
*
* @param xmlObject the XMLObject to marshall
*
* @throws MarshallingException thrown if the given object is not or the required type
*/
protected void checkXMLObjectIsTarget(XMLObject xmlObject) throws MarshallingException {
if (targetQName == null) {
log.trace("Targeted QName checking is not available for this marshaller, XMLObject {} was not verified",
xmlObject.getElementQName());
return;
}
log.trace("Checking that {} meets target criteria", xmlObject.getElementQName());
QName type = xmlObject.getSchemaType();
if (type != null && type.equals(targetQName)) {
log.trace("{} schema type matches target", xmlObject.getElementQName());
return;
} else {
QName elementQName = xmlObject.getElementQName();
if (elementQName.equals(targetQName)) {
log.trace("{} element QName matches target", xmlObject.getElementQName());
return;
}
}
String errorMsg = "This marshaller only operations on " + targetQName + " elements not "
+ xmlObject.getElementQName();
log.error(errorMsg);
throw new MarshallingException(errorMsg);
}
/**
* Marshalls the namespace prefix of the XMLObject into the DOM element.
*
* @param xmlObject the XMLObject being marshalled
* @param domElement the DOM element the XMLObject is being marshalled into
*/
protected void marshallNamespacePrefix(XMLObject xmlObject, Element domElement) {
String prefix = xmlObject.getElementQName().getPrefix();
prefix = DatatypeHelper.safeTrimOrNullString(prefix);
if (prefix != null) {
domElement.setPrefix(prefix);
}
}
/**
* Marshalls the child elements of the given XMLObject.
*
* @param xmlObject the XMLObject whose children will be marshalled
* @param domElement the DOM element that will recieved the marshalled children
*
* @throws MarshallingException thrown if there is a problem marshalling a child element
*/
protected void marshallChildElements(XMLObject xmlObject, Element domElement) throws MarshallingException {
log.trace("Marshalling child elements for XMLObject {}", xmlObject.getElementQName());
List childXMLObjects = xmlObject.getOrderedChildren();
if (childXMLObjects != null && childXMLObjects.size() > 0) {
for (XMLObject childXMLObject : childXMLObjects) {
if (childXMLObject == null) {
continue;
}
log.trace("Getting marshaller for child XMLObject {}", childXMLObject.getElementQName());
Marshaller marshaller = marshallerFactory.getMarshaller(childXMLObject);
if (marshaller == null) {
marshaller = marshallerFactory.getMarshaller(Configuration.getDefaultProviderQName());
if (marshaller == null) {
String errorMsg = "No marshaller available for " + childXMLObject.getElementQName()
+ ", child of " + xmlObject.getElementQName();
log.error(errorMsg);
throw new MarshallingException(errorMsg);
} else {
log.trace("No marshaller was registered for {}, child of {}. Using default marshaller",
childXMLObject.getElementQName(), xmlObject.getElementQName());
}
}
log.trace("Marshalling {} and adding it to DOM", childXMLObject.getElementQName());
marshaller.marshall(childXMLObject, domElement);
}
} else {
log.trace("No child elements to marshall for XMLObject {}", xmlObject.getElementQName());
}
}
/**
* Creates the xmlns attributes for any namespaces set on the given XMLObject.
*
* @param xmlObject the XMLObject
* @param domElement the DOM element the namespaces will be added to
*/
protected void marshallNamespaces(XMLObject xmlObject, Element domElement) {
log.trace("Marshalling namespace attributes for XMLObject {}", xmlObject.getElementQName());
Set namespaces = xmlObject.getNamespaces();
for (Namespace namespace : namespaces) {
if (!namespace.alwaysDeclare()) {
if(DatatypeHelper.safeEquals(namespace.getNamespacePrefix(), XMLConstants.XML_PREFIX)
|| DatatypeHelper.safeEquals(namespace.getNamespaceURI(), XMLConstants.XML_NS)) {
//the "xml" namespace never needs to be declared
continue;
}
String declared = XMLHelper.lookupNamespaceURI(domElement, namespace.getNamespacePrefix());
if (declared != null && namespace.getNamespaceURI().equals(declared)) {
log.trace("Namespace {} has already been declared on an ancestor of {} no need to add it here", namespace,
xmlObject.getElementQName());
continue;
}
}
log.trace("Adding namespace declaration {} to {}", namespace, xmlObject.getElementQName());
String nsURI = DatatypeHelper.safeTrimOrNullString(namespace.getNamespaceURI());
String nsPrefix = DatatypeHelper.safeTrimOrNullString(namespace.getNamespacePrefix());
XMLHelper.appendNamespaceDeclaration(domElement, nsURI, nsPrefix);
}
}
/**
* Creates the XSI type, schemaLocation, and noNamespaceSchemaLocation attributes for an XMLObject.
*
* @param xmlObject the XMLObject
* @param domElement the DOM element the namespaces will be added to
*
* @throws MarshallingException thrown if the schema type information is invalid
*/
protected void marshallSchemaInstanceAttributes(XMLObject xmlObject, Element domElement)
throws MarshallingException {
if (!DatatypeHelper.isEmpty(xmlObject.getSchemaLocation())) {
log.trace("Setting xsi:schemaLocation for XMLObject {} to {}", xmlObject.getElementQName(), xmlObject
.getSchemaLocation());
domElement.setAttributeNS(XMLConstants.XSI_NS, XMLConstants.XSI_PREFIX + ":schemaLocation", xmlObject
.getSchemaLocation());
}
if (!DatatypeHelper.isEmpty(xmlObject.getNoNamespaceSchemaLocation())) {
log.trace("Setting xsi:noNamespaceSchemaLocation for XMLObject {} to {}", xmlObject.getElementQName(),
xmlObject.getNoNamespaceSchemaLocation());
domElement.setAttributeNS(XMLConstants.XSI_NS, XMLConstants.XSI_PREFIX + ":noNamespaceSchemaLocation",
xmlObject.getNoNamespaceSchemaLocation());
}
if (xmlObject.isNilXSBoolean() != null && xmlObject.isNil()) {
log.trace("Setting xsi:nil for XMLObject {} to true", xmlObject.getElementQName());
domElement.setAttributeNS(XMLConstants.XSI_NS, XMLConstants.XSI_PREFIX + ":nil",
xmlObject.isNilXSBoolean().toString());
}
QName type = xmlObject.getSchemaType();
if (type == null) {
return;
}
log.trace("Setting xsi:type attribute with for XMLObject {}", xmlObject.getElementQName());
String typeLocalName = DatatypeHelper.safeTrimOrNullString(type.getLocalPart());
String typePrefix = DatatypeHelper.safeTrimOrNullString(type.getPrefix());
if (typeLocalName == null) {
throw new MarshallingException("The type QName on XMLObject " + xmlObject.getElementQName()
+ " may not have a null local name");
}
if (type.getNamespaceURI() == null) {
throw new MarshallingException("The type URI QName on XMLObject " + xmlObject.getElementQName()
+ " may not have a null namespace URI");
}
String attributeValue;
if (typePrefix == null) {
attributeValue = typeLocalName;
} else {
attributeValue = typePrefix + ":" + typeLocalName;
}
domElement.setAttributeNS(XMLConstants.XSI_NS, XMLConstants.XSI_PREFIX + ":type", attributeValue);
}
/**
* Marshalls a given XMLObject into a W3C Element. The given signing context should be blindly passed to the
* marshaller for child elements. The XMLObject passed to this method is guaranteed to be of the target name
* specified during this unmarshaller's construction.
*
* @param xmlObject the XMLObject to marshall
* @param domElement the W3C DOM element
*
* @throws MarshallingException thrown if there is a problem marshalling the element
*/
protected abstract void marshallAttributes(XMLObject xmlObject, Element domElement) throws MarshallingException;
/**
* Marshalls data from the XMLObject into content of the DOM Element.
*
* @param xmlObject the XMLObject
* @param domElement the DOM element recieving the content
*
* @throws MarshallingException thrown if the textual content can not be added to the DOM element
*/
protected abstract void marshallElementContent(XMLObject xmlObject, Element domElement) throws MarshallingException;
/**
* Prepares the given DOM caching XMLObject for adoption into another document. If the XMLObject has a parent then
* all visible namespaces used by the given XMLObject and its descendants are declared within that subtree and the
* parent's DOM is invalidated.
*
* @param domCachingObject the XMLObject to prepare for adoption
*
* @throws MarshallingException thrown if a namespace within the XMLObject's DOM subtree can not be resolved.
*/
private void prepareForAdoption(XMLObject domCachingObject) throws MarshallingException {
if (domCachingObject.getParent() != null) {
log.trace("Rooting all visible namespaces of XMLObject {} before adding it to new parent Element",
domCachingObject.getElementQName());
try {
XMLHelper.rootNamespaces(domCachingObject.getDOM());
} catch (XMLParserException e) {
String errorMsg = "Unable to root namespaces of cached DOM element, "
+ domCachingObject.getElementQName();
log.error(errorMsg, e);
throw new MarshallingException(errorMsg, e);
}
log.trace("Release DOM of XMLObject parent");
domCachingObject.releaseParentDOM(true);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy