| /* |
| * 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.internal.oxm; |
| |
| import java.util.Iterator; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Vector; |
| |
| import javax.xml.namespace.QName; |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.descriptors.InheritancePolicy; |
| import org.eclipse.persistence.descriptors.MultitenantPolicy; |
| import org.eclipse.persistence.exceptions.DescriptorException; |
| import org.eclipse.persistence.internal.core.helper.CoreClassConstants; |
| import org.eclipse.persistence.internal.core.helper.CoreField; |
| import org.eclipse.persistence.internal.helper.DatabaseTable; |
| import org.eclipse.persistence.internal.helper.Helper; |
| import org.eclipse.persistence.internal.sessions.AbstractRecord; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.oxm.NamespaceResolver; |
| import org.eclipse.persistence.oxm.XMLDescriptor; |
| import org.eclipse.persistence.oxm.XMLField; |
| import org.eclipse.persistence.oxm.record.XMLRecord; |
| |
| /** |
| * INTERNAL: |
| * <p><b>Purpose</b>: A Subclass of Inheritance Policy to be used with XML |
| * Descriptors. If the class indicator field is an xsi:type, the value of that |
| * field may be a qualified type name. For example xsi:type="myns:my-type-name". |
| * Since any given XML document can use different prefixes for these namespaces, |
| * we must be able to find the class based on QName instead of just the string |
| * "myns:my-type-name".</p> |
| * @author mmacivor |
| * @since 10.1.3 |
| */ |
| public class QNameInheritancePolicy extends InheritancePolicy { |
| //used for initialization. Prefixed type names will be changed to QNames. |
| private NamespaceResolver namespaceResolver; |
| |
| private boolean usesXsiType = false; |
| |
| public QNameInheritancePolicy() { |
| super(); |
| } |
| |
| public QNameInheritancePolicy(ClassDescriptor desc) { |
| super(desc); |
| } |
| |
| /** |
| * Override to control order of uniqueTables, child tablenames should be first since |
| * getDefaultRootElement on an XMLDescriptor will return the first table. |
| */ |
| @Override |
| protected void updateTables(){ |
| // Unique is required because the builder can add the same table many times. |
| Vector<DatabaseTable> childTables = getDescriptor().getTables(); |
| Vector<DatabaseTable> parentTables = getParentDescriptor().getTables(); |
| Vector<DatabaseTable> uniqueTables = Helper.concatenateUniqueVectors(childTables, parentTables); |
| getDescriptor().setTables(uniqueTables); |
| |
| if(getDescriptor().isXMLDescriptor() && getParentDescriptor().isXMLDescriptor()){ |
| if(((XMLDescriptor)getDescriptor()).getDefaultRootElementField() == null){ |
| ((XMLDescriptor)getDescriptor()).setDefaultRootElementField(((XMLDescriptor)getParentDescriptor()).getDefaultRootElementField()); |
| } |
| } |
| |
| // After filtering out any duplicate tables, set the default table |
| // if one is not already set. This must be done now before any other |
| // initialization occurs. In a joined strategy case, the default |
| // table will be at an index greater than 0. Which is where |
| // setDefaultTable() assumes it is. Therefore, we need to send the |
| // actual default table instead. |
| if (childTables.isEmpty()) { |
| getDescriptor().setInternalDefaultTable(); |
| } else { |
| getDescriptor().setInternalDefaultTable(uniqueTables.get(uniqueTables.indexOf(childTables.get(0)))); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Allow the inheritance properties of the descriptor to be initialized. |
| * The descriptor's parent must first be initialized. |
| */ |
| @Override |
| public void preInitialize(AbstractSession session) throws DescriptorException { |
| // Override for OXM inheritence to avoid initializing unrequired fields |
| // on the descriptor |
| if (isChildDescriptor()) { |
| updateTables(); |
| |
| // Clone the multitenant policy and set on child descriptor. |
| if (getParentDescriptor().hasMultitenantPolicy()) { |
| MultitenantPolicy clonedMultitenantPolicy = getParentDescriptor().getMultitenantPolicy().clone(getDescriptor()); |
| getDescriptor().setMultitenantPolicy(clonedMultitenantPolicy); |
| } |
| |
| setClassIndicatorMapping(getParentDescriptor().getInheritancePolicy().getClassIndicatorMapping()); |
| setShouldUseClassNameAsIndicator(getParentDescriptor().getInheritancePolicy().shouldUseClassNameAsIndicator()); |
| |
| // Initialize properties. |
| getDescriptor().setPrimaryKeyFields(getParentDescriptor().getPrimaryKeyFields()); |
| getDescriptor().setAdditionalTablePrimaryKeyFields(Helper.concatenateMaps(getParentDescriptor().getAdditionalTablePrimaryKeyFields(), getDescriptor().getAdditionalTablePrimaryKeyFields())); |
| |
| setClassIndicatorField(getParentDescriptor().getInheritancePolicy().getClassIndicatorField()); |
| |
| //if child has sequencing setting, do not bother to call the parent |
| if (!getDescriptor().usesSequenceNumbers()) { |
| getDescriptor().setSequenceNumberField(getParentDescriptor().getSequenceNumberField()); |
| getDescriptor().setSequenceNumberName(getParentDescriptor().getSequenceNumberName()); |
| } |
| } else { |
| // This must be done now before any other initialization occurs. |
| getDescriptor().setInternalDefaultTable(); |
| } |
| |
| initializeClassExtractor(session); |
| |
| if (!isChildDescriptor()) { |
| // build abstract class indicator field. |
| if ((getClassIndicatorField() == null) && (!hasClassExtractor())) { |
| session.getIntegrityChecker().handleError(DescriptorException.classIndicatorFieldNotFound(getDescriptor(), getDescriptor())); |
| } |
| if (getClassIndicatorField() != null) { |
| setClassIndicatorField(getDescriptor().buildField(getClassIndicatorField())); |
| // Determine and set the class indicator classification. |
| if (shouldUseClassNameAsIndicator()) { |
| getClassIndicatorField().setType(CoreClassConstants.STRING); |
| } else if (!getClassIndicatorMapping().isEmpty()) { |
| Class type = null; |
| Iterator fieldValuesEnum = getClassIndicatorMapping().values().iterator(); |
| while (fieldValuesEnum.hasNext() && (type == null)) { |
| Object value = fieldValuesEnum.next(); |
| if (value.getClass() != getClass().getClass()) { |
| type = value.getClass(); |
| } |
| } |
| getClassIndicatorField().setType(type); |
| } |
| getDescriptor().getFields().addElement(getClassIndicatorField()); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Initialized the inheritance properties of the descriptor once the mappings are initialized. |
| * This is done before formal postInitialize during the end of mapping initialize. |
| */ |
| @Override |
| public void initialize(AbstractSession session) { |
| super.initialize(session); |
| |
| // If we have a namespace resolver, check any of the class-indicator values |
| // for prefixed type names and resolve the namespaces. |
| if (!this.shouldUseClassNameAsIndicator()){ |
| if(classIndicatorField != null){ |
| XPathFragment frag = ((XMLField) classIndicatorField).getXPathFragment(); |
| if (frag.getLocalName().equals(Constants.SCHEMA_TYPE_ATTRIBUTE) && javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI.equals(frag.getNamespaceURI())) { |
| usesXsiType = true; |
| } |
| } |
| |
| // Must first clone the map to avoid concurrent modification. |
| Iterator<Map.Entry> entries = new HashMap(getClassIndicatorMapping()).entrySet().iterator(); |
| while (entries.hasNext()) { |
| Map.Entry entry = entries.next(); |
| Object key = entry.getKey(); |
| if (key instanceof String) { |
| XPathQName qname; |
| |
| String indicatorValue = (String) key; |
| if (!usesXsiType || namespaceResolver == null) { |
| qname = new XPathQName(indicatorValue, true); |
| } else { |
| int index = indicatorValue.indexOf(Constants.COLON); |
| if (index != -1 && namespaceResolver != null) { |
| String prefix = indicatorValue.substring(0, index); |
| String localPart = indicatorValue.substring(index + 1); |
| String uri = namespaceResolver.resolveNamespacePrefix(prefix); |
| qname = new XPathQName(uri, localPart, true); |
| } else { |
| qname = new XPathQName(namespaceResolver.getDefaultNamespaceURI(), indicatorValue, true); |
| } |
| } |
| getClassIndicatorMapping().put(qname, entry.getValue()); |
| } else if (key instanceof QName) { |
| XPathQName xpathQName = new XPathQName((QName) key, true); |
| getClassIndicatorMapping().put(xpathQName, entry.getValue()); |
| } |
| } |
| } |
| |
| //bug 6012173 - changed to initialize namespare uri on indicator field |
| //need to be able to compare uri and local name during marshal to see if field is xsi type field |
| if(getClassIndicatorField() != null){ |
| XMLField classIndicatorXMLField; |
| try { |
| classIndicatorXMLField = (XMLField)getClassIndicatorField(); |
| } catch (ClassCastException ex) { |
| classIndicatorXMLField = new XMLField(getClassIndicatorField().getName()); |
| setClassIndicatorField(classIndicatorXMLField); |
| } |
| XPathFragment frag = classIndicatorXMLField.getLastXPathFragment(); |
| if ((frag != null) && frag.hasNamespace() && frag.getPrefix() !=null && (namespaceResolver != null)) { |
| String uri = namespaceResolver.resolveNamespacePrefix(frag.getPrefix()); |
| classIndicatorXMLField.getLastXPathFragment().setNamespaceURI(uri); |
| } |
| } |
| } |
| |
| |
| |
| /** |
| * INTERNAL: |
| * This method is invoked only for the abstract descriptors. |
| */ |
| @Override |
| public Class classFromRow(AbstractRecord rowFromDatabase, AbstractSession session) throws DescriptorException { |
| ((XMLRecord) rowFromDatabase).setSession(session); |
| if (hasClassExtractor() || shouldUseClassNameAsIndicator()) { |
| return super.classFromRow(rowFromDatabase, session); |
| } |
| Object indicator = rowFromDatabase.get(getClassIndicatorField()); |
| |
| if (indicator == AbstractRecord.noEntry) { |
| return null; |
| } |
| |
| if (indicator == null) { |
| return null; |
| } |
| |
| Class concreteClass; |
| if (indicator instanceof String) { |
| boolean namespaceAware = ((XMLRecord) rowFromDatabase).isNamespaceAware(); |
| String indicatorValue = (String)indicator; |
| int index = -1; |
| if(namespaceAware){ |
| index = indicatorValue.indexOf(((XMLRecord)rowFromDatabase).getNamespaceSeparator()); |
| } |
| if (index == -1) { |
| if (namespaceAware && usesXsiType) { |
| String uri = ((XMLRecord)rowFromDatabase).resolveNamespacePrefix(null); |
| if (uri == null && ((XMLRecord)rowFromDatabase).getNamespaceResolver() != null) { |
| uri = ((XMLRecord)rowFromDatabase).getNamespaceResolver().getDefaultNamespaceURI(); |
| } |
| XPathQName qname = new XPathQName(uri, indicatorValue, namespaceAware); |
| concreteClass = (Class)this.classIndicatorMapping.get(qname); |
| } else { |
| XPathQName qname = new XPathQName(indicatorValue, namespaceAware); |
| concreteClass = (Class)this.classIndicatorMapping.get(qname); |
| } |
| } else { |
| String prefix = indicatorValue.substring(0, index); |
| String localPart = indicatorValue.substring(index + 1); |
| String uri = ((XMLRecord)rowFromDatabase).resolveNamespacePrefix(prefix); |
| if (uri != null) { |
| XPathQName qname = new XPathQName(uri, localPart, namespaceAware); |
| concreteClass = (Class)this.classIndicatorMapping.get(qname); |
| } else { |
| concreteClass = (Class)this.classIndicatorMapping.get(indicatorValue); |
| } |
| } |
| } else { |
| concreteClass = (Class)this.classIndicatorMapping.get(indicator); |
| } |
| if (concreteClass == null) { |
| throw DescriptorException.missingClassForIndicatorFieldValue(indicator, getDescriptor()); |
| } |
| return concreteClass; |
| } |
| |
| public void setNamespaceResolver(NamespaceResolver resolver) { |
| this.namespaceResolver = resolver; |
| } |
| |
| /** |
| * PUBLIC: |
| * To set the class indicator field name. |
| * This is the name of the field in the table that stores what type of object this is. |
| */ |
| @Override |
| public void setClassIndicatorFieldName(String fieldName) { |
| if (fieldName == null) { |
| setClassIndicatorField(null); |
| } else { |
| setClassIndicatorField(new XMLField(fieldName)); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Add abstract class indicator information to the database row. This is |
| * required when building a row for an insert or an update of a concrete child |
| * descriptor. |
| */ |
| @Override |
| public void addClassIndicatorFieldToRow(AbstractRecord databaseRow) { |
| if (hasClassExtractor()) { |
| return; |
| } |
| |
| CoreField field = getClassIndicatorField(); |
| Object value = getClassIndicatorValue(); |
| |
| if(usesXsiType){ |
| boolean namespaceAware = ((XMLRecord)databaseRow).isNamespaceAware() || ((XMLRecord)databaseRow).hasCustomNamespaceMapper(); |
| if(value instanceof String){ |
| if(namespaceAware){ |
| if(((XMLRecord)databaseRow).getNamespaceSeparator() != Constants.COLON){ |
| value= ((String)value).replace(Constants.COLON, ((XMLRecord)databaseRow).getNamespaceSeparator()); |
| } |
| }else{ |
| int colonIndex = ((String)value).indexOf(Constants.COLON); |
| if(colonIndex > -1){ |
| value = ((String)value).substring(colonIndex + 1); |
| } |
| } |
| } |
| } |
| |
| databaseRow.put(field, value); |
| } |
| } |