blob: c8ae8a4730067ed253862aba043dd6eeb80ddc5c [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
// 14/05/2012-2.4 Guy Pelletier
// - 376603: Provide for table per tenant support for multitenant applications
package org.eclipse.persistence.eis;
import java.util.List;
import java.util.Vector;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.DescriptorQueryManager;
import org.eclipse.persistence.descriptors.InheritancePolicy;
import org.eclipse.persistence.eis.mappings.EISCompositeCollectionMapping;
import org.eclipse.persistence.eis.mappings.EISCompositeDirectCollectionMapping;
import org.eclipse.persistence.eis.mappings.EISCompositeObjectMapping;
import org.eclipse.persistence.eis.mappings.EISDirectMapping;
import org.eclipse.persistence.eis.mappings.EISOneToManyMapping;
import org.eclipse.persistence.eis.mappings.EISOneToOneMapping;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.internal.databaseaccess.DatasourceCall;
import org.eclipse.persistence.internal.databaseaccess.DatasourcePlatform;
import org.eclipse.persistence.internal.expressions.SQLStatement;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.DatabaseTable;
import org.eclipse.persistence.internal.oxm.QNameInheritancePolicy;
import org.eclipse.persistence.internal.oxm.XMLObjectBuilder;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.mappings.AggregateMapping;
import org.eclipse.persistence.mappings.CollectionMapping;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.ObjectReferenceMapping;
import org.eclipse.persistence.mappings.foundation.AbstractDirectMapping;
import org.eclipse.persistence.oxm.NamespaceResolver;
import org.eclipse.persistence.oxm.XMLField;
import org.eclipse.persistence.queries.DatabaseQuery;
/**
*
* <p>An <code>EISDescriptor</code> defines the mapping from a JCA data
* structure to a Java object. There are two types of EIS descriptors:
* <ul>
* <li>Root - indicates to the EclipseLink runtime that the EIS descriptor's
* reference class is a parent class: no other class will reference it by way of
* a composite object mapping or composite collection mapping. For an EIS root
* descriptor, EIS interactions can be defined to invoke methods on an EIS
* <li>Composite - indicates to the EclipseLink runtime that the EIS descriptor's
* reference class may be referenced by a composite object mapping or composite
* collection mapping
* </ul>
*
* @see org.eclipse.persistence.eis.interactions.EISInteraction
* @see org.eclipse.persistence.eis.mappings.EISMapping
*
* @author James
* @since OracleAS TopLink 10<i>g</i> (10.0.3)
*/
public class EISDescriptor extends ClassDescriptor {
/** Define the type of data the descriptor maps to. */
protected String dataFormat;
/** Define the valid data formats that the descriptor can map to. */
public static final String MAPPED = "mapped";
public static final String INDEXED = "indexed";
public static final String XML = "xml";
/** Allow namespaces to be specified for XML type descriptors. */
protected NamespaceResolver namespaceResolver;
/**
* Default constructor.
*/
public EISDescriptor() {
super();
this.shouldOrderMappings = false;
this.dataFormat = XML;
}
@Override
protected void validateMappingType(DatabaseMapping mapping) {
if (!(mapping.isEISMapping())) {
throw DescriptorException.invalidMappingType(mapping);
}
}
/**
* PUBLIC:
* Specify the data type name for the class of objects the descriptor maps.
* This may be the XML schema complex type name, or the JCA record name for the type being mapped.
*/
public void setDataTypeName(String dataTypeName) throws DescriptorException {
this.setTableName(dataTypeName);
}
/**
* PUBLIC:
* Return the XML namespace resolver.
* XML type EIS descriptor can use a namespace resolver to support XML schema namespaces.
*/
public NamespaceResolver getNamespaceResolver() {
return namespaceResolver;
}
/**
* PUBLIC:
* The inheritance policy is used to define how a descriptor takes part in inheritance.
* All inheritance properties for both child and parent classes is configured in inheritance policy.
* Caution must be used in using this method as it lazy initializes an inheritance policy.
* Calling this on a descriptor that does not use inheritance will cause problems, #hasInheritance() must always first be called.
*/
@Override
public InheritancePolicy getInheritancePolicy() {
if (inheritancePolicy == null) {
if(isXMLFormat()) {
// Lazy initialize to conserve space in non-inherited classes.
setInheritancePolicy(new org.eclipse.persistence.internal.oxm.QNameInheritancePolicy(this));
} else {
setInheritancePolicy(new InheritancePolicy(this));
}
}
return inheritancePolicy;
}
/**
* PUBLIC:
* Set the XML namespace resolver.
* XML type EIS descriptor can use a namespace resolver to support XML schema namespaces.
*/
public void setNamespaceResolver(NamespaceResolver namespaceResolver) {
this.namespaceResolver = namespaceResolver;
}
/**
* INTERNAL:
* Avoid SDK initialization.
*/
@Override
public void setQueryManager(DescriptorQueryManager queryManager) {
this.queryManager = queryManager;
if (queryManager != null) {
queryManager.setDescriptor(this);
}
}
/**
* INTERNAL:
* Configure the object builder for the correct dataFormat.
*/
@Override
public void preInitialize(AbstractSession session) {
// Must not initialize if already done.
if (isInitialized(PREINITIALIZED)) {
return;
}
if (isXMLFormat()) {
setObjectBuilder(new XMLObjectBuilder(this));
if(this.hasInheritance()) {
((QNameInheritancePolicy)getInheritancePolicy()).setNamespaceResolver(this.namespaceResolver);
}
}
// initializeQueryManager();
super.preInitialize(session);
}
/**
* INTERNAL:
* Initialize the query manager specific to the descriptor type.
* Allow the platform to initialize the CRUD queries to defaults.
*/
@Override
public void initialize(DescriptorQueryManager queryManager, AbstractSession session) {
((DatasourcePlatform)session.getDatasourcePlatform()).initializeDefaultQueries(queryManager, session);
super.initialize(queryManager, session);
}
public boolean isXMLFormat() {
return this.dataFormat.equals(XML);
}
public boolean isMappedFormat() {
return this.dataFormat.equals(MAPPED);
}
public boolean isIndexedFormat() {
return this.dataFormat.equals(INDEXED);
}
/**
* PUBLIC:
* Return the data format that the descriptor maps to.
*/
public String getDataFormat() {
return dataFormat;
}
/**
* PUBLIC:
* Specify the data type name for the class of objects the descriptor maps.
* This may be the XML schema complex type name, or the JCA record name for the type being mapped.
*/
public String getDataTypeName() throws DescriptorException {
return this.getTableName();
}
/**
* PUBLIC:
* Configure the data format that the descriptor maps to.
*/
public void setDataFormat(String dataFormat) {
this.dataFormat = dataFormat;
}
/**
* PUBLIC:
* Configure the data format to use mapped records.
*/
public void useMappedRecordFormat() {
setDataFormat(MAPPED);
}
/**
* PUBLIC:
* Configure the data format to use indexed records.
*/
public void useIndexedRecordFormat() {
setDataFormat(INDEXED);
}
/**
* PUBLIC:
* Configure the data format to use xml records.
*/
public void useXMLRecordFormat() {
setDataFormat(XML);
}
/**
* INTERNAL:
* Build the nested row.
*/
@Override
public AbstractRecord buildNestedRowFromFieldValue(Object fieldValue) {
if (fieldValue instanceof AbstractRecord) {
return (AbstractRecord)fieldValue;
}
// BUG#2667762 if the tag was empty this could be a string of whitespace.
if (!(fieldValue instanceof List)) {
return getObjectBuilder().createRecord(0, null);
}
List<?> nestedRows = (List<?>)fieldValue;
if (nestedRows.isEmpty()) {
return getObjectBuilder().createRecord(0, null);
} else {
// BUG#2667762 if the tag was empty this could be a string of whitespace.
if (!(nestedRows.get(0) instanceof AbstractRecord)) {
return getObjectBuilder().createRecord(0, null);
}
return (AbstractRecord)nestedRows.get(0);
}
}
/**
* INTERNAL:
* Build the nested rows.
*/
@Override
public Vector buildNestedRowsFromFieldValue(Object fieldValue, AbstractSession session) {
if (!isXMLFormat()) {
if (!(fieldValue instanceof List)) {
return new Vector<>();
}
return new Vector<>((List<?>)fieldValue);
}
// BUG#2667762 if the tag was empty this could be a string of whitespace.
if (!(fieldValue instanceof Vector)) {
return new Vector<>(0);
}
return (Vector)fieldValue;
}
/**
* INTERNAL:
* Extract the direct values from the specified field value.
* Return them in a vector.
* The field value could be a vector or could be a text value if only a single value.
*/
@Override
public Vector buildDirectValuesFromFieldValue(Object fieldValue) {
if (!(fieldValue instanceof Vector)) {
Vector<Object> fieldValues = new Vector<>(1);
fieldValues.add(fieldValue);
return fieldValues;
}
return (Vector)fieldValue;
}
/**
* INTERNAL:
* Build the appropriate field value for the specified
* set of direct values.
* The database better be expecting a Vector.
*/
@Override
public Object buildFieldValueFromDirectValues(Vector directValues, String elementDataTypeName, AbstractSession session) {
return directValues;
}
/**
* INTERNAL:
* Build and return the field value from the specified nested database row.
*/
@Override
public Object buildFieldValueFromNestedRow(AbstractRecord nestedRow, AbstractSession session) throws DatabaseException {
Vector<AbstractRecord> nestedRows = new Vector<>(1);
nestedRows.add(nestedRow);
return this.buildFieldValueFromNestedRows(nestedRows, "", session);
}
/**
* INTERNAL:
* Build and return the appropriate field value for the specified
* set of nested rows.
*/
@Override
public Object buildFieldValueFromNestedRows(Vector nestedRows, String structureName, AbstractSession session) throws DatabaseException {
return nestedRows;
}
/**
* INTERNAL:
* XML type descriptors should use XMLFields.
*/
@Override
public DatabaseField buildField(String fieldName) {
if (isXMLFormat()) {
XMLField xmlField = new XMLField(fieldName);
xmlField.setNamespaceResolver(this.getNamespaceResolver());
xmlField.initialize();
return xmlField;
} else {
return super.buildField(fieldName);
}
}
/**
* INTERNAL:
* If the field is an XMLField then set the namespace resolver from the descriptor.
* This allows the resolver to only be set in the descriptor.
*/
@Override
public DatabaseField buildField(DatabaseField field) {
if (isXMLFormat()) {
if(!(field instanceof XMLField)) {
String xPath = field.getName();
// Moxy requires /text on elements.
if ((xPath.indexOf('@') == -1) && (xPath.indexOf("/text()") == -1)) {
xPath = xPath + "/text()";
}
field = new XMLField(xPath);
}
((XMLField)field).setNamespaceResolver(getNamespaceResolver());
((XMLField)field).initialize();
}
return super.buildField(field);
}
/**
* Return a new direct/basic mapping for this type of descriptor.
*/
@Override
public AbstractDirectMapping newDirectMapping() {
return new EISDirectMapping();
}
/**
* Return a new aggregate/embedded mapping for this type of descriptor.
*/
@Override
public AggregateMapping newAggregateMapping() {
return new EISCompositeObjectMapping();
}
/**
* Return a new aggregate collection/element collection mapping for this type of descriptor.
*/
@Override
public DatabaseMapping newAggregateCollectionMapping() {
return new EISCompositeCollectionMapping();
}
/**
* Return a new direct collection/element collection mapping for this type of descriptor.
*/
@Override
public DatabaseMapping newDirectCollectionMapping() {
return new EISCompositeDirectCollectionMapping();
}
/**
* Return a new one to one mapping for this type of descriptor.
*/
@Override
public ObjectReferenceMapping newOneToOneMapping() {
return new EISOneToOneMapping();
}
/**
* Return a new many to one mapping for this type of descriptor.
*/
@Override
public ObjectReferenceMapping newManyToOneMapping() {
return new EISOneToOneMapping();
}
/**
* Return a new one to many mapping for this type of descriptor.
*/
@Override
public CollectionMapping newOneToManyMapping() {
return new EISOneToManyMapping();
}
/**
* Return a new one to many mapping for this type of descriptor.
*/
@Override
public CollectionMapping newUnidirectionalOneToManyMapping() {
return new EISOneToManyMapping();
}
/**
* Return a new one to many mapping for this type of descriptor.
*/
@Override
public CollectionMapping newManyToManyMapping() {
return new EISOneToManyMapping();
}
/**
* PUBLIC: Add a direct mapping to the receiver. The new mapping specifies
* that an instance variable of the class of objects which the receiver
* describes maps in the default manner for its type to the indicated
* database field.
*
* @param attributeName
* instanceVariableName is the name of an instance variable of
* the class which the receiver describes.
* @param fieldName
* fieldName is the name of the xml element or attribute which
* corresponds with the designated instance variable.
* @return The newly created DatabaseMapping is returned.
*/
@Override
public DatabaseMapping addDirectMapping(String attributeName, String fieldName) {
EISDirectMapping mapping = new EISDirectMapping();
mapping.setAttributeName(attributeName);
if (isXMLFormat()) {
mapping.setXPath(fieldName);
} else {
mapping.setFieldName(fieldName);
}
return addMapping(mapping);
}
/**
* PUBLIC:
* Add a direct to node mapping to the receiver. The new mapping specifies that
* a variable accessed by the get and set methods of the class of objects which
* the receiver describes maps in the default manner for its type to the indicated
* database field.
*/
@Override
public DatabaseMapping addDirectMapping(String attributeName, String getMethodName, String setMethodName, String fieldName) {
EISDirectMapping mapping = new EISDirectMapping();
mapping.setAttributeName(attributeName);
mapping.setSetMethodName(setMethodName);
mapping.setGetMethodName(getMethodName);
if (isXMLFormat()) {
mapping.setXPath(fieldName);
} else {
mapping.setFieldName(fieldName);
}
return addMapping(mapping);
}
/**
* PUBLIC:
* Specify the primary key field.
* This should be called for each field that make up the primary key.
* For EIS XML Descriptors use the addPrimaryKeyField(DatabaseField) API
* and supply an org.eclipse.persistence.oxm.XMLField parameter instead of using this method
*/
@Override
public void addPrimaryKeyFieldName(String fieldName) {
if (isXMLFormat()) {
addPrimaryKeyField(new XMLField(fieldName));
} else {
super.addPrimaryKeyFieldName(fieldName);
}
}
/**
* PUBLIC:
* Set the sequence number field name.
* This is the field in the descriptors table that needs its value to be generated.
* This is normally the primary key field of the descriptor.
* For EIS XML Descriptors use the setSequenceNumberFieldName(DatabaseField) API
* and supply an org.eclipse.persistence.oxm.XMLField parameter instead of using this method
*/
@Override
public void setSequenceNumberFieldName(String fieldName) {
super.setSequenceNumberFieldName(fieldName);
}
/**
* INTERNAL:
* Override this method to throw an exception. SQL should not be generated for
* EIS Calls.
*/
@Override
public DatasourceCall buildCallFromStatement(SQLStatement statement, DatabaseQuery query, AbstractSession session) {
return ((EISPlatform)session.getDatasourcePlatform()).buildCallFromStatement(statement, query, session);
}
@Override
public void initialize(AbstractSession session) throws DescriptorException {
if (isXMLFormat()) {
for(int x = 0, primaryKeyFieldsSize = this.primaryKeyFields.size(); x<primaryKeyFieldsSize; x++) {
XMLField pkField = (XMLField) this.primaryKeyFields.get(x);
pkField.setNamespaceResolver(this.namespaceResolver);
pkField.initialize();
}
}
super.initialize(session);
}
/**
* INTERNAL: This is needed by regular aggregate descriptors * but not by
* EIS aggregate descriptors.
*/
@Override
public void initializeAggregateInheritancePolicy(AbstractSession session) {
// do nothing, since the parent descriptor was already modified during pre-initialize
}
/**
* INTERNAL:
* XML descriptors are initialized normally, since they do
* not need to be cloned by ESI aggregate mappings.
*/
@Override
public boolean requiresInitialization(AbstractSession session) {
return (!isDescriptorForInterface());
}
/**
* Aggregates use a dummy table as default.
*/
@Override
protected DatabaseTable extractDefaultTable() {
if (this.isAggregateDescriptor()) {
return new DatabaseTable();
}
return super.extractDefaultTable();
}
/**
* INTERNAL:
* Indicates if a return type is required for the field set on the
* returning policy. For EIS descriptors, this should always
* return false.
*/
@Override
public boolean isReturnTypeRequiredForReturningPolicy() {
return false;
}
/**
* PUBLIC:
* Return if the descriptor maps to an EIS or NoSQL datasource.
*/
@Override
public boolean isEISDescriptor() {
return true;
}
/**
* INTERNAL:
* Return if change sets are required for new objects.
*/
@Override
public boolean shouldUseFullChangeSetsForNewObjects() {
// This is currently set to allow EIS tests to pass the same as before.
// TODO: It should be removed, and the test issues fixed (bug was logged).
return true;
}
}