| /* |
| * 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 |
| // 01/03/2019 - SureshKumar Balakrishnan |
| // - 544934 - Added Empty Check in the stack before doing peek() method. |
| package org.eclipse.persistence.sdo.helper; |
| |
| import commonj.sdo.DataObject; |
| import commonj.sdo.Property; |
| import commonj.sdo.Type; |
| import commonj.sdo.helper.DataFactory; |
| import commonj.sdo.helper.HelperContext; |
| import java.lang.reflect.Modifier; |
| import java.util.Stack; |
| import javax.xml.namespace.QName; |
| import org.eclipse.persistence.sdo.SDOConstants; |
| import org.eclipse.persistence.sdo.SDODataObject; |
| import org.eclipse.persistence.sdo.SDOProperty; |
| import org.eclipse.persistence.sdo.SDOType; |
| import org.eclipse.persistence.sdo.SDOXMLDocument; |
| import org.eclipse.persistence.exceptions.DescriptorException; |
| import org.eclipse.persistence.exceptions.XMLMarshalException; |
| import org.eclipse.persistence.internal.oxm.StrBuffer; |
| import org.eclipse.persistence.internal.oxm.TreeObjectBuilder; |
| import org.eclipse.persistence.internal.oxm.record.XMLRecord; |
| import org.eclipse.persistence.internal.oxm.record.namespaces.UnmarshalNamespaceResolver; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.oxm.XMLConstants; |
| import org.eclipse.persistence.oxm.XMLDescriptor; |
| import org.eclipse.persistence.oxm.record.UnmarshalRecord; |
| import org.eclipse.persistence.oxm.unmapped.UnmappedContentHandler; |
| import org.xml.sax.Attributes; |
| import org.xml.sax.Locator; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.SAXNotRecognizedException; |
| import org.xml.sax.SAXNotSupportedException; |
| |
| /** |
| * <p><b>Purpose</b>: Called during XMLHelper load methods when there is unknown content in an XML document. This |
| * means when there is no corresponding SDO Type found. |
| * |
| * @see commonj.sdo.helper.XMLHelper |
| */ |
| public class SDOUnmappedContentHandler implements UnmappedContentHandler { |
| private UnmarshalRecord parentRecord; |
| private SDOXMLDocument xmlDocument; |
| private QName currentSchemaType; |
| private StrBuffer currentBuffer; |
| private Stack currentDataObjects; |
| private Stack currentProperties; |
| private boolean rootProcessed; |
| private boolean isInCharacterBlock; |
| private HelperContext aHelperContext; |
| private int lastEvent = -1; |
| private static final int START_ELEMENT = 0; |
| private static final int END_ELEMENT = 1; |
| private UnmarshalNamespaceResolver unmarshalNamespaceResolver; |
| private static final String NO_NAMESPACE = null; |
| private int depth = 0; |
| |
| public SDOUnmappedContentHandler() { |
| isInCharacterBlock = false; |
| currentDataObjects = new Stack(); |
| currentProperties = new Stack(); |
| currentBuffer = new StrBuffer(); |
| } |
| |
| @Override |
| public void setDocumentLocator(Locator locator) { |
| } |
| |
| @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); |
| } |
| |
| @Override |
| public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { |
| if (isInCharacterBlock) { |
| if (!currentBuffer.toString().trim().equals("") && !currentDataObjects.empty()) { |
| DataObject dObj = (DataObject) currentDataObjects.peek(); |
| dObj.getSequence().addText(currentBuffer.toString()); |
| |
| } |
| currentBuffer.reset(); |
| } |
| if (!rootProcessed) { |
| processRoot(namespaceURI, localName, qName, atts); |
| } else { |
| processNonRoot(namespaceURI, localName, qName, atts); |
| } |
| lastEvent = START_ELEMENT; |
| } |
| |
| /** |
| * Return a QName representing the value of the xsi:type attribute or null if one is not present |
| */ |
| private QName getTypeAttributeQName(Attributes atts) { |
| int attributeSize = atts.getLength(); |
| for (int i = 0; i < attributeSize; i++) { |
| String stringValue = atts.getValue(i); |
| String uri = atts.getURI(i); |
| String attrName = atts.getLocalName(i); |
| if (javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI.equals(uri) && XMLConstants.SCHEMA_TYPE_ATTRIBUTE.equals(attrName)) { |
| int colonIndex = stringValue.indexOf(':'); |
| String localPrefix = stringValue.substring(0, colonIndex); |
| String localURI = unmarshalNamespaceResolver.getNamespaceURI(localPrefix); |
| if (localURI != null) { |
| String localName = stringValue.substring(colonIndex + 1, stringValue.length()); |
| QName theQName = new QName(localURI, localName); |
| currentSchemaType = theQName; |
| return theQName; |
| } else { |
| throw XMLMarshalException.namespaceNotFound(localPrefix); |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| private void processAttributes(Attributes atts, DataObject dataObject, boolean isRoot) { |
| int attributeSize = atts.getLength(); |
| for (int i = 0; i < attributeSize; i++) { |
| String stringValue = atts.getValue(i); |
| String uri = atts.getURI(i); |
| String attrName = atts.getLocalName(i); |
| |
| if ((atts.getQName(i) != null) && atts.getQName(i).startsWith(javax.xml.XMLConstants.XMLNS_ATTRIBUTE + ":")) { |
| //namespace declaration - do nothing because namespaces were already handled |
| } else if (isRoot && XMLConstants.SCHEMA_LOCATION.equals(attrName)) { |
| getXmlDocument().setSchemaLocation(stringValue); |
| } else if (isRoot && XMLConstants.NO_NS_SCHEMA_LOCATION.equals(attrName)) { |
| getXmlDocument().setNoNamespaceSchemaLocation(stringValue); |
| } else if (javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI.equals(uri) && XMLConstants.SCHEMA_TYPE_ATTRIBUTE.equals(attrName)) { |
| //do nothing |
| } else if (SDOConstants.CHANGESUMMARY_REF.equals(attrName) && SDOConstants.SDO_URL.equals(uri)) { |
| ((SDODataObject)dataObject)._setSdoRef(stringValue); |
| } else { |
| HelperContext aHelperContext = ((SDOType)dataObject.getType()).getHelperContext(); |
| Property prop = aHelperContext.getXSDHelper().getGlobalProperty(uri, attrName, false); |
| if (prop != null) { |
| Object convertedValue = ((SDODataHelper)aHelperContext.getDataHelper()).convertFromStringValue(stringValue, prop.getType()); |
| dataObject.set(prop, convertedValue); |
| } else { |
| Object convertedValue = ((SDODataHelper)aHelperContext.getDataHelper()).convertFromStringValue(stringValue, SDOConstants.SDO_STRING); |
| |
| //can't use create on demand property via a set by string name operation because that would create an element |
| prop = defineNewSDOProperty(uri, attrName, false, SDOConstants.SDO_STRING); |
| dataObject.set(prop, convertedValue); |
| } |
| } |
| } |
| } |
| |
| @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 { |
| } |
| |
| @Override |
| public void endElement(String namespaceURI, String localName, String qName) throws SAXException { |
| if ((currentDataObjects.size() == 0) && (currentProperties.size() == 0)) { |
| return; |
| } |
| |
| if ((currentDataObjects.size() == 1) && (currentProperties.size() == 0)) { |
| parentRecord.getUnmarshaller().getUnmarshalListener().afterUnmarshal(currentDataObjects.peek(), null); |
| currentDataObjects.pop(); |
| depth--; |
| return; |
| } else { |
| setElementPropertyValue(); |
| return; |
| } |
| } |
| |
| @Override |
| public void characters(char[] ch, int start, int length) throws SAXException { |
| if (!isInCharacterBlock) { |
| currentBuffer.reset(); |
| isInCharacterBlock = true; |
| } |
| currentBuffer.append(ch, start, length); |
| } |
| |
| private void setElementPropertyValue() { |
| Property currentProperty = (Property)currentProperties.pop(); |
| boolean simple = true; |
| |
| if (lastEvent == END_ELEMENT) { |
| simple = false; |
| } else { |
| //last event was start element so is usually a simple thing |
| //if depth is greater than the stack size it means dataType == true |
| if (depth > currentDataObjects.size()) { |
| simple = true; |
| } else { |
| //if depth and stack size are the same it means complex or simple. |
| DataObject nextDO = (DataObject)currentDataObjects.peek(); |
| |
| if (nextDO.getInstanceProperties().size() > 0) { |
| simple = false; |
| if (!currentBuffer.toString().trim().equals("")) { |
| DataObject dObj = (DataObject) currentDataObjects.peek(); |
| dObj.getSequence().addText(currentBuffer.toString()); |
| } |
| } else { |
| currentDataObjects.pop(); |
| } |
| } |
| depth--; |
| } |
| |
| lastEvent = END_ELEMENT; |
| if (simple && (!isInCharacterBlock || (currentBuffer.length() == 0))) { |
| return; |
| } |
| |
| DataObject currentDataObject = (DataObject)currentDataObjects.peek(); |
| |
| if (currentProperty != null) { |
| Object value = null; |
| if (simple) { |
| value = currentBuffer.toString(); |
| ((SDOProperty)currentProperty).setType(SDOConstants.SDO_STRING); |
| ((SDOProperty)currentProperty).setContainment(false); |
| } else { |
| value = currentDataObject; |
| currentDataObjects.pop(); |
| depth--; |
| if (currentDataObjects.isEmpty()) { |
| currentDataObject = null; |
| } else { |
| currentDataObject = (DataObject)currentDataObjects.peek(); |
| } |
| } |
| HelperContext aHelperContext = ((SDOType)currentDataObject.getType()).getHelperContext(); |
| if (currentSchemaType != null) { |
| Type sdoType = ((SDOTypeHelper)aHelperContext.getTypeHelper()).getSDOTypeFromXSDType(currentSchemaType); |
| |
| if (sdoType != null) { |
| ((SDOProperty)currentProperty).setType(sdoType); |
| } |
| |
| if ((currentProperty.getType() != null) && simple) { |
| value = ((SDODataHelper)aHelperContext.getDataHelper()).convertFromStringValue((String)value, currentProperty.getType(), currentSchemaType); |
| } |
| currentSchemaType = null; |
| } else if ((currentProperty.getType() != null) && currentProperty.getType().isDataType()) { |
| value = ((SDODataHelper)aHelperContext.getDataHelper()).convertFromStringValue((String)value, currentProperty.getType()); |
| } |
| |
| if (currentDataObject != null) { |
| if (!simple) { |
| parentRecord.getUnmarshaller().getUnmarshalListener().afterUnmarshal(value, currentDataObject); |
| } |
| |
| //newly defined Properties will be many=true |
| if (currentProperty.isMany()) { |
| currentDataObject.getList(currentProperty).add(value); |
| } else { |
| currentDataObject.set(currentProperty, value); |
| } |
| } |
| currentBuffer.reset(); |
| } |
| } |
| |
| private void processNonRoot(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { |
| DataObject owner = (DataObject)currentDataObjects.peek(); |
| |
| if ((owner != null) && !owner.getType().isOpen()) { |
| return; |
| } |
| Property globalProperty = aHelperContext.getXSDHelper().getGlobalProperty(namespaceURI, localName, true); |
| if (globalProperty != null) { |
| currentProperties.push(globalProperty); |
| SDOType theType = ((SDOType)globalProperty.getType()); |
| |
| if (globalProperty.getType().isDataType()) { |
| depth++; |
| } else { |
| XMLDescriptor xmlDescriptor = theType.getXmlDescriptor(); |
| giveToOXToProcess(namespaceURI, localName, qName, atts, xmlDescriptor); |
| } |
| } else { |
| String typeName = localName; |
| String typeUri = namespaceURI; |
| QName typeAttribute = getTypeAttributeQName(atts); |
| Type newType = null; |
| if (typeAttribute != null) { |
| typeName = typeAttribute.getLocalPart(); |
| typeUri = typeAttribute.getNamespaceURI(); |
| newType = aHelperContext.getTypeHelper().getType(typeUri, typeName); |
| } |
| if (newType == null) { |
| newType = aHelperContext.getTypeHelper().getType(SDOConstants.ORACLE_SDO_URL, "OpenSequencedType"); |
| Type dataObjectType = aHelperContext.getTypeHelper().getType(SDOConstants.SDO_URL, "DataObject"); |
| Property property = defineNewSDOProperty(namespaceURI, localName, true, dataObjectType); |
| DataObject newDO = aHelperContext.getDataFactory().create(newType); |
| processAttributes(atts, newDO, false); |
| currentDataObjects.push(newDO); |
| depth++; |
| parentRecord.setCurrentObject(newDO); |
| |
| currentProperties.push(property); |
| } else { |
| //this means that type is a known type which will have a descriptor |
| XMLDescriptor xmlDescriptor = ((SDOType)newType).getXmlDescriptor(); |
| giveToOXToProcess(namespaceURI, localName, qName, atts, xmlDescriptor); |
| Property property = defineNewSDOProperty(namespaceURI, localName, true, newType); |
| currentProperties.push(property); |
| return; |
| } |
| } |
| } |
| |
| private void processRoot(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { |
| DataFactory dataFactory = aHelperContext.getDataFactory(); |
| SDOTypeHelper typeHelper = (SDOTypeHelper)aHelperContext.getTypeHelper(); |
| |
| getXmlDocument().setRootElementName(localName); |
| getXmlDocument().setRootElementURI(namespaceURI); |
| Property rootElementProperty = aHelperContext.getXSDHelper().getGlobalProperty(namespaceURI, localName, true); |
| Type rootObjectType = null; |
| |
| if (rootElementProperty != null) { |
| rootObjectType = rootElementProperty.getType(); |
| } else { |
| QName typeQName = getTypeAttributeQName(atts); |
| if (typeQName != null) { |
| String typeName = typeQName.getLocalPart(); |
| String typeUri = null; |
| String prefix = typeQName.getPrefix(); |
| if (prefix == null) { |
| typeUri = null; |
| } else { |
| typeUri = unmarshalNamespaceResolver.getNamespaceURI(prefix); |
| } |
| rootObjectType = typeHelper.getType(typeUri, typeName); |
| } |
| } |
| |
| DataObject rootObject = null; |
| if (rootObjectType != null) { |
| giveToOXToProcess(namespaceURI, localName, qName, atts, ((SDOType)rootObjectType).getXmlDescriptor()); |
| return; |
| } else { |
| Type rootType = aHelperContext.getTypeHelper().getType(SDOConstants.ORACLE_SDO_URL, "OpenSequencedType"); |
| rootObject = dataFactory.create(rootType); |
| } |
| currentDataObjects.push(rootObject); |
| depth++; |
| processAttributes(atts, rootObject, true); |
| |
| getXmlDocument().setRootObject(rootObject); |
| rootProcessed = true; |
| parentRecord.setCurrentObject(getXmlDocument()); |
| } |
| |
| private SDOProperty defineNewSDOProperty(String uri, String localName, boolean isElement, Type type) { |
| DataObject currentDataObject = (DataObject)currentDataObjects.peek(); |
| |
| if ((uri != null) && uri.equals("")) { |
| uri = NO_NAMESPACE; |
| } |
| |
| //Check if the current DataObject has a property by this name and if so return it, |
| //otherwise create a new property |
| SDOProperty lookedUp = (SDOProperty)currentDataObject.getInstanceProperty(localName); |
| if ((lookedUp != null) && equalStrings(lookedUp.getUri(), uri)) { |
| if (isElement && aHelperContext.getXSDHelper().isElement(lookedUp)) { |
| return lookedUp; |
| } |
| if (!isElement && aHelperContext.getXSDHelper().isAttribute(lookedUp)) { |
| return lookedUp; |
| } |
| } |
| |
| //This Property will not be registered with XSDHelper or TypeHelper |
| SDOProperty property = new SDOProperty(aHelperContext); |
| property.setName(localName); |
| property.setMany(isElement); |
| property.setContainment(!type.isDataType()); |
| property.setType(type); |
| property.setUri(uri); |
| property.setInstanceProperty(SDOConstants.XMLELEMENT_PROPERTY, isElement); |
| |
| return property; |
| } |
| |
| private void giveToOXToProcess(String namespaceURI, String localName, String qName, Attributes atts, XMLDescriptor xmlDescriptor) throws SAXException { |
| AbstractSession session = ((SDOXMLHelper)aHelperContext.getXMLHelper()).getXmlContext().getReadSession(xmlDescriptor); |
| |
| //taken from SAXUnmarshallerHandler start Element |
| UnmarshalRecord unmarshalRecord; |
| if (xmlDescriptor.hasInheritance()) { |
| unmarshalRecord = new UnmarshalRecord((TreeObjectBuilder)xmlDescriptor.getObjectBuilder()); |
| unmarshalRecord.setUnmarshalNamespaceResolver(unmarshalNamespaceResolver); |
| unmarshalRecord.setAttributes(atts); |
| if(parentRecord != null){ |
| unmarshalRecord.setXMLReader(parentRecord.getXMLReader()); |
| } |
| Class classValue = xmlDescriptor.getInheritancePolicy().classFromRow(unmarshalRecord, 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 = (XMLDescriptor)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((XMLRecord) unmarshalRecord, xmlDescriptor.getInheritancePolicy().getDescriptor()); |
| } |
| } |
| } |
| |
| unmarshalRecord = (UnmarshalRecord)xmlDescriptor.getObjectBuilder().createRecord(session); |
| unmarshalRecord.setParentRecord(parentRecord); |
| unmarshalRecord.setUnmarshaller(parentRecord.getUnmarshaller()); |
| unmarshalRecord.setXMLReader(parentRecord.getXMLReader()); |
| unmarshalRecord.startDocument(); |
| unmarshalRecord.setUnmarshalNamespaceResolver(unmarshalNamespaceResolver); |
| unmarshalRecord.startElement(namespaceURI, localName, qName, atts); |
| |
| parentRecord.getXMLReader().setContentHandler(unmarshalRecord); |
| |
| try { |
| unmarshalRecord.getXMLReader().setProperty("http://xml.org/sax/properties/lexical-handler", unmarshalRecord); |
| } catch (SAXNotRecognizedException ex) { |
| } catch (SAXNotSupportedException ex) { |
| //if lexical handling is not supported by this parser, just ignore. |
| } |
| |
| currentDataObjects.push(unmarshalRecord.getCurrentObject()); |
| depth++; |
| } |
| |
| private SDOXMLDocument getXmlDocument() { |
| if (xmlDocument == null) { |
| xmlDocument = new SDOXMLDocument(); |
| } |
| return xmlDocument; |
| } |
| |
| @Override |
| public void setUnmarshalRecord(UnmarshalRecord unmarshalRecord) { |
| parentRecord = unmarshalRecord; |
| aHelperContext = (HelperContext)unmarshalRecord.getUnmarshaller().getProperty(SDOConstants.SDO_HELPER_CONTEXT); |
| |
| if (parentRecord.getParentRecord() == null) { |
| rootProcessed = false; |
| } else { |
| rootProcessed = true; |
| if (parentRecord.getParentRecord().getCurrentObject() instanceof DataObject) { |
| currentDataObjects.push(parentRecord.getParentRecord().getCurrentObject()); |
| depth++; |
| } |
| } |
| unmarshalNamespaceResolver = parentRecord.getUnmarshalNamespaceResolver(); |
| } |
| |
| private boolean equalStrings(String string1, String string2) { |
| if (string1 == null) { |
| if (string2 == null) { |
| return true; |
| } else { |
| return false; |
| } |
| } else { |
| if (string2 == null) { |
| return false; |
| } else { |
| return string1.equals(string2); |
| } |
| } |
| } |
| } |