| /* |
| * 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 |
| // 07/13/2012-2.5 Guy Pelletier |
| // - 350487: JPA 2.1 Specification defined support for Stored Procedure Calls |
| // 08/24/2012-2.5 Guy Pelletier |
| // - 350487: JPA 2.1 Specification defined support for Stored Procedure Calls |
| // 09/12/2018 - Will Dazey |
| // - 391279: Add support for Unidirectional OneToMany mappings with non-nullable values |
| package org.eclipse.persistence.internal.queries; |
| |
| import java.io.Serializable; |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Vector; |
| |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.descriptors.DescriptorEvent; |
| import org.eclipse.persistence.descriptors.DescriptorEventManager; |
| import org.eclipse.persistence.descriptors.DescriptorQueryManager; |
| import org.eclipse.persistence.descriptors.VersionLockingPolicy; |
| import org.eclipse.persistence.exceptions.DatabaseException; |
| import org.eclipse.persistence.exceptions.OptimisticLockException; |
| import org.eclipse.persistence.exceptions.QueryException; |
| import org.eclipse.persistence.expressions.Expression; |
| import org.eclipse.persistence.internal.databaseaccess.DatabaseCall; |
| import org.eclipse.persistence.internal.databaseaccess.DatasourceCall; |
| import org.eclipse.persistence.internal.descriptors.OptimisticLockingPolicy; |
| import org.eclipse.persistence.internal.helper.DatabaseField; |
| import org.eclipse.persistence.internal.sessions.AbstractRecord; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.internal.sessions.CommitManager; |
| import org.eclipse.persistence.internal.sessions.ObjectChangeSet; |
| import org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork; |
| import org.eclipse.persistence.internal.sessions.UnitOfWorkChangeSet; |
| import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl; |
| import org.eclipse.persistence.mappings.DatabaseMapping.WriteType; |
| import org.eclipse.persistence.queries.DatabaseQuery; |
| import org.eclipse.persistence.queries.DoesExistQuery; |
| import org.eclipse.persistence.queries.ModifyQuery; |
| import org.eclipse.persistence.queries.ObjectBuildingQuery; |
| import org.eclipse.persistence.queries.ReadObjectQuery; |
| import org.eclipse.persistence.queries.WriteObjectQuery; |
| import org.eclipse.persistence.sessions.DatabaseRecord; |
| import org.eclipse.persistence.tools.profiler.QueryMonitor; |
| |
| /** |
| * <p><b>Purpose</b>: |
| * Abstract class for all database query mechanism objects. |
| * DatabaseQueryMechanism is actually a helper class and currently is required |
| * for all types of queries. Most of the work performed by the query framework is |
| * performed in the query mechanism. The query mechanism contains the internal |
| * knowledge necessary to perform the specific database operation. |
| * </p> |
| * <p><b>Responsibilities</b>: |
| * Provide a common protocol for query mechanism objects. |
| * Provides all of the database specific work for the assigned query.</p> |
| * |
| * @author Yvon Lavoie |
| * @since TOPLink/Java 1.0 |
| */ |
| public abstract class DatabaseQueryMechanism implements Cloneable, Serializable { |
| |
| /** The database query that uses this mechanism. */ |
| protected DatabaseQuery query; |
| |
| /** |
| * Initialize the state of the query. |
| */ |
| protected DatabaseQueryMechanism() { |
| } |
| |
| /** |
| * Initialize the state of the query |
| * @param query - owner of mechanism |
| */ |
| protected DatabaseQueryMechanism(DatabaseQuery query) { |
| this.query = query; |
| } |
| |
| /** |
| * Add the initial write lock value to the row for insert. |
| */ |
| protected void addWriteLockFieldForInsert() { |
| if (getDescriptor().usesOptimisticLocking()) { |
| getDescriptor().getOptimisticLockingPolicy().setupWriteFieldsForInsert(getWriteObjectQuery()); |
| } |
| } |
| |
| /** |
| * Internal: |
| * In the case of EJBQL, an expression needs to be generated. Build the required expression. |
| */ |
| public void buildSelectionCriteria(AbstractSession session) { |
| // Default is do nothing |
| } |
| |
| /** |
| * Perform a cache lookup for the query. If the translation row contains |
| * all the parameters (which are part of the primary key) from the prepared |
| * call, then a cache check will be performed. |
| * |
| * If the object is found in the cache, return it; otherwise return null. |
| */ |
| public Object checkCacheForObject(AbstractRecord translationRow, AbstractSession session) { |
| // Null check added for CR#4295 - TW |
| if ((translationRow == null) || (translationRow.isEmpty())) { |
| return null; |
| } |
| |
| // Bug 5529564 |
| // The query wasn't prepared most likely because arguments were |
| // provided. Use them for the cache lookup. |
| List queryFields; |
| if (query.getCall() == null) { |
| // TODO: This is a bug, arguments is a list of Strings, not fields. |
| // Also if the call was null, it would be an expression, if it was not |
| // prepared the parameters would be empty. |
| queryFields = query.getArguments(); |
| } else { |
| // The query was prepared. Use the parameters of the call to look |
| // up the object in cache. |
| queryFields = query.getCall().getParameters(); |
| } |
| |
| ClassDescriptor descriptor = getDescriptor(); |
| List<DatabaseField> primaryKeyFields = descriptor.getPrimaryKeyFields(); |
| |
| // Check that the query is by primary key. |
| for (DatabaseField primaryKeyField : primaryKeyFields) { |
| if (!queryFields.contains(primaryKeyField)) { |
| return null; |
| } |
| } |
| Object primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromRow(translationRow, session); |
| if (primaryKey == null) { |
| return null; |
| } |
| |
| if (query.isObjectBuildingQuery() && ((ObjectBuildingQuery)query).requiresDeferredLocks() || descriptor.shouldAcquireCascadedLocks()) { |
| return session.getIdentityMapAccessorInstance().getFromIdentityMapWithDeferredLock(primaryKey, getReadObjectQuery().getReferenceClass(), false, descriptor); |
| } else { |
| return session.getIdentityMapAccessorInstance().getFromLocalIdentityMap(primaryKey, getReadObjectQuery().getReferenceClass(), false, descriptor); |
| } |
| } |
| |
| /** |
| * Clone the mechanism |
| */ |
| @Override |
| public Object clone() { |
| try { |
| return super.clone(); |
| } catch (CloneNotSupportedException e) { |
| throw new InternalError(); |
| } |
| } |
| |
| /** |
| * Clone the mechanism for the specified query clone. |
| */ |
| public DatabaseQueryMechanism clone(DatabaseQuery queryClone) { |
| DatabaseQueryMechanism clone = (DatabaseQueryMechanism)clone(); |
| clone.setQuery(queryClone); |
| return clone; |
| } |
| |
| /** |
| * Read all rows from the database using a cursored stream. |
| * @exception DatabaseException - an error has occurred on the database |
| */ |
| public abstract DatabaseCall cursorSelectAllRows() throws DatabaseException; |
| |
| /** |
| * Delete a collection of objects |
| * This should be overridden by subclasses. |
| * @exception DatabaseException - an error has occurred on the database |
| */ |
| public boolean isJPQLCallQueryMechanism() { |
| return false; |
| } |
| |
| public abstract Integer deleteAll() throws DatabaseException; |
| |
| /** |
| * Delete an object |
| * This should be overridden by subclasses. |
| * @return the row count. |
| */ |
| public abstract Integer deleteObject() throws DatabaseException; |
| |
| /** |
| * Execute a execute SQL call. |
| * This should be overridden by subclasses. |
| * @return true if the first result is a result set and false if it is an |
| * update count or there are no results other than through INOUT and OUT |
| * parameterts, if any. |
| */ |
| public abstract Object execute() throws DatabaseException; |
| |
| /** |
| * Execute a non selecting SQL call |
| * This should be overridden by subclasses. |
| * @return the row count. |
| */ |
| public abstract Integer executeNoSelect() throws DatabaseException; |
| |
| /** |
| * Execute a select SQL call and return the rows. |
| * This should be overriden by subclasses. |
| */ |
| public abstract Vector executeSelect() throws DatabaseException; |
| |
| /** |
| * Check whether the object already exists on the database; then |
| * perform an insert or update, as appropriate. |
| * This write is used for non-unit of work (old-commit) operations. |
| * Return the object being written. |
| */ |
| public Object executeWrite() throws DatabaseException, OptimisticLockException { |
| WriteObjectQuery writeQuery = getWriteObjectQuery(); |
| Object object = writeQuery.getObject(); |
| CommitManager commitManager = getSession().getCommitManager(); |
| |
| // if the object has already been committed, no work is required |
| if (commitManager.isCommitCompletedInPostOrIgnore(object)) { |
| return object; |
| } |
| |
| // check whether the object is already being committed - |
| // if it is and it is new, then a shallow insert must be done |
| if (commitManager.isCommitInPreModify(object)) { |
| shallowInsertObjectForWrite(object, writeQuery, commitManager); |
| return object; |
| } |
| |
| try { |
| getSession().beginTransaction(); |
| |
| if (writeQuery.getObjectChangeSet() == null) { |
| // PERF: Avoid events if no listeners. |
| if (getDescriptor().getEventManager().hasAnyEventListeners()) { |
| // only throw the events if there is no changeset otherwise the event will be thrown twice |
| // once by the calculate changes code and here |
| getDescriptor().getEventManager().executeEvent(new DescriptorEvent(DescriptorEventManager.PreWriteEvent, writeQuery)); |
| } |
| } |
| writeQuery.executeCommit(); |
| |
| // PERF: Avoid events if no listeners. |
| if (getDescriptor().getEventManager().hasAnyEventListeners()) { |
| getDescriptor().getEventManager().executeEvent(new DescriptorEvent(DescriptorEventManager.PostWriteEvent, writeQuery)); |
| } |
| |
| getSession().commitTransaction(); |
| |
| // notify the commit manager of the completion to the commit |
| commitManager.markCommitCompleted(object); |
| |
| return object; |
| |
| } catch (RuntimeException exception) { |
| getSession().rollbackTransaction(); |
| commitManager.markCommitCompleted(object); |
| throw exception; |
| } |
| } |
| |
| /** |
| * Execute the call that was deferred to the commit manager. |
| * This is used to allow multiple table batching and deadlock avoidance. |
| */ |
| public void executeDeferredCall(DatasourceCall call) { |
| // Do nothing by default. |
| } |
| |
| /** |
| * Check whether the object already exists on the cadatabase; then |
| * perform an insert or update, as appropriate. |
| * This method was moved here, from WriteObjectQuery.execute(), |
| * so we can hide the source. |
| * Return the object being written. |
| */ |
| public Object executeWriteWithChangeSet() throws DatabaseException, OptimisticLockException { |
| WriteObjectQuery writeQuery = getWriteObjectQuery(); |
| ObjectChangeSet objectChangeSet = writeQuery.getObjectChangeSet(); |
| ClassDescriptor descriptor = getDescriptor(); |
| AbstractSession session = getSession(); |
| CommitManager commitManager = session.getCommitManager(); |
| Object object = writeQuery.getObject(); |
| // If there are no changes then there is no work required |
| // Check for forcedUpdate Version and Optimistic read lock (hasForcedChanges() set in ObjectChangePolicy) |
| if (!objectChangeSet.hasChanges() && !objectChangeSet.hasForcedChanges()) { |
| commitManager.markCommitCompleted(object); |
| return object; |
| } |
| // If the object has already been committed, no work is required |
| // need to check for the object to ensure insert wasn't completed already. |
| if (commitManager.isCommitCompletedInPostOrIgnore(object)) { |
| return object; |
| } |
| try { |
| writeQuery.executeCommitWithChangeSet(); |
| |
| // PERF: Avoid events if no listeners. |
| if (descriptor.getEventManager().hasAnyEventListeners()) { |
| descriptor.getEventManager().executeEvent(new DescriptorEvent(DescriptorEventManager.PostWriteEvent, writeQuery)); |
| } |
| |
| // Notify the commit manager of the completion to the commit. |
| commitManager.markCommitCompleted(object); |
| |
| return object; |
| |
| } catch (RuntimeException exception) { |
| commitManager.markCommitCompleted(object); |
| throw exception; |
| } |
| } |
| |
| /** |
| * Convenience method |
| */ |
| protected ClassDescriptor getDescriptor() { |
| return this.query.getDescriptor(); |
| } |
| |
| /** |
| * Convenience method |
| */ |
| public AbstractRecord getModifyRow() { |
| if (this.query.isModifyQuery()) { |
| return ((ModifyQuery)this.query).getModifyRow(); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Return the query that uses the mechanism. |
| */ |
| public DatabaseQuery getQuery() { |
| return query; |
| } |
| |
| /** |
| * Convenience method |
| */ |
| protected ReadObjectQuery getReadObjectQuery() { |
| return (ReadObjectQuery)this.query; |
| } |
| |
| /** |
| * Return the selection criteria for the mechanism. |
| * By default this is null. This method exists because both statement and expression |
| * mechanisms use an expression and some code in the mappings depends on returning this. |
| */ |
| public Expression getSelectionCriteria() { |
| return null; |
| } |
| |
| /** |
| * Convenience method |
| */ |
| protected AbstractSession getSession() { |
| return this.query.getSession(); |
| } |
| |
| /** |
| * Convenience method |
| */ |
| protected AbstractSession getExecutionSession() { |
| return this.query.getExecutionSession(); |
| } |
| |
| /** |
| * Convenience method |
| */ |
| protected AbstractRecord getTranslationRow() { |
| return this.query.getTranslationRow(); |
| } |
| |
| /** |
| * Convenience method |
| */ |
| protected WriteObjectQuery getWriteObjectQuery() { |
| return (WriteObjectQuery)this.query; |
| } |
| |
| /** |
| * Insert an object. |
| */ |
| public abstract void insertObject() throws DatabaseException; |
| |
| /** |
| * Insert an object and provide the opportunity to reprepare prior to the insert. |
| * This will be overridden |
| * CR#3237 |
| */ |
| public void insertObject(boolean reprepare) { |
| insertObject(); |
| } |
| |
| /** |
| * Insert an object in the database. |
| * This is used for both uow and non-uow (old-commit and change-set) operations. |
| */ |
| public void insertObjectForWrite() { |
| WriteObjectQuery writeQuery = getWriteObjectQuery(); |
| ClassDescriptor descriptor = getDescriptor(); |
| DescriptorQueryManager queryManager = descriptor.getQueryManager(); |
| boolean isFKUpdate = false; // Bug 319276 |
| |
| // check for user-defined query |
| if ((!writeQuery.isUserDefined())// this is not a user-defined query |
| && queryManager.hasInsertQuery()// there is a user-defined query |
| && isExpressionQueryMechanism()) {// this is not a hand-coded call (custom SQL etc.) |
| performUserDefinedInsert(); |
| return; |
| } |
| Object object = writeQuery.getObject(); |
| AbstractSession session = writeQuery.getSession(); |
| ObjectChangeSet changeSet = writeQuery.getObjectChangeSet(); |
| CommitManager commitManager = session.getCommitManager(); |
| DescriptorEventManager eventManager = descriptor.getEventManager(); |
| |
| // This must be done after the custom query check, otherwise it will be done twice. |
| commitManager.markPreModifyCommitInProgress(object); |
| |
| if (changeSet == null) { |
| // PERF: Avoid events if no listeners. |
| if (eventManager.hasAnyEventListeners()) { |
| // only throw the events if there is no changeset otherwise the event will be thrown twice |
| // once by the calculate changes code and here |
| eventManager.executeEvent(new DescriptorEvent(DescriptorEventManager.PreInsertEvent, writeQuery)); |
| } |
| } |
| |
| // check whether deep shallow modify is turned on |
| if (writeQuery.shouldCascadeParts()) { |
| queryManager.preInsert(writeQuery); |
| } |
| |
| // In a unit of work/writeObjects the preInsert may have caused a shallow insert of this object, |
| // in this case this second write must do an update. |
| if (commitManager.isShallowCommitted(object)) { |
| isFKUpdate = true; // Bug 319276 |
| updateForeignKeyFieldAfterInsert(); |
| } else { |
| AbstractRecord modifyRow = writeQuery.getModifyRow(); |
| if (modifyRow == null) {// Maybe have been passed in as in aggregate collection. |
| if (writeQuery.shouldCascadeParts()) { |
| writeQuery.setModifyRow(descriptor.getObjectBuilder().buildRow(object, session, WriteType.INSERT)); |
| } else { |
| writeQuery.setModifyRow(descriptor.getObjectBuilder().buildRowForShallowInsert(object, session)); |
| } |
| } else { |
| if (writeQuery.shouldCascadeParts()) { |
| writeQuery.setModifyRow(descriptor.getObjectBuilder().buildRow(modifyRow, object, session, WriteType.INSERT)); |
| } else { |
| writeQuery.setModifyRow(descriptor.getObjectBuilder().buildRowForShallowInsert(modifyRow, object, session)); |
| } |
| } |
| modifyRow = getModifyRow(); |
| // the modify row and the translation row are the same for insert |
| writeQuery.setTranslationRow(modifyRow); |
| if (!descriptor.isAggregateCollectionDescriptor()) {// Should/cannot be recomputed in aggregate collection. |
| writeQuery.setPrimaryKey(descriptor.getObjectBuilder().extractPrimaryKeyFromObject(object, session)); |
| } |
| addWriteLockFieldForInsert(); |
| if (descriptor.hasSerializedObjectPolicy()) { |
| descriptor.getSerializedObjectPolicy().putObjectIntoRow(modifyRow, object, session); |
| } |
| |
| // CR#3237 |
| // Store the size of the modify row so we can determine if the user has added to the row in the insert. |
| int modifyRowSize = modifyRow.size(); |
| |
| // PERF: Avoid events if no listeners. |
| if (eventManager.hasAnyEventListeners()) { |
| DescriptorEvent event = new DescriptorEvent(DescriptorEventManager.AboutToInsertEvent, writeQuery); |
| event.setRecord(modifyRow); |
| eventManager.executeEvent(event); |
| } |
| |
| if (QueryMonitor.shouldMonitor()) { |
| QueryMonitor.incrementInsert(writeQuery); |
| } |
| // CR#3237 |
| // Call insert with a boolean that tells it to reprepare if the user has altered the modify row. |
| insertObject(modifyRowSize != modifyRow.size()); |
| |
| // register the object before post insert to resolve possible cycles |
| registerObjectInIdentityMap(object, descriptor, session); |
| } |
| |
| commitManager.markPostModifyCommitInProgress(object); |
| // Verify if deep shallow modify is turned on. |
| if (writeQuery.shouldCascadeParts()) { |
| queryManager.postInsert(writeQuery); |
| } |
| |
| if ((descriptor.getHistoryPolicy() != null) && descriptor.getHistoryPolicy().shouldHandleWrites()) { |
| if (isFKUpdate) { // Bug 319276 |
| descriptor.getHistoryPolicy().postUpdate(writeQuery, true); |
| } |
| else { |
| descriptor.getHistoryPolicy().postInsert(writeQuery); |
| } |
| } |
| |
| // PERF: Avoid events if no listeners. |
| if (eventManager.hasAnyEventListeners()) { |
| eventManager.executeEvent(new DescriptorEvent(DescriptorEventManager.PostInsertEvent, writeQuery)); |
| } |
| } |
| |
| /** |
| * Return true if this is a call query mechanism |
| */ |
| public boolean isCallQueryMechanism() { |
| return false; |
| } |
| |
| /** |
| * Return true if this is an expression query mechanism |
| */ |
| public boolean isExpressionQueryMechanism() { |
| return false; |
| } |
| |
| /** |
| * Return true if this is a query by example mechanism |
| */ |
| public boolean isQueryByExampleMechanism() { |
| return false; |
| } |
| |
| /** |
| * Return true if this is a statement query mechanism |
| */ |
| public boolean isStatementQueryMechanism() { |
| return false; |
| } |
| |
| /** |
| * Insert the object using the user defined query. |
| * This ensures that the query is cloned and prepared correctly. |
| */ |
| protected void performUserDefinedInsert() { |
| performUserDefinedWrite(getDescriptor().getQueryManager().getInsertQuery()); |
| } |
| |
| /** |
| * Update the object using the user defined query. |
| * This ensures that the query is cloned and prepared correctly. |
| */ |
| protected void performUserDefinedUpdate() { |
| performUserDefinedWrite(getDescriptor().getQueryManager().getUpdateQuery()); |
| } |
| |
| /** |
| * Write the object using the specified user-defined query. |
| * This ensures that the query is cloned and prepared correctly. |
| */ |
| protected void performUserDefinedWrite(WriteObjectQuery userDefinedWriteQuery) { |
| WriteObjectQuery query = getWriteObjectQuery(); |
| userDefinedWriteQuery.checkPrepare(query.getSession(), query.getTranslationRow()); |
| |
| WriteObjectQuery writeQuery = (WriteObjectQuery)userDefinedWriteQuery.clone(); |
| writeQuery.setIsExecutionClone(true); |
| writeQuery.setObject(query.getObject()); |
| writeQuery.setObjectChangeSet(query.getObjectChangeSet()); |
| writeQuery.setCascadePolicy(query.getCascadePolicy()); |
| writeQuery.setShouldMaintainCache(query.shouldMaintainCache()); |
| writeQuery.setTranslationRow(query.getTranslationRow()); |
| writeQuery.setModifyRow(query.getModifyRow()); |
| writeQuery.setPrimaryKey(query.getPrimaryKey()); |
| writeQuery.setSession(query.getSession()); |
| |
| // If there is a changeset, the change set method must be used. |
| if (writeQuery.getObjectChangeSet() != null) { |
| writeQuery.executeCommitWithChangeSet(); |
| } else { |
| writeQuery.executeCommit(); |
| } |
| } |
| |
| /** |
| * This is different from 'prepareForExecution()' |
| * in that this is called on the original query, |
| * and the other is called on the clone of the query. |
| * This query is copied for concurrency so this prepare can only setup things that |
| * will apply to any future execution of this query. |
| */ |
| public void prepare() throws QueryException { |
| // the default is to do nothing |
| } |
| |
| /** |
| * Pre-pare for a cursored execute. |
| * This is sent to the original query before cloning. |
| */ |
| public abstract void prepareCursorSelectAllRows() throws QueryException; |
| |
| /** |
| * Prepare for a delete all. |
| * This is sent to the original query before cloning. |
| */ |
| public abstract void prepareDeleteAll() throws QueryException; |
| |
| /** |
| * Prepare for a delete. |
| * This is sent to the original query before cloning. |
| */ |
| public abstract void prepareDeleteObject() throws QueryException; |
| |
| /** |
| * Pre-pare for a select execute. |
| * This is sent to the original query before cloning. |
| */ |
| public abstract void prepareDoesExist(DatabaseField field) throws QueryException; |
| |
| /** |
| * Prepare for a raw (non-object), non-selecting call. |
| * This is sent to the original query before cloning. |
| */ |
| public abstract void prepareExecuteNoSelect() throws QueryException; |
| |
| /** |
| * Prepare for a raw execute call. |
| * This is sent to the original query before cloning. |
| */ |
| public abstract void prepareExecute() throws QueryException; |
| |
| /** |
| * Prepare for a raw (non-object) select call. |
| * This is sent to the original query before cloning. |
| */ |
| public abstract void prepareExecuteSelect() throws QueryException; |
| |
| /** |
| * Prepare for an insert. |
| * This is sent to the original query before cloning. |
| */ |
| public abstract void prepareInsertObject() throws QueryException; |
| |
| /** |
| * Pre-pare for a select execute. |
| * This is sent to the original query before cloning. |
| */ |
| public abstract void prepareReportQuerySelectAllRows() throws QueryException; |
| |
| /** |
| * Pre-pare a report query for a sub-select. |
| */ |
| public abstract void prepareReportQuerySubSelect() throws QueryException; |
| |
| /** |
| * Prepare for a select returning (possibly) multiple rows. |
| * This is sent to the original query before cloning. |
| */ |
| public abstract void prepareSelectAllRows() throws QueryException; |
| |
| /** |
| * Prepare for a select returning a single row. |
| * This is sent to the original query before cloning. |
| */ |
| public abstract void prepareSelectOneRow() throws QueryException; |
| |
| /** |
| * Prepare for an update. |
| * This is sent to the original query before cloning. |
| */ |
| public abstract void prepareUpdateObject() throws QueryException; |
| |
| /** |
| * Prepare for an update all. |
| * This is sent to the original query before cloning. |
| */ |
| public abstract void prepareUpdateAll() throws QueryException; |
| |
| /** |
| * Store the query object in the identity map. |
| */ |
| protected void registerObjectInIdentityMap(Object object, ClassDescriptor descriptor, AbstractSession session) { |
| WriteObjectQuery query = getWriteObjectQuery(); |
| if (query.shouldMaintainCache()) { |
| if (descriptor.usesOptimisticLocking()) { |
| Object optimisticLockValue = descriptor.getOptimisticLockingPolicy().getValueToPutInCache(query.getModifyRow(), session); |
| session.getIdentityMapAccessorInstance().putInIdentityMap(object, query.getPrimaryKey(), optimisticLockValue, System.currentTimeMillis(), descriptor); |
| } else { |
| session.getIdentityMapAccessorInstance().putInIdentityMap(object, query.getPrimaryKey(), null, System.currentTimeMillis(), descriptor); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Read all rows from the database. |
| */ |
| public abstract Vector selectAllReportQueryRows() throws DatabaseException; |
| |
| /** |
| * Read and return rows from the database. |
| */ |
| public abstract Vector selectAllRows() throws DatabaseException; |
| |
| /** |
| * Read and return a row from the database. |
| */ |
| public abstract AbstractRecord selectOneRow() throws DatabaseException; |
| |
| /** |
| * Read and return a row from the database for an existence check. |
| */ |
| public abstract AbstractRecord selectRowForDoesExist(DatabaseField field) throws DatabaseException; |
| |
| /** |
| * Set the query that uses this mechanism. |
| */ |
| public void setQuery(DatabaseQuery query) { |
| this.query = query; |
| } |
| |
| /** |
| * Shallow insert the specified object, if necessary. |
| */ |
| protected void shallowInsertObjectForWrite(Object object, WriteObjectQuery writeQuery, CommitManager commitManager) throws DatabaseException, OptimisticLockException { |
| boolean doesExist; |
| |
| if (getSession().isUnitOfWork()) { |
| UnitOfWorkImpl uow = (UnitOfWorkImpl)getSession(); |
| doesExist = !uow.isCloneNewObject(object); |
| if (doesExist) { |
| doesExist = uow.isObjectRegistered(object); |
| } |
| } else { |
| // clone and initialize the does exist query |
| DoesExistQuery existQuery = (DoesExistQuery)getDescriptor().getQueryManager().getDoesExistQuery().clone(); |
| existQuery.setObject(object); |
| existQuery.setPrimaryKey(writeQuery.getPrimaryKey()); |
| existQuery.setDescriptor(getDescriptor()); |
| existQuery.setTranslationRow(getTranslationRow()); |
| |
| doesExist = (Boolean) getSession().executeQuery(existQuery); |
| } |
| |
| if (!doesExist) { |
| // a shallow insert must be performed |
| writeQuery.dontCascadeParts(); |
| insertObjectForWrite(); |
| // mark this object as shallow committed so that the insert will do an update |
| commitManager.markShallowCommit(object); |
| } |
| } |
| |
| /** |
| * Update the foreign key fields when resolving a bi-directonal reference in a UOW. |
| * This must always be dynamic as it is called within an insert query and is really part of the insert |
| * and does not fire update events or worry about locking. |
| */ |
| protected void updateForeignKeyFieldAfterInsert() { |
| WriteObjectQuery writeQuery = getWriteObjectQuery(); |
| Object object = writeQuery.getObject(); |
| |
| writeQuery.setPrimaryKey(getDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(object, getSession())); |
| // reset the translation row because the insert has occurred and the id has |
| // been assigned to the object, but not the row |
| writeQuery.setTranslationRow(getDescriptor().getObjectBuilder().buildRowForTranslation(object, getSession())); |
| |
| updateForeignKeyFieldAfterInsert(writeQuery); |
| } |
| |
| /** |
| * Issue update SQL statement |
| */ |
| public abstract Integer updateAll() throws DatabaseException; |
| |
| /** |
| * Update an object. |
| * Return the row count. |
| */ |
| public abstract Integer updateObject() throws DatabaseException; |
| |
| /** |
| * Update the foreign key fields when resolving a bi-directonal reference in a UOW. |
| * This must always be dynamic as it is called within an insert query and is really part of the insert |
| * and does not fire update events or worry about locking. |
| */ |
| protected abstract void updateForeignKeyFieldAfterInsert(WriteObjectQuery writeQuery); |
| |
| /** |
| * Update the foreign key fields to null when resolving a deletion cycle. |
| * This must always be dynamic as it is called within an delete query and is really part of the delete |
| * and does not fire update events or worry about locking. |
| */ |
| public void updateForeignKeyFieldBeforeDelete() { |
| // Nothing by default. |
| } |
| |
| protected void updateObjectAndRowWithReturnRow(Collection returnFields, boolean isFirstCallForInsert) { |
| WriteObjectQuery writeQuery = getWriteObjectQuery(); |
| AbstractRecord outputRow = (AbstractRecord)writeQuery.getProperties().get("output"); |
| if ((outputRow == null) || outputRow.isEmpty()) { |
| return; |
| } |
| AbstractRecord row = new DatabaseRecord(); |
| for (Iterator iterator = returnFields.iterator(); iterator.hasNext();) { |
| DatabaseField field = (DatabaseField)iterator.next(); |
| if (outputRow.containsKey(field)) { |
| row.put(field, outputRow.get(field)); |
| } |
| } |
| if (row.isEmpty()) { |
| return; |
| } |
| |
| Object object = writeQuery.getObject(); |
| |
| ObjectChangeSet objectChangeSet = null; |
| if (getSession().isUnitOfWork()) { |
| objectChangeSet = writeQuery.getObjectChangeSet(); |
| if ((objectChangeSet == null) && (((UnitOfWorkImpl)getSession()).getUnitOfWorkChangeSet() != null)) { |
| objectChangeSet = (ObjectChangeSet)((UnitOfWorkImpl)getSession()).getUnitOfWorkChangeSet().getObjectChangeSetForClone(object); |
| } |
| } |
| getDescriptor().getObjectBuilder().assignReturnRow(object, writeQuery.getSession(), row, objectChangeSet); |
| |
| Object primaryKey = null; |
| if (isFirstCallForInsert) { |
| AbstractRecord pkToModify = new DatabaseRecord(); |
| List<DatabaseField> primaryKeyFields = getDescriptor().getPrimaryKeyFields(); |
| for (int i = 0; i < primaryKeyFields.size(); i++) { |
| DatabaseField field = primaryKeyFields.get(i); |
| if (row.containsKey(field)) { |
| pkToModify.put(field, row.get(field)); |
| } |
| } |
| if (!pkToModify.isEmpty()) { |
| primaryKey = getDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(object, getSession()); |
| writeQuery.setPrimaryKey(primaryKey); |
| // Now I need to update the row |
| getModifyRow().putAll(pkToModify); |
| getDescriptor().getObjectBuilder().addPrimaryKeyForNonDefaultTable(getModifyRow(), object, getSession()); |
| } |
| } |
| |
| if (objectChangeSet != null) { |
| if (primaryKey != null) { |
| objectChangeSet.setId(primaryKey); |
| } |
| } |
| } |
| |
| /** |
| * Update the object's primary key by fetching a new sequence number from the accessor. |
| */ |
| protected void updateObjectAndRowWithSequenceNumber() throws DatabaseException { |
| WriteObjectQuery writeQuery = getWriteObjectQuery(); |
| writeQuery.getDescriptor().getObjectBuilder().assignSequenceNumber(writeQuery); |
| } |
| |
| /** |
| * Update the object. |
| * This is only used for non-unit-of-work updates. |
| */ |
| public void updateObjectForWrite() { |
| WriteObjectQuery writeQuery = getWriteObjectQuery(); |
| ClassDescriptor descriptor = getDescriptor(); |
| DescriptorQueryManager queryManager = descriptor.getQueryManager(); |
| // check for user-defined query |
| if ((!writeQuery.isUserDefined())// this is not a user-defined query |
| && queryManager.hasUpdateQuery()// there is a user-defined query |
| && isExpressionQueryMechanism()) {// this is not a hand-coded call (custom SQL etc.) |
| performUserDefinedUpdate(); |
| return; |
| } |
| Object object = writeQuery.getObject(); |
| AbstractSession session = getSession(); |
| CommitManager commitManager = session.getCommitManager(); |
| // This must be done after the custom query check, otherwise it will be done twice. |
| commitManager.markPreModifyCommitInProgress(object); |
| DescriptorEventManager eventManager = descriptor.getEventManager(); |
| if (writeQuery.getObjectChangeSet() == null) { |
| // PERF: Avoid events if no listeners. |
| if (eventManager.hasAnyEventListeners()) { |
| // only throw the events if there is no changeset otherwise the event will be thrown twice |
| // once by the calculate changes code and here |
| eventManager.executeEvent(new DescriptorEvent(DescriptorEventManager.PreUpdateEvent, writeQuery)); |
| } |
| } |
| |
| // Verify if deep shallow modify is turned on |
| if (writeQuery.shouldCascadeParts()) { |
| queryManager.preUpdate(writeQuery); |
| } |
| |
| // The row must not be built until after preUpdate in case the object reference has changed. |
| // For a user defined update in the uow to row must be built twice to check if any update is required. |
| if ((writeQuery.isUserDefined() || writeQuery.isCallQuery()) && (!getSession().isUnitOfWork())) { |
| writeQuery.setModifyRow(descriptor.getObjectBuilder().buildRow(object, getSession(), WriteType.UNDEFINED)); |
| } else { |
| writeQuery.setModifyRow(descriptor.getObjectBuilder().buildRowForUpdate(writeQuery)); |
| } |
| |
| // Optimistic read lock implementation |
| Boolean shouldModifyVersionField = null; |
| if (session.isUnitOfWork() && ((UnitOfWorkImpl)session).hasOptimisticReadLockObjects()) { |
| shouldModifyVersionField = (Boolean)((UnitOfWorkImpl)session).getOptimisticReadLockObjects().get(writeQuery.getObject()); |
| } |
| |
| if (!getModifyRow().isEmpty() || (shouldModifyVersionField != null) || ((descriptor.getCMPPolicy() != null) && (descriptor.getCMPPolicy().getForceUpdate()))) { |
| // If user defined the entire row is required. Must not be built until change is known. |
| if ((writeQuery.isUserDefined() || writeQuery.isCallQuery()) && getSession().isUnitOfWork()) { |
| writeQuery.setModifyRow(descriptor.getObjectBuilder().buildRow(object, getSession(), WriteType.UNDEFINED)); |
| } |
| |
| // Update the write lock field if required. |
| if (descriptor.usesOptimisticLocking()) { |
| OptimisticLockingPolicy policy = descriptor.getOptimisticLockingPolicy(); |
| policy.addLockValuesToTranslationRow(writeQuery); |
| |
| if (!getModifyRow().isEmpty() || shouldModifyVersionField) { |
| // Update the row with newer lock value. |
| policy.updateRowAndObjectForUpdate(writeQuery, object); |
| } else if (!shouldModifyVersionField && (policy instanceof VersionLockingPolicy)) { |
| // Add the existing write lock value to the for a "read" lock (requires something to update). |
| ((VersionLockingPolicy)policy).writeLockValueIntoRow(writeQuery, object); |
| } |
| } |
| if (descriptor.hasSerializedObjectPolicy()) { |
| descriptor.getSerializedObjectPolicy().putObjectIntoRow(getModifyRow(), object, session); |
| } |
| |
| // PERF: Avoid events if no listeners. |
| if (eventManager.hasAnyEventListeners()) { |
| DescriptorEvent event = new DescriptorEvent(DescriptorEventManager.AboutToUpdateEvent, writeQuery); |
| event.setRecord(getModifyRow()); |
| eventManager.executeEvent(event); |
| } |
| |
| if (QueryMonitor.shouldMonitor()) { |
| QueryMonitor.incrementUpdate(getWriteObjectQuery()); |
| } |
| int rowCount = updateObject(); |
| |
| if (rowCount < 1) { |
| if (session.hasEventManager()) { |
| session.getEventManager().noRowsModified(writeQuery, object); |
| } |
| } |
| if (descriptor.usesOptimisticLocking()) { |
| descriptor.getOptimisticLockingPolicy().validateUpdate(rowCount, object, writeQuery); |
| } |
| } |
| |
| commitManager.markPostModifyCommitInProgress(object); |
| |
| // Verify if deep shallow modify is turned on |
| if (writeQuery.shouldCascadeParts()) { |
| queryManager.postUpdate(writeQuery); |
| } |
| if ((descriptor.getHistoryPolicy() != null) && descriptor.getHistoryPolicy().shouldHandleWrites()) { |
| descriptor.getHistoryPolicy().postUpdate(writeQuery); |
| } |
| |
| // PERF: Avoid events if no listeners. |
| if (eventManager.hasAnyEventListeners()) { |
| eventManager.executeEvent(new DescriptorEvent(DescriptorEventManager.PostUpdateEvent, writeQuery)); |
| } |
| } |
| |
| /** |
| * Update the object. |
| * This is used by the unit-of-work update. |
| */ |
| public void updateObjectForWriteWithChangeSet() { |
| WriteObjectQuery writeQuery = getWriteObjectQuery(); |
| ObjectChangeSet changeSet = writeQuery.getObjectChangeSet(); |
| Object object = writeQuery.getObject(); |
| ClassDescriptor descriptor = getDescriptor(); |
| DescriptorQueryManager queryManager = descriptor.getQueryManager(); |
| AbstractSession session = getSession(); |
| CommitManager commitManager = session.getCommitManager(); |
| // check for user-defined query |
| if ((!writeQuery.isUserDefined())// this is not a user-defined query |
| && queryManager.hasUpdateQuery()// there is a user-defined query |
| && isExpressionQueryMechanism()) {// this is not a hand-coded call (custom SQL etc.) |
| // This must be done here because the user defined update does not use a changeset so it will not be set otherwise |
| commitManager.markPreModifyCommitInProgress(object); |
| performUserDefinedUpdate(); |
| return; |
| } |
| // This must be done after the custom query check, otherwise it will be done twice. |
| commitManager.markPreModifyCommitInProgress(object); |
| DescriptorEventManager eventManager = descriptor.getEventManager(); |
| |
| if (changeSet.hasChanges()) { |
| // PERF: Avoid events if no listeners. |
| if (eventManager.hasAnyEventListeners()) { |
| DescriptorEvent event = new DescriptorEvent(DescriptorEventManager.PreUpdateWithChangesEvent, writeQuery); |
| eventManager.executeEvent(event); |
| |
| // PreUpdateWithChangesEvent listeners may have altered the object - should recalculate the change set. |
| UnitOfWorkChangeSet uowChangeSet = (UnitOfWorkChangeSet)((UnitOfWorkImpl)session).getUnitOfWorkChangeSet(); |
| if (!uowChangeSet.isChangeSetFromOutsideUOW() && writeQuery.getObjectChangeSet().shouldRecalculateAfterUpdateEvent()){ |
| // writeQuery.getObjectChangeSet() is mapped to object in uowChangeSet. |
| // It is first cleared then re-populated by calculateChanges method. |
| if (!descriptor.getObjectChangePolicy().isAttributeChangeTrackingPolicy() ){ |
| // clear the change set without clearing the maps keys since they are not alterable by the event |
| // if the map is changed, it will be changed in the owning object and the |
| // change set will be changed there as well. |
| writeQuery.getObjectChangeSet().clear(false); |
| } |
| if (descriptor.getObjectChangePolicy().calculateChangesForExistingObject(object, uowChangeSet, ((UnitOfWorkImpl)session), descriptor, false) == null) { |
| // calculateChanges returns null in case the changeSet doesn't have changes. |
| // It should be removed from the list of ObjectChangeSets that have changes in uowChangeSet. |
| uowChangeSet.getAllChangeSets().remove(writeQuery.getObjectChangeSet()); |
| } |
| } |
| } |
| } |
| |
| // Verify if deep shallow modify is turned on |
| if (writeQuery.shouldCascadeParts()) { |
| queryManager.preUpdate(writeQuery); |
| } |
| |
| // The row must not be built until after preUpdate in case the object reference has changed. |
| // For a user defined update in the uow to row must be built twice to check if any update is required. |
| writeQuery.setModifyRow(descriptor.getObjectBuilder().buildRowForUpdateWithChangeSet(writeQuery)); |
| |
| Boolean shouldModifyVersionField = changeSet.shouldModifyVersionField(); |
| if (!getModifyRow().isEmpty() || shouldModifyVersionField != null || changeSet.hasCmpPolicyForcedUpdate()) { |
| // If user defined the entire row is required. Must not be built until change is known. |
| if (writeQuery.isUserDefined() || writeQuery.isCallQuery()) { |
| writeQuery.setModifyRow(descriptor.getObjectBuilder().buildRow(object, session, WriteType.UNDEFINED)); |
| } |
| OptimisticLockingPolicy lockingPolicy = descriptor.getOptimisticLockingPolicy(); |
| |
| // Update the write lock field if required. |
| if (lockingPolicy != null) { |
| lockingPolicy.addLockValuesToTranslationRow(writeQuery); |
| // Do not lock an object that has previously been optimistically locked within the RWUoW |
| boolean existingOptimisticLock = false; |
| if (session instanceof RepeatableWriteUnitOfWork) { |
| RepeatableWriteUnitOfWork uow = (RepeatableWriteUnitOfWork)session; |
| if (uow.getOptimisticReadLockObjects().get(object) != null && uow.getCumulativeUOWChangeSet() != null |
| && uow.getCumulativeUOWChangeSet().getObjectChangeSetForClone(object) != null) { |
| existingOptimisticLock = true; |
| } |
| } |
| if (!existingOptimisticLock) { |
| // update the row and object if shouldModifyVersionField is non null and has a value of true (a forced update), |
| // or if there is no forced update and modifyRow has modifications |
| if ((shouldModifyVersionField != null && shouldModifyVersionField) || !getModifyRow().isEmpty()) { |
| // Update the row with newer lock value. |
| lockingPolicy.updateRowAndObjectForUpdate(writeQuery, object); |
| } else if (!shouldModifyVersionField && (lockingPolicy instanceof VersionLockingPolicy)) { |
| // Add the existing write lock value to the for a "read" lock (requires something to update). |
| ((VersionLockingPolicy)lockingPolicy).writeLockValueIntoRow(writeQuery, object); |
| } |
| } |
| } |
| if (descriptor.hasSerializedObjectPolicy()) { |
| descriptor.getSerializedObjectPolicy().putObjectIntoRow(getModifyRow(), object, session); |
| } |
| |
| // PERF: Avoid events if no listeners. |
| if (eventManager.hasAnyEventListeners()) { |
| DescriptorEvent event = new DescriptorEvent(DescriptorEventManager.AboutToUpdateEvent, writeQuery); |
| event.setRecord(getModifyRow()); |
| eventManager.executeEvent(event); |
| } |
| |
| if (QueryMonitor.shouldMonitor()) { |
| QueryMonitor.incrementUpdate(getWriteObjectQuery()); |
| } |
| int rowCount = updateObject(); |
| |
| if (rowCount < 1) { |
| if (session.hasEventManager()) { |
| session.getEventManager().noRowsModified(writeQuery, object); |
| } |
| } |
| if (lockingPolicy != null) { |
| lockingPolicy.validateUpdate(rowCount, object, writeQuery); |
| } |
| } |
| |
| commitManager.markPostModifyCommitInProgress(object); |
| |
| // Verify if deep shallow modify is turned on |
| if (writeQuery.shouldCascadeParts()) { |
| queryManager.postUpdate(writeQuery); |
| } |
| if ((descriptor.getHistoryPolicy() != null) && descriptor.getHistoryPolicy().shouldHandleWrites()) { |
| descriptor.getHistoryPolicy().postUpdate(writeQuery); |
| } |
| |
| // PERF: Avoid events if no listeners. |
| if (eventManager.hasAnyEventListeners()) { |
| eventManager.executeEvent(new DescriptorEvent(DescriptorEventManager.PostUpdateEvent, writeQuery)); |
| } |
| } |
| |
| /** |
| * Unprepare the call if required. |
| */ |
| public void unprepare() { |
| |
| } |
| } |