blob: 06cdb863876e342564460453b17546dd9c523164 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020 IBM Corporation. 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
// 08/23/2010-2.2 Michael O'Brien
// - 323043: application.xml module ordering may cause weaving not to occur causing an NPE.
// warn if expected "_persistence_//_vh" method not found
// instead of throwing NPE during deploy validation.
// 11/19/2012-2.5 Guy Pelletier
// - 389090: JPA 2.1 DDL Generation Support (foreign key metadata support)
// 12/07/2012-2.5 Guy Pelletier
// - 389090: JPA 2.1 DDL Generation Support (foreign key metadata support)
// 08/12/2015-2.6 Mythily Parthasarathy
// - 474752: Address NPE for Embeddable with 1-M association
package org.eclipse.persistence.mappings;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import org.eclipse.persistence.annotations.BatchFetchType;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.partitioning.PartitioningPolicy;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.exceptions.QueryException;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.expressions.ExpressionBuilder;
import org.eclipse.persistence.history.AsOfClause;
import org.eclipse.persistence.indirection.ValueHolder;
import org.eclipse.persistence.indirection.ValueHolderInterface;
import org.eclipse.persistence.internal.descriptors.DescriptorIterator;
import org.eclipse.persistence.internal.descriptors.InstanceVariableAttributeAccessor;
import org.eclipse.persistence.internal.descriptors.MethodAttributeAccessor;
import org.eclipse.persistence.internal.expressions.ForUpdateOfClause;
import org.eclipse.persistence.internal.expressions.ObjectExpression;
import org.eclipse.persistence.internal.helper.ClassConstants;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.helper.NonSynchronizedSubVector;
import org.eclipse.persistence.internal.helper.NonSynchronizedVector;
import org.eclipse.persistence.internal.identitymaps.CacheId;
import org.eclipse.persistence.internal.identitymaps.CacheKey;
import org.eclipse.persistence.internal.indirection.BasicIndirectionPolicy;
import org.eclipse.persistence.internal.indirection.ContainerIndirectionPolicy;
import org.eclipse.persistence.internal.indirection.DatabaseValueHolder;
import org.eclipse.persistence.internal.indirection.IndirectionPolicy;
import org.eclipse.persistence.internal.indirection.NoIndirectionPolicy;
import org.eclipse.persistence.internal.indirection.WeavedObjectBasicIndirectionPolicy;
import org.eclipse.persistence.internal.queries.AttributeItem;
import org.eclipse.persistence.internal.queries.JoinedAttributeManager;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.security.PrivilegedClassForName;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.ChangeRecord;
import org.eclipse.persistence.internal.sessions.MergeManager;
import org.eclipse.persistence.internal.sessions.UnitOfWorkChangeSet;
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
import org.eclipse.persistence.internal.sessions.remote.RemoteSessionController;
import org.eclipse.persistence.internal.sessions.remote.RemoteValueHolder;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.queries.BatchFetchPolicy;
import org.eclipse.persistence.queries.Call;
import org.eclipse.persistence.queries.DatabaseQuery;
import org.eclipse.persistence.queries.FetchGroup;
import org.eclipse.persistence.queries.ObjectBuildingQuery;
import org.eclipse.persistence.queries.ObjectLevelModifyQuery;
import org.eclipse.persistence.queries.ObjectLevelReadQuery;
import org.eclipse.persistence.queries.ReadAllQuery;
import org.eclipse.persistence.queries.ReadObjectQuery;
import org.eclipse.persistence.queries.ReadQuery;
import org.eclipse.persistence.queries.ReportQuery;
import org.eclipse.persistence.sessions.DatabaseRecord;
import org.eclipse.persistence.sessions.remote.DistributedSession;
/**
* <b>Purpose</b>: Abstract class for relationship mappings
*/
public abstract class ForeignReferenceMapping extends DatabaseMapping {
/** Query parameter name used for IN batch ids. */
public static final String QUERY_BATCH_PARAMETER = "query-batch-parameter";
/** This is used only in descriptor proxy in remote session */
protected Class referenceClass;
protected String referenceClassName;
/** The session is temporarily used for initialization. Once used, it is set to null */
protected transient AbstractSession tempInitSession;
/** The descriptor of the reference class. */
protected transient ClassDescriptor referenceDescriptor;
/** This query is used to read referenced objects for this mapping. */
protected ReadQuery selectionQuery;
/** Indicates whether the referenced object is privately owned or not. */
protected boolean isPrivateOwned;
/**
* Indicates whether the referenced object should always be batch read on read all queries,
* and defines the type of batch fetch to use.
*/
protected BatchFetchType batchFetchType;
/** Implements indirection behavior */
protected IndirectionPolicy indirectionPolicy;
/** Indicates whether the selection query is TopLink generated or defined by the user. */
protected transient boolean hasCustomSelectionQuery;
/** Used to reference the other half of a bi-directional relationship. */
protected DatabaseMapping relationshipPartner;
/** Set by users, used to retrieve the backpointer for this mapping */
protected String relationshipPartnerAttributeName;
/** Cascading flags used by the EntityManager */
protected boolean cascadePersist;
protected boolean cascadeMerge;
protected boolean cascadeRefresh;
protected boolean cascadeRemove;
protected boolean cascadeDetach;
/** Flag used to determine if we need to weave the transient annotation on weaved fields.*/
protected boolean requiresTransientWeavedFields;
/** Define if the relationship should always be join fetched. */
protected int joinFetch = NONE;
/** Specify any INNER join on a join fetch. */
public static final int INNER_JOIN = 1;
/** Specify any OUTER join on a join fetch. */
public static final int OUTER_JOIN = 2;
/** Specify no join fetch, this is the default. */
public static final int NONE = 0;
/** This is a way (after cloning) to force the initialization of the selection criteria */
protected boolean forceInitializationOfSelectionCriteria;
/**
* Indicates whether and how pessimistic lock scope should be extended
*/
enum ExtendPessimisticLockScope {
// should not be extended.
NONE,
// should be extended in mapping's selectQuery.
TARGET_QUERY,
// should be extended in the source query.
SOURCE_QUERY,
// should be extended in a dedicated query (which doesn't do anything else).
DEDICATED_QUERY
}
ExtendPessimisticLockScope extendPessimisticLockScope;
/** Support delete cascading on the database relationship constraint. */
protected boolean isCascadeOnDeleteSetOnDatabase;
/** Allow the mapping's queries to be targeted at specific connection pools. */
protected PartitioningPolicy partitioningPolicy;
/** Allow the mapping's queries to be targeted at specific connection pools. */
protected String partitioningPolicyName;
/** Stores JPA metadata about whether another mapping is the owning mapping. Only populated for JPA models **/
protected String mappedBy;
protected ForeignReferenceMapping() {
this.isPrivateOwned = false;
this.hasCustomSelectionQuery = false;
this.useBasicIndirection();
this.cascadePersist = false;
this.cascadeMerge = false;
this.cascadeRefresh = false;
this.cascadeRemove = false;
this.requiresTransientWeavedFields = true;
this.forceInitializationOfSelectionCriteria = false;
this.extendPessimisticLockScope = ExtendPessimisticLockScope.NONE;
}
/**
* ADVANCED: Allows the retrieval of the owning mapping for a particular
* mapping. Note: This will only be set for JPA models
*
*/
public String getMappedBy() {
return mappedBy;
}
/**
* PUBLIC:
* Return the mapping's partitioning policy.
*/
public PartitioningPolicy getPartitioningPolicy() {
return partitioningPolicy;
}
/**
* PUBLIC:
* Set the mapping's partitioning policy.
* A PartitioningPolicy is used to partition, load-balance or replicate data across multiple difference databases
* or across a database cluster such as Oracle RAC.
* Partitioning can provide improved scalability by allowing multiple database machines to service requests.
* Setting a policy on a mapping will set the policy on all of its mappings.
*/
public void setPartitioningPolicy(PartitioningPolicy partitioningPolicy) {
this.partitioningPolicy = partitioningPolicy;
}
/**
* PUBLIC:
* Return the name of the mapping's partitioning policy.
* A PartitioningPolicy with the same name must be defined on the Project.
* A PartitioningPolicy is used to partition the data for a class across multiple difference databases
* or across a database cluster such as Oracle RAC.
* Partitioning can provide improved scalability by allowing multiple database machines to service requests.
*/
public String getPartitioningPolicyName() {
return partitioningPolicyName;
}
/**
* PUBLIC:
* Set the name of the mapping's partitioning policy.
* A PartitioningPolicy with the same name must be defined on the Project.
* A PartitioningPolicy is used to partition the data for a class across multiple difference databases
* or across a database cluster such as Oracle RAC.
* Partitioning can provide improved scalability by allowing multiple database machines to service requests.
*/
public void setPartitioningPolicyName(String partitioningPolicyName) {
this.partitioningPolicyName = partitioningPolicyName;
}
/**
* INTERNAL:
* Retrieve the value through using batch reading.
* This executes a single query to read the target for all of the objects and stores the
* result of the batch query in the original query to allow the other objects to share the results.
*/
protected Object batchedValueFromRow(AbstractRecord row, ObjectLevelReadQuery query, CacheKey parentCacheKey) {
ReadQuery batchQuery = (ReadQuery)query.getProperty(this);
if (batchQuery == null) {
if (query.hasBatchReadAttributes()) {
Map<DatabaseMapping, ReadQuery> queries = query.getBatchFetchPolicy().getMappingQueries();
if (queries != null) {
batchQuery = queries.get(this);
}
}
if (batchQuery == null) {
batchQuery = prepareNestedBatchQuery(query);
batchQuery.setIsExecutionClone(true);
} else {
batchQuery = (ReadQuery)batchQuery.clone();
batchQuery.setIsExecutionClone(true);
}
query.setProperty(this, batchQuery);
}
return this.indirectionPolicy.valueFromBatchQuery(batchQuery, row, query, parentCacheKey);
}
/**
* INTERNAL:
* Clone the attribute from the clone and assign it to the backup.
*/
@Override
public void buildBackupClone(Object clone, Object backup, UnitOfWorkImpl unitOfWork) {
Object attributeValue = getAttributeValueFromObject(clone);
Object clonedAttributeValue = this.indirectionPolicy.backupCloneAttribute(attributeValue, clone, backup, unitOfWork);
setAttributeValueInObject(backup, clonedAttributeValue);
}
/**
* INTERNAL:
* Used during building the backup shallow copy to copy the
* target object without re-registering it.
*/
@Override
public abstract Object buildBackupCloneForPartObject(Object attributeValue, Object clone, Object backup, UnitOfWorkImpl unitOfWork);
/**
* INTERNAL:
* Clone the attribute from the original and assign it to the clone.
*/
@Override
public void buildClone(Object original, CacheKey cacheKey, Object clone, Integer refreshCascade, AbstractSession cloningSession) {
Object attributeValue = null;
if (!this.isCacheable && (cacheKey != null && !cacheKey.isIsolated())){
ReadObjectQuery query = new ReadObjectQuery(descriptor.getJavaClass());
query.setSession(cloningSession);
attributeValue = valueFromRow(cacheKey.getProtectedForeignKeys(), null, query, cacheKey, cloningSession, true, null);
}else{
attributeValue = getAttributeValueFromObject(original);
}
attributeValue = this.indirectionPolicy.cloneAttribute(attributeValue, original, cacheKey, clone, refreshCascade, cloningSession, false); // building clone from an original not a row.
//GFBug#404 - fix moved to ObjectBuildingQuery.registerIndividualResult
setAttributeValueInObject(clone, attributeValue);
}
/**
* INTERNAL:
* A combination of readFromRowIntoObject and buildClone.
* <p>
* buildClone assumes the attribute value exists on the original and can
* simply be copied.
* <p>
* readFromRowIntoObject assumes that one is building an original.
* <p>
* Both of the above assumptions are false in this method, and actually
* attempts to do both at the same time.
* <p>
* Extract value from the row and set the attribute to this value in the
* working copy clone.
* In order to bypass the shared cache when in transaction a UnitOfWork must
* be able to populate working copies directly from the row.
*/
@Override
public void buildCloneFromRow(AbstractRecord databaseRow, JoinedAttributeManager joinManager, Object clone, CacheKey sharedCacheKey, ObjectBuildingQuery sourceQuery, UnitOfWorkImpl unitOfWork, AbstractSession executionSession) {
Boolean[] wasCacheUsed = new Boolean[]{Boolean.FALSE};
Object attributeValue = valueFromRow(databaseRow, joinManager, sourceQuery, sharedCacheKey, executionSession, true, wasCacheUsed);
Object clonedAttributeValue = this.indirectionPolicy.cloneAttribute(attributeValue, null, sharedCacheKey,clone, null, unitOfWork, !wasCacheUsed[0]);// building clone directly from row.
if (executionSession.isUnitOfWork() && sourceQuery.shouldRefreshIdentityMapResult()){
// check whether the attribute is fully build before calling getAttributeValueFromObject because that
// call may fully build the attribute
boolean wasAttributeValueFullyBuilt = isAttributeValueFullyBuilt(clone);
Object oldAttribute = this.getAttributeValueFromObject(clone);
setAttributeValueInObject(clone, clonedAttributeValue); // set this first to prevent infinite recursion
if (wasAttributeValueFullyBuilt && this.indirectionPolicy.objectIsInstantiatedOrChanged(oldAttribute)){
this.indirectionPolicy.instantiateObject(clone, clonedAttributeValue);
}
}else{
setAttributeValueInObject(clone, clonedAttributeValue);
}
if((joinManager != null && joinManager.isAttributeJoined(this.descriptor, this)) || (isExtendingPessimisticLockScope(sourceQuery) && extendPessimisticLockScope == ExtendPessimisticLockScope.TARGET_QUERY) || databaseRow.hasSopObject()) {
// need to instantiate to extended the lock beyond the source object table(s).
this.indirectionPolicy.instantiateObject(clone, clonedAttributeValue);
}
}
/**
* INTERNAL:
* Require for cloning, the part must be cloned.
*/
@Override
public abstract Object buildCloneForPartObject(Object attributeValue, Object original, CacheKey cacheKey, Object clone, AbstractSession cloningSession, Integer refreshCascade, boolean isExisting, boolean isFromSharedCache);
/**
* INTERNAL:
* The mapping clones itself to create deep copy.
*/
@Override
public Object clone() {
ForeignReferenceMapping clone = (ForeignReferenceMapping)super.clone();
clone.setIndirectionPolicy((IndirectionPolicy)indirectionPolicy.clone());
clone.setSelectionQuery((ReadQuery)getSelectionQuery().clone());
return clone;
}
/**
* INTERNAL:
* This method will access the target relationship and create a list of information to rebuild the relationship.
* This method is used in combination with the CachedValueHolder to store references to PK's to be loaded from
* a cache instead of a query.
*/
public abstract Object[] buildReferencesPKList(Object entity, Object attribute, AbstractSession session);
/**
* INTERNAL: Compare the attributes belonging to this mapping for the
* objects.
*/
@Override
public boolean compareObjects(Object firstObject, Object secondObject, AbstractSession session) {
if (isPrivateOwned()) {
return compareObjectsWithPrivateOwned(firstObject, secondObject, session);
} else {
return compareObjectsWithoutPrivateOwned(firstObject, secondObject, session);
}
}
/**
* Compare two objects if their parts are not private owned
*/
protected abstract boolean compareObjectsWithoutPrivateOwned(Object first, Object second, AbstractSession session);
/**
* Compare two objects if their parts are private owned
*/
protected abstract boolean compareObjectsWithPrivateOwned(Object first, Object second, AbstractSession session);
/**
* INTERNAL:
* Convert all the class-name-based settings in this mapping to actual class-based
* settings. This method is used when converting a project that has been built
* with class names to a project with classes.
*/
@Override
public void convertClassNamesToClasses(ClassLoader classLoader){
super.convertClassNamesToClasses(classLoader);
// DirectCollection mappings don't require a reference class.
if (getReferenceClassName() != null) {
Class referenceClass = null;
try{
if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
try {
referenceClass = AccessController.doPrivileged(new PrivilegedClassForName(getReferenceClassName(), true, classLoader));
} catch (PrivilegedActionException exception) {
throw ValidationException.classNotFoundWhileConvertingClassNames(getReferenceClassName(), exception.getException());
}
} else {
referenceClass = PrivilegedAccessHelper.getClassForName(getReferenceClassName(), true, classLoader);
}
} catch (ClassNotFoundException exc){
throw ValidationException.classNotFoundWhileConvertingClassNames(getReferenceClassName(), exc);
}
setReferenceClass(referenceClass);
}
if (getSelectionQuery() != null) {
getSelectionQuery().convertClassNamesToClasses(classLoader);
}
}
/**
* INTERNAL:
* Builder the unit of work value holder.
* Ignore the original object.
* @param buildDirectlyFromRow indicates that we are building the clone directly
* from a row as opposed to building the original from the row, putting it in
* the shared cache, and then cloning the original.
*/
@Override
public DatabaseValueHolder createCloneValueHolder(ValueHolderInterface attributeValue, Object original, Object clone, AbstractRecord row, AbstractSession cloningSession, boolean buildDirectlyFromRow) {
return cloningSession.createCloneQueryValueHolder(attributeValue, clone, row, this);
}
/**
* INTERNAL:
* Return true if the merge should be bypassed. This would be the case for several reasons, depending on
* the kind of merge taking place.
*/
protected boolean dontDoMerge(Object target, Object source, MergeManager mergeManager) {
if (!shouldMergeCascadeReference(mergeManager)) {
return true;
}
if (mergeManager.isForRefresh()) {
// For reverts we are more concerned about the target than the source.
if (!isAttributeValueInstantiated(target)) {
return true;
}
} else {
if (mergeManager.shouldRefreshRemoteObject() && shouldMergeCascadeParts(mergeManager) && usesIndirection()) {
return true;
} else {
if (!isAttributeValueInstantiated(source)) {
return true;
}
}
}
return false;
}
/**
* PUBLIC:
* Indicates whether the referenced object should always be batch read on read all queries.
* Batch reading will read all of the related objects in a single query when accessed from an originating read all.
* This should only be used if it is know that the related objects are always required with the source object, or indirection is not used.
*/
public void dontUseBatchReading() {
setUsesBatchReading(false);
}
/**
* PUBLIC:
* Indirection means that a ValueHolder will be put in-between the attribute and the real object.
* This allows for the reading of the target from the database to be delayed until accessed.
* This defaults to true and is strongly suggested as it give a huge performance gain.
*/
public void dontUseIndirection() {
setIndirectionPolicy(new NoIndirectionPolicy());
}
/**
* INTERNAL:
* Called if shouldExtendPessimisticLockScopeInTargetQuery() is true.
* Adds locking clause to the target query to extend pessimistic lock scope.
*/
protected void extendPessimisticLockScopeInTargetQuery(ObjectLevelReadQuery targetQuery, ObjectBuildingQuery sourceQuery) {
targetQuery.setLockMode(sourceQuery.getLockMode());
}
/**
* INTERNAL:
* Called if shouldExtendPessimisticLockScopeInSourceQuery is true.
* Adds fields to be locked to the where clause of the source query.
* Note that the sourceQuery must be ObjectLevelReadQuery so that it has ExpressionBuilder.
*
* This method must be implemented in subclasses that allow
* setting shouldExtendPessimisticLockScopeInSourceQuery to true.
*/
public void extendPessimisticLockScopeInSourceQuery(ObjectLevelReadQuery sourceQuery) {
}
/**
* INTERNAL:
* Extract the value from the batch optimized query, this should be supported by most query types.
*/
public Object extractResultFromBatchQuery(ReadQuery batchQuery, CacheKey parentCacheKey, AbstractRecord sourceRow, AbstractSession session, ObjectLevelReadQuery originalQuery) throws QueryException {
Map<Object, Object> batchedObjects;
Object result;
Object sourceKey = extractBatchKeyFromRow(sourceRow, session);
if (sourceKey == null) {
// If the foreign key was null, then just return null.
return null;
}
Object cachedObject = checkCacheForBatchKey(sourceRow, sourceKey, null, batchQuery, originalQuery, session);
if (cachedObject != null) {
// If the object is already in the cache, then just return it.
return cachedObject;
}
// Ensure the query is only executed once.
synchronized (batchQuery) {
// Check if query was already executed.
batchedObjects = batchQuery.getBatchObjects();
BatchFetchPolicy originalPolicy = originalQuery.getBatchFetchPolicy();
if (batchedObjects == null) {
batchedObjects = new Hashtable<>();
batchQuery.setBatchObjects(batchedObjects);
} else {
result = batchedObjects.get(sourceKey);
if (result == Helper.NULL_VALUE) {
return null;
// If IN may not have that batch yet, or it may have been null.
} else if ((result != null) || (!originalPolicy.isIN())) {
return result;
}
}
// In case of IN the batch including this row may not have been executed yet.
AbstractRecord translationRow = originalQuery.getTranslationRow();
if (translationRow == null) {
translationRow = new DatabaseRecord();
}
// Execute query and index resulting object sets by key.
if (originalPolicy.isIN()) {
// Need to extract all foreign key values from all parent rows for IN parameter.
List<AbstractRecord> parentRows = originalPolicy.getDataResults(this);
// Execute queries by batch if too many rows.
int rowsSize = parentRows.size();
int size = Math.min(rowsSize, originalPolicy.getSize());
if (size == 0) {
return null;
}
int startIndex = 0;
if (size != rowsSize) {
// If only fetching a page, need to make sure the row we want is in the page.
startIndex = parentRows.indexOf(sourceRow);
}
List foreignKeyValues = new ArrayList(size);
Set foreignKeys = new HashSet(size);
int index = 0;
int offset = startIndex;
for (int count = 0; count < size; count++) {
if (index >= rowsSize) {
// Processed all rows, done.
break;
} else if ((offset + index) >= rowsSize) {
// If passed the end, go back to start.
offset = index * -1;
}
AbstractRecord row = parentRows.get(offset + index);
// Handle duplicate rows in the ComplexQueryResult being replaced with null, as a
// result of duplicate filtering being true for constructing the ComplexQueryResult
if (row != null) {
Object foreignKey = extractBatchKeyFromRow(row, session);
if (foreignKey == null) {
// Ignore null foreign keys.
count--;
} else {
cachedObject = checkCacheForBatchKey(row, foreignKey, batchedObjects, batchQuery, originalQuery, session);
if (cachedObject != null) {
// Avoid fetching things a cache hit occurs for.
count--;
} else {
// Ensure the same id is not selected twice.
if (foreignKeys.contains(foreignKey)) {
count--;
} else {
Object[] key = ((CacheId)foreignKey).getPrimaryKey();
Object foreignKeyValue = key[0];
// Support composite keys using nested IN.
if (key.length > 1) {
foreignKeyValue = Arrays.asList(key);
}
foreignKeyValues.add(foreignKeyValue);
foreignKeys.add(foreignKey);
}
}
}
}
index++;
}
// Need to compute remaining rows, this is tricky because a page in the middle could have been processed.
List<AbstractRecord> remainingParentRows;
if (startIndex == 0) {
// Tail
remainingParentRows = new ArrayList<>(parentRows.subList(index, rowsSize));
} else if (startIndex == offset) {
// Head and tail.
remainingParentRows = new ArrayList<>(parentRows.subList(0, startIndex));
remainingParentRows.addAll(parentRows.subList(startIndex + index, rowsSize));
} else {
// Middle
remainingParentRows = new ArrayList<>(parentRows.subList(offset + index, startIndex));
}
originalPolicy.setDataResults(this, remainingParentRows);
translationRow = translationRow.clone();
translationRow.put(QUERY_BATCH_PARAMETER, foreignKeyValues);
// Register each id as null, in case it has no relationship.
for (Object foreignKey : foreignKeys) {
batchedObjects.put(foreignKey, Helper.NULL_VALUE);
}
} else if (batchQuery.isReadAllQuery() && ((ReadAllQuery)batchQuery).getBatchFetchPolicy().isIN()) {
throw QueryException.originalQueryMustUseBatchIN(this, originalQuery);
}
executeBatchQuery(batchQuery, parentCacheKey, batchedObjects, session, translationRow);
batchQuery.setSession(null);
}
result = batchedObjects.get(sourceKey);
if (result == Helper.NULL_VALUE) {
return null;
} else {
return result;
}
}
/**
* INTERNAL:
* Extract the batch key value from the source row.
* Used for batch reading, most following same order and fields as in the mapping.
* The method should be overridden by classes that support batch reading.
*/
protected Object extractBatchKeyFromRow(AbstractRecord targetRow, AbstractSession session) {
throw QueryException.batchReadingNotSupported(this, null);
}
/**
* 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
*/
@Override
public abstract void collectQueryParameters(Set<DatabaseField> cacheFields);
/**
* INTERNAL:
* Check if the target object is in the cache if possible based on the source row.
* If in the cache, add the object to the batch results.
* Return null if not possible or not in the cache.
*/
protected Object checkCacheForBatchKey(AbstractRecord sourceRow, Object foreignKey, Map batchObjects, ReadQuery batchQuery, ObjectLevelReadQuery originalQuery, AbstractSession session) {
return null;
}
/**
* INTERNAL:
* Prepare and execute the batch query and store the
* results for each source object in a map keyed by the
* mappings source keys of the source objects.
*/
protected void executeBatchQuery(DatabaseQuery query, CacheKey parentCacheKey, Map referenceObjectsByKey, AbstractSession session, AbstractRecord row) {
throw QueryException.batchReadingNotSupported(this, query);
}
/**
* INTERNAL:
* Clone and prepare the JoinedAttributeManager nested JoinedAttributeManager.
* This is used for nested joining as the JoinedAttributeManager passed to the joined build object.
*/
public ObjectLevelReadQuery prepareNestedJoins(JoinedAttributeManager joinManager, ObjectBuildingQuery baseQuery, AbstractSession session) {
// A nested query must be built to pass to the descriptor that looks like the real query execution would.
ObjectLevelReadQuery nestedQuery = (ObjectLevelReadQuery)((ObjectLevelReadQuery)getSelectionQuery()).deepClone();
nestedQuery.setSession(session);
nestedQuery.setShouldUseSerializedObjectPolicy(baseQuery.shouldUseSerializedObjectPolicy());
// Must cascade for nested partial/join attributes, the expressions must be filter to only the nested ones.
if (baseQuery.hasPartialAttributeExpressions()) {
nestedQuery.setPartialAttributeExpressions(extractNestedExpressions(((ObjectLevelReadQuery)baseQuery).getPartialAttributeExpressions(), nestedQuery.getExpressionBuilder()));
// bug 5501751: USING GETALLOWINGNULL() WITH ADDPARTIALATTRIBUTE() BROKEN IN 10.1.3
// The query against Employee with
// query.addPartialAttribute(builder.getAllowingNull("address"));
// in case there's no address returns null instead of Address object.
// Note that in case
// query.addPartialAttribute(builder.getAllowingNull("address").get("city"));
// in case there's no address an empty Address object (all atributes are nulls) is returned.
if(nestedQuery.getPartialAttributeExpressions().isEmpty()) {
if(hasRootExpressionThatShouldUseOuterJoin(((ObjectLevelReadQuery)baseQuery).getPartialAttributeExpressions())) {
nestedQuery.setShouldBuildNullForNullPk(true);
}
}
} else {
if(nestedQuery.getDescriptor().hasFetchGroupManager()) {
FetchGroup sourceFG = baseQuery.getExecutionFetchGroup();
if (sourceFG != null) {
FetchGroup targetFetchGroup = sourceFG.getGroup(getAttributeName());
if (targetFetchGroup != null) {
nestedQuery.setFetchGroup(targetFetchGroup);
nestedQuery.prepareFetchGroup();
}
}
}
List nestedJoins = extractNestedNonAggregateExpressions(joinManager.getJoinedAttributeExpressions(), nestedQuery.getExpressionBuilder(), false);
if (nestedJoins.size() > 0) {
// Recompute the joined indexes based on the nested join expressions.
nestedQuery.getJoinedAttributeManager().clear();
nestedQuery.getJoinedAttributeManager().setJoinedAttributeExpressions_(nestedJoins);
// the next line sets isToManyJoinQuery flag
nestedQuery.getJoinedAttributeManager().prepareJoinExpressions(session);
nestedQuery.getJoinedAttributeManager().computeJoiningMappingQueries(session);
nestedQuery.getJoinedAttributeManager().computeJoiningMappingIndexes(true, session, 0);
} else if (nestedQuery.hasJoining()) {
// Clear any mapping level joins.
nestedQuery.setJoinedAttributeManager(null);
}
// Configure nested locking clause.
if (baseQuery.isLockQuery()) {
if (((ObjectLevelReadQuery)baseQuery).getLockingClause().isForUpdateOfClause()) {
ForUpdateOfClause clause = (ForUpdateOfClause)((ObjectLevelReadQuery)baseQuery).getLockingClause().clone();
clause.setLockedExpressions(extractNestedNonAggregateExpressions(clause.getLockedExpressions(), nestedQuery.getExpressionBuilder(), true));
nestedQuery.setLockingClause(clause);
} else {
nestedQuery.setLockingClause(((ObjectLevelReadQuery)baseQuery).getLockingClause());
}
}
}
nestedQuery.setShouldMaintainCache(baseQuery.shouldMaintainCache());
nestedQuery.setShouldRefreshIdentityMapResult(baseQuery.shouldRefreshIdentityMapResult());
// Bug 385700 - Populate session & query class if not initialized by
// ObjectLevelReadQuery.computeBatchReadMappingQueries() in case batch query
// has been using inheritance and child descriptors can have different mappings.
// Code below will add nested batch IN support to joining, not currently enabled as JOIN and EXISTS batch types not supported yet.
/*if (baseQuery.isObjectLevelReadQuery() && ((ObjectLevelReadQuery)baseQuery).hasBatchReadAttributes()) {
ObjectLevelReadQuery baseObjectQuery = (ObjectLevelReadQuery) baseQuery;
for (Expression expression : baseObjectQuery.getBatchReadAttributeExpressions()) {
ObjectExpression batchReadExpression = (ObjectExpression) expression;
// Batch Read Attribute Expressions may not have initialized.
ExpressionBuilder expressionBuilder = batchReadExpression.getBuilder();
if (expressionBuilder.getQueryClass() == null) {
expressionBuilder.setQueryClass(baseQuery.getReferenceClass());
}
if (expressionBuilder.getSession() == null) {
expressionBuilder.setSession(baseQuery.getSession().getRootSession(null));
}
}
// Computed nested batch attribute expressions, and add them to batch query.
List<Expression> nestedExpressions = extractNestedExpressions(baseObjectQuery.getBatchReadAttributeExpressions(), nestedQuery.getExpressionBuilder());
nestedQuery.getBatchReadAttributeExpressions().addAll(nestedExpressions);
nestedQuery.setBatchFetchType(baseObjectQuery.getBatchFetchPolicy().getType());
nestedQuery.setBatchFetchSize(baseObjectQuery.getBatchFetchPolicy().getSize());
}*/
// For flashback: Must still propagate all properties, as the
// attributes of this joined attribute may be read later too.
if (baseQuery.isObjectLevelReadQuery() && ((ObjectLevelReadQuery)baseQuery).hasAsOfClause()) {
nestedQuery.setAsOfClause(((ObjectLevelReadQuery)baseQuery).getAsOfClause());
}
nestedQuery.setCascadePolicy(baseQuery.getCascadePolicy());
if (nestedQuery.hasJoining()) {
nestedQuery.getJoinedAttributeManager().computeJoiningMappingQueries(session);
}
nestedQuery.setSession(null);
nestedQuery.setRequiresDeferredLocks(baseQuery.requiresDeferredLocks());
return nestedQuery;
}
/**
* INTERNAL:
* Allow the mapping the do any further batch preparation.
*/
protected void postPrepareNestedBatchQuery(ReadQuery batchQuery, ObjectLevelReadQuery query) {
// Do nothing.
}
/**
* INTERNAL:
* Return the selection criteria used to IN batch fetching.
*/
protected Expression buildBatchCriteria(ExpressionBuilder builder, ObjectLevelReadQuery query) {
throw QueryException.batchReadingNotSupported(this, null);
}
/**
* INTERNAL:
* Clone and prepare the selection query as a nested batch read query.
* This is used for nested batch reading.
*/
public ReadQuery prepareNestedBatchQuery(ObjectLevelReadQuery query) {
// For CR#2646-S.M. In case of inheritance the descriptor to use may not be that
// of the source query (the base class descriptor), but that of the subclass, if the
// attribute is only of the subclass. Thus in this case use the descriptor from the mapping.
// Also: for Bug 5478648 - Do not switch the descriptor if the query's descriptor is an aggregate
ClassDescriptor descriptorToUse = query.getDescriptor();
if ((descriptorToUse != this.descriptor) && (!descriptorToUse.getMappings().contains(this)) && (!this.descriptor.isDescriptorTypeAggregate())) {
descriptorToUse = this.descriptor;
}
ExpressionBuilder builder = new ExpressionBuilder(this.referenceClass);
builder.setQueryClassAndDescriptor(this.referenceClass, getReferenceDescriptor());
ReadAllQuery batchQuery = new ReadAllQuery(this.referenceClass, builder);
batchQuery.setName(getAttributeName());
batchQuery.setDescriptor(getReferenceDescriptor());
batchQuery.setSession(query.getSession());
batchQuery.setShouldUseSerializedObjectPolicy(query.shouldUseSerializedObjectPolicy());
//bug 3965568
// we should not wrap the results as this is an internal query
batchQuery.setShouldUseWrapperPolicy(false);
if (query.shouldCascadeAllParts() || (query.shouldCascadePrivateParts() && isPrivateOwned()) || (query.shouldCascadeByMapping() && this.cascadeRefresh)) {
batchQuery.setShouldRefreshIdentityMapResult(query.shouldRefreshIdentityMapResult());
batchQuery.setCascadePolicy(query.getCascadePolicy());
batchQuery.setShouldMaintainCache(query.shouldMaintainCache());
if (query.hasAsOfClause()) {
batchQuery.setAsOfClause(query.getAsOfClause());
}
//bug 3802197 - cascade binding and prepare settings
batchQuery.setShouldBindAllParameters(query.getShouldBindAllParameters());
batchQuery.setShouldPrepare(query.shouldPrepare());
}
batchQuery.setShouldOuterJoinSubclasses(query.shouldOuterJoinSubclasses());
//CR #4365
batchQuery.setQueryId(query.getQueryId());
Expression batchSelectionCriteria = null;
// Build the batch query, either using joining, or an exist sub-select.
BatchFetchType batchType = query.getBatchFetchPolicy().getType();
if (this.batchFetchType != null) {
batchType = this.batchFetchType;
}
if (batchType == BatchFetchType.EXISTS) {
// Using a EXISTS sub-select (WHERE EXIST (<original-query> AND <mapping-join> AND <mapping-join>)
ExpressionBuilder subBuilder = new ExpressionBuilder(descriptorToUse.getJavaClass());
subBuilder.setQueryClassAndDescriptor(descriptorToUse.getJavaClass(), descriptorToUse);
ReportQuery subQuery = new ReportQuery(descriptorToUse.getJavaClass(), subBuilder);
subQuery.setDescriptor(descriptorToUse);
subQuery.setShouldRetrieveFirstPrimaryKey(true);
Expression subCriteria = subBuilder.twist(getSelectionCriteria(), builder);
if (query.getSelectionCriteria() != null) {
// For bug 2612567, any query can have batch attributes, so the
// original selection criteria can be quite complex, with multiple
// builders (i.e. for parallel selects).
// Now uses cloneUsing(newBase) instead of rebuildOn(newBase).
subCriteria = query.getSelectionCriteria().cloneUsing(subBuilder).and(subCriteria);
}
// Check for history and set asOf.
if (descriptorToUse.getHistoryPolicy() != null) {
if (query.getSession().getAsOfClause() != null) {
subBuilder.asOf(query.getSession().getAsOfClause());
} else if (batchQuery.getAsOfClause() == null) {
subBuilder.asOf(AsOfClause.NO_CLAUSE);
} else {
subBuilder.asOf(batchQuery.getAsOfClause());
}
}
subQuery.setSelectionCriteria(subCriteria);
batchSelectionCriteria = builder.exists(subQuery);
} else if (batchType == BatchFetchType.IN) {
// Using a IN with foreign key values (WHERE FK IN :QUERY_BATCH_PARAMETER)
batchSelectionCriteria = buildBatchCriteria(builder, query);
} else {
// Using a join, (WHERE <orginal-query-criteria> AND <mapping-join>)
// Join the query where clause with the mapping's,
// this will cause a join that should bring in all of the target objects.
Expression backRef = builder.getManualQueryKey(getAttributeName() + "-back-ref", descriptorToUse);
batchSelectionCriteria = backRef.twist(getSelectionCriteria(), builder);
if (query.getSelectionCriteria() != null) {
// For bug 2612567, any query can have batch attributes, so the
// original selection criteria can be quite complex, with multiple
// builders (i.e. for parallel selects).
// Now uses cloneUsing(newBase) instead of rebuildOn(newBase).
batchSelectionCriteria = batchSelectionCriteria.and(query.getSelectionCriteria().cloneUsing(backRef));
}
// Since a manual query key expression does not really get normalized,
// it must get its additional expressions added in here. Probably best
// to somehow keep all this code inside QueryKeyExpression.normalize.
if (descriptorToUse.getQueryManager().getAdditionalJoinExpression() != null) {
batchSelectionCriteria = batchSelectionCriteria.and(descriptorToUse.getQueryManager().getAdditionalJoinExpression().rebuildOn(backRef));
}
// Check for history and add history expression.
if (descriptorToUse.getHistoryPolicy() != null) {
if (query.getSession().getAsOfClause() != null) {
backRef.asOf(query.getSession().getAsOfClause());
} else if (batchQuery.getAsOfClause() == null) {
backRef.asOf(AsOfClause.NO_CLAUSE);
} else {
backRef.asOf(batchQuery.getAsOfClause());
}
batchSelectionCriteria = batchSelectionCriteria.and(descriptorToUse.getHistoryPolicy().additionalHistoryExpression(backRef, backRef));
}
}
batchQuery.setSelectionCriteria(batchSelectionCriteria);
if (query.isDistinctComputed()) {
// Only recompute if it has not already been set by the user
batchQuery.setDistinctState(query.getDistinctState());
}
// Add batch reading attributes contained in the mapping's query.
ReadQuery mappingQuery = this.selectionQuery;
if (mappingQuery.isReadAllQuery()) {
// CR#3238 clone these vectors so they will not grow with each call to the query. -TW
batchQuery.setOrderByExpressions(new ArrayList<>(((ReadAllQuery)mappingQuery).getOrderByExpressions()));
if (((ReadAllQuery)mappingQuery).hasBatchReadAttributes()) {
for (Expression expression : ((ReadAllQuery)mappingQuery).getBatchReadAttributeExpressions()) {
batchQuery.addBatchReadAttribute(expression);
}
}
}
// Bug 385700 - Populate session & query class if not initialized by
// ObjectLevelReadQuery.computeBatchReadMappingQueries() in case batch query
// has been using inheritance and child descriptors can have different mappings.
if (query.hasBatchReadAttributes()) {
for (Expression expression : query.getBatchReadAttributeExpressions()) {
ObjectExpression batchReadExpression = (ObjectExpression) expression;
// Batch Read Attribute Expressions may not have initialized.
ExpressionBuilder expressionBuilder = batchReadExpression.getBuilder();
if (expressionBuilder.getQueryClass() == null) {
expressionBuilder.setQueryClass(query.getReferenceClass());
}
if (expressionBuilder.getSession() == null) {
expressionBuilder.setSession(query.getSession().getRootSession(null));
}
}
// Computed nested batch attribute expressions, and add them to batch query.
List<Expression> nestedExpressions = extractNestedExpressions(query.getBatchReadAttributeExpressions(), batchQuery.getExpressionBuilder());
batchQuery.getBatchReadAttributeExpressions().addAll(nestedExpressions);
}
batchQuery.setBatchFetchType(batchType);
batchQuery.setBatchFetchSize(query.getBatchFetchPolicy().getSize());
// Allow subclasses to further prepare.
postPrepareNestedBatchQuery(batchQuery, query);
// Set nested fetch group.
if (batchQuery.getDescriptor().hasFetchGroupManager()) {
FetchGroup sourceFetchGroup = query.getExecutionFetchGroup();
if (sourceFetchGroup != null) {
FetchGroup targetFetchGroup = sourceFetchGroup.getGroup(getAttributeName());
if (targetFetchGroup != null) {
batchQuery.setFetchGroup(targetFetchGroup);
}
}
}
if (batchQuery.shouldPrepare()) {
batchQuery.checkPrepare(query.getSession(), query.getTranslationRow());
}
batchQuery.setSession(null);
return batchQuery;
}
/**
* INTERNAL:
* An object has been serialized from the server to the client.
* Replace the transient attributes of the remote value holders
* with client-side objects.
*/
@Override
public void fixObjectReferences(Object object, Map objectDescriptors, Map processedObjects, ObjectLevelReadQuery query, DistributedSession session) {
this.indirectionPolicy.fixObjectReferences(object, objectDescriptors, processedObjects, query, session);
}
/**
* INTERNAL:
* Return the value of an attribute which this mapping represents for an object.
*/
@Override
public Object getAttributeValueFromObject(Object object) throws DescriptorException {
Object attributeValue = super.getAttributeValueFromObject(object);
Object indirectionValue = this.indirectionPolicy.validateAttributeOfInstantiatedObject(attributeValue);
// PERF: Allow the indirection policy to initialize null attribute values,
// this allows the indirection objects to not be initialized in the constructor.
if (indirectionValue != attributeValue) {
setAttributeValueInObject(object, indirectionValue);
attributeValue = indirectionValue;
}
return attributeValue;
}
/**
* INTERNAL:
* Returns the attribute value from the reference object.
* If the attribute is using indirection the value of the value-holder is returned.
* If the value holder is not instantiated then it is instantiated.
*/
public Object getAttributeValueWithClonedValueHolders(Object object) {
Object attributeValue = getAttributeValueFromObject(object);
if (attributeValue instanceof DatabaseValueHolder){
return ((DatabaseValueHolder)attributeValue).clone();
} else if (attributeValue instanceof ValueHolder){
return ((ValueHolder)attributeValue).clone();
}
return attributeValue;
}
/**
* INTERNAL:
* Return source key fields for translation by an AggregateObjectMapping
* By default, return an empty NonSynchronizedVector
*/
public Collection getFieldsForTranslationInAggregate() {
return new NonSynchronizedVector(0);
}
/**
* INTERNAL:
* Should be overridden by subclass that allows setting
* extendPessimisticLockScope to DEDICATED_QUERY.
*/
protected ReadQuery getExtendPessimisticLockScopeDedicatedQuery(AbstractSession session, short lockMode) {
return null;
}
/**
* INTERNAL:
* Return the mapping's indirection policy.
*/
public IndirectionPolicy getIndirectionPolicy() {
return indirectionPolicy;
}
/**
* INTERNAL:
* Return whether the specified object is instantiated.
*/
@Override
public boolean isAttributeValueFromObjectInstantiated(Object object) {
return this.indirectionPolicy.objectIsInstantiated(getAttributeValueFromObject(object));
}
/**
* INTERNAL:
* Returns the join criteria stored in the mapping selection query. This criteria
* is used to read reference objects across the tables from the database.
*/
public Expression getJoinCriteria(ObjectExpression context, Expression base) {
Expression selectionCriteria = getSelectionCriteria();
return context.getBaseExpression().twist(selectionCriteria, base);
}
/**
* INTERNAL:
* return the object on the client corresponding to the specified object.
* ForeignReferenceMappings have to worry about
* maintaining object identity.
*/
@Override
public Object getObjectCorrespondingTo(Object object, DistributedSession session, Map objectDescriptors, Map processedObjects, ObjectLevelReadQuery query) {
return session.getObjectCorrespondingTo(object, objectDescriptors, processedObjects, query);
}
/**
* INTERNAL:
* Returns the attribute value from the reference object.
* If the attribute is using indirection the value of the value-holder is returned.
* If the value holder is not instantiated then it is instantiated.
*/
@Override
public Object getRealAttributeValueFromAttribute(Object attributeValue, Object object, AbstractSession session) {
return this.indirectionPolicy.getRealAttributeValueFromObject(object, attributeValue);
}
/**
* Return if this mapping is lazy.
* For relationship mappings this should normally be the same value as indirection,
* however for eager relationships this can be used with indirection to allow
* indirection locking and change tracking, but still always force instantiation.
*/
@Override
public boolean isLazy() {
if (isLazy == null) {
// False by default for mappings without indirection.
isLazy = usesIndirection();
}
return isLazy;
}
/**
* INTERNAL:
* Return whether this mapping should be traversed when we are locking.
*/
@Override
public boolean isLockableMapping(){
return !(this.usesIndirection()) && !referenceDescriptor.getCachePolicy().isIsolated();
}
/**
* INTERNAL:
* Trigger the instantiation of the attribute if lazy.
*/
@Override
public void instantiateAttribute(Object object, AbstractSession session) {
this.indirectionPolicy.instantiateObject(object, getAttributeValueFromObject(object));
}
/**
* PUBLIC:
* Returns the reference class.
*/
public Class getReferenceClass() {
return referenceClass;
}
/**
* INTERNAL:
* Returns the reference class name.
*/
public String getReferenceClassName() {
if ((referenceClassName == null) && (referenceClass != null)) {
referenceClassName = referenceClass.getName();
}
return referenceClassName;
}
/**
* INTERNAL:
* Return the referenceDescriptor. This is a descriptor which is associated with
* the reference class.
*/
@Override
public ClassDescriptor getReferenceDescriptor() {
if (referenceDescriptor == null) {
if (getTempSession() == null) {
return null;
} else {
referenceDescriptor = getTempSession().getDescriptor(getReferenceClass());
}
}
return referenceDescriptor;
}
/**
* INTERNAL:
* Return the relationshipPartner mapping for this bi-directional mapping. If the relationshipPartner is null then
* this is a uni-directional mapping.
*/
@Override
public DatabaseMapping getRelationshipPartner() {
if ((this.relationshipPartner == null) && (this.relationshipPartnerAttributeName != null)) {
setRelationshipPartner(getReferenceDescriptor().getObjectBuilder().getMappingForAttributeName(getRelationshipPartnerAttributeName()));
}
return this.relationshipPartner;
}
/**
* PUBLIC:
* Use this method retrieve the relationship partner attribute name of this bidirectional Mapping.
*/
public String getRelationshipPartnerAttributeName() {
return this.relationshipPartnerAttributeName;
}
/**
* INTERNAL:
* Returns the selection criteria stored in the mapping selection query. This criteria
* is used to read reference objects from the database. It will return null before
* initialization. To obtain the selection criteria before initialization (e.g., in a
* customizer) you can use the buildSelectionCriteria() method defined by some subclasses.
*
* @see org.eclipse.persistence.mappings.OneToOneMapping#buildSelectionCriteria()
* @see org.eclipse.persistence.mappings.OneToManyMapping#buildSelectionCriteria()
*/
public Expression getSelectionCriteria() {
return getSelectionQuery().getSelectionCriteria();
}
/**
* INTERNAL:
* Returns the read query associated with the mapping.
*/
public ReadQuery getSelectionQuery() {
return selectionQuery;
}
protected AbstractSession getTempSession() {
return tempInitSession;
}
/**
* INTERNAL:
* Extract and return the appropriate value from the
* specified remote value holder.
*/
@Override
public Object getValueFromRemoteValueHolder(RemoteValueHolder remoteValueHolder) {
return this.indirectionPolicy.getValueFromRemoteValueHolder(remoteValueHolder);
}
/**
* INTERNAL:
* Indicates whether the selection query is TopLink generated or defined by
* the user.
*/
public boolean hasCustomSelectionQuery() {
return hasCustomSelectionQuery;
}
/**
* INTERNAL:
* Indicates whether the mapping (or at least one of its nested mappings, at any nested depth)
* references an entity.
* To return true the mapping (or nested mapping) should be ForeignReferenceMapping with non-null and non-aggregate reference descriptor.
*/
@Override
public boolean hasNestedIdentityReference() {
return true;
}
/**
* INTERNAL:
* Initialize the state of mapping.
*/
@Override
public void preInitialize(AbstractSession session) throws DescriptorException {
super.preInitialize(session);
// If weaving was used the mapping must be configured to use the weaved get/set methods.
if ((this.indirectionPolicy instanceof BasicIndirectionPolicy) && ClassConstants.PersistenceWeavedLazy_Class.isAssignableFrom(getDescriptor().getJavaClass())) {
Class attributeType = getAttributeAccessor().getAttributeClass();
// Check that not already weaved or coded.
if (!(ClassConstants.ValueHolderInterface_Class.isAssignableFrom(attributeType))) {
if (!indirectionPolicy.isWeavedObjectBasicIndirectionPolicy()){
if(getAttributeAccessor().isMethodAttributeAccessor()) {
useWeavedIndirection(getGetMethodName(), getSetMethodName(), true);
} else if(getAttributeAccessor().isInstanceVariableAttributeAccessor()) {
useWeavedIndirection(Helper.getWeavedGetMethodName(getAttributeName()), Helper.getWeavedSetMethodName(getAttributeName()), false);
}
}
setGetMethodName(Helper.getWeavedValueHolderGetMethodName(getAttributeName()));
setSetMethodName(Helper.getWeavedValueHolderSetMethodName(getAttributeName()));
// Must re-initialize the attribute accessor.
super.preInitialize(session);
}
}
if (getPartitioningPolicyName() != null) {
PartitioningPolicy policy = session.getProject().getPartitioningPolicy(getPartitioningPolicyName());
if (policy == null) {
session.getIntegrityChecker().handleError(DescriptorException.missingPartitioningPolicy(getPartitioningPolicyName(), null, this));
}
setPartitioningPolicy(policy);
}
if (this.isCascadeOnDeleteSetOnDatabase && !session.getPlatform().supportsDeleteOnCascade()) {
this.isCascadeOnDeleteSetOnDatabase = false;
}
}
/**
* INTERNAL:
* Initialize the state of mapping.
*/
@Override
public void initialize(AbstractSession session) throws DescriptorException {
super.initialize(session);
//474752 : InitializeReferenceDescriptor before
//addMappingsPostCalculateChanges
initializeReferenceDescriptor(session);
if (this.isPrivateOwned && (this.descriptor != null)) {
this.descriptor.addMappingsPostCalculateChanges(this);
}
initializeSelectionQuery(session);
this.indirectionPolicy.initialize();
if ((this.referenceDescriptor != null) && this.referenceDescriptor.getCachePolicy().isIsolated()) {
this.isCacheable = false;
}
}
/**
* Initialize and set the descriptor for the referenced class in this mapping.
*/
protected void initializeReferenceDescriptor(AbstractSession session) throws DescriptorException {
if (getReferenceClass() == null) {
throw DescriptorException.referenceClassNotSpecified(this);
}
ClassDescriptor refDescriptor = session.getDescriptor(getReferenceClass());
if (refDescriptor == null) {
throw DescriptorException.descriptorIsMissing(getReferenceClass().getName(), this);
}
if (refDescriptor.isAggregateDescriptor() && (!isAggregateCollectionMapping())) {
throw DescriptorException.referenceDescriptorCannotBeAggregate(this);
}
// can not be isolated if it is null. Seems that only aggregates do not set
// the owning descriptor on the mapping.
setReferenceDescriptor(refDescriptor);
}
/**
* INTERNAL:
* The method validateAttributeOfInstantiatedObject(Object attributeValue) fixes the value of the attributeValue
* in cases where it is null and indirection requires that it contain some specific data structure. Return whether this will happen.
* This method is used to help determine if indirection has been triggered
* @see org.eclipse.persistence.internal.indirection.IndirectionPolicy#validateAttributeOfInstantiatedObject(Object)
*/
public boolean isAttributeValueFullyBuilt(Object object){
Object attributeValue = super.getAttributeValueFromObject(object);
return this.indirectionPolicy.isAttributeValueFullyBuilt(attributeValue);
}
/**
* A subclass should implement this method if it wants non default behavior.
*/
protected void initializeSelectionQuery(AbstractSession session) throws DescriptorException {
if (getSelectionQuery().getReferenceClass() == null) {
throw DescriptorException.referenceClassNotSpecified(this);
}
getSelectionQuery().setName(getAttributeName());
getSelectionQuery().setDescriptor(getReferenceDescriptor());
getSelectionQuery().setSourceMapping(this);
if (getSelectionQuery().getPartitioningPolicy() == null) {
getSelectionQuery().setPartitioningPolicy(getPartitioningPolicy());
}
}
/**
* INTERNAL:
* The referenced object is checked if it is instantiated or not
*/
public boolean isAttributeValueInstantiated(Object object) {
return this.indirectionPolicy.objectIsInstantiated(getAttributeValueFromObject(object));
}
/**
* PUBLIC:
* Check cascading value for the detach operation.
*/
public boolean isCascadeDetach() {
return this.cascadeDetach;
}
/**
* PUBLIC:
* Check cascading value for the CREATE operation.
*/
public boolean isCascadePersist() {
return this.cascadePersist;
}
/**
* PUBLIC:
* Check cascading value for the MERGE operation.
*/
public boolean isCascadeMerge() {
return this.cascadeMerge;
}
/**
* PUBLIC:
* Check cascading value for the REFRESH operation.
*/
public boolean isCascadeRefresh() {
return this.cascadeRefresh;
}
/**
* PUBLIC:
* Check cascading value for the REMOVE operation.
*/
public boolean isCascadeRemove() {
return this.cascadeRemove;
}
/**
* INTERNAL:
* Return if the mapping has any ownership or other dependency over its target object(s).
*/
@Override
public boolean hasDependency() {
return isPrivateOwned() || isCascadeRemove();
}
/**
* INTERNAL:
*/
@Override
public boolean isForeignReferenceMapping() {
return true;
}
/**
* INTERNAL:
* Return if this mapping supports joining.
*/
@Override
public boolean isJoiningSupported() {
return false;
}
/**
* PUBLIC:
* Return true if referenced objects are privately owned else false.
*/
@Override
public boolean isPrivateOwned() {
return isPrivateOwned;
}
/**
* INTERNAL:
* Iterate on the iterator's current object's attribute defined by this mapping.
* The iterator's settings for cascading and value holders determine how the
* iteration continues from here.
*/
@Override
public void iterate(DescriptorIterator iterator) {
Object attributeValue = this.getAttributeValueFromObject(iterator.getVisitedParent());
this.indirectionPolicy.iterateOnAttributeValue(iterator, attributeValue);
}
/**
* INTERNAL:
* Iterate on the attribute value.
* The value holder has already been processed.
*/
@Override
public abstract void iterateOnRealAttributeValue(DescriptorIterator iterator, Object realAttributeValue);
/**
* Force instantiation of the load group.
*/
@Override
public void load(final Object object, AttributeItem item, final AbstractSession session, final boolean fromFetchGroup) {
instantiateAttribute(object, session);
if (item.getGroup() != null && (!fromFetchGroup || session.isUnitOfWork())) {
// if fromFetchGroup then the above instantiate already loaded the elements unless this is in UOW
// in which case the clones must be loaded as well.
Object value = getRealAttributeValueFromObject(object, session);
session.load(value, item.getGroup(), getReferenceDescriptor(), fromFetchGroup);
}
}
/**
* INTERNAL:
* Replace the client value holder with the server value holder,
* after copying some of the settings from the client value holder.
*/
public void mergeRemoteValueHolder(Object clientSideDomainObject, Object serverSideDomainObject, MergeManager mergeManager) {
this.indirectionPolicy.mergeRemoteValueHolder(clientSideDomainObject, serverSideDomainObject, mergeManager);
}
/**
* PUBLIC:
* Sets the reference object to be a private owned.
* The default behavior is non private owned, or independent.
* @see #setIsPrivateOwned(boolean)
*/
public void privateOwnedRelationship() {
setIsPrivateOwned(true);
}
/**
* INTERNAL:
* Extract value from the row and set the attribute to this value in the object.
* return value as this value will have been converted to the appropriate type for
* the object.
*/
@Override
public Object readFromRowIntoObject(AbstractRecord databaseRow, JoinedAttributeManager joinManager, Object targetObject, CacheKey parentCacheKey, ObjectBuildingQuery sourceQuery, AbstractSession executionSession, boolean isTargetProtected) throws DatabaseException {
Boolean[] wasCacheUsed = new Boolean[]{Boolean.FALSE};
Object attributeValue = valueFromRow(databaseRow, joinManager, sourceQuery, parentCacheKey, executionSession, isTargetProtected, wasCacheUsed);
if (wasCacheUsed[0]){
//must clone here as certain mappings require the clone object to clone the attribute.
Integer refreshCascade = null;
if (sourceQuery != null && sourceQuery.isObjectBuildingQuery() && sourceQuery.shouldRefreshIdentityMapResult()) {
refreshCascade = sourceQuery.getCascadePolicy();
}
attributeValue = this.indirectionPolicy.cloneAttribute(attributeValue, parentCacheKey.getObject(), parentCacheKey, targetObject, refreshCascade, executionSession, false);
}
if (executionSession.isUnitOfWork() && sourceQuery.shouldRefreshIdentityMapResult() || databaseRow.hasSopObject()){
// check whether the attribute is fully build before calling getAttributeValueFromObject because that
// call may fully build the attribute
boolean wasAttributeValueFullyBuilt = isAttributeValueFullyBuilt(targetObject);
Object oldAttribute = this.getAttributeValueFromObject(targetObject);
setAttributeValueInObject(targetObject, attributeValue); // set this first to prevent infinite recursion
if (wasAttributeValueFullyBuilt && this.indirectionPolicy.objectIsInstantiatedOrChanged(oldAttribute)){
this.indirectionPolicy.instantiateObject(targetObject, attributeValue);
}
}else{
setAttributeValueInObject(targetObject, attributeValue);
}
if (parentCacheKey != null){
this.indirectionPolicy.setSourceObject(parentCacheKey.getObject(), attributeValue);
}
return attributeValue;
}
/**
* INTERNAL:
* Once descriptors are serialized to the remote session. All its mappings and reference descriptors are traversed. Usually
* mappings are initialized and serialized reference descriptors are replaced with local descriptors if they already exist on the
* remote session.
*/
@Override
public void remoteInitialization(DistributedSession session) {
super.remoteInitialization(session);
setTempSession(session);
}
/**
* INTERNAL:
* replace the value holders in the specified reference object(s)
*/
@Override
public Map replaceValueHoldersIn(Object object, RemoteSessionController controller) {
return controller.replaceValueHoldersIn(object);
}
/**
* Returns true if this mappings associated weaved field requires a
* transient setting to avoid metadata processing.
*/
public boolean requiresTransientWeavedFields() {
return requiresTransientWeavedFields;
}
/**
* PUBLIC:
* Sets the cascading for all JPA operations.
*/
public void setCascadeAll(boolean value) {
setCascadePersist(value);
setCascadeMerge(value);
setCascadeRefresh(value);
setCascadeRemove(value);
setCascadeDetach(value);
}
/**
* PUBLIC:
* Sets the cascading for the JPA detach operation.
*/
public void setCascadeDetach(boolean value) {
this.cascadeDetach = value;
}
/**
* PUBLIC:
* Sets the cascading for the JPA CREATE operation.
*/
public void setCascadePersist(boolean value) {
this.cascadePersist = value;
}
/**
* PUBLIC:
* Sets the cascading for the JPA MERGE operation.
*/
public void setCascadeMerge(boolean value) {
this.cascadeMerge = value;
}
/**
* PUBLIC:
* Sets the cascading for the JPA REFRESH operation.
*/
public void setCascadeRefresh(boolean value) {
this.cascadeRefresh = value;
}
/**
* PUBLIC:
* Sets the cascading for the JPA REMOVE operation.
*/
public void setCascadeRemove(boolean value) {
this.cascadeRemove = value;
}
/**
* PUBLIC:
* Relationship mappings creates a read query to read reference objects. If this default
* query needs to be customize then user can specify its own read query to do the reading
* of reference objects. One must instance of ReadQuery or subclasses of the ReadQuery.
*/
public void setCustomSelectionQuery(ReadQuery query) {
setSelectionQuery(query);
setHasCustomSelectionQuery(true);
}
protected void setHasCustomSelectionQuery(boolean bool) {
hasCustomSelectionQuery = bool;
}
/**
* INTERNAL:
* A way of forcing the selection criteria to be rebuilt.
*/
public void setForceInitializationOfSelectionCriteria(boolean bool) {
forceInitializationOfSelectionCriteria = bool;
}
/**
* ADVANCED:
* Set the indirection policy.
*/
public void setIndirectionPolicy(IndirectionPolicy indirectionPolicy) {
this.indirectionPolicy = indirectionPolicy;
indirectionPolicy.setMapping(this);
}
/**
* PUBLIC:
* Set if the relationship is privately owned.
* A privately owned relationship means the target object is a dependent part of the source
* object and is not referenced by any other object and cannot exist on its own.
* Private ownership causes many operations to be cascaded across the relationship,
* including, deletion, insertion, refreshing, locking (when cascaded).
* It also ensures that private objects removed from collections are deleted and object added are inserted.
*/
public void setIsPrivateOwned(boolean isPrivateOwned) {
if (this.descriptor != null && ! this.isMapKeyMapping()){ // initialized
if (isPrivateOwned && !this.isPrivateOwned){
this.descriptor.addMappingsPostCalculateChanges(this);
if (getDescriptor().hasInheritance()){
for (ClassDescriptor descriptor: getDescriptor().getInheritancePolicy().getAllChildDescriptors()) {
descriptor.addMappingsPostCalculateChanges(this);
}
}
}else if (!isPrivateOwned && this.isPrivateOwned){
this.descriptor.getMappingsPostCalculateChanges().remove(this);
if (getDescriptor().hasInheritance()){
for (ClassDescriptor descriptor: getDescriptor().getInheritancePolicy().getAllChildDescriptors()) {
descriptor.getMappingsPostCalculateChanges().remove(this);
}
}
}
}
this.isPrivateOwned = isPrivateOwned;
}
/**
* INTERNAL:
* Set the value of the attribute mapped by this mapping,
* placing it inside a value holder if necessary.
* If the value holder is not instantiated then it is instantiated.
*/
@Override
public void setRealAttributeValueInObject(Object object, Object value) throws DescriptorException {
this.indirectionPolicy.setRealAttributeValueInObject(object, value);
}
/**
* PUBLIC:
* Set the referenced class.
*/
public void setReferenceClass(Class referenceClass) {
this.referenceClass = referenceClass;
if (referenceClass != null) {
setReferenceClassName(referenceClass.getName());
// Make sure the reference class of the selectionQuery is set.
setSelectionQuery(getSelectionQuery());
}
}
/**
* INTERNAL:
* Used by MW.
*/
public void setReferenceClassName(String referenceClassName) {
this.referenceClassName = referenceClassName;
}
/**
* Set the referenceDescriptor. This is a descriptor which is associated with
* the reference class.
*/
protected void setReferenceDescriptor(ClassDescriptor aDescriptor) {
referenceDescriptor = aDescriptor;
}
/**
* INTERNAL:
* Sets the relationshipPartner mapping for this bi-directional mapping. If the relationshipPartner is null then
* this is a uni-directional mapping.
*/
public void setRelationshipPartner(DatabaseMapping mapping) {
this.relationshipPartner = mapping;
}
/**
* PUBLIC:
* Use this method to specify the relationship partner attribute name of a bidirectional Mapping.
* TopLink will use the attribute name to find the back pointer mapping to maintain referential integrity of
* the bi-directional mappings.
*/
public void setRelationshipPartnerAttributeName(String attributeName) {
this.relationshipPartnerAttributeName = attributeName;
}
/**
* Set this flag if this mappings associated weaved field requires a
* transient setting to avoid metadata processing.
*/
public void setRequiresTransientWeavedFields(boolean requiresTransientWeavedFields) {
this.requiresTransientWeavedFields = requiresTransientWeavedFields;
}
/**
* PUBLIC:
* Sets the selection criteria to be used as a where clause to read
* reference objects. This criteria is automatically generated by the
* TopLink if not explicitly specified by the user.
*/
public void setSelectionCriteria(Expression anExpression) {
getSelectionQuery().setSelectionCriteria(anExpression);
}
/**
* Sets the query
*/
protected void setSelectionQuery(ReadQuery aQuery) {
selectionQuery = aQuery;
// Make sure the reference class of the selectionQuery is set.
if ((selectionQuery != null) && selectionQuery.isObjectLevelReadQuery() && (selectionQuery.getReferenceClassName() == null)) {
((ObjectLevelReadQuery)selectionQuery).setReferenceClass(getReferenceClass());
}
}
/**
* PUBLIC:
* This is a property on the mapping which will allow custom SQL to be
* substituted for reading a reference object.
*/
public void setSelectionSQLString(String sqlString) {
getSelectionQuery().setSQLString(sqlString);
setCustomSelectionQuery(getSelectionQuery());
}
/**
* PUBLIC:
* This is a property on the mapping which will allow custom call to be
* substituted for reading a reference object.
*/
public void setSelectionCall(Call call) {
getSelectionQuery().setCall(call);
setCustomSelectionQuery(getSelectionQuery());
}
/**
* ADVANCED:
* Indicates whether pessimistic lock of ObjectLevelReadQuery with isPessimisticLockScopeExtended set to true
* should be applied through this mapping beyond the tables mapped to the source object.
*/
public void setShouldExtendPessimisticLockScope(boolean shouldExtend) {
extendPessimisticLockScope = shouldExtend ? ExtendPessimisticLockScope.TARGET_QUERY : ExtendPessimisticLockScope.NONE;
}
protected void setTempSession(AbstractSession session) {
this.tempInitSession = session;
}
/**
* PUBLIC:
* Indicates whether the referenced object should always be batch read on read all queries.
* Batch reading will read all of the related objects in a single query when accessed from an originating read all.
* This should only be used if it is know that the related objects are always required with the source object, or indirection is not used.
* @see #setBatchFetchType(BatchFetchType)
*/
public void setUsesBatchReading(boolean usesBatchReading) {
if (usesBatchReading) {
setBatchFetchType(BatchFetchType.JOIN);
} else {
setBatchFetchType(null);
}
}
/**
* PUBLIC:
* Indirection means that a ValueHolder will be put in-between the attribute and the real object.
* This allows for the reading of the target from the database to be delayed until accessed.
* This defaults to true and is strongly suggested as it give a huge performance gain.
* @see #useBasicIndirection()
* @see #dontUseIndirection()
*/
public void setUsesIndirection(boolean usesIndirection) {
if (usesIndirection) {
useBasicIndirection();
} else {
dontUseIndirection();
}
}
/**
* INTERNAL:
* Indicates whether pessimistic lock of ObjectLevelReadQuery with isPessimisticLockScopeExtended set to true
* should be applied through this mapping beyond the tables mapped to the source object.
*/
public boolean shouldExtendPessimisticLockScope() {
return extendPessimisticLockScope != ExtendPessimisticLockScope.NONE;
}
public boolean shouldExtendPessimisticLockScopeInSourceQuery() {
return extendPessimisticLockScope == ExtendPessimisticLockScope.SOURCE_QUERY;
}
public boolean shouldExtendPessimisticLockScopeInTargetQuery() {
return extendPessimisticLockScope == ExtendPessimisticLockScope.TARGET_QUERY;
}
public boolean shouldExtendPessimisticLockScopeInDedicatedQuery() {
return extendPessimisticLockScope == ExtendPessimisticLockScope.DEDICATED_QUERY;
}
/**
* INTERNAL:
*/
protected boolean shouldForceInitializationOfSelectionCriteria() {
return forceInitializationOfSelectionCriteria;
}
protected boolean shouldInitializeSelectionCriteria() {
if (shouldForceInitializationOfSelectionCriteria()) {
return true;
}
if (hasCustomSelectionQuery()) {
return false;
}
if (getSelectionCriteria() == null) {
return true;
}
return false;
}
/**
* INTERNAL:
* Returns true if the merge should cascade to the mappings reference's parts.
*/
public boolean shouldMergeCascadeParts(MergeManager mergeManager) {
return (mergeManager.shouldCascadeByMapping() && ((this.isCascadeMerge() && !mergeManager.isForRefresh()) || (this.isCascadeRefresh() && mergeManager.isForRefresh()) )) || mergeManager.shouldCascadeAllParts() || (mergeManager.shouldCascadePrivateParts() && isPrivateOwned());
}
/**
* INTERNAL:
* Returns true if the merge should cascade to the mappings reference's parts.
*/
public boolean shouldRefreshCascadeParts(MergeManager mergeManager) {
return (mergeManager.shouldCascadeByMapping() && this.isCascadeRefresh()) || mergeManager.shouldCascadeAllParts() || (mergeManager.shouldCascadePrivateParts() && isPrivateOwned());
}
/**
* Returns true if the merge should cascade to the mappings reference.
*/
protected boolean shouldMergeCascadeReference(MergeManager mergeManager) {
if (mergeManager.shouldCascadeReferences()) {
return true;
}
// P2.0.1.3: Was merging references on non-privately owned parts
// Same logic in:
return shouldMergeCascadeParts(mergeManager);
}
/**
* Returns true if any process leading to object modification should also affect its parts
* Usually used by write, insert, update and delete.
*/
protected boolean shouldObjectModifyCascadeToParts(ObjectLevelModifyQuery query) {
if (this.isReadOnly) {
return false;
}
// Only cascade dependents writes in uow.
if (query.shouldCascadeOnlyDependentParts()) {
return hasConstraintDependency();
}
if (this.isPrivateOwned) {
return true;
}
return query.shouldCascadeAllParts();
}
/**
* PUBLIC:
* Indicates whether the referenced object should always be batch read on read all queries.
* Batch reading will read all of the related objects in a single query when accessed from an originating read all.
* This should only be used if it is know that the related objects are always required with the source object, or indirection is not used.
*/
public boolean shouldUseBatchReading() {
return this.batchFetchType != null;
}
/**
* PUBLIC:
* Indirection means that a ValueHolder will be put in-between the attribute and the real object.
* This allows for the reading of the target from the database to be delayed until accessed.
* This defaults to true and is strongly suggested as it give a huge performance gain.
*/
public void useBasicIndirection() {
setIndirectionPolicy(new BasicIndirectionPolicy());
}
/**
* PUBLIC:
* Indicates whether the referenced object should always be batch read on read all queries.
* Batch reading will read all of the related objects in a single query when accessed from an originating read all.
* This should only be used if it is know that the related objects are always required with the source object, or indirection is not used.
*/
public void useBatchReading() {
setBatchFetchType(BatchFetchType.JOIN);
}
/**
* INTERNAL:
* Configures the mapping to used weaved indirection.
* This requires that the toplink-agent be used to weave indirection into the class.
* This policy is only require for method access.
* @param getMethodName is the name of the original (or weaved in field access case) set method for the mapping.
* @param setMethodName is the name of the original (or weaved in field access case) set method for the mapping.
* @param hasUsedMethodAccess indicates whether method or field access was originally used.
*/
public void useWeavedIndirection(String getMethodName, String setMethodName, boolean hasUsedMethodAccess){
setIndirectionPolicy(new WeavedObjectBasicIndirectionPolicy(getMethodName, setMethodName, null, hasUsedMethodAccess));
}
/**
* PUBLIC:
* Indirection means that a IndirectContainer (wrapping a ValueHolder) will be put in-between the attribute and the real object.
* This allows for an application specific class to be used which wraps the value holder.
* The purpose of this is that the domain objects will not require to import the ValueHolderInterface class.
* Refer also to transparent indirection for a transparent solution to indirection.
*/
public void useContainerIndirection(Class containerClass) {
ContainerIndirectionPolicy policy = new ContainerIndirectionPolicy();
policy.setContainerClass(containerClass);
setIndirectionPolicy(policy);
}
/**
* PUBLIC:
* Indirection means that some sort of indirection object will be put in-between the attribute and the real object.
* This allows for the reading of the target from the database to be delayed until accessed.
* This defaults to true and is strongly suggested as it give a huge performance gain.
*/
public boolean usesIndirection() {
return this.indirectionPolicy.usesIndirection();
}
/**
* INTERNAL:
* Update a ChangeRecord to replace the ChangeSet for the old entity with the changeSet for the new Entity. This is
* used when an Entity is merged into itself and the Entity reference new or detached entities.
*/
public abstract void updateChangeRecordForSelfMerge(ChangeRecord changeRecord, Object source, Object target, UnitOfWorkChangeSet parentUOWChangeSet, UnitOfWorkImpl unitOfWork);
/**
* PUBLIC:
* Indicates whether the referenced object(s) should always be joined on read queries.
* Joining will join the two classes tables to read all of the data in a single query.
* This should only be used if it is know that the related objects are always required with the source object,
* or indirection is not used.
* A join-fetch can either use an INNER_JOIN or OUTER_JOIN,
* if the relationship may reference null or an empty collection an outer join should be used to avoid filtering the source objects from the queries.
* Join fetch can also be specified on the query, and it is normally more efficient to do so as some queries may not require the related objects.
* Typically batch reading is more efficient than join fetching and should be considered, especially for collection relationships.
* @see org.eclipse.persistence.queries.ObjectLevelReadQuery#addJoinedAttribute(String)
* @see org.eclipse.persistence.queries.ReadAllQuery#addBatchReadAttribute(String)
*/
public void setJoinFetch(int joinFetch) {
this.joinFetch = joinFetch;
}
/**
* PUBLIC:
* Return if this relationship should always be join fetched.
*/
public int getJoinFetch() {
return joinFetch;
}
/**
* INTERNAL: Called by JPA metadata processing to store the owning mapping
* for this mapping
*
*/
public void setMappedBy(String mappedBy) {
this.mappedBy = mappedBy;
}
/**
* PUBLIC:
* Return if this relationship should always be join fetched.
*/
public boolean isJoinFetched() {
return getJoinFetch() != NONE;
}
/**
* PUBLIC:
* Return if this relationship should always be INNER join fetched.
*/
public boolean isInnerJoinFetched() {
return getJoinFetch() == INNER_JOIN;
}
/**
* PUBLIC:
* Return if this relationship should always be OUTER join fetched.
*/
public boolean isOuterJoinFetched() {
return getJoinFetch() == OUTER_JOIN;
}
/**
* PUBLIC:
* Specify this relationship to always be join fetched using an INNER join.
*/
public void useInnerJoinFetch() {
setJoinFetch(INNER_JOIN);
}
/**
* PUBLIC:
* Specify this relationship to always be join fetched using an OUTER join.
*/
public void useOuterJoinFetch() {
setJoinFetch(OUTER_JOIN);
}
/**
* ADVANCED:
* Return if delete cascading has been set on the database for the
* mapping's foreign key constraint.
*/
public boolean isCascadeOnDeleteSetOnDatabase() {
return isCascadeOnDeleteSetOnDatabase;
}
/**
* ADVANCED:
* Set if delete cascading has been set on the database for the
* mapping's foreign key constraint.
* The behavior is dependent on the mapping.
* <p>OneToOne (target foreign key) - deletes target object (private owned)
* <p>OneToMany, AggregateCollection - deletes target objects (private owned)
* <p>ManyToMany - deletes from join table (only)
* <p>DirectCollection - delete from direct table
*/
public void setIsCascadeOnDeleteSetOnDatabase(boolean isCascadeOnDeleteSetOnDatabase) {
this.isCascadeOnDeleteSetOnDatabase = isCascadeOnDeleteSetOnDatabase;
}
/**
* Used to signal that this mapping references a protected/isolated entity and requires
* special merge/object building behaviour.
*/
@Override
public void setIsCacheable(boolean cacheable) {
this.isCacheable = cacheable;
}
/**
* INTERNAL:
* To validate mappings declaration
*/
@Override
public void validateBeforeInitialization(AbstractSession session) throws DescriptorException {
super.validateBeforeInitialization(session);
// If a lazy mapping required weaving for lazy, and weaving did not occur,
// then the mapping must be reverted to no use indirection.
if ((this.indirectionPolicy instanceof WeavedObjectBasicIndirectionPolicy) && !ClassConstants.PersistenceWeavedLazy_Class.isAssignableFrom(getDescriptor().getJavaClass())) {
Object[] args = new Object[2];
args[0] = getAttributeName();
args[1] = getDescriptor().getJavaClass();
session.log(SessionLog.WARNING, SessionLog.METADATA, "metadata_warning_ignore_lazy", args);
setIndirectionPolicy(new NoIndirectionPolicy());
}
if (getAttributeAccessor() instanceof InstanceVariableAttributeAccessor) {
Class attributeType = ((InstanceVariableAttributeAccessor)getAttributeAccessor()).getAttributeType();
this.indirectionPolicy.validateDeclaredAttributeType(attributeType, session.getIntegrityChecker());
} else if (getAttributeAccessor().isMethodAttributeAccessor()) {
// 323148
Class returnType = ((MethodAttributeAccessor)getAttributeAccessor()).getGetMethodReturnType();
this.indirectionPolicy.validateGetMethodReturnType(returnType, session.getIntegrityChecker());
Class parameterType = ((MethodAttributeAccessor)getAttributeAccessor()).getSetMethodParameterType();
this.indirectionPolicy.validateSetMethodParameterType(parameterType, session.getIntegrityChecker());
}
}
/**
* This method is used to load a relationship from a list of PKs. This list
* may be available if the relationship has been cached.
*/
public abstract Object valueFromPKList(Object[] pks, AbstractRecord foreignKeys, AbstractSession session);
/**
* INTERNAL: Return the value of the reference attribute or a value holder.
* Check whether the mapping's attribute should be optimized through batch
* and joining.
*/
@Override
public Object valueFromRow(AbstractRecord row, JoinedAttributeManager joinManager, ObjectBuildingQuery sourceQuery, CacheKey cacheKey, AbstractSession executionSession, boolean isTargetProtected, Boolean[] wasCacheUsed) throws DatabaseException {
if (this.descriptor.getCachePolicy().isProtectedIsolation()) {
if (this.isCacheable && isTargetProtected && cacheKey != null) {
//cachekey will be null when isolating to uow
//used cached collection
Object cached = cacheKey.getObject();
if (cached != null) {
if (wasCacheUsed != null){
wasCacheUsed[0] = Boolean.TRUE;
}
//this will just clone the indirection.
//the indirection object is responsible for cloning the value.
return getAttributeValueFromObject(cached);
}
} else if (!this.isCacheable && !isTargetProtected && cacheKey != null) {
return this.indirectionPolicy.buildIndirectObject(new ValueHolder(null));
}
}
if (row.hasSopObject()) {
// DirectCollection or AggregateCollection that doesn't reference entities: no need to build members back into cache - just return the whole collection from sopObject.
if (!hasNestedIdentityReference()) {
return getAttributeValueFromObject(row.getSopObject());
} else {
return valueFromRowInternal(row, null, sourceQuery, executionSession, true);
}
}
// PERF: Direct variable access.
if (shouldUseValueFromRowWithJoin(joinManager, sourceQuery)) {
return valueFromRowInternalWithJoin(row, joinManager, sourceQuery, cacheKey, executionSession, isTargetProtected);
}
// If the query uses batch reading, return a special value holder
// or retrieve the object from the query property.
if (sourceQuery.isObjectLevelReadQuery() && (((ObjectLevelReadQuery)sourceQuery).isAttributeBatchRead(this.descriptor, getAttributeName())
|| (sourceQuery.isReadAllQuery() && shouldUseBatchReading()))) {
return batchedValueFromRow(row, (ObjectLevelReadQuery)sourceQuery, cacheKey);
} else {
return valueFromRowInternal(row, joinManager, sourceQuery, executionSession, false);
}
}
/**
* INTERNAL:
* Indicates whether valueFromRow should call valueFromRowInternalWithJoin (true)
* or valueFromRowInternal (false)
*/
protected boolean shouldUseValueFromRowWithJoin(JoinedAttributeManager joinManager, ObjectBuildingQuery sourceQuery) {
return ((joinManager != null) && (joinManager.isAttributeJoined(this.descriptor, this))) || sourceQuery.hasPartialAttributeExpressions();
}
/**
* INTERNAL:
* If the query used joining or partial attributes, build the target object directly.
* If isJoiningSupported()==true then this method must be overridden.
*/
protected Object valueFromRowInternalWithJoin(AbstractRecord row, JoinedAttributeManager joinManager, ObjectBuildingQuery sourceQuery, CacheKey parentCacheKey, AbstractSession executionSession, boolean isTargetProtected) throws DatabaseException {
throw ValidationException.mappingDoesNotOverrideValueFromRowInternalWithJoin(Helper.getShortClassName(this.getClass()));
}
/**
* INTERNAL:
* Return the value of the reference attribute or a value holder.
* Check whether the mapping's attribute should be optimized through batch and joining.
*/
protected Object valueFromRowInternal(AbstractRecord row, JoinedAttributeManager joinManager, ObjectBuildingQuery sourceQuery, AbstractSession executionSession) throws DatabaseException {
return valueFromRowInternal(row, joinManager, sourceQuery, executionSession, false);
}
/**
* INTERNAL:
* Return the value of the reference attribute or a value holder.
* Check whether the mapping's attribute should be optimized through batch and joining.
* @param shouldUseSopObject indicates whether sopObject stored in the row should be used to extract the value (and fields/values stored in the row ignored).
*/
protected Object valueFromRowInternal(AbstractRecord row, JoinedAttributeManager joinManager, ObjectBuildingQuery sourceQuery, AbstractSession executionSession, boolean shouldUseSopObject) throws DatabaseException {
// PERF: Direct variable access.
ReadQuery targetQuery = this.selectionQuery;
if (shouldUseSopObject) {
Object sopAttribute = getAttributeValueFromObject(row.getSopObject());
Object sopRealAttribute;
if (isCollectionMapping()) {
if (sopAttribute == null) {
return getContainerPolicy().containerInstance();
}
sopRealAttribute = getIndirectionPolicy().getRealAttributeValueFromObject(row.getSopObject(), sopAttribute);
if (getContainerPolicy().isEmpty(sopRealAttribute)) {
return sopAttribute;
}
} else {
if (sopAttribute == null) {
return this.indirectionPolicy.nullValueFromRow();
}
// As part of SOP object the indirection should be already triggered
sopRealAttribute = getIndirectionPolicy().getRealAttributeValueFromObject(row.getSopObject(), sopAttribute);
if (sopRealAttribute == null) {
return sopAttribute;
}
}
DatabaseRecord sopRow = new DatabaseRecord(0);
sopRow.setSopObject(sopRealAttribute);
row = sopRow;
}
// Bug 464088
if (executionSession.isHistoricalSession() && !targetQuery.isPrepared()) {
targetQuery = (ReadQuery) targetQuery.clone();
targetQuery.setIsExecutionClone(true);
}
// Copy nested fetch group from the source query
if (targetQuery.isObjectLevelReadQuery() && targetQuery.getDescriptor().hasFetchGroupManager()) {
FetchGroup sourceFG = sourceQuery.getExecutionFetchGroup(this.getDescriptor());
if (sourceFG != null) {
FetchGroup targetFetchGroup = sourceFG.getGroup(getAttributeName());
if(targetFetchGroup != null) {
// perf: bug#4751950, first prepare the query before cloning.
if (targetQuery.shouldPrepare()) {
targetQuery.checkPrepare(executionSession, row);
}
targetQuery = (ReadQuery) targetQuery.clone();
targetQuery.setIsExecutionClone(true);
((ObjectLevelReadQuery)targetQuery).setFetchGroup(targetFetchGroup);
}
}
}
// CR #4365, 3610825 - moved up from the block below, needs to be set with
// indirection off. Clone the query and set its id.
// All indirections are triggered in sopObject, therefore if sopObject is used then indirection on targetQuery to be triggered, too.
if (!this.indirectionPolicy.usesIndirection() || shouldUseSopObject) {
if (targetQuery == this.selectionQuery) {
// perf: bug#4751950, first prepare the query before cloning.
if (targetQuery.shouldPrepare()) {
targetQuery.checkPrepare(executionSession, row);
}
targetQuery = (ReadQuery) targetQuery.clone();
targetQuery.setIsExecutionClone(true);
}
targetQuery.setQueryId(sourceQuery.getQueryId());
if (sourceQuery.usesResultSetAccessOptimization()) {
targetQuery.setAccessors(sourceQuery.getAccessors());
}
if(targetQuery.isObjectLevelReadQuery()) {
((ObjectLevelReadQuery)targetQuery).setRequiresDeferredLocks(sourceQuery.requiresDeferredLocks());
}
}
// If the source query is cascading then the target query must use the same settings.
if (targetQuery.isObjectLevelReadQuery()) {
if (sourceQuery.shouldCascadeAllParts() || (this.isPrivateOwned && sourceQuery.shouldCascadePrivateParts()) || (this.cascadeRefresh && sourceQuery.shouldCascadeByMapping())) {
// If the target query has already been cloned (we're refreshing) avoid
// re-cloning the query again.
if (targetQuery == this.selectionQuery) {
// perf: bug#4751950, first prepare the query before cloning.
if (targetQuery.shouldPrepare()) {
targetQuery.checkPrepare(executionSession, row);
}
targetQuery = (ReadQuery) targetQuery.clone();
targetQuery.setIsExecutionClone(true);
}
((ObjectLevelReadQuery)targetQuery).setShouldRefreshIdentityMapResult(sourceQuery.shouldRefreshIdentityMapResult());
targetQuery.setCascadePolicy(sourceQuery.getCascadePolicy());
// For queries that have turned caching off, such as aggregate collection, leave it off.
if (targetQuery.shouldMaintainCache()) {
targetQuery.setShouldMaintainCache(sourceQuery.shouldMaintainCache());
}
// For flashback: Read attributes as of the same time if required.
if (sourceQuery.isObjectLevelReadQuery() && ((ObjectLevelReadQuery)sourceQuery).hasAsOfClause()) {
targetQuery.setSelectionCriteria((Expression)targetQuery.getSelectionCriteria().clone());
((ObjectLevelReadQuery)targetQuery).setAsOfClause(((ObjectLevelReadQuery)sourceQuery).getAsOfClause());
}
}
if (isExtendingPessimisticLockScope(sourceQuery)) {
if (this.extendPessimisticLockScope == ExtendPessimisticLockScope.TARGET_QUERY) {
if (targetQuery == this.selectionQuery) {
// perf: bug#4751950, first prepare the query before cloning.
if (targetQuery.shouldPrepare()) {
targetQuery.checkPrepare(executionSession, row);
}
targetQuery = (ReadQuery) targetQuery.clone();
targetQuery.setIsExecutionClone(true);
}
extendPessimisticLockScopeInTargetQuery((ObjectLevelReadQuery)targetQuery, sourceQuery);
} else if (this.extendPessimisticLockScope == ExtendPessimisticLockScope.DEDICATED_QUERY) {
ReadQuery dedicatedQuery = getExtendPessimisticLockScopeDedicatedQuery(executionSession, sourceQuery.getLockMode());
executionSession.executeQuery(dedicatedQuery, row);
}
}
}
targetQuery = prepareHistoricalQuery(targetQuery, sourceQuery, executionSession);
return this.indirectionPolicy.valueFromQuery(targetQuery, row, executionSession);
}
/**
* INTERNAL:
* Indicates whether the source query's pessimistic lock scope scope should be extended in the target query.
*/
protected boolean isExtendingPessimisticLockScope(ObjectBuildingQuery sourceQuery) {
// TODO: What if sourceQuery is NOT ObjectLevelReadQuery? Should we somehow handle this?
// Or alternatively define ObjectBuildingQuery.shouldExtendPessimisticLockScope() to always return false?
return sourceQuery.isLockQuery() && sourceQuery.isObjectLevelReadQuery() && ((ObjectLevelReadQuery)sourceQuery).shouldExtendPessimisticLockScope();
}
/**
* INTERNAL:
* Allow for the mapping to perform any historical query additions.
* Return the new target query.
*/
protected ReadQuery prepareHistoricalQuery(ReadQuery targetQuery, ObjectBuildingQuery sourceQuery, AbstractSession executionSession) {
return targetQuery;
}
/**
* INTERNAL:
* Return a sub-partition of the row starting at the index for the mapping.
*/
public AbstractRecord trimRowForJoin(AbstractRecord row, JoinedAttributeManager joinManager, AbstractSession executionSession) {
// The field for many objects may be in the row,
// so build the subpartion of the row through the computed values in the query,
// this also helps the field indexing match.
if ((joinManager != null) && (joinManager.getJoinedMappingIndexes_() != null)) {
Object value = joinManager.getJoinedMappingIndexes_().get(this);
if (value != null) {
return trimRowForJoin(row, value, executionSession);
}
}
return row;
}
/**
* INTERNAL:
* Return a sub-partition of the row starting at the index.
*/
public AbstractRecord trimRowForJoin(AbstractRecord row, Object value, AbstractSession executionSession) {
// CR #... the field for many objects may be in the row,
// so build the subpartion of the row through the computed values in the query,
// this also helps the field indexing match.
int fieldStartIndex;
if (value instanceof Integer) {
fieldStartIndex = (Integer) value;
} else {
// must be Map of classes to Integers
Map map = (Map)value;
Class cls;
if (getDescriptor().hasInheritance() && getDescriptor().getInheritancePolicy().shouldReadSubclasses()) {
cls = getDescriptor().getInheritancePolicy().classFromRow(row, executionSession);
} else {
cls = getDescriptor().getJavaClass();
}
fieldStartIndex = (Integer) map.get(cls);
}
Vector trimedFields = new NonSynchronizedSubVector(row.getFields(), fieldStartIndex, row.size());
Vector trimedValues = new NonSynchronizedSubVector(row.getValues(), fieldStartIndex, row.size());
return new DatabaseRecord(trimedFields, trimedValues);
}
/**
* INTERNAL:
* Prepare the clone of the nested query for joining.
* The nested query clones are stored on the execution (clone) joinManager to avoid cloning per row.
*/
protected ObjectLevelReadQuery prepareNestedJoinQueryClone(AbstractRecord row, List dataResults, JoinedAttributeManager joinManager, ObjectBuildingQuery sourceQuery, AbstractSession executionSession) {
// A nested query must be built to pass to the descriptor that looks like the real query execution would,
// these should be cached on the query during prepare.
ObjectLevelReadQuery nestedQuery = null;
// This is also call for partial object reading.
if (joinManager == null) {
nestedQuery = prepareNestedJoins(null, sourceQuery, executionSession);
nestedQuery.setSession(executionSession);
nestedQuery.setPrefetchedCacheKeys(sourceQuery.getPrefetchedCacheKeys());
return nestedQuery;
}
// PERF: Also store the clone of the nested query on the execution query to avoid
// cloning per row.
if (joinManager.getJoinedMappingQueryClones() == null) {
joinManager.setJoinedMappingQueryClones(new HashMap(5));
}
nestedQuery = joinManager.getJoinedMappingQueryClones().get(this);
if (nestedQuery == null) {
if (joinManager.getJoinedMappingQueries_() != null) {
nestedQuery = joinManager.getJoinedMappingQueries_().get(this);
nestedQuery = (ObjectLevelReadQuery)nestedQuery.clone();
} else {
nestedQuery = prepareNestedJoins(joinManager, sourceQuery, executionSession);
}
nestedQuery.setSession(executionSession);
//CR #4365 - used to prevent infinite recursion on refresh object cascade all
nestedQuery.setQueryId(joinManager.getBaseQuery().getQueryId());
nestedQuery.setExecutionTime(joinManager.getBaseQuery().getExecutionTime());
joinManager.getJoinedMappingQueryClones().put(this, nestedQuery);
}
nestedQuery.setPrefetchedCacheKeys(sourceQuery.getPrefetchedCacheKeys());
// Must also set data results to the nested query if it uses to-many joining.
if (nestedQuery.hasJoining() && nestedQuery.getJoinedAttributeManager().isToManyJoin()) {
// The data results only of the child object are required, they must also be trimmed.
List nestedDataResults = dataResults;
if (nestedDataResults == null) {
// Extract the primary key of the source object, to filter only the joined rows for that object.
Object sourceKey = this.descriptor.getObjectBuilder().extractPrimaryKeyFromRow(row, executionSession);
nestedDataResults = joinManager.getDataResultsByPrimaryKey().get(sourceKey);
}
nestedDataResults = new ArrayList(nestedDataResults);
Object indexObject = joinManager.getJoinedMappingIndexes_().get(this);
// Trim results to start at nested row index.
for (int index = 0; index < nestedDataResults.size(); index++) {
AbstractRecord sourceRow = (AbstractRecord)nestedDataResults.get(index);
nestedDataResults.set(index, trimRowForJoin(sourceRow, indexObject, executionSession));
}
nestedQuery.getJoinedAttributeManager().setDataResults(nestedDataResults, executionSession);
}
// Must also set data results to the nested query if it uses nested IN batch fetching.
/*if (nestedQuery.hasBatchReadAttributes() && nestedQuery.getBatchFetchPolicy().isIN()) {
List sourceDataResults = ((ObjectLevelReadQuery)sourceQuery).getBatchFetchPolicy().getAllDataResults();
// The data results only of the child object are required, they must also be trimmed.
List nestedDataResults = new ArrayList(sourceDataResults.size());
Object indexObject = joinManager.getJoinedMappingIndexes_().get(this);
// Trim results to start at nested row index.
for (int index = 0; index < sourceDataResults.size(); index++) {
AbstractRecord sourceRow = (AbstractRecord)sourceDataResults.get(index);
if (sourceRow != null) {
nestedDataResults.add(trimRowForJoin(sourceRow, indexObject, executionSession));
}
}
nestedQuery.getBatchFetchPolicy().setDataResults(nestedDataResults);
}*/
nestedQuery.setRequiresDeferredLocks(sourceQuery.requiresDeferredLocks());
return nestedQuery;
}
/**
* PUBLIC:
* Return the type of batch fetching to use for all queries for this class if configured.
*/
public BatchFetchType getBatchFetchType() {
return batchFetchType;
}
/**
* PUBLIC:
* Set the type of batch fetching to use for all queries for this class.
*/
public void setBatchFetchType(BatchFetchType batchFetchType) {
this.batchFetchType = batchFetchType;
}
/**
* INTERNAL:
* Allow subclass to define a foreign key in the target's table.
*/
public void addTargetForeignKeyField(DatabaseField targetForeignKeyField, DatabaseField sourcePrimaryKeyField) {
throw new UnsupportedOperationException("addTargetForeignKeyField");
}
/**
* INTERNAL:
* Allow subclass to define a foreign key in the source's table.
*/
public void addForeignKeyField(DatabaseField sourceForeignKeyField, DatabaseField targetPrimaryKeyField) {
throw new UnsupportedOperationException("addForeignKeyField");
}
/**
* INTERNAL:
* Relationships order by their target primary key fields by default.
*/
@Override
public List<Expression> getOrderByNormalizedExpressions(Expression base) {
List<Expression> orderBys = new ArrayList(this.referenceDescriptor.getPrimaryKeyFields().size());
for (DatabaseField field : this.referenceDescriptor.getPrimaryKeyFields()) {
orderBys.add(base.getField(field));
}
return orderBys;
}
}