blob: 17ef5227987348a379dd73807a23e460d6cb98e3 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.oxm;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.validation.Schema;
import javax.xml.validation.Validator;
import org.eclipse.persistence.exceptions.XMLMarshalException;
import org.eclipse.persistence.internal.oxm.Root;
import org.eclipse.persistence.internal.oxm.XMLObjectBuilder;
import org.eclipse.persistence.internal.oxm.documentpreservation.XMLBinderPolicy;
import org.eclipse.persistence.internal.oxm.record.DOMReader;
import org.eclipse.persistence.internal.oxm.record.SAXUnmarshaller;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.oxm.documentpreservation.DocumentPreservationPolicy;
import org.eclipse.persistence.oxm.record.DOMRecord;
import org.eclipse.persistence.platform.xml.XMLTransformer;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.ErrorHandler;
/**
* PUBLIC:
* <p><b>Purpose:</b>Provide a runtime public interface for preserving unmapped content from an
* XML Document.
* <p><b>Responsibilities:</b><ul>
* <li>Unmarshal XML into JavaObjects and maintain the associations between nodes and objects</li>
* <li>Update the cached XML based on changes to the object</li>
* <li>Update the cached objects based on changes to the XML Document</li>
* <li>Provide API to access the cached Node for a given object</li>
* <li>Provide API to access the cached Object for a given XML Node</li>
* </ul>
*
* <p>The XML Binder is a runtime class that allows an association to be maintained between the
* original XML Document and the Java Objects built from the Document. It allows unmapped content
* (such as comments, processing instructions or other unmapped elements and attributes) to be
* preserved. The XMLBinder is created through an XMLContext.
*
* @see org.eclipse.persistence.oxm.XMLContext
* @author mmacivor
*/
public class XMLBinder {
SAXUnmarshaller saxUnmarshaller;
XMLContext context;
XMLMarshaller marshaller;
XMLUnmarshaller unmarshaller;
DocumentPreservationPolicy documentPreservationPolicy;
DOMReader reader;
public XMLBinder(XMLContext context) {
this.context = new XMLContext(context.getXMLContextState());
marshaller = this.context.createMarshaller();
unmarshaller = this.context.createUnmarshaller();
initialize();
}
public XMLBinder(XMLContext context, XMLMarshaller marshaller, XMLUnmarshaller unmarshaller) {
this.context = new XMLContext(context.getXMLContextState());
this.marshaller = marshaller;
this.unmarshaller = unmarshaller;
initialize();
}
private void initialize() {
saxUnmarshaller = new SAXUnmarshaller(unmarshaller, null);
documentPreservationPolicy = new XMLBinderPolicy();
reader = new DOMReader(unmarshaller);
}
/**
* This method will unmarshal the provided node into mapped java objects. The original node
* will be cached rather than thrown away.
* @return The root object unmarshalled from the provided node.
*/
public Object unmarshal(org.w3c.dom.Node node) {
validateNode(node);
reader.setDocPresPolicy(documentPreservationPolicy);
Object toReturn = saxUnmarshaller.unmarshal(reader, node);
return toReturn;
}
private void validateNode(org.w3c.dom.Node node) {
if (getSchema() != null) {
Validator validator = getSchema().newValidator();
validator.setErrorHandler(getErrorHandler());
try {
validator.validate(new DOMSource(node));
} catch (Exception e) {
throw XMLMarshalException.validateException(e);
}
}
}
public XMLRoot unmarshal(org.w3c.dom.Node node, Class javaClass) {
validateNode(node);
reader.setDocPresPolicy(documentPreservationPolicy);
return buildXMLRootFromObject(saxUnmarshaller.unmarshal(reader, node, javaClass));
}
/**
* This method will update the cached XML node for the provided object. If no node exists for this
* object, then no operation is performed.
*/
public void updateXML(Object obj) {
if(obj instanceof Root) {
obj = ((Root)obj).getObject();
}
Node associatedNode = documentPreservationPolicy.getNodeForObject(obj);
if(associatedNode == null) {
return;
}
updateXML(obj, associatedNode);
}
public void marshal(Object obj, Node node) {
XMLDescriptor desc = null;
boolean isXMLRoot = obj instanceof Root;
if (isXMLRoot) {
Object o = ((Root) obj).getObject();
desc = (XMLDescriptor) context.getSession(o).getDescriptor(o);
} else {
desc = (XMLDescriptor) context.getSession(obj).getDescriptor(obj);
}
DOMRecord domRecord = null;
if (!isXMLRoot) {
domRecord = new DOMRecord(desc.getDefaultRootElement(), desc.getNamespaceResolver());
domRecord.setDocPresPolicy(getDocumentPreservationPolicy());
}
Node n = this.marshaller.objectToXML(obj, node, desc, domRecord, isXMLRoot, this.getDocumentPreservationPolicy());
validateNode(n);
DOMResult result = new DOMResult(node);
XMLTransformer transformer = marshaller.getTransformer();
if (isXMLRoot) {
String oldEncoding = transformer.getEncoding();
String oldVersion = transformer.getVersion();
if (((Root) obj).getEncoding() != null) {
transformer.setEncoding(((Root) obj).getEncoding());
}
if (((Root) obj).getXMLVersion() != null) {
transformer.setVersion(((Root) obj).getXMLVersion());
}
transformer.transform(n, result);
if(oldEncoding != null){
transformer.setEncoding(oldEncoding);
}
if(oldVersion != null){
transformer.setVersion(oldVersion);
}
} else {
transformer.transform(n, result);
}
}
public void updateXML(Object obj, Node associatedNode) {
if (obj instanceof Root) {
obj = ((Root)obj).getObject();
}
Node objNode = this.getXMLNode(obj);
AbstractSession session = context.getSession(obj);
if (objNode == associatedNode) {
DOMRecord root = new DOMRecord((Element)associatedNode);
root.setMarshaller(marshaller);
root.setDocPresPolicy(this.documentPreservationPolicy);
XMLDescriptor rootDescriptor = (XMLDescriptor) session.getDescriptor(obj);
((XMLObjectBuilder)rootDescriptor.getObjectBuilder()).buildIntoNestedRow(root, obj, session);
}
}
/**
* Gets the XML Node associated with the provided object.
* @return an XML Node used to construct the given object. Null if no node exists for this object.
*/
public Node getXMLNode(Object object) {
return documentPreservationPolicy.getNodeForObject(object);
}
/**
* Gets the Java Object associated with the provided XML Node.
* @return the Java Object associated with this node. If no object is associated then returns null
*/
public Object getObject(Node node) {
return documentPreservationPolicy.getObjectForNode(node);
}
/**
* Updates the object associated with the provided node to reflect any changed made to that node.
* If this Binder has no object associated with the given node, then no operation is performed.
*/
public void updateObject(org.w3c.dom.Node node) {
if (node.getNodeType() == Node.DOCUMENT_NODE) {
node = ((Document) node).getDocumentElement();
}
Object cachedObject = documentPreservationPolicy.getObjectForNode(node);
if (cachedObject != null) {
unmarshal(node);
} else {
throw XMLMarshalException.objectNotFoundInCache(node.getNodeName());
}
}
/**
* Gets this XMLBinder's document preservation policy.
* @return an instance of DocumentPreservationPolicy
* @see org.eclipse.persistence.oxm.documentpreservation.DocumentPreservationPolicy
*/
public DocumentPreservationPolicy getDocumentPreservationPolicy() {
return documentPreservationPolicy;
}
public XMLMarshaller getMarshaller() {
return marshaller;
}
public void setMarshaller(XMLMarshaller marshaller) {
this.marshaller = marshaller;
}
public void setSchema(Schema aSchema) {
this.unmarshaller.setSchema(aSchema);
this.saxUnmarshaller.setSchema(aSchema);
}
public Schema getSchema() {
return this.unmarshaller.getSchema();
}
public void setErrorHandler(ErrorHandler errorHandler) {
this.unmarshaller.setErrorHandler(errorHandler);
this.saxUnmarshaller.setErrorHandler(errorHandler);
}
public ErrorHandler getErrorHandler() {
return this.unmarshaller.getErrorHandler();
}
/**
* Create an XMLRoot instance. If the object is an instance of XMLRoot
* it will simply be returned. Otherwise, we will create a new XMLRoot
* using the object's descriptor default root element - any prefixes
* will be resolved - and the given object
*
* @return an XMLRoot instance encapsulating the given object
*/
private XMLRoot buildXMLRootFromObject(Object obj) {
if (obj instanceof XMLRoot) {
return (XMLRoot) obj;
}
XMLRoot xmlRoot = new XMLRoot();
xmlRoot.setObject(obj);
// at this point, the default root element of the object being
// marshalled to == the root element - here we need to create
// an XMLRoot instance using information from the returned
// object
org.eclipse.persistence.sessions.Session sess = this.unmarshaller.getXMLContext().getSession(obj);
XMLDescriptor desc = (XMLDescriptor) sess.getClassDescriptor(obj);
// here we are assuming that if we've gotten this far, there
// must be a default root element set on the descriptor. if
// this is incorrect, we need to check for null and throw an
// exception
String rootName = desc.getDefaultRootElement();
if (rootName == null) {
return xmlRoot;
}
String rootNamespaceUri = null;
int idx = rootName.indexOf(':');
if (idx != -1) {
rootNamespaceUri = desc.getNamespaceResolver().resolveNamespacePrefix(rootName.substring(0, idx));
rootName = rootName.substring(idx + 1);
}
xmlRoot.setLocalName(rootName);
xmlRoot.setNamespaceURI(rootNamespaceUri);
return xmlRoot;
}
}