blob: da8ccac15ffd1f9b6618cba1da82a41cf51308ad [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;
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);
}
}