Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Overview of OperationContext design
Note: There are some details that need to be discussed. I will create a separate gist
for those details shortly.
POC: https://github.com/gbadner/hibernate-core/tree/HHH-10478-OperationContext
Design Overview
An OperationContext is intended to allow data important to an operation that is currently in progress,
that is needed in different areas of hibernate ORM code base. It allows data to be accessible without
having to pass it along as an argument from method to method to where it is needed.
So far, there is an OperationContext for each event listener that cascades . The term "operation" is
used insted of "event" because it may be useful to create an OperationContext for other types of
operations that are not events.
Here is org.hibernate.engine.operationContext.spi.OperationContext:
public interface OperationContext {
/**
* Gets the operation context type.
* @return the operation context type.
*/
OperationContextType getOperationContextType();
}
These are OperationContext types in org.hibernate.engine.operationContext.spi.OperationContext<T>
(T indicates the OperationContext interface for the corresponding OperationContextType):
public static final OperationContextType<PersistOperationContext> PERSIST ...
public static final OperationContextType<SaveOrUpdateOperationContext> SAVE_OR_UPDATE ...
public static final OperationContextType<MergeOperationContext> MERGE ...
public static final OperationContextType<LockOperationContext> LOCK ...
public static final OperationContextType<DeleteOperationContext> DELETE ...
public static final OperationContextType<RefreshOperationContext> REFRESH ...
public static final OperationContextType<ReplicateOperationContext> REPLICATE ...
Most OperationContext sub-interfaces are very simple, for example
org.hibernate.engine.operationContext.spi.LockOperationContext:
public interface LockOperationContext extends OperationContext {
LockOptions getLockOptions();
}
The most interesting is org.hibernate.engine.operationContext.spi.MergeOperationContext
(which is no longer a Map implementation):
public interface MergeOperationContext extends OperationContext {
/**
* Returns true if the {@link org.hibernate.event.spi.MergeEventListener} is actively
* performing or has already performed the merge operation on the specified merge entity.
* <p/>
* An entity is considered to be "in the merge process" if <code>mergeEntity</code>
* was added using {@link #addMergeData(EntityStatus mergeEntityStatus, Object mergeEntity, Object entityCopy)},
* or if added using {@link #addTransientMergeDataBeforeInMergeProcess(Object, Object)}
* followed by {@link #markTransientMergeDataInMergeProcess}.
*
* @param mergeEntity - the merge entity; must be non-null.
* returns true if <code>mergeEntity</code> was added using
* {@link #addMergeData(EntityStatus mergeEntityStatus, Object mergeEntity, Object entityCopy)},
* or if added using {@link #addTransientMergeDataBeforeInMergeProcess(Object, Object)}
* followed by {@link #markTransientMergeDataInMergeProcess}; false, otherwise.
* @throws IllegalArgumentException if <code>mergeEntity</code> is null.
* @throws IllegalStateException if a merge operation is not currently
* in progress.
*/
boolean isInMergeProcess(Object mergeEntity);
/**
* Returns the entity copy associated with the specified merge Entity.
*
* @param mergeEntity the merge entity; must be non-null
* @return the entity copy associated with the specified merge entity,
* or null if this {@link MergeOperationContext} does not contain
* a cross-reference for <code>mergeEntity</code>.
*
* @throws IllegalArgumentException if mergeEntity is null
* @throws IllegalStateException if a merge operation is not currently
* in progress.
*/
Object getEntityCopyFromMergeEntity(Object mergeEntity);
/**
* Returns the merge entity associated with the specified entity copy.
*
* @param entityCopy the entity copy; must be non-null
* @return the merge entity associated with the specified entity copy,
* or null if this {@link MergeOperationContext} does not contain
* a cross-reference for <code>entityCopy</code>.
*
* @throws IllegalArgumentException if mergeEntity is null
* @throws IllegalStateException if a merge operation is not currently
* in progress.
*/
Object getMergeEntityFromEntityCopy(Object entityCopy);
/**
* Gets the status of the merge entity. The status of the entity copy
* may not be the same as for the merge entity (e.g., a transient
* mergeEntity may have a corresponding entity copy that is persistent).
*
* @param mergeEntity the merge entity; must be non-null.
* @return the status.
*
* @throws IllegalArgumentException if mergeEntity is null or
* this {@link MergeOperationContext} does not contain
* a cross-reference for <code>mergeEntity</code>.
* @throws IllegalStateException if a merge operation is not currently
* in progress.
*/
EntityStatus getMergeEntityStatus(Object mergeEntity);
/**
* Associates the specified merge entity with the specified entity copy in this
* {@link MergeOperationContext}. This method should only be used when the merge
* entity is actively being merged (e.g., by
* {@link org.hibernate.event.spi.MergeEventListener#onMerge(MergeEvent)}.
* <p/>
* If this {@link MergeOperationContext} already
* contains a cross-reference for <code>mergeEntity</code> when this
* method is called, then <code>entityCopy</code> must be the same
* as what is already associated with <code>mergeEntity</code>.
*
* @param mergeEntityStatus the merge entity status; must be non-null.
* @param mergeEntity - the merge entity; must be non-null.
* @param entityCopy - the entity copy; must be non-null.
*
* @return true, if the merge entity and entity copy cross-reference was added
* to this {@link MergeOperationContext}; false, otherwise.
*
* @throws IllegalArgumentException if <code>mergeEntityStatus</code>,
* <code>mergeEntity</code> or <code>entityCopy</code> is null, or
* <code>entityCopy</code> is not the same as the previous entity copy
* associated with <code>mergeEntity</code>
* @throws IllegalStateException if a merge operation is not currently
* in progress.
*/
boolean addMergeData(EntityStatus mergeEntityStatus, Object mergeEntity, Object entityCopy);
/**
* Associates the specified transient merge entity with the specified entity copy
* in this {@link MergeOperationContext}. This method should only be used in cases
* where the entity copy needs to be created before the merge operation cascades to
* merge entity.
* <p/>
* @param mergeEntity - the transient merge entity; must be non-null.
* @param entityCopy - the entity copy; must be non-null.
*
* @throws IllegalArgumentException if <code>mergeEntity</code> or <code>entityCopy</code> is null, or
* @throws IllegalStateException if {@link MergeOperationContext} already contains
* <code>mergeEntity</code> or <code>entityCopy</code>, or if a merge operation is
* not currently in progress.
*/
void addTransientMergeDataBeforeInMergeProcess(Object mergeEntity, Object entityCopy);
/**
* Indicate that the listener is performing the merge operation on a transient
* merge entity that had previously been added before it was in the merge
* process.
*
* specified merge entity.
*
* @param mergeEntity; the merge entity; must be non-null.
* .
* @throws IllegalArgumentException if <code>mergeEntity</code> is null.
* @throws IllegalStateException if the previously added merge entity is
* already in the merge process, this MergeContext does not contain a
* cross-reference for <code>mergeEntity</code>, or if a merge operation is not currently
* in progress.
*/
void markTransientMergeDataInMergeProcess(Object mergeEntity);
/**
* Gets an unmodifiable collection of all associated merge entity / entity copy pairs.
* If there more than 1 merge entity that is associated with the same entity copy, then
* the returned collection will contain a separate {@link MergeData} element for each merge
* entity.
* <p/>
* Teh collection will include {@link MergeData} for any merge entity / entity copy
* pairs addded using {@link #addTransientMergeDataBeforeInMergeProcess(Object, Object)}.
*
* @return an unmodifiable collection of all associated merge entity / entity copy pairs.
*
* @throws IllegalStateException if a merge operation is not currently
* in progress.
*/
Collection<MergeData> getAllMergeData();
}
New methods are added to SessionImplementor for obtaining an OperationContext of a particular type
that is "in progress". For example, a MergeOperationContext is put "in progress" before starting
to execute the MergeEventListeners, and is no longer "in progress" after the MergeEventListeners
complete (more about how OperationContexts are managed below...)
Added to SessionImplementor :
/**
* Indicates if an {@link OperationContext} of the specified type is already
* in progress.
*
* @param operationContextType - the operation context type.
* @return true, if an {@link OperationContext} of the specified type is already
* in progress; false, otherwise.
*/
boolean isOperationInProgress(OperationContextType operationContextType);
/**
* Gets the {@link OperationContext} of the specified type for the operation
* currently in progress.
* <p/>
* The requested {@link OperationContext} must be in progress. Callers should
* use {@link #isOperationInProgress(OperationContextType)} to ensure the
* specified type of {@link OperationContext} is in progress before calling
* this method.
*
* @return the {@link OperationContext} of the specified type.
* @throws IllegalStateException if the {@link OperationContext} is not in progress.
<T extends OperationContext> T getOperationContext(OperationContextType<T> operationContextType);
SessionImpl delegates OperationContext management to
org.hibernate.engine.operationContext.internal.OperationContextManager:
@Override
public boolean isOperationInProgress(OperationContextType operationContextType) {
return operationContextManager.isOperationInProgress( operationContextType );
}
@Override
public <T extends OperationContext> T getOperationContext(OperationContextType<T> operationContextType) {
return operationContextManager.getOperationContextInProgress( operationContextType );
}
SessionImpl also delegates operation execution to OperationContextManager by providing an OperationExecutor.
public interface OperationExecutor<T> {
/**
* Gets data required by the {@link OperationContext}.
*
* @return data required by the {@link OperationContext}
*/
T getOperationContextData();
/**
* Executes the operation.
*/
void execute();
}
For example, here is SessionImpl#fireMerge:
private Object fireMerge(final MergeEvent event) {
errorIfClosed();
checkTransactionSynchStatus();
final OperationExecutor<MergeEvent> executor = new OperationExecutor<MergeEvent>() {
@Override
public MergeEvent getOperationContextData() {
return event;
}
@Override
public void execute() {
for ( MergeEventListener listener : listeners( EventType.MERGE ) ) {
listener.onMerge( event );
}
delayedAfterCompletion();
}
};
operationContextManager.merge( executor );
return event.getResult();
}
OperationContextManager instantiates, tracks, and manages the OperationContext objects,
which implement org.hibernate.engine.operationContext.internal.ManageableOperationContext.
public interface ManageableOperationContext<T> extends OperationContext {
/**
* Indicates if the operation is currently in progress.
*
* @return {@code true}, if the operation is in progress; {@code false}, otherwise.
*/
boolean isInProgress();
/**
* Called just before starting the operation. This method should not
* be called if the operation is already in progress (i.e., when
* {@link #isInProgress()} returns {@code true}).
* <p/>
* Implementations that do integrity checks should throw
* {@link IllegalStateException} on failure.
* <p/>
* After this method completes, {@link #isInProgress} should return {@code true}.
*
* @param operationContextData - the "top-level" operationContextData being processed;
* must be non-null.
*
* @throws IllegalStateException if the operation is already in progress
* (i.e., {@link #isInProgress()} returns {@code true}), or if an
* integrity check fails.
* @throws IllegalArgumentException if {@code operationContextData} is null.
*/
void beforeOperation(T operationContextData);
/**
* Called just after the operation completes. This method should
* not be called if the operation failed.
* <p/>
* Implementations may do integrity checks if {@code success} is {@code true}.
* If an integrity check fails, {@link IllegalStateException} should be thrown.
* <p/>
* Resources held by implementations will be cleared by calling {@link #clear()}
* (even when {@code success} is {@code false}).
* <p/>
* After this method completes, {@link #isInProgress} should return {@code false}.
*
* @param operationContextData - the same operationContextData as used when
* {@link #beforeOperation was called.
* @param success - {@code true}, if the operation was successful; {@code false}, otherwise.
*
* @throws IllegalStateException if the operation is not in progress,
* {@code operationContextData} is not the same operationContextData as used
* when {@link #beforeOperation} was called, or if an integrity check fails.
* @throws IllegalArgumentException if {@code operationContextData} is null.
*/
void afterOperation(T operationContextData, boolean success);
/**
* Clears operation-specific data. After the method executes {@link #isInProgress()}
* will return {@code false}.
*/
void clear();
}
OperationContextManager executes operations as follows:
private <T> void executeOperation(
ManageableOperationContext<T> operationContext,
OperationExecutor<T> operationExecutor) {
final boolean isTopLevel = isTopLevel( operationContext );
if ( isTopLevel ) {
operationContext.beforeOperation( operationExecutor.getOperationContextData() );
}
boolean success = false;
try {
operationExecutor.execute();
success = true;
}
finally {
if ( isTopLevel ) {
operationContext.afterOperation(
operationExecutor.getOperationContextData(),
success
);
}
}
}
NOTE: the proper way to determine if an operation is at the top-level needs to be better defined.
I will adress those details separately.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment