blob: b925697a490b6c08ea9cc7209ceb62335fea1f2a [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:
// 07/16/2009 Andrei Ilitchev
// - Bug 282553: JPA 2.0 JoinTable support for OneToOne and ManyToOne
// 14/05/2012-2.4 Guy Pelletier
// - 376603: Provide for table per tenant support for multitenant applications
package org.eclipse.persistence.mappings;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.TablePerMultitenantPolicy;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.expressions.ExpressionBuilder;
import org.eclipse.persistence.internal.databaseaccess.Platform;
import org.eclipse.persistence.internal.descriptors.ObjectBuilder;
import org.eclipse.persistence.internal.expressions.FieldExpression;
import org.eclipse.persistence.internal.expressions.ForUpdateClause;
import org.eclipse.persistence.internal.expressions.ForUpdateOfClause;
import org.eclipse.persistence.internal.expressions.SQLDeleteStatement;
import org.eclipse.persistence.internal.expressions.SQLInsertStatement;
import org.eclipse.persistence.internal.expressions.SQLSelectStatement;
import org.eclipse.persistence.internal.helper.ConversionManager;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.DatabaseTable;
import org.eclipse.persistence.internal.identitymaps.CacheId;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.mappings.ForeignReferenceMapping.ExtendPessimisticLockScope;
import org.eclipse.persistence.queries.Call;
import org.eclipse.persistence.queries.DataModifyQuery;
import org.eclipse.persistence.queries.DirectReadQuery;
import org.eclipse.persistence.queries.ObjectBuildingQuery;
import org.eclipse.persistence.queries.ObjectLevelReadQuery;
import org.eclipse.persistence.queries.ReadAllQuery;
import org.eclipse.persistence.queries.ReadQuery;
import org.eclipse.persistence.sessions.DatabaseRecord;
/**
* <p><b>Purpose</b>: Contains relation table functionality
* that was originally defined in ManyToManyMapping
* and now is shared with OneToOneMapping.
*/
public class RelationTableMechanism implements Cloneable, java.io.Serializable {
/** The intermediate relation table. */
protected DatabaseTable relationTable;
/** The field in the source table that corresponds to the key in the relation table */
protected Vector<DatabaseField> sourceKeyFields;
/** The field in the target table that corresponds to the key in the relation table */
protected Vector<DatabaseField> targetKeyFields;
/** The field in the intermediate table that corresponds to the key in the source table */
protected Vector<DatabaseField> sourceRelationKeyFields;
/** The field in the intermediate table that corresponds to the key in the target table */
protected Vector<DatabaseField> targetRelationKeyFields;
/** Query used for single row deletion. */
protected DataModifyQuery deleteQuery;
protected boolean hasCustomDeleteQuery;
/** Used for insertion. */
protected DataModifyQuery insertQuery;
protected boolean hasCustomInsertQuery;
protected ReadQuery lockRelationTableQuery;
public RelationTableMechanism() {
this.insertQuery = new DataModifyQuery();
this.deleteQuery = new DataModifyQuery();
this.sourceRelationKeyFields = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(1);
this.targetRelationKeyFields = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(1);
this.sourceKeyFields = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(1);
this.targetKeyFields = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(1);
this.hasCustomDeleteQuery = false;
this.hasCustomInsertQuery = false;
}
/**
* PUBLIC:
* Add the fields in the intermediate table that corresponds to the primary
* key in the source table. This method is used if the keys are composite.
*/
public void addSourceRelationKeyField(DatabaseField sourceRelationKeyField, DatabaseField sourcePrimaryKeyField) {
getSourceRelationKeyFields().addElement(sourceRelationKeyField);
getSourceKeyFields().addElement(sourcePrimaryKeyField);
}
/**
* PUBLIC:
* Add the fields in the intermediate table that corresponds to the primary
* key in the source table. This method is used if the keys are composite.
*/
public void addSourceRelationKeyFieldName(String sourceRelationKeyFieldName, String sourcePrimaryKeyFieldName) {
addSourceRelationKeyField(new DatabaseField(sourceRelationKeyFieldName), new DatabaseField(sourcePrimaryKeyFieldName));
}
/**
* PUBLIC:
* Add the fields in the intermediate table that corresponds to the primary
* key in the target table. This method is used if the keys are composite.
*/
public void addTargetRelationKeyField(DatabaseField targetRelationKeyField, DatabaseField targetPrimaryKeyField) {
getTargetRelationKeyFields().addElement(targetRelationKeyField);
getTargetKeyFields().addElement(targetPrimaryKeyField);
}
/**
* PUBLIC:
* Add the fields in the intermediate table that corresponds to the primary
* key in the target table. This method is used if the keys are composite.
*/
public void addTargetRelationKeyFieldName(String targetRelationKeyFieldName, String targetPrimaryKeyFieldName) {
addTargetRelationKeyField(new DatabaseField(targetRelationKeyFieldName), new DatabaseField(targetPrimaryKeyFieldName));
}
/**
* INTERNAL:
* Selection criteria is created to read target records from the table.
*/
Expression buildSelectionCriteria(ForeignReferenceMapping mapping, Expression criteria) {
return buildSelectionCriteriaAndAddFieldsToQueryInternal(mapping, criteria, true, false);
}
Expression buildSelectionCriteriaAndAddFieldsToQuery(ForeignReferenceMapping mapping, Expression criteria) {
return buildSelectionCriteriaAndAddFieldsToQueryInternal(mapping, criteria, true, true);
}
/**
* INTERNAL:
* Build the selection criteria to join the source, relation, and target tables.
*/
public Expression buildSelectionCriteriaAndAddFieldsToQueryInternal(ForeignReferenceMapping mapping, Expression criteria, boolean shouldAddTargetFields, boolean shouldAddFieldsToQuery) {
Expression builder = new ExpressionBuilder();
Expression linkTable = builder.getTable(this.relationTable);
if (shouldAddTargetFields) {
Iterator<DatabaseField> targetKeyIterator = getTargetKeyFields().iterator();
Iterator<DatabaseField> relationKeyIterator = getTargetRelationKeyFields().iterator();
while (targetKeyIterator.hasNext()) {
DatabaseField relationKey = relationKeyIterator.next();
DatabaseField targetKey = targetKeyIterator.next();
Expression expression = builder.getField(targetKey).equal(linkTable.getField(relationKey));
if (criteria == null) {
criteria = expression;
} else {
criteria = expression.and(criteria);
}
}
}
Iterator<DatabaseField> relationKeyIterator = getSourceRelationKeyFields().iterator();
Iterator<DatabaseField> sourceKeyIterator = getSourceKeyFields().iterator();
while (relationKeyIterator.hasNext()) {
DatabaseField relationKey = relationKeyIterator.next();
DatabaseField sourceKey = sourceKeyIterator.next();
Expression expression = linkTable.getField(relationKey).equal(builder.getParameter(sourceKey));
if (criteria == null) {
criteria = expression;
} else {
criteria = expression.and(criteria);
}
}
if (shouldAddFieldsToQuery && mapping.isCollectionMapping()) {
mapping.getContainerPolicy().addAdditionalFieldsToQuery(mapping.getSelectionQuery(), linkTable);
}
return criteria;
}
/**
* INTERNAL:
* This method is used to store the FK fields that can be cached that correspond to noncacheable mappings
* the FK field values will be used to re-issue the query when cloning the shared cache entity
*/
protected void collectQueryParameters(Set<DatabaseField> cacheFields){
for (DatabaseField field : getSourceKeyFields()) {
cacheFields.add(field);
}
}
/**
* INTERNAL:
* The mapping clones itself to create deep copy.
*/
@Override
public Object clone() {
RelationTableMechanism clone;
try {
clone = (RelationTableMechanism)super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
clone.setTargetKeyFields(cloneFields(getTargetKeyFields()));
clone.setSourceKeyFields(cloneFields(getSourceKeyFields()));
clone.setTargetRelationKeyFields(cloneFields(getTargetRelationKeyFields()));
clone.setSourceRelationKeyFields(cloneFields(getSourceRelationKeyFields()));
clone.setInsertQuery((DataModifyQuery) insertQuery.clone());
clone.setDeleteQuery((DataModifyQuery) deleteQuery.clone());
if(lockRelationTableQuery != null) {
clone.lockRelationTableQuery = (DirectReadQuery)lockRelationTableQuery.clone();
}
return clone;
}
/**
* INTERNAL:
* Helper method to clone vector of fields (used in aggregate initialization cloning).
*/
protected Vector cloneFields(Vector fields) {
Vector clonedFields = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance();
for (Enumeration fieldsEnum = fields.elements(); fieldsEnum.hasMoreElements();) {
clonedFields.addElement(((DatabaseField)fieldsEnum.nextElement()).clone());
}
return clonedFields;
}
protected DataModifyQuery getDeleteQuery() {
return deleteQuery;
}
/**
* INTERNAL:
* Returns a query that
*/
ReadQuery getLockRelationTableQueryClone(AbstractSession session, short lockMode) {
DirectReadQuery lockRelationTableQueryClone = (DirectReadQuery)lockRelationTableQuery.clone();
SQLSelectStatement statement = new SQLSelectStatement();
statement.addTable(this.relationTable);
statement.addField(this.sourceRelationKeyFields.get(0).clone());
statement.setWhereClause((Expression)lockRelationTableQuery.getSelectionCriteria().clone());
statement.setLockingClause(new ForUpdateClause(lockMode));
statement.normalize(session, null);
lockRelationTableQueryClone.setSQLStatement(statement);
lockRelationTableQueryClone.setIsExecutionClone(true);
return lockRelationTableQueryClone;
}
/**
* INTERNAL:
* Return relation table locking clause.
*/
public void setRelationTableLockingClause(ObjectLevelReadQuery targetQuery, ObjectBuildingQuery sourceQuery) {
ForUpdateOfClause lockingClause = new ForUpdateOfClause();
lockingClause.setLockMode(sourceQuery.getLockMode());
FieldExpression exp = (FieldExpression)targetQuery.getExpressionBuilder().getTable(this.relationTable).getField(this.sourceRelationKeyFields.get(0));
lockingClause.addLockedExpression(exp);
targetQuery.setLockingClause(lockingClause);
// locking clause is not compatible with DISTINCT
targetQuery.setShouldOuterJoinSubclasses(true);
}
protected DataModifyQuery getInsertQuery() {
return insertQuery;
}
/**
* INTERNAL:
* Return the relation table associated with the mapping.
*/
public DatabaseTable getRelationTable() {
return relationTable;
}
/**
* PUBLIC:
* Return the relation table name associated with the mapping.
*/
public String getRelationTableName() {
if (relationTable == null) {
return null;
}
return relationTable.getName();
}
/**
* PUBLIC:
* Return the relation table qualified name associated with the mapping.
*/
public String getRelationTableQualifiedName() {
if (relationTable == null) {
return null;
}
return relationTable.getQualifiedName();
}
/**
* PUBLIC:
* Return the source key field names associated with the mapping.
* These are in-order with the sourceRelationKeyFieldNames.
*/
public Vector getSourceKeyFieldNames() {
Vector fieldNames = new Vector(getSourceKeyFields().size());
for (Enumeration<DatabaseField> fieldsEnum = getSourceKeyFields().elements();
fieldsEnum.hasMoreElements();) {
fieldNames.addElement(fieldsEnum.nextElement().getQualifiedName());
}
return fieldNames;
}
/**
* INTERNAL:
* Return the selection criteria used to IN batch fetching.
*/
protected Expression buildBatchCriteria(ExpressionBuilder builder, ObjectLevelReadQuery query) {
Expression linkTable = builder.getTable(this.relationTable);
Expression criteria = null;
int size = this.targetRelationKeyFields.size();
for (int index = 0; index < size; index++) {
DatabaseField relationKey = this.targetRelationKeyFields.get(index);
DatabaseField targetKey = this.targetKeyFields.get(index);
criteria = builder.getField(targetKey).equal(linkTable.getField(relationKey)).and(criteria);
}
size = this.sourceRelationKeyFields.size();
if (size > 1) {
// Support composite keys using nested IN.
List<Expression> fields = new ArrayList<>(size);
for (DatabaseField sourceRelationKeyField : this.sourceRelationKeyFields) {
fields.add(linkTable.getField(sourceRelationKeyField));
}
return criteria.and(query.getSession().getPlatform().buildBatchCriteriaForComplexId(builder, fields));
} else {
return criteria.and(query.getSession().getPlatform().buildBatchCriteria(builder, linkTable.getField(this.sourceRelationKeyFields.get(0))));
}
}
/**
* INTERNAL:
* Add the addition join fields to the batch query.
*/
public void postPrepareNestedBatchQuery(ReadQuery batchQuery, ObjectLevelReadQuery query) {
ReadAllQuery mappingBatchQuery = (ReadAllQuery)batchQuery;
mappingBatchQuery.setShouldIncludeData(true);
Expression linkTable = mappingBatchQuery.getExpressionBuilder().getTable(this.relationTable);
for (DatabaseField relationField : this.sourceRelationKeyFields) {
mappingBatchQuery.getAdditionalFields().add(linkTable.getField(relationField));
}
}
/**
* INTERNAL:
* Extract the foreign key value from the source row.
*/
protected Object extractBatchKeyFromRow(AbstractRecord row, AbstractSession session) {
Object[] key;
ConversionManager conversionManager = session.getDatasourcePlatform().getConversionManager();
List<DatabaseField> sourceKeyFields = this.sourceKeyFields;
int size = sourceKeyFields.size();
key = new Object[size];
for (int index = 0; index < size; index++) {
DatabaseField field = sourceKeyFields.get(index);
Object value = row.get(field);
// Must ensure the classification gets a cache hit.
key[index] = conversionManager.convertObject(value, field.getType());
}
return new CacheId(key);
}
/**
* INTERNAL:
* Extract the source primary key value from the relation row.
* Used for batch reading, most following same order and fields as in the mapping.
*/
protected Object extractKeyFromTargetRow(AbstractRecord row, AbstractSession session) {
int size = getSourceRelationKeyFields().size();
Object[] key = new Object[size];
ConversionManager conversionManager = session.getDatasourcePlatform().getConversionManager();
for (int index = 0; index < size; index++) {
DatabaseField relationField = this.sourceRelationKeyFields.get(index);
DatabaseField sourceField = this.sourceKeyFields.get(index);
Object value = row.get(relationField);
// Must ensure the classification gets a cache hit.
value = conversionManager.convertObject(value, sourceField.getType());
key[index] = value;
}
return new CacheId(key);
}
/**
* INTERNAL:
* Return all the source key fields associated with the mapping.
*/
public Vector<DatabaseField> getSourceKeyFields() {
return sourceKeyFields;
}
/**
* PUBLIC:
* Return the source relation key field names associated with the mapping.
* These are in-order with the sourceKeyFieldNames.
*/
public Vector getSourceRelationKeyFieldNames() {
Vector fieldNames = new Vector(getSourceRelationKeyFields().size());
for (Enumeration<DatabaseField> fieldsEnum = getSourceRelationKeyFields().elements();
fieldsEnum.hasMoreElements();) {
fieldNames.addElement(fieldsEnum.nextElement().getQualifiedName());
}
return fieldNames;
}
/**
* INTERNAL:
* Return all the source relation key fields associated with the mapping.
*/
public Vector<DatabaseField> getSourceRelationKeyFields() {
return sourceRelationKeyFields;
}
/**
* PUBLIC:
* Return the target key field names associated with the mapping.
* These are in-order with the targetRelationKeyFieldNames.
*/
public Vector getTargetKeyFieldNames() {
Vector fieldNames = new Vector(getTargetKeyFields().size());
for (Enumeration<DatabaseField> fieldsEnum = getTargetKeyFields().elements();
fieldsEnum.hasMoreElements();) {
fieldNames.addElement(fieldsEnum.nextElement().getQualifiedName());
}
return fieldNames;
}
/**
* INTERNAL:
* Return the relation field for the target field.
*/
public DatabaseField getRelationFieldForTargetField(DatabaseField targetField) {
int index = this.targetKeyFields.indexOf(targetField);
if (index == -1) {
return null;
}
return this.targetRelationKeyFields.get(index);
}
/**
* INTERNAL:
* Return all the target keys associated with the mapping.
*/
public Vector<DatabaseField> getTargetKeyFields() {
return targetKeyFields;
}
/**
* PUBLIC:
* Return the target relation key field names associated with the mapping.
* These are in-order with the targetKeyFieldNames.
*/
public Vector getTargetRelationKeyFieldNames() {
Vector fieldNames = new Vector(getTargetRelationKeyFields().size());
for (Enumeration<DatabaseField> fieldsEnum = getTargetRelationKeyFields().elements();
fieldsEnum.hasMoreElements();) {
fieldNames.addElement(fieldsEnum.nextElement().getQualifiedName());
}
return fieldNames;
}
/**
* INTERNAL:
* Return all the target relation key fields associated with the mapping.
*/
public Vector<DatabaseField> getTargetRelationKeyFields() {
return targetRelationKeyFields;
}
protected boolean hasCustomDeleteQuery() {
return hasCustomDeleteQuery;
}
protected boolean hasCustomInsertQuery() {
return hasCustomInsertQuery;
}
/**
* INTERNAL:
* Indicates whether the mechanism has relation table.
*/
public boolean hasRelationTable() {
return relationTable != null && relationTable.getName().length() > 0;
}
/**
* INTERNAL:
* Initialize
*/
public void initialize(AbstractSession session, ForeignReferenceMapping mapping) throws DescriptorException {
initializeRelationTable(session, mapping);
initializeSourceRelationKeys(mapping);
initializeTargetRelationKeys(mapping);
if (isSingleSourceRelationKeySpecified()) {
initializeSourceKeysWithDefaults(mapping);
} else {
initializeSourceKeys(mapping);
}
if (isSingleTargetRelationKeySpecified()) {
initializeTargetKeysWithDefaults(session, mapping);
} else {
initializeTargetKeys(session, mapping);
}
if (getRelationTable().getName().indexOf(' ') != -1) {
//table names contains a space so needs to be quoted.
String beginQuote = session.getDatasourcePlatform().getStartDelimiter();
String endQuote = session.getDatasourcePlatform().getEndDelimiter();
//Ensure this table name hasn't already been quoted.
if (getRelationTable().getName().indexOf(beginQuote) == -1) {
getRelationTable().setName(beginQuote + getRelationTable().getName() + endQuote);
}
}
if (mapping.isCollectionMapping()) {
mapping.getContainerPolicy().initialize(session, getRelationTable());
}
initializeInsertQuery(session, mapping);
initializeDeleteQuery(session, mapping);
if (mapping.extendPessimisticLockScope != ExtendPessimisticLockScope.NONE) {
initializeExtendPessipisticLockScope(session, mapping);
}
}
/**
* INTERNAL:
* Initialize delete query. This query is used to delete a specific row from the join table in uow,
* given the objects on both sides of the relation.
*/
protected void initializeDeleteQuery(AbstractSession session, ForeignReferenceMapping mapping) {
if (!getDeleteQuery().hasSessionName()) {
getDeleteQuery().setSessionName(session.getName());
}
if (getDeleteQuery().getPartitioningPolicy() == null) {
getDeleteQuery().setPartitioningPolicy(mapping.getPartitioningPolicy());
}
getInsertQuery().setName(mapping.getAttributeName());
if (hasCustomDeleteQuery()) {
return;
}
// Build where clause expression.
Expression whereClause = null;
Expression builder = new ExpressionBuilder();
for (DatabaseField relationKey : getSourceRelationKeyFields()) {
Expression expression = builder.getField(relationKey).equal(builder.getParameter(relationKey));
whereClause = expression.and(whereClause);
}
if (mapping.isCollectionMapping()) {
for (DatabaseField relationKey : getTargetRelationKeyFields()) {
Expression expression = builder.getField(relationKey).equal(builder.getParameter(relationKey));
whereClause = expression.and(whereClause);
}
}
SQLDeleteStatement statement = new SQLDeleteStatement();
statement.setTable(getRelationTable());
statement.setWhereClause(whereClause);
getDeleteQuery().setSQLStatement(statement);
}
/**
* INTERNAL:
* Initialize extendPessimisticLockeScope and lockRelationTableQuery (if required).
*/
protected void initializeExtendPessipisticLockScope(AbstractSession session, ForeignReferenceMapping mapping) {
if(mapping.usesIndirection()) {
if(session.getPlatform().isForUpdateCompatibleWithDistinct() && session.getPlatform().supportsLockingQueriesWithMultipleTables()) {
mapping.extendPessimisticLockScope = ExtendPessimisticLockScope.SOURCE_QUERY;
} else {
mapping.extendPessimisticLockScope = ExtendPessimisticLockScope.DEDICATED_QUERY;
}
} else {
if(session.getPlatform().supportsIndividualTableLocking() && session.getPlatform().supportsLockingQueriesWithMultipleTables()) {
mapping.extendPessimisticLockScope = ExtendPessimisticLockScope.TARGET_QUERY;
} else {
mapping.extendPessimisticLockScope = ExtendPessimisticLockScope.DEDICATED_QUERY;
}
}
if(mapping.extendPessimisticLockScope == ExtendPessimisticLockScope.DEDICATED_QUERY) {
Expression startCriteria = mapping.getSelectionQuery().getSelectionCriteria();
if(startCriteria != null) {
startCriteria = (Expression)startCriteria.clone();
}
initializeLockRelationTableQuery(session, mapping, startCriteria);
}
}
/**
* INTERNAL:
* Initialize insert query. This query is used to insert the collection of objects into the
* relation table.
*/
protected void initializeInsertQuery(AbstractSession session, ForeignReferenceMapping mapping) {
if (!getInsertQuery().hasSessionName()) {
getInsertQuery().setSessionName(session.getName());
}
if (getInsertQuery().getPartitioningPolicy() == null) {
getInsertQuery().setPartitioningPolicy(mapping.getPartitioningPolicy());
}
getInsertQuery().setName(mapping.getAttributeName());
if (hasCustomInsertQuery()) {
return;
}
SQLInsertStatement statement = new SQLInsertStatement();
statement.setTable(getRelationTable());
AbstractRecord joinRow = new DatabaseRecord();
for (DatabaseField field : getTargetRelationKeyFields()) {
joinRow.put(field, null);
}
for (DatabaseField field : getSourceRelationKeyFields()) {
joinRow.put(field, null);
}
if (mapping.isCollectionMapping()) {
CollectionMapping collectionMapping = (CollectionMapping)mapping;
if (collectionMapping.getListOrderField() != null) {
joinRow.put(collectionMapping.getListOrderField(), null);
}
collectionMapping.getContainerPolicy().addFieldsForMapKey(joinRow);
}
statement.setModifyRow(joinRow);
getInsertQuery().setSQLStatement(statement);
getInsertQuery().setModifyRow(joinRow);
}
/**
* INTERNAL:
* Initialize lockRelationTableQuery.
*/
protected void initializeLockRelationTableQuery(AbstractSession session, ForeignReferenceMapping mapping, Expression startCriteria) {
lockRelationTableQuery = new DirectReadQuery();
Expression criteria = buildSelectionCriteriaAndAddFieldsToQueryInternal(mapping, startCriteria, false, false);
SQLSelectStatement statement = new SQLSelectStatement();
statement.addTable(this.relationTable);
statement.addField(this.sourceRelationKeyFields.get(0).clone());
statement.setWhereClause(criteria);
statement.normalize(session, null);
lockRelationTableQuery.setSQLStatement(statement);
lockRelationTableQuery.setSessionName(session.getName());
}
/**
* INTERNAL:
* Set the table qualifier on the relation table if required
*/
protected void initializeRelationTable(AbstractSession session, ForeignReferenceMapping mapping) throws DescriptorException {
Platform platform = session.getDatasourcePlatform();
// We need to look up the relation table name from the reference
// descriptor if we are the non owning side of a bidirectional mapping
// to a table per tenant descriptor.
if (mapping.isReadOnly() && mapping.getReferenceDescriptor().hasTablePerMultitenantPolicy()) {
setRelationTable(((TablePerMultitenantPolicy) mapping.getReferenceDescriptor().getMultitenantPolicy()).getTable(getRelationTable()));
}
if (!hasRelationTable()) {
throw DescriptorException.noRelationTable(mapping);
}
if (platform.getTableQualifier().length() > 0) {
if (getRelationTable().getTableQualifier().length() == 0) {
getRelationTable().setTableQualifier(platform.getTableQualifier());
}
}
}
/**
* INTERNAL:
* All the source key field names are converted to DatabaseField and stored.
*/
protected void initializeSourceKeys(ForeignReferenceMapping mapping) {
for (int index = 0; index < getSourceKeyFields().size(); index++) {
DatabaseField field = mapping.getDescriptor().buildField(getSourceKeyFields().get(index));
if (mapping.usesIndirection()) {
field.setKeepInRow(true);
}
getSourceKeyFields().set(index, field);
}
}
/**
* INTERNAL:
* If a user does not specify the source key then the primary keys of the source table are used.
*/
protected void initializeSourceKeysWithDefaults(DatabaseMapping mapping) {
List<DatabaseField> primaryKeyFields = mapping.getDescriptor().getPrimaryKeyFields();
for (int index = 0; index < primaryKeyFields.size(); index++) {
DatabaseField field = primaryKeyFields.get(index);
if (((ForeignReferenceMapping)mapping).usesIndirection()) {
field.setKeepInRow(true);
}
getSourceKeyFields().addElement(field);
}
}
/**
* INTERNAL:
* All the source relation key field names are converted to DatabaseField and stored.
*/
protected void initializeSourceRelationKeys(ForeignReferenceMapping mapping) throws DescriptorException {
if (getSourceRelationKeyFields().size() == 0) {
throw DescriptorException.noSourceRelationKeysSpecified(mapping);
}
for (Enumeration<DatabaseField> entry = getSourceRelationKeyFields().elements(); entry.hasMoreElements();) {
DatabaseField field = entry.nextElement();
// Update the fields table first if the mapping is from a table per tenant entity.
ClassDescriptor sourceDescriptor = mapping.getDescriptor();
if (sourceDescriptor.hasTablePerMultitenantPolicy()) {
field.setTable(((TablePerMultitenantPolicy) sourceDescriptor.getMultitenantPolicy()).getTable(field.getTable()));
}
if (field.hasTableName() && (!(field.getTableName().equals(getRelationTable().getName())))) {
throw DescriptorException.relationKeyFieldNotProperlySpecified(field, mapping);
}
field.setTable(getRelationTable());
}
}
/**
* INTERNAL:
* All the target key field names are converted to DatabaseField and stored.
*/
protected void initializeTargetKeys(AbstractSession session, ForeignReferenceMapping mapping) {
for (int index = 0; index < getTargetKeyFields().size(); index++) {
DatabaseField field = mapping.getReferenceDescriptor().buildField(getTargetKeyFields().get(index));
getTargetKeyFields().set(index, field);
}
}
/**
* INTERNAL:
* If a user does not specify the target key then the primary keys of the target table are used.
*/
protected void initializeTargetKeysWithDefaults(AbstractSession session, ForeignReferenceMapping mapping) {
List<DatabaseField> primaryKeyFields = mapping.getReferenceDescriptor().getPrimaryKeyFields();
for (int index = 0; index < primaryKeyFields.size(); index++) {
getTargetKeyFields().addElement(primaryKeyFields.get(index));
}
}
/**
* INTERNAL:
* All the target relation key field names are converted to DatabaseField and stored.
*/
protected void initializeTargetRelationKeys(ForeignReferenceMapping mapping) {
if (getTargetRelationKeyFields().size() == 0) {
throw DescriptorException.noTargetRelationKeysSpecified(mapping);
}
for (Enumeration<DatabaseField> targetEnum = getTargetRelationKeyFields().elements(); targetEnum.hasMoreElements();) {
DatabaseField field = targetEnum.nextElement();
// Update the fields table first if the mapping is from a table per tenant entity.
ClassDescriptor referenceDescriptor = mapping.getReferenceDescriptor();
if (referenceDescriptor.hasTablePerMultitenantPolicy()) {
field.setTable(((TablePerMultitenantPolicy) referenceDescriptor.getMultitenantPolicy()).getTable(field.getTable()));
}
if (field.hasTableName() && (!(field.getTableName().equals(getRelationTable().getName())))) {
throw DescriptorException.relationKeyFieldNotProperlySpecified(field, mapping);
}
field.setTable(getRelationTable());
}
}
/**
* INTERNAL:
* Checks if a single source key was specified.
*/
protected boolean isSingleSourceRelationKeySpecified() {
return getSourceKeyFields().isEmpty();
}
/**
* INTERNAL:
* Checks if a single target key was specified.
*/
protected boolean isSingleTargetRelationKeySpecified() {
return getTargetKeyFields().isEmpty();
}
/**
* INTERNAL:
* Adds to the passed expression a single relation table field joined to source field.
* Used to extend pessimistic locking clause in source query.
*/
public Expression joinRelationTableField(Expression expression, Expression baseExpression) {
return baseExpression.getField(this.sourceKeyFields.get(0)).equal(baseExpression.getTable(relationTable).getField(this.sourceRelationKeyFields.get(0))).and(expression);
}
/**
* PUBLIC:
* The default delete query for mapping can be overridden by specifying the new query.
* This query must delete the row from the M-M join table.
*/
public void setCustomDeleteQuery(DataModifyQuery query) {
setDeleteQuery(query);
setHasCustomDeleteQuery(true);
}
/**
* PUBLIC:
* The default insert query for mapping can be overridden by specifying the new query.
* This query must insert the row into the M-M join table.
*/
public void setCustomInsertQuery(DataModifyQuery query) {
setInsertQuery(query);
setHasCustomInsertQuery(true);
}
protected void setDeleteQuery(DataModifyQuery deleteQuery) {
this.deleteQuery = deleteQuery;
}
/**
* PUBLIC:
* Set the receiver's delete SQL string. This allows the user to override the SQL
* generated by TOPLink, with there own SQL or procedure call. The arguments are
* translated from the fields of the source row, through replacing the field names
* marked by '#' with the values for those fields.
* This is used to delete a single entry from the M-M join table.
* Example, 'delete from PROJ_EMP where PROJ_ID = #PROJ_ID AND EMP_ID = #EMP_ID'.
*/
public void setDeleteSQLString(String sqlString) {
DataModifyQuery query = new DataModifyQuery();
query.setSQLString(sqlString);
setCustomDeleteQuery(query);
}
/**
* PUBLIC:
* Set the receiver's delete Call. This allows the user to override the SQL
* generated by TOPLink, with there own SQL or procedure call. The arguments are
* translated from the fields of the source row.
* This is used to delete a single entry from the M-M join table.
* Example, 'new SQLCall("delete from PROJ_EMP where PROJ_ID = #PROJ_ID AND EMP_ID = #EMP_ID")'.
*/
public void setDeleteCall(Call call) {
DataModifyQuery query = new DataModifyQuery();
query.setCall(call);
setCustomDeleteQuery(query);
}
protected void setHasCustomDeleteQuery(boolean hasCustomDeleteQuery) {
this.hasCustomDeleteQuery = hasCustomDeleteQuery;
}
protected void setHasCustomInsertQuery(boolean bool) {
hasCustomInsertQuery = bool;
}
protected void setInsertQuery(DataModifyQuery insertQuery) {
this.insertQuery = insertQuery;
}
/**
* PUBLIC:
* Set the receiver's insert SQL string. This allows the user to override the SQL
* generated by TOPLink, with there own SQL or procedure call. The arguments are
* translated from the fields of the source row, through replacing the field names
* marked by '#' with the values for those fields.
* This is used to insert an entry into the M-M join table.
* Example, 'insert into PROJ_EMP (EMP_ID, PROJ_ID) values (#EMP_ID, #PROJ_ID)'.
*/
public void setInsertSQLString(String sqlString) {
DataModifyQuery query = new DataModifyQuery();
query.setSQLString(sqlString);
setCustomInsertQuery(query);
}
/**
* PUBLIC:
* Set the receiver's insert Call. This allows the user to override the SQL
* generated by TOPLink, with there own SQL or procedure call. The arguments are
* translated from the fields of the source row.
* This is used to insert an entry into the M-M join table.
* Example, 'new SQLCall("insert into PROJ_EMP (EMP_ID, PROJ_ID) values (#EMP_ID, #PROJ_ID)")'.
*/
public void setInsertCall(Call call) {
DataModifyQuery query = new DataModifyQuery();
query.setCall(call);
setCustomInsertQuery(query);
}
/**
* PUBLIC:
* Set the relational table.
* This is the join table that store both the source and target primary keys.
*/
public void setRelationTable(DatabaseTable relationTable) {
this.relationTable = relationTable;
}
/**
* PUBLIC:
* Set the name of the relational table.
* This is the join table that store both the source and target primary keys.
*/
public void setRelationTableName(String tableName) {
relationTable = new DatabaseTable(tableName);
}
/**
* PUBLIC:
* Set the name of the session to execute the mapping's queries under.
* This can be used by the session broker to override the default session
* to be used for the target class.
*/
public void setSessionName(String name) {
getInsertQuery().setSessionName(name);
getDeleteQuery().setSessionName(name);
}
/**
* PUBLIC:
* Set the source key field names associated with the mapping.
* These must be in-order with the sourceRelationKeyFieldNames.
*/
public void setSourceKeyFieldNames(Vector fieldNames) {
Vector fields = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(fieldNames.size());
for (Enumeration fieldNamesEnum = fieldNames.elements(); fieldNamesEnum.hasMoreElements();) {
fields.addElement(new DatabaseField((String)fieldNamesEnum.nextElement()));
}
setSourceKeyFields(fields);
}
/**
* INTERNAL:
* Set the source fields.
*/
public void setSourceKeyFields(Vector<DatabaseField> sourceKeyFields) {
this.sourceKeyFields = sourceKeyFields;
}
/**
* PUBLIC:
* Set the source key field in the relation table.
* This is the name of the foreign key in the relation table to the source's primary key field.
* This method is used if the source primary key is a singleton only.
*/
public void setSourceRelationKeyFieldName(String sourceRelationKeyFieldName) {
getSourceRelationKeyFields().addElement(new DatabaseField(sourceRelationKeyFieldName));
}
/**
* PUBLIC:
* Set the source relation key field names associated with the mapping.
* These must be in-order with the sourceKeyFieldNames.
*/
public void setSourceRelationKeyFieldNames(Vector fieldNames) {
Vector fields = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(fieldNames.size());
for (Enumeration fieldNamesEnum = fieldNames.elements(); fieldNamesEnum.hasMoreElements();) {
fields.addElement(new DatabaseField((String)fieldNamesEnum.nextElement()));
}
setSourceRelationKeyFields(fields);
}
/**
* INTERNAL:
* Set the source fields.
*/
public void setSourceRelationKeyFields(Vector<DatabaseField> sourceRelationKeyFields) {
this.sourceRelationKeyFields = sourceRelationKeyFields;
}
/**
* INTERNAL:
* Set the target key field names associated with the mapping.
* These must be in-order with the targetRelationKeyFieldNames.
*/
public void setTargetKeyFieldNames(Vector fieldNames) {
Vector fields = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(fieldNames.size());
for (Enumeration fieldNamesEnum = fieldNames.elements(); fieldNamesEnum.hasMoreElements();) {
fields.addElement(new DatabaseField((String)fieldNamesEnum.nextElement()));
}
setTargetKeyFields(fields);
}
/**
* INTERNAL:
* Set the target fields.
*/
public void setTargetKeyFields(Vector<DatabaseField> targetKeyFields) {
this.targetKeyFields = targetKeyFields;
}
/**
* PUBLIC:
* Set the target key field in the relation table.
* This is the name of the foreign key in the relation table to the target's primary key field.
* This method is used if the target's primary key is a singleton only.
*/
public void setTargetRelationKeyFieldName(String targetRelationKeyFieldName) {
getTargetRelationKeyFields().addElement(new DatabaseField(targetRelationKeyFieldName));
}
/**
* INTERNAL:
* Set the target relation key field names associated with the mapping.
* These must be in-order with the targetKeyFieldNames.
*/
public void setTargetRelationKeyFieldNames(Vector fieldNames) {
Vector fields = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(fieldNames.size());
for (Enumeration fieldNamesEnum = fieldNames.elements(); fieldNamesEnum.hasMoreElements();) {
fields.addElement(new DatabaseField((String)fieldNamesEnum.nextElement()));
}
setTargetRelationKeyFields(fields);
}
/**
* INTERNAL:
* Set the target fields.
*/
public void setTargetRelationKeyFields(Vector<DatabaseField> targetRelationKeyFields) {
this.targetRelationKeyFields = targetRelationKeyFields;
}
/**
* INTERNAL:
* Create a row that contains source relation fields with values extracted from the source object.
*/
public AbstractRecord buildRelationTableSourceRow(Object sourceObject, AbstractSession session, ForeignReferenceMapping mapping) {
AbstractRecord databaseRow = new DatabaseRecord();
return addRelationTableSourceRow(sourceObject, session, databaseRow, mapping);
}
/**
* INTERNAL:
* Add to a row source relation fields with values extracted from the source object.
*/
public AbstractRecord addRelationTableSourceRow(Object sourceObject, AbstractSession session, AbstractRecord databaseRow, ForeignReferenceMapping mapping) {
ObjectBuilder builder = mapping.getDescriptor().getObjectBuilder();
int size = sourceKeyFields.size();
for(int i=0; i < size; i++) {
Object sourceValue = builder.extractValueFromObjectForField(sourceObject, sourceKeyFields.get(i), session);
databaseRow.put(sourceRelationKeyFields.get(i), sourceValue);
}
return databaseRow;
}
/**
* INTERNAL:
* Create a row that contains source relation fields with values extracted from the source row.
*/
public AbstractRecord buildRelationTableSourceRow(AbstractRecord sourceRow) {
AbstractRecord databaseRow = new DatabaseRecord();
return addRelationTableSourceRow(sourceRow, databaseRow);
}
/**
* INTERNAL:
* Add to a row source relation fields with values extracted from the source row.
*/
public AbstractRecord addRelationTableSourceRow(AbstractRecord sourceRow, AbstractRecord databaseRow) {
int size = sourceKeyFields.size();
for(int i=0; i < size; i++) {
Object sourceValue = sourceRow.get(sourceKeyFields.get(i));
databaseRow.put(sourceRelationKeyFields.get(i), sourceValue);
}
return databaseRow;
}
/**
* INTERNAL:
* Add to a row target relation fields with values extracted from the target object.
*/
public AbstractRecord addRelationTableTargetRow(Object targetObject, AbstractSession session, AbstractRecord databaseRow, ForeignReferenceMapping mapping) {
ObjectBuilder builder = mapping.getReferenceDescriptor().getObjectBuilder();
int size = targetKeyFields.size();
for(int i=0; i < size; i++) {
Object sourceValue = builder.extractValueFromObjectForField(targetObject, targetKeyFields.get(i), session);
databaseRow.put(targetRelationKeyFields.get(i), sourceValue);
}
return databaseRow;
}
/**
* INTERNAL:
* Create a row that contains source relation fields with values extracted from the source object
* and target relation fields with values extracted from the target object.
*/
public AbstractRecord buildRelationTableSourceAndTargetRow(Object sourceObject, Object targetObject, AbstractSession session, ForeignReferenceMapping mapping) {
AbstractRecord databaseRow = buildRelationTableSourceRow(sourceObject, session, mapping);
databaseRow = addRelationTableTargetRow(targetObject, session, databaseRow, mapping);
return databaseRow;
}
/**
* INTERNAL:
* Create a row that contains source relation fields with values extracted from the source row
* and target relation fields with values extracted from the target object.
*/
public AbstractRecord buildRelationTableSourceAndTargetRow(AbstractRecord sourceRow, Object targetObject, AbstractSession session, ForeignReferenceMapping mapping) {
AbstractRecord databaseRow = buildRelationTableSourceRow(sourceRow);
databaseRow = addRelationTableTargetRow(targetObject, session, databaseRow, mapping);
return databaseRow;
}
}