| /* |
| * Copyright (c) 1998, 2020 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. |
| * @param node |
| * @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. |
| * @param obj |
| */ |
| 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. |
| * @param 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. |
| * @param 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. |
| * @param node |
| */ |
| 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 |
| * |
| * @param obj |
| * @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; |
| } |
| } |