org.eclipse.persistence.internal.oxm.QNameInheritancePolicy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of eclipselink Show documentation
Show all versions of eclipselink Show documentation
EclipseLink build based upon Git transaction f2b9fc5
/*
* 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:
* Purpose: 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".
* @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 childTables = getDescriptor().getTables();
Vector parentTables = getParentDescriptor().getTables();
Vector 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 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);
}
}