/******************************************************************************* * Copyright (c) 1998, 2015 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 * 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 ******************************************************************************/ 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. * * @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. */ public DatabaseQueryMechanism() { } /** * Initialize the state of the query * @param query - owner of mechanism */ public 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 */ 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. * @exception DatabaseException * @return the row count. */ public abstract Integer deleteObject() throws DatabaseException; /** * Execute a execute SQL call. * This should be overridden by subclasses. * @exception DatabaseException * @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. * @exception DatabaseException * @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. * @exception DatabaseException */ 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)).booleanValue(); } 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 primaryKeyFields = getDescriptor().getPrimaryKeyFields(); for (int i = 0; i < primaryKeyFields.size(); i++) { DatabaseField field = (DatabaseField)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.booleanValue()) { // Update the row with newer lock value. policy.updateRowAndObjectForUpdate(writeQuery, object); } else if (!shouldModifyVersionField.booleanValue() && (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().intValue(); 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().intValue(); 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() { } }