blob: 0abfb3768715594eaaae9691849acc1fa1d28847 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 1998, 2016 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 v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Oracle - initial API and implementation from Oracle TopLink
* 04/01/2011-2.3 Guy Pelletier
* - 337323: Multi-tenant with shared schema support (part 2)
* 09/09/2011-2.3.1 Guy Pelletier
* - 356197: Add new VPD type to MultitenantType
* 01/06/2011-2.3 Guy Pelletier
* - 371453: JPA Multi-Tenancy in Bidirectional OneToOne Relation throws ArrayIndexOutOfBoundsException
******************************************************************************/
package org.eclipse.persistence.queries;
import java.util.*;
import java.sql.*;
import org.eclipse.persistence.internal.databaseaccess.*;
import org.eclipse.persistence.internal.identitymaps.CacheId;
import org.eclipse.persistence.internal.indirection.ProxyIndirectionPolicy;
import org.eclipse.persistence.internal.descriptors.*;
import org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism;
import org.eclipse.persistence.internal.sessions.remote.*;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.ResultSetRecord;
import org.eclipse.persistence.internal.sessions.SimpleResultSetRecord;
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
import org.eclipse.persistence.internal.helper.*;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.exceptions.*;
import org.eclipse.persistence.expressions.*;
import org.eclipse.persistence.sessions.DatabaseRecord;
import org.eclipse.persistence.sessions.SessionProfiler;
import org.eclipse.persistence.sessions.remote.*;
import org.eclipse.persistence.tools.profiler.QueryMonitor;
/**
* <p><b>Purpose</b>:
* Concrete class for all read queries involving a single object.
* <p>
* <p><b>Responsibilities</b>:
* Return a single object for the query.
* Implements the inheritance feature when dealing with abstract descriptors.
*
* @author Yvon Lavoie
* @since TOPLink/Java 1.0
*/
public class ReadObjectQuery extends ObjectLevelReadQuery {
/** Object that can be used in place of a selection criteria. */
protected transient Object selectionObject;
/** Key that can be used in place of a selection criteria. */
protected Object selectionId;
/** Can be used to refresh a specific non-cached instance from the database. */
protected boolean shouldLoadResultIntoSelectionObject = false;
/**
* PUBLIC:
* Return a new read object query.
* A reference class must be specified before execution.
* It is better to provide the class and expression builder on construction to esnure a single expression builder is used.
* If no selection criteria is specified this will reads the first object found in the database.
*/
public ReadObjectQuery() {
super();
}
/**
* PUBLIC:
* Return a new read object query.
* By default, the query has no selection criteria. Executing this query without
* selection criteria will always result in a database access to read the first
* instance of the specified Class found in the database. This is true no
* matter how cache usage is configured and even if an instance of the
* specified Class exists in the cache.
* Executing a query with selection criteria allows you to avoid a database
* access if the selected instance is in the cache. For this reason, you may whish to use a ReadObjectQuery constructor that takes selection criteria, such as: {@link #ReadObjectQuery(Class, Call)}, {@link #ReadObjectQuery(Class, Expression)}, {@link #ReadObjectQuery(Class, ExpressionBuilder)}, {@link #ReadObjectQuery(ExpressionBuilder)}, {@link #ReadObjectQuery(Object)}, or {@link #ReadObjectQuery(Object, QueryByExamplePolicy)}.
*/
public ReadObjectQuery(Class classToRead) {
this();
this.referenceClass = classToRead;
}
/**
* PUBLIC:
* Return a new read object query for the class and the selection criteria.
*/
public ReadObjectQuery(Class classToRead, Expression selectionCriteria) {
this();
this.referenceClass = classToRead;
setSelectionCriteria(selectionCriteria);
}
/**
* PUBLIC:
* Return a new read object query for the class.
* The expression builder must be used for all associated expressions used with the query.
*/
public ReadObjectQuery(Class classToRead, ExpressionBuilder builder) {
this();
this.defaultBuilder = builder;
this.referenceClass = classToRead;
}
/**
* PUBLIC:
* Return a new read object query.
* The call represents a database interaction such as SQL, Stored Procedure.
*/
public ReadObjectQuery(Class classToRead, Call call) {
this();
this.referenceClass = classToRead;
setCall(call);
}
/**
* PUBLIC:
* Return a new read object query.
* The call represents a database interaction such as SQL, Stored Procedure.
*/
public ReadObjectQuery(Call call) {
this();
setCall(call);
}
/**
* PUBLIC:
* Return a query to read the object with the same primary key as the provided object.
* Note: This is not a query by example object, only the primary key will be used for the selection criteria.
*/
public ReadObjectQuery(Object objectToRead) {
this();
setSelectionObject(objectToRead);
}
/**
* PUBLIC:
* Return a query by example query to find an object matching the attributes of the example object.
*/
public ReadObjectQuery(Object exampleObject, QueryByExamplePolicy policy) {
this();
setExampleObject(exampleObject);
setQueryByExamplePolicy(policy);
}
/**
* PUBLIC:
* The expression builder should be provide on creation to ensure only one is used.
*/
public ReadObjectQuery(ExpressionBuilder builder) {
this();
this.defaultBuilder = builder;
}
/**
* INTERNAL:
* <P> This method is called by the object builder when building an original.
* It will cause the original to be cached in the query results if the query
* is set to do so.
*/
public void cacheResult(Object object) {
Object cachableObject = object;
if (object == null) {
this.temporaryCachedQueryResults = InvalidObject.instance();
} else {
if (this.shouldUseWrapperPolicy) {
cachableObject = this.session.wrapObject(object);
}
this.temporaryCachedQueryResults = cachableObject;
}
}
/**
* PUBLIC:
* The cache will be checked only if the query contains exactly the primary key.
* Queries can be configured to use the cache at several levels.
* Other caching option are available.
* @see #setCacheUsage(int)
*/
public void checkCacheByExactPrimaryKey() {
setCacheUsage(CheckCacheByExactPrimaryKey);
}
/**
* PUBLIC:
* This is the default, the cache will be checked only if the query contains the primary key.
* Queries can be configured to use the cache at several levels.
* Other caching option are available.
* @see #setCacheUsage(int)
*/
public void checkCacheByPrimaryKey() {
setCacheUsage(CheckCacheByPrimaryKey);
}
/**
* PUBLIC:
* The cache will be checked completely, then if the object is not found or the query too complex the database will be queried.
* Queries can be configured to use the cache at several levels.
* Other caching option are available.
* @see #setCacheUsage(int)
*/
public void checkCacheThenDatabase() {
setCacheUsage(CheckCacheThenDatabase);
}
/**
* INTERNAL:
* Ensure that the descriptor has been set.
*/
public void checkDescriptor(AbstractSession session) throws QueryException {
if (this.descriptor == null) {
if (getReferenceClass() == null) {
throw QueryException.referenceClassMissing(this);
}
ClassDescriptor referenceDescriptor;
//Bug#3947714 In case getSelectionObject() is proxy
if (getSelectionObject() != null && session.getProject().hasProxyIndirection()) {
referenceDescriptor = session.getDescriptor(getSelectionObject());
} else {
referenceDescriptor = session.getDescriptor(getReferenceClass());
}
if (referenceDescriptor == null) {
throw QueryException.descriptorIsMissing(getReferenceClass(), this);
}
setDescriptor(referenceDescriptor);
}
}
/**
* INTERNAL:
* The cache check is done before the prepare as a hit will not require the work to be done.
*/
protected Object checkEarlyReturnLocal(AbstractSession session, AbstractRecord translationRow) {
if (shouldCheckCache() && shouldMaintainCache() && (!shouldRefreshIdentityMapResult() && (!shouldRetrieveBypassCache()))
&& (!(session.isRemoteSession() && (shouldRefreshRemoteIdentityMapResult() || this.descriptor.shouldDisableCacheHitsOnRemote())))
&& (!(shouldCheckDescriptorForCacheUsage() && this.descriptor.shouldDisableCacheHits())) && (!this.descriptor.isDescriptorForInterface())) {
Object cachedObject = getQueryMechanism().checkCacheForObject(translationRow, session);
this.isCacheCheckComplete = true;
// Optimization: If find deleted object by exact primary
// key expression or selection object/key just abort.
if (cachedObject == InvalidObject.instance) {
return cachedObject;
}
if (cachedObject != null) {
if (shouldLoadResultIntoSelectionObject()) {
ObjectBuilder builder = this.descriptor.getObjectBuilder();
builder.copyInto(cachedObject, getSelectionObject());
//put this object into the cache. This may cause some loss of identity
session.getIdentityMapAccessorInstance().putInIdentityMap(getSelectionObject());
cachedObject = getSelectionObject();
}
// check locking. If clone has not been locked, do not early return cached object
if (isLockQuery() && (session.isUnitOfWork() && !((UnitOfWorkImpl)session).isPessimisticLocked(cachedObject))) {
return null;
}
if (QueryMonitor.shouldMonitor()) {
QueryMonitor.incrementReadObjectHits(this);
}
session.incrementProfile(SessionProfiler.CacheHits, this);
} else {
if (!session.isUnitOfWork()) {
if (QueryMonitor.shouldMonitor()) {
QueryMonitor.incrementReadObjectMisses(this);
}
session.incrementProfile(SessionProfiler.CacheMisses, this);
}
}
if (shouldUseWrapperPolicy()) {
cachedObject = this.descriptor.getObjectBuilder().wrapObject(cachedObject, session);
}
return cachedObject;
} else {
if (!session.isUnitOfWork()) {
if (QueryMonitor.shouldMonitor()) {
QueryMonitor.incrementReadObjectMisses(this);
}
session.incrementProfile(SessionProfiler.CacheMisses, this);
}
return null;
}
}
/**
* INTERNAL:
* Check to see if a custom query should be used for this query.
* This is done before the query is copied and prepared/executed.
* null means there is none.
*/
protected DatabaseQuery checkForCustomQuery(AbstractSession session, AbstractRecord translationRow) {
Boolean useCustomQuery = isCustomQueryUsed;
checkDescriptor(session);
if (useCustomQuery == null) {
// Check if user defined a custom query in the query manager.
if (!this.isUserDefined) {
if (!isCallQuery()
// By default all descriptors have a custom ("static") read-object query.
// This allows the read-object query and SQL to be prepare once.
&& this.descriptor.getQueryManager().hasReadObjectQuery()) {
// If the query require special SQL generation or execution do not use the static read object query.
// PERF: the read-object query should always be static to ensure no regeneration of SQL.
if ((!hasJoining() || !this.joinedAttributeManager.hasJoinedAttributeExpressions()) && (!hasPartialAttributeExpressions())
&& (redirector == null) && !doNotRedirect && (!hasAsOfClause()) && (!hasNonDefaultFetchGroup())
&& (this.shouldUseSerializedObjectPolicy == shouldUseSerializedObjectPolicyDefault)
&& this.wasDefaultLockMode && (shouldBindAllParameters == null) && (this.hintString == null)) {
if ((this.selectionId != null) || (this.selectionObject != null)) {// Must be primary key.
useCustomQuery = Boolean.TRUE;
} else {
Expression selectionCriteria = getSelectionCriteria();
if (selectionCriteria != null) {
AbstractRecord primaryKeyRow = this.descriptor.getObjectBuilder().extractPrimaryKeyRowFromExpression(selectionCriteria, translationRow, session);
// Only execute the query if the selection criteria has the primary key fields set
if (primaryKeyRow != null) {
useCustomQuery = Boolean.TRUE;
}
}
}
}
}
}
}
//#436871 - attempt to limit cases for race-condition
if (useCustomQuery != null && useCustomQuery.booleanValue()) {
ReadObjectQuery customQuery = this.descriptor.getQueryManager().getReadObjectQuery();
if (this.accessors != null) {
customQuery = (ReadObjectQuery) customQuery.clone();
customQuery.setIsExecutionClone(true);
customQuery.setAccessors(this.accessors);
}
isCustomQueryUsed = useCustomQuery;
return customQuery;
}
isCustomQueryUsed = useCustomQuery;
return null;
}
/**
* INTERNAL:
* Conform the result in the UnitOfWork.
*/
protected Object conformResult(Object result, UnitOfWorkImpl unitOfWork, AbstractRecord databaseRow, boolean buildDirectlyFromRows) {
// Note that if the object does not conform even though other objects might exist on the database null is returned.
// Note that new objects is checked before the read is executed so does not have to be re-checked.
// Must unwrap as the built object is always wrapped.
// Note the object is unwrapped on the parent which it belongs to, as we
// do not want to trigger a registration just yet.
Object clone = null;
if (buildDirectlyFromRows) {
clone = buildObject((AbstractRecord)result);
} else {
clone = registerIndividualResult(
this.descriptor.getObjectBuilder().unwrapObject(result, unitOfWork.getParent()),
null, unitOfWork, null, null);
}
Expression selectionCriteria = getSelectionCriteria();
if ((selectionCriteria != null) && (this.selectionId == null) && (this.selectionObject == null)) {
ExpressionBuilder builder = selectionCriteria.getBuilder();
builder.setSession(unitOfWork.getRootSession(null));
builder.setQueryClass(getReferenceClass());
}
clone = conformIndividualResult(clone, unitOfWork, databaseRow, selectionCriteria, null);
if (clone == null) {
return clone;
}
if (shouldUseWrapperPolicy()) {
return this.descriptor.getObjectBuilder().wrapObject(clone, unitOfWork);
} else {
return clone;
}
}
/**
* PUBLIC:
* Do not refesh/load into the selection object, this is the default.
* This property allows for the selection object of the query to be refreshed or put into the TopLink cache.
* By default on a read or refresh the object in the cache is refreshed and returned or a new object is built from the database,
* in some cases such as EJB BMP it is desirable to refresh or load into the object passed into the read object query.
* <p>Note: This forces the selection object into the cache a replaces any existing object that may already be there,
* this is a strict violation of object identity and other objects can still be refering to the old object.
*/
public void dontLoadResultIntoSelectionObject() {
setShouldLoadResultIntoSelectionObject(false);
}
/**
* INTERNAL:
* Execute the query. If there are cached results return those.
* This must override the super to support result caching.
*
* @param session the session in which the receiver will be executed.
* @return An object or vector, the result of executing the query.
* @exception DatabaseException - an error has occurred on the database
*/
public Object execute(AbstractSession session, AbstractRecord row) throws DatabaseException {
if (shouldCacheQueryResults()) {
if (shouldConformResultsInUnitOfWork()) {
throw QueryException.cannotConformAndCacheQueryResults(this);
}
if (isPrepared()) {// only prepared queries can have cached results.
Object result = getQueryResults(session, row, true);
// Bug6138532 - if result is "cached no results", return null immediately
if (result == InvalidObject.instance) {
return null;
}
if (result != null) {
if (session.isUnitOfWork()) {
result = ((UnitOfWorkImpl)session).registerExistingObject(result);
}
return result;
}
}
}
return super.execute(session, row);
}
/**
* INTERNAL:
* Execute the query.
* Do a cache lookup and build object from row if required.
* @exception DatabaseException - an error has occurred on the database
* @return object - the first object found or null if none.
*/
protected Object executeObjectLevelReadQuery() throws DatabaseException {
if (this.descriptor.isDescriptorForInterface() || this.descriptor.hasTablePerClassPolicy()) {
Object returnValue = this.descriptor.getInterfacePolicy().selectOneObjectUsingMultipleTableSubclassRead(this);
if (this.descriptor.hasTablePerClassPolicy() && (!this.descriptor.isAbstract()) && (returnValue == null)) {
// let it fall through to query the root.
} else {
this.executionTime = System.currentTimeMillis();
return returnValue;
}
}
boolean shouldSetRowsForJoins = hasJoining() && this.joinedAttributeManager.isToManyJoin();
AbstractSession session = getSession();
Object result = null;
AbstractRecord row = null;
Object sopObject = getTranslationRow().getSopObject();
boolean useOptimization = false;
if (sopObject == null) {
useOptimization = usesResultSetAccessOptimization();
}
if (useOptimization) {
DatabaseCall call = ((DatasourceCallQueryMechanism)this.queryMechanism).selectResultSet();
this.executionTime = System.currentTimeMillis();
boolean exceptionOccured = false;
ResultSet resultSet = call.getResult();
DatabaseAccessor dbAccessor = (DatabaseAccessor)getAccessor();
try {
if (resultSet.next()) {
ResultSetMetaData metaData = call.getResult().getMetaData();
boolean useSimple = this.descriptor.getObjectBuilder().isSimple();
DatabasePlatform platform = dbAccessor.getPlatform();
boolean optimizeData = platform.shouldOptimizeDataConversion();
if (useSimple) {
row = new SimpleResultSetRecord(call.getFields(), call.getFieldsArray(), resultSet, metaData, dbAccessor, getExecutionSession(), platform, optimizeData);
if (this.descriptor.isDescriptorTypeAggregate()) {
// Aggregate Collection may have an unmapped primary key referencing the owner, the corresponding field will not be used when the object is populated and therefore may not be cleared.
((SimpleResultSetRecord)row).setShouldKeepValues(true);
}
} else {
row = new ResultSetRecord(call.getFields(), call.getFieldsArray(), resultSet, metaData, dbAccessor, getExecutionSession(), platform, optimizeData);
}
if (session.isUnitOfWork()) {
result = registerResultInUnitOfWork(row, (UnitOfWorkImpl)session, this.translationRow, true);
} else {
result = buildObject(row);
}
if (!useSimple && this.descriptor.getObjectBuilder().shouldKeepRow()) {
if (((ResultSetRecord)row).hasResultSet()) {
// ResultSet has not been fully triggered - that means the cached object was used.
// Yet the row still may be cached in a value holder (see loadBatchReadAttributes and loadJoinedAttributes methods).
// Remove ResultSet to avoid attempt to trigger it (already closed) when pk or fk values (already extracted) accessed when the value holder is instantiated.
((ResultSetRecord)row).removeResultSet();
} else {
((ResultSetRecord)row).removeNonIndirectionValues();
}
}
}
} catch (SQLException exception) {
exceptionOccured = true;
DatabaseException commException = dbAccessor.processExceptionForCommError(session, exception, call);
if (commException != null) {
throw commException;
}
throw DatabaseException.sqlException(exception, call, getAccessor(), session, false);
} finally {
try {
if (resultSet != null) {
resultSet.close();
}
if (dbAccessor != null) {
if (call.getStatement() != null) {
dbAccessor.releaseStatement(call.getStatement(), call.getSQLString(), call, session);
}
}
if (call.hasAllocatedConnection()) {
getExecutionSession().releaseConnectionAfterCall(this);
}
} catch (RuntimeException cleanupException) {
if (!exceptionOccured) {
throw cleanupException;
}
} catch (SQLException cleanupSQLException) {
if (!exceptionOccured) {
throw DatabaseException.sqlException(cleanupSQLException, call, dbAccessor, session, false);
}
}
}
} else {
if (sopObject != null) {
row = new DatabaseRecord(0);
row.setSopObject(sopObject);
} else {
// If using 1-m joins, must select all rows.
if (shouldSetRowsForJoins) {
List rows = getQueryMechanism().selectAllRows();
if (rows.size() > 0) {
row = (AbstractRecord)rows.get(0);
}
getJoinedAttributeManager().setDataResults(rows, session);
} else {
row = getQueryMechanism().selectOneRow();
}
}
this.executionTime = System.currentTimeMillis();
if (row != null) {
if (session.isUnitOfWork()) {
result = registerResultInUnitOfWork(row, (UnitOfWorkImpl)session, this.translationRow, true);
} else {
result = buildObject(row);
}
if (sopObject != null) {
// remove sopObject so it's not stuck in a value holder.
row.setSopObject(null);
}
}
}
if ((result == null) && shouldCacheQueryResults()) {
cacheResult(null);
}
if ((result == null) && this.shouldRefreshIdentityMapResult) {
// bug5955326, should invalidate the shared cached if refreshed object no longer exists.
if (this.selectionId != null) {
session.getParentIdentityMapSession(this.descriptor, true, true).getIdentityMapAccessor().invalidateObject(this.selectionId, this.referenceClass);
} else if (this.selectionObject != null) {
session.getParentIdentityMapSession(this.descriptor, true, true).getIdentityMapAccessor().invalidateObject(this.selectionObject);
}
}
if (this.shouldIncludeData && (sopObject == null)) {
ComplexQueryResult complexResult = new ComplexQueryResult();
complexResult.setResult(result);
complexResult.setData(row);
return complexResult;
}
return result;
}
/**
* INTERNAL:
* Execute the query building the objects directly from the database result-set.
* @exception DatabaseException - an error has occurred on the database
* @return object - the first object found or null if none.
*/
protected Object executeObjectLevelReadQueryFromResultSet() throws DatabaseException {
AbstractSession session = this.session;
DatabasePlatform platform = session.getPlatform();
DatabaseCall call = ((DatasourceCallQueryMechanism)this.queryMechanism).selectResultSet();
Statement statement = call.getStatement();
ResultSet resultSet = call.getResult();
DatabaseAccessor accessor = (DatabaseAccessor)((List<Accessor>)this.accessors).get(0);
boolean exceptionOccured = false;
try {
if (!resultSet.next()) {
return null;
}
ResultSetMetaData metaData = resultSet.getMetaData();
return this.descriptor.getObjectBuilder().buildObjectFromResultSet(this, null, resultSet, session, accessor, metaData, platform, call.getFields(), call.getFieldsArray());
} catch (SQLException exception) {
exceptionOccured = true;
DatabaseException commException = accessor.processExceptionForCommError(session, exception, call);
if (commException != null) {
throw commException;
}
throw DatabaseException.sqlException(exception, call, accessor, session, false);
} finally {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
accessor.releaseStatement(statement, call.getSQLString(), call, session);
}
if (accessor != null) {
session.releaseReadConnection(accessor);
}
} catch (SQLException exception) {
if (!exceptionOccured) {
//in the case of an external connection pool the connection may be null after the statement release
// if it is null we will be unable to check the connection for a comm error and
//therefore must return as if it was not a comm error.
DatabaseException commException = accessor.processExceptionForCommError(session, exception, call);
if (commException != null) {
throw commException;
}
throw DatabaseException.sqlException(exception, call, accessor, session, false);
}
}
}
}
/**
* INTERNAL:
* Extract the correct query result from the transporter.
*/
public Object extractRemoteResult(Transporter transporter) {
return ((DistributedSession)getSession()).getObjectCorrespondingTo(transporter.getObject(), transporter.getObjectDescriptors(), new IdentityHashMap(), this);
}
/**
* INTERNAL:
* Returns the specific default redirector for this query type. There are numerous default query redirectors.
* See ClassDescriptor for their types.
*/
protected QueryRedirector getDefaultRedirector(){
return descriptor.getDefaultReadObjectQueryRedirector();
}
/**
* PUBLIC:
* The primary key can be specified if used instead of an expression or selection object.
* If composite the primary must be in the same order as defined in the descriptor.
* @Depreacted since EclipseLink 2.1, replaced by getSelectionId()
* @see #getSelectionId()
*/
@Deprecated
public Vector getSelectionKey() {
if (this.selectionId instanceof CacheId) {
return new Vector(Arrays.asList(((CacheId)this.selectionId).getPrimaryKey()));
}
Vector primaryKey = new Vector(1);
primaryKey.add(this.selectionId);
return (Vector)selectionId;
}
/**
* PUBLIC:
* Return the selection object of the query.
* This can be used instead of a where clause expression for single object primary key queries.
* The selection object given should have a primary key defined,
* this primary key will be used to query the database instance of the same object.
* This is a basic form of query by example where only the primary key is required,
* it can be used for simple query forms, or testing.
*/
public Object getSelectionObject() {
return selectionObject;
}
/**
* PUBLIC:
* Return if this is a read object query.
*/
public boolean isReadObjectQuery() {
return true;
}
/**
* INTERNAL:
* Return if the query is by primary key.
*/
public boolean isPrimaryKeyQuery() {
return (this.selectionId != null) || (this.selectionObject != null);
}
/**
* PUBLIC:
* Allow for the selection object of the query to be refreshed or put into the EclipseLink cache.
* By default on a read or refresh the object in the cache is refreshed and returned or a new object is built from the database,
* in some cases such as EJB BMP it is desirable to refresh or load into the object passed into the read object query.
* <p>Note: This forces the selection object into the cache a replaces any existing object that may already be there,
* this is a strict violation of object identity and other objects can still be referring to the old object.
*/
public void loadResultIntoSelectionObject() {
setShouldLoadResultIntoSelectionObject(true);
}
/**
* INTERNAL:
* Copy all setting from the query.
* This is used to morph queries from one type to the other.
* By default this calls prepareFromQuery, but additional properties may be required
* to be copied as prepareFromQuery only copies properties that affect the SQL.
*/
public void copyFromQuery(DatabaseQuery query) {
super.copyFromQuery(query);
if (query.isReadObjectQuery()) {
ReadObjectQuery readQuery = (ReadObjectQuery)query;
this.selectionId = readQuery.selectionId;
this.selectionObject = readQuery.selectionObject;
this.shouldLoadResultIntoSelectionObject = readQuery.shouldLoadResultIntoSelectionObject;
}
}
/**
* INTERNAL:
* Prepare the receiver for execution in a session.
*/
protected void prepare() throws QueryException {
if (prepareFromCachedQuery()) {
return;
}
super.prepare();
if ((this.selectionId != null) || (this.selectionObject != null)) {
// The expression is set in the prepare as params.
setSelectionCriteria(this.descriptor.getObjectBuilder().getPrimaryKeyExpression());
setExpressionBuilder(getSelectionCriteria().getBuilder());
extendPessimisticLockScope();
// For bug 2989998 the translation row is required to be set at this point.
if (!shouldPrepare()) {
if (this.selectionId != null) {
// Row must come from the key.
setTranslationRow(this.descriptor.getObjectBuilder().buildRowFromPrimaryKeyValues(this.selectionId, this.session));
} else {//(getSelectionObject() != null)
setTranslationRow(this.descriptor.getObjectBuilder().buildRowForTranslation(this.selectionObject, this.session));
}
}
}
if (this.descriptor.isDescriptorForInterface()) {
return;
}
// PERF: Disable cache check if not a primary key query.
if (isExpressionQuery()) {
Expression selectionCriteria = getSelectionCriteria();
if (selectionCriteria != null) {
if (((this.cacheUsage == CheckCacheByPrimaryKey)
&& (!this.descriptor.getObjectBuilder().isPrimaryKeyExpression(false, selectionCriteria, this.session)))
|| ((this.cacheUsage == CheckCacheByExactPrimaryKey)
&& (!this.descriptor.getObjectBuilder().isPrimaryKeyExpression(true, selectionCriteria, this.session)))) {
this.cacheUsage = DoNotCheckCache;
}
}
}
// If using 1-m joining select all rows.
if ((this.joinedAttributeManager != null) && this.joinedAttributeManager.isToManyJoin()) {
getQueryMechanism().prepareSelectAllRows();
} else {
getQueryMechanism().prepareSelectOneRow();
}
// should be called after prepareSelectRow so that the call knows whether it returns ResultSet
prepareResultSetAccessOptimization();
}
/**
* INTERNAL:
* Set the properties needed to be cascaded into the custom query including the translation row.
* This is used only for primary key queries, as the descriptor query manager
* stores a predefined query for this query to avoid having to re-prepare and allow for customization.
*/
protected void prepareCustomQuery(DatabaseQuery customQuery) {
super.prepareCustomQuery(customQuery);
ReadObjectQuery customReadQuery = (ReadObjectQuery)customQuery;
customReadQuery.shouldRefreshIdentityMapResult = this.shouldRefreshIdentityMapResult;
customReadQuery.cascadePolicy = this.cascadePolicy;
customReadQuery.shouldMaintainCache = this.shouldMaintainCache;
customReadQuery.shouldUseWrapperPolicy = this.shouldUseWrapperPolicy;
// CR... was missing some values, execution could cause infinite loop.
customReadQuery.queryId = this.queryId;
customReadQuery.executionTime = this.executionTime;
customReadQuery.shouldLoadResultIntoSelectionObject = this.shouldLoadResultIntoSelectionObject;
AbstractRecord primaryKeyRow;
if (this.selectionObject != null) {
// CR#... Must also set the selection object as may be loading into the object (refresh)
customReadQuery.selectionObject = this.selectionObject;
// The translation/primary key row will be set in prepareForExecution.
} else if (this.selectionId != null) {
customReadQuery.selectionId = this.selectionId;
} else {
// The primary key row must be used.
primaryKeyRow = customQuery.getDescriptor().getObjectBuilder().extractPrimaryKeyRowFromExpression(getSelectionCriteria(), customQuery.getTranslationRow(), customReadQuery.getSession());
customReadQuery.setTranslationRow(primaryKeyRow);
}
}
/**
* INTERNAL:
* Prepare the receiver for execution in a session.
*/
public void prepareForExecution() throws QueryException {
super.prepareForExecution();
// For bug 2989998 the translation row now sometimes set earlier in prepare.
if (shouldPrepare()) {
if (this.selectionId != null) {
// Row must come from the key.
this.translationRow = this.descriptor.getObjectBuilder().buildRowFromPrimaryKeyValues(this.selectionId, getSession());
} else if (this.selectionObject != null) {
// The expression is set in the prepare as params.
this.translationRow = this.descriptor.getObjectBuilder().buildRowForTranslation(this.selectionObject, getSession());
}
}
// If we have tenant discriminator fields we need to add them to the
// database row when doing a primary key query.
// Modifying the translation row here will modify it on the original
// query which is not good (will append the tenant field to the sql call
// for subsequent queries) The translation row must be cloned to isolate
// this.
if (getDescriptor().hasMultitenantPolicy()) {
this.translationRow = this.translationRow.clone();
getDescriptor().getMultitenantPolicy().addFieldsToRow(this.translationRow, getSession());
}
}
/**
* INTERNAL:
* Prepare the receiver for execution in a session.
*/
protected void prePrepare() throws QueryException {
super.prePrepare();
//Bug#3947714 In case getSelectionObject() is proxy
if (getSelectionObject() != null && getSession().getProject().hasProxyIndirection()) {
setSelectionObject(ProxyIndirectionPolicy.getValueFromProxy(getSelectionObject()));
}
}
/**
* INTERNAL:
* All objects queried via a UnitOfWork get registered here. If the query
* went to the database.
* <p>
* Involves registering the query result individually and in totality, and
* hence refreshing / conforming is done here.
* @param result may be collection (read all) or an object (read one),
* or even a cursor. If in transaction the shared cache will
* be bypassed, meaning the result may not be originals from the parent
* but raw database rows.
* @param unitOfWork the unitOfWork the result is being registered in.
* @param arguments the original arguments/parameters passed to the query
* execution. Used by conforming
* @param buildDirectlyFromRows If in transaction must construct
* a registered result from raw database rows.
* @return the final (conformed, refreshed, wrapped) UnitOfWork query result
*/
public Object registerResultInUnitOfWork(Object result, UnitOfWorkImpl unitOfWork, AbstractRecord arguments, boolean buildDirectlyFromRows) {
if (result == null) {
return null;
}
if (unitOfWork.hasCloneMapping() // PERF: Avoid conforming empty uow.
&& (shouldConformResultsInUnitOfWork() || this.descriptor.shouldAlwaysConformResultsInUnitOfWork())) {
return conformResult(result, unitOfWork, arguments, buildDirectlyFromRows);
}
Object clone = null;
if (buildDirectlyFromRows) {
clone = buildObject((AbstractRecord)result);
} else {
clone = registerIndividualResult(result, null, unitOfWork, null, null);
}
if (shouldUseWrapperPolicy()) {
clone = this.descriptor.getObjectBuilder().wrapObject(clone, unitOfWork);
}
return clone;
}
protected Object remoteExecute() {
// Do a cache lookup.
checkDescriptor(session);
// As the selection object is transient, compute the key.
if (getSelectionObject() != null) {
// Must be checked separately as the expression and row is not yet set.
setSelectionId(getDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(getSelectionObject(), session));
}
Object cacheHit = checkEarlyReturn(getSession(), getTranslationRow());
if ((cacheHit != null) || shouldCheckCacheOnly()) {
if (cacheHit == InvalidObject.instance) {
return null;
}
return cacheHit;
}
return super.remoteExecute();
}
/**
* INTERNAL:
* replace the value holders in the specified result object(s)
*/
public Map replaceValueHoldersIn(Object object, RemoteSessionController controller) {
return controller.replaceValueHoldersIn(object);
}
/**
* INTERNAL:
* Return the primary key stored in this query
*
* @return the selection id of this ReadObjectQuery
*/
@Override
protected Object getQueryPrimaryKey(){
return getSelectionId();
}
/**
* PUBLIC:
* Return Id of the object to be selected by the query.
*/
public Object getSelectionId() {
return this.selectionId;
}
/**
* PUBLIC:
* The Id of the object to be selected by the query.
* This will generate a query by primary key.
* This can be used instead of an Expression, SQL, or JPQL, or example object.
* The Id is the Id value for a singleton primary key,
* for a composite it is an instance of CacheId.
* @see CacheId
*/
public void setSelectionId(Object id) {
this.selectionId = id;
}
/**
* PUBLIC:
* The primary key can be specified if used instead of an expression or selection object.
* If composite the primary must be in the same order as defined in the descriptor.
* @depreacted since EclipseLink 2.1, replaced by setSelectionId(Object)
* @see #setSelectionId(Object)
*/
@Deprecated
public void setSelectionKey(List selectionKey) {
if (selectionKey == null) {
this.selectionId = null;
} else if (selectionKey.size() == 1) {
this.selectionId = selectionKey.get(0);
} else {
this.selectionId = new CacheId(selectionKey.toArray());
}
}
/**
* PUBLIC:
* Used to set the where clause of the query.
* This can be used instead of a where clause expression for single object primary key queries.
* The selection object given should have a primary key defined,
* this primary key will be used to query the database instance of the same object.
* This is a basic form of query by example where only the primary key is required,
* it can be used for simple query forms, or testing.
*/
public void setSelectionObject(Object selectionObject) {
if (selectionObject == null) {
throw QueryException.selectionObjectCannotBeNull(this);
}
setSelectionId(null);
// Check if the query needs to be unprepared.
if ((this.selectionObject == null) || (this.selectionObject.getClass() != selectionObject.getClass())) {
setIsPrepared(false);
}
setReferenceClass(selectionObject.getClass());
this.selectionObject = selectionObject;
}
/**
* PUBLIC:
* Allow for the selection object of the query to be refreshed or put into the EclipseLink cache.
* By default on a read or refresh the object in the cache is refreshed and returned or a new object is built from the database,
* in some cases such as EJB BMP it is desirable to refresh or load into the object passed into the read object query.
* <p>Note: This forces the selection object into the cache a replaces any existing object that may already be there,
* this is a strict violation of object identity and other objects can still be referring to the old object.
*/
public void setShouldLoadResultIntoSelectionObject(boolean shouldLoadResultIntoSelectionObject) {
this.shouldLoadResultIntoSelectionObject = shouldLoadResultIntoSelectionObject;
}
/**
* PUBLIC:
* The primary key can be specified if used instead of an expression or selection object.
* @Depreacted since EclipseLink 2.1, replaced by setSelectionId(Object)
* @see #setSelectionId(Object)
*/
@Deprecated
public void setSingletonSelectionKey(Object selectionKey) {
Vector key = new NonSynchronizedVector();
key.add(selectionKey);
setSelectionKey(key);
}
/**
* PUBLIC:
* Return if cache should be checked.
*/
public boolean shouldCheckCacheByExactPrimaryKey() {
return this.cacheUsage == CheckCacheByExactPrimaryKey;
}
/**
* PUBLIC:
* Return if cache should be checked.
*/
public boolean shouldCheckCacheByPrimaryKey() {
return (this.cacheUsage == CheckCacheByPrimaryKey) || (this.cacheUsage == UseDescriptorSetting);
}
/**
* PUBLIC:
* Return if cache should be checked.
*/
public boolean shouldCheckCacheThenDatabase() {
return this.cacheUsage == CheckCacheThenDatabase;
}
/**
* PUBLIC:
* return true if the result should be loaded into the passed in selection Object
*/
public boolean shouldLoadResultIntoSelectionObject() {
return shouldLoadResultIntoSelectionObject;
}
/**
* INTERNAL:
* Return if the query has an non-default fetch group defined for itself.
*/
protected boolean hasNonDefaultFetchGroup() {
return this.descriptor.hasFetchGroupManager() && ((this.fetchGroup != null) || (this.fetchGroupName != null)
|| (!this.shouldUseDefaultFetchGroup && (this.descriptor.getFetchGroupManager().getDefaultFetchGroup() != null)));
}
}