| /* |
| * 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<? extends Object> 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; |
| } |
| } |