blob: d82646f61796a5141b6e645e22bf70c3e5b5706b [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.queries;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.exceptions.*;
import org.eclipse.persistence.internal.helper.*;
import org.eclipse.persistence.internal.identitymaps.CacheKey;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
import org.eclipse.persistence.internal.sessions.AbstractSession;
/**
* <p><b>Purpose</b>:
* This should only be used by the descriptor, this should not be executed directly.
* Used to determine if an object resides on the database.
* DoesExistQuery is normally used to determine whether to make an update
* or insert statement when writing an object.
*
* <p><b>Responsibilities</b>:
* Verify the existence of an object. Used only by a write object query.
*
* @author Yvon Lavoie
* @since TOPLink/Java 1.0
*/
public class DoesExistQuery extends DatabaseQuery {
public static final int AssumeNonExistence = 1;
public static final int AssumeExistence = 2;
public static final int CheckCache = 3;
public static final int CheckDatabase = 4;
/** Query that is performing the does exist check. */
protected Object primaryKey;
protected Object object;
/** Flag to determine existence check policy. */
protected int existencePolicy;
/**
* Flag to determine cache invalidation policy support. This overrides
* the CheckCache existence setting if the object is invalid or if the
* cache cannot be trusted because a flush or DML has occurred.
* The default is true.
*/
protected boolean checkDatabaseIfInvalid; //default to true, allows users to override
/**
* Flag to determine if the cache should be check first in addition to another option.
* The default is true;
*/
public boolean checkCacheFirst; //default to true
/**
* PUBLIC:
* Initialize the state of the query .
* By default the cache is checked, if non cache is used the descriptor should throw a exception and validate.
*/
public DoesExistQuery() {
this.existencePolicy = CheckCache;
this.checkDatabaseIfInvalid = true;
this.checkCacheFirst = true;
}
/**
* PUBLIC:
* Create a query to check if the object exists.
*/
public DoesExistQuery(Object object) {
this();
this.object = object;
}
/**
* PUBLIC:
* Create a query to check if the object exists.
*/
public DoesExistQuery(Call call) {
this();
setCall(call);
}
/**
* PUBLIC:
* Assume that if the objects primary key does not include null then it must exist.
* This may be used if the user's system guarantees that an object with non-null key exists.
*/
public void assumeExistenceForDoesExist() {
setExistencePolicy(AssumeExistence);
}
/**
* PUBLIC:
* Assume that the object does not exist.
* This may be used if the user's system guarantees objects must always be inserted.
*/
public void assumeNonExistenceForDoesExist() {
setExistencePolicy(AssumeNonExistence);
}
/**
* PUBLIC:
* Assume that if the objects primary key does not include null
* and it is in the cache, then is must exist.
* This should only be used if a full identity map is being used,
* and a new object in the client cannot have been inserted by another client.
*/
public void checkCacheForDoesExist() {
setExistencePolicy(CheckCache);
}
/**
* PUBLIC:
* Perform does exist check on the database through selecting the primary key.
*/
public void checkDatabaseForDoesExist() {
setExistencePolicy(CheckDatabase);
}
/**
* INTERNAL:
* Check if existence can be determined without going to the database.
* Note that custom query check is not require for does exist as the custom is always used.
* Used by unit of work, and will return null if checkDatabaseIfInvalid is set and the cachekey is invalidated
*/
public Object checkEarlyReturn(Object object, Object primaryKey, AbstractSession session, AbstractRecord translationRow) {
// For bug 3136413/2610803 building the selection criteria from an EJBQL string or
// an example object is done just in time.
buildSelectionCriteria(session);
// Return false on null since it can't exist. Little more done in case PK not set in the query
if (object == null){
return Boolean.FALSE;
}
ClassDescriptor descriptor = session.getDescriptor(object.getClass());
if (primaryKey == null) {
primaryKey = getPrimaryKey();
if (primaryKey == null) {
primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(object, session, true);
}
}
if (primaryKey == null) {
return Boolean.FALSE;
}
// Need to do the cache check first if flag set or if we should check the cache only for existence.
if ((shouldCheckCacheForDoesExist() ||this.checkCacheFirst) && !descriptor.isDescriptorForInterface()) {
// If this is a UOW and modification queries have been executed, the cache cannot be trusted.
if (this.checkDatabaseIfInvalid && (session.isUnitOfWork() && ((UnitOfWorkImpl)session).shouldReadFromDB())) {
return null;
}
CacheKey cacheKey;
Class objectClass = object.getClass();
AbstractSession tempSession = session;
if (tempSession.isUnitOfWork()){
cacheKey = tempSession.getIdentityMapAccessorInstance().getCacheKeyForObjectForLock(primaryKey, objectClass, descriptor);
if (cacheKey != null) {
// If in the UOW cache it can't be invalid.
return Boolean.TRUE;
}
while (((UnitOfWorkImpl)tempSession).isNestedUnitOfWork() ){ //could be nested lets check all UOWs
tempSession = tempSession.getParent();
cacheKey = tempSession.getIdentityMapAccessorInstance().getCacheKeyForObjectForLock(primaryKey, objectClass, descriptor);
if (cacheKey != null) {
// If in the UOW cache it can't be invalid.
return Boolean.TRUE;
}
}
tempSession = tempSession.getParentIdentityMapSession(descriptor, false, true);
}
// Did not find it registered in UOW so check main cache and check for invalidation.
cacheKey = tempSession.getIdentityMapAccessorInstance().getCacheKeyForObject(primaryKey,objectClass, descriptor, false);
if ((cacheKey != null)) {
// Assume that if there is a cachekey, object exists.
if (this.checkDatabaseIfInvalid) {
checkDescriptor(object, session);
if (this.descriptor.getCacheInvalidationPolicy().isInvalidated(cacheKey, System.currentTimeMillis())) {
return null;
}
}
Object objectFromCache = cacheKey.getObject();
if ((session.isUnitOfWork()) && ((UnitOfWorkImpl)session).wasDeleted(objectFromCache)) {
if (shouldCheckCacheForDoesExist()) {
return Boolean.FALSE;
}
} else {
return Boolean.TRUE;
}
} else if (shouldCheckCacheForDoesExist()) {
// We know its not in cache, and a checkcache policy so return false.
return Boolean.FALSE;
}
}
// Check if we have to assume that the object does not exist.
if (shouldAssumeNonExistenceForDoesExist()) {
return Boolean.FALSE;
}
// Check to see if we only need to check that the object contains a primary key.
if (shouldAssumeExistenceForDoesExist()) {
return Boolean.TRUE;
}
return null;
}
/**
* INTERNAL:
* Check if existence can be determined without going to the database.
* Note that custom query check is not require for does exist as the custom is always used.
*/
@Override
public Object checkEarlyReturn(AbstractSession session, AbstractRecord translationRow) {
return checkEarlyReturn(getObject(), getPrimaryKey(), session, translationRow);
}
/**
* INTERNAL:
* Return if the object exists on the database.
* This must be a Boolean object to conform with returning an object.
* If using optimistic locking, check that the value matches.
* @exception DatabaseException - an error has occurred on the database.
*/
@Override
public Object executeDatabaseQuery() throws DatabaseException {
// Get the required fields for does exist check.
DatabaseField field = getDoesExistField();
// Get row from database
AbstractRecord databaseRow = getQueryMechanism().selectRowForDoesExist(field);
// Null means no row was returned.
return databaseRow != null;
}
/**
* INTERNAL:
* Return the write lock field or the first primary key field if not using locking.
*/
protected DatabaseField getDoesExistField() {
return this.descriptor.getPrimaryKeyFields().get(0);
}
/**
* INTERNAL:
* Return the existence policy for this existence Query
*/
public int getExistencePolicy() {
return this.existencePolicy;
}
/**
* PUBLIC:
* Return the object.
*/
public Object getObject() {
return object;
}
/**
* INTERNAL:
* Return the primaryKey.
*/
public Object getPrimaryKey() {
return primaryKey;
}
/**
* Return the domain class associated with this query.
*/
@Override
public Class getReferenceClass() {
if (getObject() == null) {
return null;
}
return getObject().getClass();
}
/**
* INTERNAL:
* Return the name of the reference class for this query
* Note: Although the API is designed to avoid requirement of classes being on the classpath,
* this is not a user defined query type, so it is ok to access the class.
*/
@Override
public String getReferenceClassName() {
return getReferenceClass().getName();
}
/**
* INTERNAL:
* Prepare the receiver for execution in a session.
*/
@Override
protected void prepare() throws QueryException {
if (getObject() != null) {// Prepare can be called without the object set yet.
checkDescriptor(getObject(), getSession());
setObject(getDescriptor().getObjectBuilder().unwrapObject(getObject(), getSession()));
}
super.prepare();
// It will only get to prepare if check database if required.
getQueryMechanism().prepareDoesExist(getDoesExistField());
}
/**
* INTERNAL:
* Ensure that the descriptor has been set.
*/
public void checkDescriptor(Object object, AbstractSession session) throws QueryException {
if (this.descriptor == null) {
if (object == null) {
throw QueryException.objectToModifyNotSpecified(this);
}
//Bug#3947714 Pass the object instead of class in case object is proxy
ClassDescriptor referenceDescriptor = session.getDescriptor(object);
if (referenceDescriptor == null) {
throw QueryException.descriptorIsMissing(object.getClass(), this);
}
setDescriptor(referenceDescriptor);
}
}
/**
* INTERNAL:
* Prepare the receiver for execution in a session.
*/
@Override
public void prepareForExecution() throws QueryException {
super.prepareForExecution();
if (getObject() == null) {
throw QueryException.objectToModifyNotSpecified(this);
}
setObject(this.descriptor.getObjectBuilder().unwrapObject(getObject(), getSession()));
if (this.descriptor == null) {
setDescriptor(getSession().getDescriptor(getObject().getClass()));
}
if (getPrimaryKey() == null) {
setPrimaryKey(this.descriptor.getObjectBuilder().extractPrimaryKeyFromObject(getObject(), getSession()));
}
if ((getTranslationRow() == null) || (getTranslationRow().isEmpty())) {
setTranslationRow(this.descriptor.getObjectBuilder().buildRowForTranslation(getObject(), getSession()));
}
}
/**
* INTERNAL:
* Set if the existence policy, this must be set to one of the constants.
*/
public void setExistencePolicy(int existencePolicy) {
this.existencePolicy = existencePolicy;
}
/**
* PUBLIC:
* Set the object.
*/
public void setObject(Object object) {
this.object = object;
}
/**
* INTERNAL:
* Set the primaryKey.
*/
public void setPrimaryKey(Object primaryKey) {
this.primaryKey = primaryKey;
}
/**
* PUBLIC:
* Returns true if the does exist check should be based only
* on whether the primary key of the object is set
*/
public boolean shouldAssumeExistenceForDoesExist() {
return existencePolicy == AssumeExistence;
}
/**
* PUBLIC:
* Returns true if the does exist check should assume non existence.
*/
public boolean shouldAssumeNonExistenceForDoesExist() {
return existencePolicy == AssumeNonExistence;
}
/**
* PUBLIC:
* Returns true if the does exist check should be based only
* on a cache check. Default behavior.
*/
public boolean shouldCheckCacheForDoesExist() {
return existencePolicy == CheckCache;
}
/**
* PUBLIC:
* Returns true if the does exist check should query the database.
*/
public boolean shouldCheckDatabaseForDoesExist() {
return existencePolicy == CheckDatabase;
}
/**
* INTERNAL:
* Sets checkCacheFirst flag. If true, existence check will first go to the
* cache. It will then check other options if it is not found in the cache
*/
public void setCheckCacheFirst(boolean checkCacheFirst){
this.checkCacheFirst = checkCacheFirst;
}
/**
* INTERNAL:
*/
public boolean getCheckCacheFirst(){
return this.checkCacheFirst;
}
/**
* INTERNAL:
* Sets checkDatabaseIfInvalid flag. If true, query will go to the
* database when it finds the object in the cache and it is invalid.
* This is only valid when it checks the cache, and is true by default
*/
public void setCheckDatabaseIfInvalid(boolean checkCacheFirst){
this.checkCacheFirst = checkCacheFirst;
}
/**
* INTERNAL:
*/
public boolean getCheckDatabaseIfInvalid(){
return this.checkCacheFirst;
}
}