blob: 862ea3c7619660e4cdce98dc3bdf110ef57c8392 [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.lang.reflect.Modifier;
import javax.xml.namespace.QName;
import org.eclipse.persistence.core.queries.CoreAttributeGroup;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.exceptions.EclipseLinkException;
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.Context;
import org.eclipse.persistence.internal.oxm.ConversionManager;
import org.eclipse.persistence.internal.oxm.Root;
import org.eclipse.persistence.internal.oxm.Unmarshaller;
import org.eclipse.persistence.internal.oxm.XPathFragment;
import org.eclipse.persistence.internal.oxm.XPathQName;
import org.eclipse.persistence.internal.oxm.mappings.Descriptor;
import org.eclipse.persistence.internal.oxm.mappings.UnmarshalKeepAsElementPolicy;
import org.eclipse.persistence.internal.oxm.record.namespaces.StackUnmarshalNamespaceResolver;
import org.eclipse.persistence.internal.oxm.record.namespaces.UnmarshalNamespaceResolver;
import org.eclipse.persistence.internal.oxm.unmapped.UnmappedContentHandler;
import org.eclipse.persistence.internal.security.PrivilegedNewInstanceFromClass;
import org.eclipse.persistence.oxm.MediaType;
import org.eclipse.persistence.platform.xml.SAXDocumentBuilder;
import org.w3c.dom.Node;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.ext.Locator2;
/**
* INTERNAL:
* <p><b>Purpose:</b>An implementation of ContentHandler used to handle the root element of an
* XML Document during unmarshal.
* <p><b>Responsibilities:</b><ul>
* <li>Implement ContentHandler interface</li>
* <li>Handle startElement event for the root-level element of an xml document</li>
* <li>Handle inheritance, and descriptor lookup to determine the correct class associated with
* this XML Element.</li>
* </ul>
*
* @author bdoughan
*
*/
public class SAXUnmarshallerHandler implements ExtendedContentHandler {
private XMLReader xmlReader;
private Context xmlContext;
private UnmarshalRecord rootRecord;
private Object object;
private Descriptor descriptor;
private boolean shouldWrap;
private Unmarshaller unmarshaller;
private CoreAbstractSession session;
private UnmarshalNamespaceResolver unmarshalNamespaceResolver;
private UnmarshalKeepAsElementPolicy keepAsElementPolicy = new UnmarshalKeepAsElementPolicy() {
@Override
public boolean isKeepAllAsElement() {
return false;
}
@Override
public boolean isKeepNoneAsElement() {
return true;
}
@Override
public boolean isKeepUnknownAsElement() {
return false;
}
};
private SAXDocumentBuilder documentBuilder;
private Locator2 locator;
private boolean isNil;
public SAXUnmarshallerHandler(Context xmlContext) {
super();
this.xmlContext = xmlContext;
this.shouldWrap = true;
unmarshalNamespaceResolver = new StackUnmarshalNamespaceResolver();
}
public XMLReader getXMLReader() {
return this.xmlReader;
}
public void setXMLReader(XMLReader xmlReader) {
this.xmlReader = xmlReader;
}
public Object getObject() {
if(object == null) {
if(this.descriptor != null) {
if(this.unmarshaller.isResultAlwaysXMLRoot() || descriptor.isResultAlwaysXMLRoot() || shouldWrap){
object = this.descriptor.wrapObjectInXMLRoot(this.rootRecord, (this.unmarshaller.isResultAlwaysXMLRoot() || descriptor.isResultAlwaysXMLRoot()));
}else {
object = this.rootRecord.getCurrentObject();
}
} else if(documentBuilder != null) {
Node node = documentBuilder.getDocument().getDocumentElement();
Root root = unmarshaller.createRoot();
root.setLocalName(node.getLocalName());
root.setNamespaceURI(node.getNamespaceURI());
root.setObject(node);
object = root;
} else {
if(rootRecord != null) {
object = this.rootRecord.getCurrentObject();
}
}
}
return this.object;
}
public void setObject(Object object) {
this.object = object;
}
@Override
public void setDocumentLocator(Locator locator) {
if (locator instanceof Locator2) {
this.locator = (Locator2)locator;
if(xmlReader != null){
xmlReader.setLocator(locator);
}
}
}
public UnmarshalNamespaceResolver getUnmarshalNamespaceResolver() {
return this.unmarshalNamespaceResolver;
}
public void setUnmarshalNamespaceResolver(UnmarshalNamespaceResolver unmarshalNamespaceResolver) {
this.unmarshalNamespaceResolver = unmarshalNamespaceResolver;
}
@Override
public void startDocument() throws SAXException {
}
@Override
public void endDocument() throws SAXException {
}
@Override
public void startPrefixMapping(String prefix, String uri) throws SAXException {
unmarshalNamespaceResolver.push(prefix, uri);
}
@Override
public void endPrefixMapping(String prefix) throws SAXException {
unmarshalNamespaceResolver.pop(prefix);
}
/**
* INTERNAL:
*
* Resolve any mapping references.
*/
public void resolveReferences() {
if(null != rootRecord) {
rootRecord.resolveReferences(session, unmarshaller.getIDResolver());
}
}
@Override
public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
try {
String name;
if (localName == null || localName.length() == 0) {
name = qName;
} else {
name = localName;
}
XPathQName rootQName;
if (namespaceURI == null || namespaceURI.length() == 0) {
rootQName = new XPathQName(name, xmlReader.isNamespaceAware() );
} else {
rootQName = new XPathQName(namespaceURI, name, xmlReader.isNamespaceAware() );
}
Class<?> primitiveWrapperClass = null;
Descriptor xmlDescriptor = xmlContext.getDescriptor(rootQName);
//if no match on root element look for xsi:type
if (xmlDescriptor == null || (unmarshaller.getMediaType() == MediaType.APPLICATION_JSON && unmarshaller.getJsonTypeConfiguration().getJsonTypeAttributeName() != null &&
!Constants.SCHEMA_TYPE_ATTRIBUTE.equals(unmarshaller.getJsonTypeConfiguration().getJsonTypeAttributeName()))) {
boolean isPrimitiveType = false;
String type = null;
if(xmlReader.isNamespaceAware()){
type = atts.getValue(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, Constants.SCHEMA_TYPE_ATTRIBUTE);
} else if (unmarshaller.getMediaType() != MediaType.APPLICATION_JSON || unmarshaller.getJsonTypeConfiguration().useJsonTypeCompatibility()) {
type = atts.getValue(Constants.EMPTY_STRING, Constants.SCHEMA_TYPE_ATTRIBUTE);
} else if (unmarshaller.getMediaType() == MediaType.APPLICATION_JSON && unmarshaller.getJsonTypeConfiguration().getJsonTypeAttributeName() != null) {
type = atts.getValue(Constants.EMPTY_STRING, unmarshaller.getJsonTypeConfiguration().getJsonTypeAttributeName());
}
if (null != type) {
XPathFragment typeFragment = new XPathFragment(type, xmlReader.getNamespaceSeparator(), xmlReader.isNamespaceAware());
// set the prefix using a reverse key lookup by uri value on namespaceMap
if (xmlReader.isNamespaceAware() && null != unmarshalNamespaceResolver) {
typeFragment.setNamespaceURI(unmarshalNamespaceResolver.getNamespaceURI(typeFragment.getPrefix()));
}
Descriptor lookupDescriptor = xmlContext.getDescriptorByGlobalType(typeFragment);
if(lookupDescriptor == null) {
QName lookupQName = null;
if(typeFragment.getNamespaceURI() == null){
lookupQName= new QName(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI, typeFragment.getLocalName());
}else{
lookupQName= new QName(typeFragment.getNamespaceURI(), typeFragment.getLocalName());
}
//check to see if type attribute represents simple type
if(null == session) {
session = (CoreAbstractSession) xmlContext.getSession();
}
ConversionManager conversionManager = (ConversionManager) session.getDatasourcePlatform().getConversionManager();
primitiveWrapperClass = conversionManager.javaType(lookupQName);
}else{
//found descriptor based on type attribute
xmlDescriptor = lookupDescriptor;
session = xmlContext.getSession(xmlDescriptor);
}
}
} else {
if(null != xmlDescriptor.getDefaultRootElementField() && !unmarshaller.isResultAlwaysXMLRoot()){
String descLocalName = xmlDescriptor.getDefaultRootElementField().getXPathFragment().getLocalName();
if( descLocalName != null && descLocalName.equals(localName) ){
String descUri = xmlDescriptor.getDefaultRootElementField().getXPathFragment().getNamespaceURI();
if(!xmlReader.isNamespaceAware() || (xmlReader.isNamespaceAware() && ((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(xmlDescriptor.hasInheritance()){
//if descriptor has inheritance check class indicator
session = xmlContext.getSession(xmlDescriptor);
UnmarshalRecord tmpUnmarshalRecord = new UnmarshalRecordImpl(null);
tmpUnmarshalRecord.setUnmarshaller(unmarshaller);
tmpUnmarshalRecord.setUnmarshalNamespaceResolver(unmarshalNamespaceResolver);
tmpUnmarshalRecord.setXMLReader(this.getXMLReader());
tmpUnmarshalRecord.setAttributes(atts);
Class<?> classValue = xmlDescriptor.getInheritancePolicy().classFromRow(new org.eclipse.persistence.oxm.record.UnmarshalRecord(tmpUnmarshalRecord), session);
if (classValue == null) {
// no xsi:type attribute - look for type indicator on the default root element
QName leafElementType = xmlDescriptor.getDefaultRootElementType();
// if we have a user-set type, try to get the class from the inheritance policy
if (leafElementType != null) {
Object indicator = xmlDescriptor.getInheritancePolicy().getClassIndicatorMapping().get(leafElementType);
if(indicator != null) {
classValue = (Class)indicator;
}
}
}
if (classValue != null) {
xmlDescriptor = (Descriptor)session.getDescriptor(classValue);
} else {
// since there is no xsi:type attribute, we'll use the descriptor
// that was retrieved based on the rootQName - we need to make
// sure it is non-abstract
if (Modifier.isAbstract(xmlDescriptor.getJavaClass().getModifiers())) {
// need to throw an exception here
throw DescriptorException.missingClassIndicatorField(tmpUnmarshalRecord, (org.eclipse.persistence.oxm.XMLDescriptor)xmlDescriptor.getInheritancePolicy().getDescriptor());
}
}
}
}
if (null == xmlDescriptor) {
//check for a cached object and look for descriptor by class
Object obj = this.xmlReader.getCurrentObject(session, null);
if (obj != null) {
xmlDescriptor = (Descriptor)xmlContext.getSession(obj.getClass()).getDescriptor(obj.getClass());
}
}
if (null == xmlDescriptor && primitiveWrapperClass == null){
if(!this.keepAsElementPolicy.isKeepNoneAsElement()) {
this.documentBuilder = new SAXDocumentBuilder();
documentBuilder.startDocument();
//start any prefixes that have already been started
for(String prefix:this.unmarshalNamespaceResolver.getPrefixes()) {
documentBuilder.startPrefixMapping(prefix, this.unmarshalNamespaceResolver.getNamespaceURI(prefix));
}
documentBuilder.startElement(namespaceURI, localName, qName, atts);
this.xmlReader.setContentHandler(documentBuilder);
return;
}
Class<?> unmappedContentHandlerClass = unmarshaller.getUnmappedContentHandlerClass();
if (null == unmappedContentHandlerClass) {
throw XMLMarshalException.noDescriptorWithMatchingRootElement(rootQName.toString());
} else {
UnmappedContentHandler unmappedContentHandler;
try {
PrivilegedNewInstanceFromClass privilegedNewInstanceFromClass = new PrivilegedNewInstanceFromClass(unmappedContentHandlerClass);
unmappedContentHandler = (UnmappedContentHandler)privilegedNewInstanceFromClass.run();
} catch (ClassCastException e) {
throw XMLMarshalException.unmappedContentHandlerDoesntImplement(e, unmappedContentHandlerClass.getName());
} catch (IllegalAccessException e) {
throw XMLMarshalException.errorInstantiatingUnmappedContentHandler(e, unmappedContentHandlerClass.getName());
} catch (InstantiationException e) {
throw XMLMarshalException.errorInstantiatingUnmappedContentHandler(e, unmappedContentHandlerClass.getName());
}
UnmappedContentHandlerWrapper unmappedContentHandlerWrapper = new UnmappedContentHandlerWrapper(unmappedContentHandler, this);
unmappedContentHandler.startElement(namespaceURI, localName, qName, atts);
xmlReader.setContentHandler(unmappedContentHandler);
setObject(unmappedContentHandlerWrapper.getCurrentObject());
return;
}
}
if (xmlDescriptor == null && primitiveWrapperClass != null) {
session = xmlContext.getSession((Descriptor) null);
rootRecord = unmarshaller.createRootUnmarshalRecord(primitiveWrapperClass);
rootRecord.setSession((CoreAbstractSession) unmarshaller.getContext().getSession());
} else{
if(session == null){
session = xmlContext.getSession(xmlDescriptor);
}
rootRecord = unmarshaller.createUnmarshalRecord(xmlDescriptor, session);
}
this.descriptor = xmlDescriptor;
rootRecord.setUnmarshaller(this.unmarshaller);
rootRecord.setXMLReader(this.getXMLReader());
if (locator != null) {
rootRecord.setDocumentLocator(xmlReader.getLocator());
}
rootRecord.setAttributes(atts);
boolean hasNilAttribute = (atts != null && null != atts.getValue(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, Constants.SCHEMA_NIL_ATTRIBUTE));
rootRecord.setNil(isNil || hasNilAttribute);
rootRecord.setUnmarshalNamespaceResolver(unmarshalNamespaceResolver);
rootRecord.startDocument();
rootRecord.initializeRecord(null);
xmlReader.setContentHandler(rootRecord);
xmlReader.setLexicalHandler(rootRecord);
Object attributeGroup = this.unmarshaller.getUnmarshalAttributeGroup();
if(attributeGroup != null) {
if(attributeGroup.getClass() == CoreClassConstants.STRING) {
CoreAttributeGroup group = descriptor.getAttributeGroup((String)attributeGroup);
if(group != null) {
rootRecord.setUnmarshalAttributeGroup(group);
} else {
//Error
}
} else if(attributeGroup instanceof CoreAttributeGroup) {
rootRecord.setUnmarshalAttributeGroup((CoreAttributeGroup)attributeGroup);
} else {
//Error case
}
}
rootRecord.startElement(namespaceURI, localName, qName, atts);
// if we located the descriptor via xsi:type attribute, create and
// return an XMLRoot object
} catch (EclipseLinkException e) {
if (null == xmlReader.getErrorHandler()) {
throw e;
} else {
SAXParseException saxParseException = new SAXParseException(null, null, null, 0, 0, e);
xmlReader.getErrorHandler().error(saxParseException);
}
}
}
@Override
public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
}
@Override
public void characters(CharSequence characters) throws SAXException {
}
@Override
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
}
@Override
public void processingInstruction(String target, String data) throws SAXException {
}
@Override
public void skippedEntity(String name) throws SAXException {
}
public void setUnmarshaller(Unmarshaller unmarshaller) {
this.unmarshaller = unmarshaller;
}
public Unmarshaller getUnmarshaller() {
return this.unmarshaller;
}
public void setKeepAsElementPolicy(UnmarshalKeepAsElementPolicy policy) {
this.keepAsElementPolicy = policy;
}
public UnmarshalKeepAsElementPolicy getKeepAsElementPolicy() {
return this.keepAsElementPolicy;
}
@Override
public void setNil(boolean isNil) {
this.isNil = isNil;
}
}