| /* |
| * Copyright (c) 1998, 2019 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; |
| |
| } |
| |
| } |