blob: fad612fb08080ca956b67b364c5331c273849d80 [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.internal.oxm.record;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import javax.xml.namespace.QName;
import javax.xml.transform.Source;
import javax.xml.validation.Schema;
import org.eclipse.persistence.exceptions.XMLMarshalException;
import org.eclipse.persistence.internal.core.helper.CoreClassConstants;
import org.eclipse.persistence.internal.core.sessions.CoreAbstractSession;
import org.eclipse.persistence.internal.oxm.Constants;
import org.eclipse.persistence.internal.oxm.Root;
import org.eclipse.persistence.internal.oxm.XMLConversionManager;
import org.eclipse.persistence.internal.oxm.XMLObjectBuilder;
import org.eclipse.persistence.internal.oxm.XPathFragment;
import org.eclipse.persistence.internal.oxm.mappings.Descriptor;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.oxm.XMLContext;
import org.eclipse.persistence.oxm.XMLRoot;
import org.eclipse.persistence.oxm.XMLUnmarshaller;
import org.eclipse.persistence.oxm.record.DOMRecord;
import org.eclipse.persistence.platform.xml.SAXDocumentBuilder;
import org.eclipse.persistence.platform.xml.XMLParser;
import org.eclipse.persistence.platform.xml.XMLPlatform;
import org.eclipse.persistence.platform.xml.XMLPlatformException;
import org.eclipse.persistence.platform.xml.XMLPlatformFactory;
import org.eclipse.persistence.queries.ReadObjectQuery;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
/**
* INTERNAL:
* <p><b>Purpose:</b>Provide an implementation of PlatformUnmarshaller that makes use of the DOM
* unmarshal code. Used by the DOMPlatform
* <p><b>Responsibilities:</b><ul>
* <li>Implement the required unmarshal methods from platform unmarshaller</li>
* <li>Perform xml-to-object conversions</li>
* </ul>
* @author bdoughan
* @see org.eclipse.persistence.oxm.platform.DOMPlatform
*
*/
public class DOMUnmarshaller implements PlatformUnmarshaller {
private XMLParser parser;
private XMLUnmarshaller xmlUnmarshaller;
private boolean isResultAlwaysXMLRoot;
private boolean disableSecureProcessing = false;
private EntityResolver entityResolver;
private ErrorHandler errorHandler;
private Map<String, Boolean> parserFeatures;
private boolean isWhitespacePreserving;
private int validationMode = XMLParser.NONVALIDATING;
private Schema schema;
private Object[] schemas;
private boolean shouldReset = true;
public DOMUnmarshaller(XMLUnmarshaller xmlUnmarshaller, Map<String, Boolean> parserFeatures) {
super();
this.parserFeatures = parserFeatures == null ? new HashMap<>() : parserFeatures;
this.xmlUnmarshaller = xmlUnmarshaller;
}
private XMLParser getParser() {
if (parser == null || shouldReset) {
XMLPlatform xmlPlatform = XMLPlatformFactory.getInstance().getXMLPlatform();
xmlPlatform.setDisableSecureProcessing(isSecureProcessingDisabled());
parser = xmlPlatform.newXMLParser(parserFeatures);
parser.setNamespaceAware(true);
if (errorHandler != null) {
parser.setErrorHandler(errorHandler);
}
if (entityResolver != null) {
parser.setEntityResolver(entityResolver);
}
if (schemas != null) {
try {
parser.setXMLSchemas(schemas);
} catch (XMLPlatformException e) {
throw XMLMarshalException.errorSettingSchemas(e, schemas);
}
}
if (schema != null) {
parser.setXMLSchema(schema);
}
parser.setValidationMode(validationMode);
parser.setWhitespacePreserving(isWhitespacePreserving);
shouldReset = false;
}
return parser;
}
@Override
public EntityResolver getEntityResolver() {
return entityResolver;
}
@Override
public void setEntityResolver(EntityResolver entityResolver) {
this.entityResolver = entityResolver;
if (parser != null) {
parser.setEntityResolver(entityResolver);
}
}
@Override
public ErrorHandler getErrorHandler() {
return errorHandler;
}
@Override
public void setErrorHandler(ErrorHandler errorHandler) {
this.errorHandler = errorHandler;
if (parser != null) {
parser.setErrorHandler(errorHandler);
}
}
@Override
public int getValidationMode() {
return validationMode;
}
@Override
public void setValidationMode(int validationMode) {
this.validationMode = validationMode;
if (parser != null) {
parser.setValidationMode(validationMode);
}
}
@Override
public void setWhitespacePreserving(boolean isWhitespacePreserving) {
this.isWhitespacePreserving = isWhitespacePreserving;
if (parser != null) {
parser.setWhitespacePreserving(isWhitespacePreserving);
}
}
@Override
public void setSchemas(Object[] schemas) {
this.schemas = schemas;
if (parser != null) {
try {
parser.setXMLSchemas(schemas);
} catch (XMLPlatformException e) {
throw XMLMarshalException.errorSettingSchemas(e, schemas);
}
}
}
@Override
public void setSchema(Schema schema) {
this.schema = schema;
if (parser != null) {
parser.setXMLSchema(schema);
}
}
@Override
public Schema getSchema() {
return schema;
}
@Override
public Object unmarshal(File file) {
return unmarshal(file, null);
}
@Override
public Object unmarshal(File file, Class<?> clazz) {
if(!xmlUnmarshaller.isApplicationXML()){
throw XMLMarshalException.unsupportedMediaTypeForPlatform();
}
try {
Document document = null;
document = getParser().parse(file);
return xmlToObject(new DOMRecord(document), clazz);
} catch (XMLPlatformException e) {
throw XMLMarshalException.unmarshalException(e);
} finally {
xmlUnmarshaller.getStringBuffer().reset();
}
}
@Override
public Object unmarshal(InputStream inputStream) {
return unmarshal(inputStream, null);
}
@Override
public Object unmarshal(InputStream inputStream, Class<?> clazz) {
return unmarshal(new InputSource(inputStream), clazz);
}
@Override
public Object unmarshal(InputSource inputSource) {
return unmarshal(inputSource, null);
}
@Override
public Object unmarshal(InputSource inputSource, Class<?> clazz) {
if(!xmlUnmarshaller.isApplicationXML()){
throw XMLMarshalException.unsupportedMediaTypeForPlatform();
}
try {
Document document = null;
document = getParser().parse(inputSource);
return xmlToObject(new DOMRecord(document), clazz);
} catch (XMLPlatformException e) {
throw XMLMarshalException.unmarshalException(e);
} finally {
xmlUnmarshaller.getStringBuffer().reset();
}
}
@Override
public Object unmarshal(Node node) {
return unmarshal(node, null);
}
@Override
public Object unmarshal(Node node, Class<?> clazz) {
if(!xmlUnmarshaller.isApplicationXML()){
throw XMLMarshalException.unsupportedMediaTypeForPlatform();
}
Element element = null;
switch (node.getNodeType()) {
case Node.DOCUMENT_NODE: {
element = ((Document) node).getDocumentElement();
break;
}
case Node.ELEMENT_NODE: {
element = (Element) node;
break;
}
default:
throw XMLMarshalException.unmarshalException();
}
return xmlToObject(new DOMRecord(element), clazz);
}
@Override
public Object unmarshal(Reader reader) {
return unmarshal(reader, null);
}
@Override
public Object unmarshal(Reader reader, Class<?> clazz) {
return unmarshal(new InputSource(reader), clazz);
}
@Override
public Object unmarshal(Source source) {
return unmarshal(source, null);
}
@Override
public Object unmarshal(Source source, Class<?> clazz) {
if(!xmlUnmarshaller.isApplicationXML()){
throw XMLMarshalException.unsupportedMediaTypeForPlatform();
}
try {
Document document = null;
document = getParser().parse(source);
return xmlToObject(new DOMRecord(document), clazz);
} catch (XMLPlatformException e) {
throw XMLMarshalException.unmarshalException(e);
} finally {
xmlUnmarshaller.getStringBuffer().reset();
}
}
@Override
public Object unmarshal(URL url) {
return unmarshal(url, null);
}
@Override
public Object unmarshal(URL url, Class<?> clazz) {
if(!xmlUnmarshaller.isApplicationXML()){
throw XMLMarshalException.unsupportedMediaTypeForPlatform();
}
try {
Document document = null;
document = getParser().parse(url);
return xmlToObject(new DOMRecord(document), clazz);
} catch (XMLPlatformException e) {
throw XMLMarshalException.unmarshalException(e);
} finally {
xmlUnmarshaller.getStringBuffer().reset();
}
}
@Override
public Object unmarshal(XMLReader xmlReader, InputSource inputSource) {
return unmarshal(xmlReader, inputSource, null);
}
@Override
public Object unmarshal(XMLReader xmlReader, InputSource inputSource, Class<?> clazz) {
if(!xmlUnmarshaller.isApplicationXML()){
throw XMLMarshalException.unsupportedMediaTypeForPlatform();
}
try {
SAXDocumentBuilder saxDocumentBuilder = new SAXDocumentBuilder();
xmlReader.setContentHandler(saxDocumentBuilder);
xmlReader.parse(inputSource);
return xmlToObject(new DOMRecord(saxDocumentBuilder.getDocument()), clazz);
} catch(IOException e) {
throw XMLMarshalException.unmarshalException(e);
} catch(SAXException e) {
throw XMLMarshalException.unmarshalException(e);
} finally {
xmlUnmarshaller.getStringBuffer().reset();
}
}
/**
* INTERNAL: Find the Descriptor corresponding to the context node of the
* XMLRecord, and then convert the XMLRecord to an instance of the
* corresponding object.
*
* @param xmlRecord
* The XMLRecord to unmarshal from
* @return the object which resulted from unmarshalling the given XMLRecord
* @throws XMLMarshalException
* if an error occurred during unmarshalling
*/
public Object xmlToObject(DOMRecord xmlRecord) throws XMLMarshalException {
return xmlToObject(xmlRecord, null);
}
/**
* INTERNAL: Convert the Oracle XMLDocument to the reference-class.
*/
public Object xmlToObject(DOMRecord xmlRow, Class<?> referenceClass) throws XMLMarshalException {
try{
//Try to get the Encoding and Version from DOM3 APIs if available
String xmlEncoding = "UTF-8";
String xmlVersion = "1.0";
try {
xmlEncoding = xmlRow.getDocument().getXmlEncoding() != null ? xmlRow.getDocument().getXmlEncoding() : xmlEncoding;
xmlVersion = xmlRow.getDocument().getXmlVersion() != null ? xmlRow.getDocument().getXmlVersion() : xmlVersion;
} catch (Exception ex) {
//if the methods aren't available, then just use the default values
}
XMLContext xmlContext = xmlUnmarshaller.getXMLContext();
// handle case where the reference class is a primitive wrapper - in
// this case, we need to use the conversion manager to convert the
// node's value to the primitive wrapper class, then create,
// populate and return an XMLRoot
if (referenceClass != null && (XMLConversionManager.getDefaultJavaTypes().get(referenceClass) != null ||CoreClassConstants.XML_GREGORIAN_CALENDAR.isAssignableFrom(referenceClass)
||CoreClassConstants.DURATION.isAssignableFrom(referenceClass))){
// we're assuming that since we're unmarshalling to a primitive
// wrapper, the root element has a single text node
Object nodeVal;
try {
Text rootTxt = (Text) xmlRow.getDOM().getFirstChild();
nodeVal = rootTxt.getNodeValue();
} catch (Exception ex) {
// here, either the root element doesn't have a text node as a
// first child, or there is no first child at all - in any case,
// try converting null
nodeVal = null;
}
Object obj = xmlContext.getSession().getDatasourcePlatform().getConversionManager().convertObject(nodeVal, referenceClass);
Root xmlRoot = new XMLRoot();
xmlRoot.setObject(obj);
String lName = xmlRow.getDOM().getLocalName();
if (lName == null) {
lName = xmlRow.getDOM().getNodeName();
}
xmlRoot.setLocalName(lName);
xmlRoot.setNamespaceURI(xmlRow.getDOM().getNamespaceURI());
xmlRoot.setEncoding(xmlEncoding);
xmlRoot.setVersion(xmlVersion);
return xmlRoot;
}
Descriptor descriptor = null;
CoreAbstractSession readSession = null;
boolean shouldWrap = true;
if(referenceClass == null){
QName rootQName = new QName(xmlRow.getNamespaceURI(), xmlRow.getLocalName());
descriptor = xmlContext.getDescriptor(rootQName);
if (null == descriptor) {
String type = ((Element) xmlRow.getDOM()).getAttributeNS(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "type");
if (null != type) {
XPathFragment typeFragment = new XPathFragment(type);
String namespaceURI = xmlRow.resolveNamespacePrefix(typeFragment.getPrefix());
typeFragment.setNamespaceURI(namespaceURI);
descriptor = xmlContext.getDescriptorByGlobalType(typeFragment);
}
}else{
if(null != descriptor.getDefaultRootElementField() && !descriptor.isResultAlwaysXMLRoot() && !xmlUnmarshaller.isResultAlwaysXMLRoot()){
String descLocalName = descriptor.getDefaultRootElementField().getXPathFragment().getLocalName();
String localName = xmlRow.getDOM().getLocalName();
if (localName == null) {
localName = xmlRow.getDOM().getNodeName();
}
String namespaceURI = xmlRow.getDOM().getNamespaceURI();
if( descLocalName != null && descLocalName.equals(localName) ){
String descUri = descriptor.getDefaultRootElementField().getXPathFragment().getNamespaceURI();
if((namespaceURI == null && descUri == null ) || (namespaceURI !=null &&namespaceURI.length() == 0 && descUri == null ) || (namespaceURI != null && namespaceURI.equals(descUri))){
//found a descriptor based on root element then know we won't need to wrap in an XMLRoot
shouldWrap = false;
}
}
}
}
if (null == descriptor) {
throw XMLMarshalException.noDescriptorWithMatchingRootElement(rootQName.toString());
}else{
readSession = xmlContext.getSession(descriptor.getJavaClass());
}
} else {
// for XMLObjectReferenceMappings we need a non-shared cache, so
// try and get a Unit Of Work from the XMLContext
readSession = xmlContext.getSession(referenceClass);
descriptor = (Descriptor)readSession.getDescriptor(referenceClass);
if (descriptor == null) {
throw XMLMarshalException.descriptorNotFoundInProject(referenceClass.getName());
}
}
Object object = null;
if(null == xmlRow.getDOM().getAttributes().getNamedItemNS(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, Constants.SCHEMA_NIL_ATTRIBUTE)) {
xmlRow.setUnmarshaller(xmlUnmarshaller);
xmlRow.setDocPresPolicy(xmlContext.getDocumentPreservationPolicy((AbstractSession) readSession));
XMLObjectBuilder objectBuilder = (XMLObjectBuilder) descriptor.getObjectBuilder();
ReadObjectQuery query = new ReadObjectQuery();
query.setReferenceClass(referenceClass);
query.setSession((AbstractSession) readSession);
object = objectBuilder.buildObject(query, xmlRow, null);
// resolve mapping references
xmlRow.resolveReferences(readSession, xmlUnmarshaller.getIDResolver());
}
String elementNamespaceUri = xmlRow.getDOM().getNamespaceURI();
String elementLocalName = xmlRow.getDOM().getLocalName();
if (elementLocalName == null) {
elementLocalName = xmlRow.getDOM().getNodeName();
}
String elementPrefix = xmlRow.getDOM().getPrefix();
if(shouldWrap || descriptor.isResultAlwaysXMLRoot() || isResultAlwaysXMLRoot){
return descriptor.wrapObjectInXMLRoot(object, elementNamespaceUri, elementLocalName, elementPrefix, xmlEncoding, xmlVersion, this.isResultAlwaysXMLRoot, true, xmlUnmarshaller);
}else{
return object;
}
}finally{
xmlUnmarshaller.getStringBuffer().reset();
}
}
@Override
public boolean isResultAlwaysXMLRoot() {
return this.isResultAlwaysXMLRoot;
}
@Override
public void setResultAlwaysXMLRoot(boolean alwaysReturnRoot) {
this.isResultAlwaysXMLRoot = alwaysReturnRoot;
}
@Override
public void mediaTypeChanged() {
//do nothing
}
@Override
public final boolean isSecureProcessingDisabled() {
return disableSecureProcessing;
}
@Override
public final void setDisableSecureProcessing(boolean disableSecureProcessing) {
shouldReset = this.disableSecureProcessing ^ disableSecureProcessing;
this.disableSecureProcessing = disableSecureProcessing;
}
}