/* Hibernate, Relational Persistence for Idiomatic Java * * SPDX-License-Identifier: LGPL-2.1-or-later * Copyright: Red Hat Inc. and Hibernate Authors */ package org.hibernate.reactive.event.impl; import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.NonUniqueObjectException; import org.hibernate.action.internal.AbstractEntityInsertAction; import org.hibernate.engine.internal.CascadePoint; import org.hibernate.engine.internal.Versioning; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityEntryExtraState; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SelfDirtinessTracker; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.Status; import org.hibernate.event.internal.WrapVisitor; import org.hibernate.event.spi.EventSource; import org.hibernate.id.IdentifierGenerationException; import org.hibernate.id.IdentifierGenerator; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.jpa.event.spi.CallbackRegistry; import org.hibernate.jpa.event.spi.CallbackRegistryConsumer; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; import org.hibernate.reactive.engine.impl.Cascade; import org.hibernate.reactive.engine.impl.CascadingAction; import org.hibernate.reactive.engine.impl.ReactiveEntityIdentityInsertAction; import org.hibernate.reactive.engine.impl.ReactiveEntityRegularInsertAction; import org.hibernate.reactive.id.ReactiveIdentifierGenerator; import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.type.IntegerType; import org.hibernate.type.LongType; import org.hibernate.type.Type; import org.hibernate.type.TypeHelper; import java.io.Serializable; import java.util.Map; import java.util.concurrent.CompletionStage; /** * Functionality common to persist and merge event listeners. * * @see DefaultReactivePersistEventListener * @see DefaultReactivePersistOnFlushEventListener * @see DefaultReactiveMergeEventListener */ abstract class AbstractReactiveSaveEventListener<C> implements CallbackRegistryConsumer { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( AbstractReactiveSaveEventListener.class ); private CallbackRegistry callbackRegistry; public void injectCallbackRegistry(CallbackRegistry callbackRegistry) { this.callbackRegistry = callbackRegistry; } /** * Prepares the save call using the given requested id. * * @param entity The entity to be saved. * @param requestedId The id to which to associate the entity. * @param entityName The name of the entity being saved. * @param context Generally cascade-specific information. * @param source The session which is the source of this save event. * * @return The id used to save the entity. */ protected CompletionStage<Void> reactiveSaveWithRequestedId( Object entity, Serializable requestedId, String entityName, C context, EventSource source) { callbackRegistry.preCreate( entity ); return reactivePerformSave( entity, requestedId, source.getEntityPersister( entityName, entity ), false, context, source, true ); } private static Serializable assignIdIfNecessary(Object generatedId, Object entity, String entityName, EventSource source) { EntityPersister persister = source.getEntityPersister(entityName, entity); if ( generatedId != null ) { if (generatedId instanceof Long) { Long longId = (Long) generatedId; Type identifierType = persister.getIdentifierType(); if (identifierType == LongType.INSTANCE) { return longId; } else if (identifierType == IntegerType.INSTANCE) { return longId.intValue(); } else { throw new HibernateException("cannot generate identifiers of type " + identifierType.getReturnedClass().getSimpleName() + " for: " + entityName); } } else { return (Serializable) generatedId; } } else { Serializable assignedId = persister.getIdentifier( entity, source.getSession() ); if (assignedId == null) { throw new IdentifierGenerationException("ids for this class must be manually assigned before calling save(): " + entityName); } return assignedId; } } /** * Prepares the save call using a newly generated id. * * @param entity The entity to be saved * @param entityName The entity-name for the entity to be saved * @param context Generally cascade-specific information. * @param source The session which is the source of this save event. * @param requiresImmediateIdAccess does the event context require * access to the identifier immediately after execution of this method (if * not, post-insert style id generators may be postponed if we are outside * a transaction). * * @return The id used to save the entity; may be null depending on the * type of id generator used and the requiresImmediateIdAccess value */ protected CompletionStage<Void> reactiveSaveWithGeneratedId( Object entity, String entityName, C context, EventSource source, boolean requiresImmediateIdAccess) { callbackRegistry.preCreate( entity ); if ( entity instanceof SelfDirtinessTracker ) { ( (SelfDirtinessTracker) entity ).$$_hibernate_clearDirtyAttributes(); } EntityPersister persister = source.getEntityPersister( entityName, entity ); boolean autoincrement = persister.isIdentifierAssignedByInsert(); return generateId( entity, source, persister ) .thenCompose(id -> reactivePerformSave( entity, autoincrement ? null : assignIdIfNecessary( id, entity, entityName, source ), persister, autoincrement, context, source, !autoincrement || requiresImmediateIdAccess ) ); } private static CompletionStage<?> generateId(Object entity, EventSource source, EntityPersister persister) { IdentifierGenerator generator = persister.getIdentifierGenerator(); return generator instanceof ReactiveIdentifierGenerator ? ( (ReactiveIdentifierGenerator<?>) generator ).generate( (ReactiveSession) source, entity ) : CompletionStages.completedFuture( generator.generate( source.getSession(), entity ) ); } /** * Prepares the save call by checking the session caches for a pre-existing * entity and performing any lifecycle callbacks. * * @param entity The entity to be saved. * @param id The id by which to save the entity. * @param persister The entity's persister instance. * @param useIdentityColumn Is an identity column being used? * @param context Generally cascade-specific information. * @param source The session from which the event originated. * @param requiresImmediateIdAccess does the event context require * access to the identifier immediately after execution of this method (if * not, post-insert style id generators may be postponed if we are outside * a transaction). * * @return The id used to save the entity; may be null depending on the * type of id generator used and the requiresImmediateIdAccess value */ protected CompletionStage<Void> reactivePerformSave( Object entity, Serializable id, EntityPersister persister, boolean useIdentityColumn, C context, EventSource source, boolean requiresImmediateIdAccess) { if ( LOG.isTraceEnabled() ) { LOG.tracev( "Saving {0}", MessageHelper.infoString( persister, id, source.getFactory() ) ); } final EntityKey key; if ( !useIdentityColumn ) { key = source.generateEntityKey( id, persister ); final PersistenceContext persistenceContext = source.getPersistenceContextInternal(); Object old = persistenceContext.getEntity( key ); if ( old != null ) { if ( persistenceContext.getEntry( old ).getStatus() == Status.DELETED ) { source.forceFlush( persistenceContext.getEntry( old ) ); } else { return CompletionStages.failedFuture( new NonUniqueObjectException( id, persister.getEntityName() ) ); } } persister.setIdentifier( entity, id, source ); } else { key = null; } return reactivePerformSaveOrReplicate( entity, key, persister, useIdentityColumn, context, source, requiresImmediateIdAccess ); } /** * Performs all the actual work needed to save an entity (well to get the save moved to * the execution queue). * * @param entity The entity to be saved * @param key The id to be used for saving the entity (or null, in the case of identity columns) * @param persister The entity's persister instance. * @param useIdentityColumn Should an identity column be used for id generation? * @param context Generally cascade-specific information. * @param source The session which is the source of the current event. * @param requiresImmediateIdAccess Is access to the identifier required immediately * after the completion of the save? persist(), for example, does not require this... * * @return The id used to save the entity; may be null depending on the * type of id generator used and the requiresImmediateIdAccess value */ protected CompletionStage<Void> reactivePerformSaveOrReplicate( Object entity, EntityKey key, EntityPersister persister, boolean useIdentityColumn, C context, EventSource source, boolean requiresImmediateIdAccess) { Serializable id = key == null ? null : key.getIdentifier(); boolean inTreactive = source.isTransactionInProgress(); boolean shouldDelayIdentityInserts = !inTreactive && !requiresImmediateIdAccess; final PersistenceContext persistenceContext = source.getPersistenceContextInternal(); // Put a placeholder in entries, so we don't recurse back and try to save() the // same object again. QUESTION: should this be done before onSave() is called? // likewise, should it be done before onUpdate()? EntityEntry original = persistenceContext.addEntry( entity, Status.SAVING, null, null, id, null, LockMode.WRITE, useIdentityColumn, persister, false ); return cascadeBeforeSave( source, persister, entity, context ) .thenCompose(v -> { // We have to do this after cascadeBeforeSave completes, // since it could result in generation of parent ids, // which we will need as foreign keys in the insert Object[] values = persister.getPropertyValuesToInsert( entity, getMergeMap( context ), source ); Type[] types = persister.getPropertyTypes(); boolean substitute = substituteValuesIfNecessary( entity, id, values, persister, source ); if ( persister.hasCollections() ) { substitute = substitute || visitCollectionsBeforeSave( entity, id, values, types, source ); } if ( substitute ) { persister.setPropertyValues( entity, values ); } TypeHelper.deepCopy( values, types, persister.getPropertyUpdateability(), values, source ); CompletionStage<AbstractEntityInsertAction> insert = addInsertAction( values, id, entity, persister, useIdentityColumn, source, shouldDelayIdentityInserts ); EntityEntry newEntry = persistenceContext.getEntry( entity ); if ( newEntry != original ) { EntityEntryExtraState extraState = newEntry.getExtraState( EntityEntryExtraState.class ); if ( extraState == null ) { newEntry.addExtraState( original.getExtraState( EntityEntryExtraState.class ) ); } } return insert; } ) .thenCompose( vv -> cascadeAfterSave( source, persister, entity, context ) ); // .thenAccept( v -> { // postpone initializing id in case the insert has non-nullable transient dependencies // that are not resolved until cascadeAfterSave() is executed // Serializable newId = id; // if ( useIdentityColumn && insert.isEarlyInsert() ) { // if ( !EntityIdentityInsertAction.class.isInstance( insert ) ) { // throw new IllegalStateException( // "Insert should be using an identity column, but action is of unexpected type: " + // insert.getClass().getName() // ); // } // newId = ( (EntityIdentityInsertAction) insert ).getGeneratedId(); // // insert.handleNaturalIdPostSaveNotifications( newId ); // } // return newId; // } ); } protected Map<?,?> getMergeMap(Object context) { return null; } private CompletionStage<AbstractEntityInsertAction> addInsertAction( Object[] values, Serializable id, Object entity, EntityPersister persister, boolean useIdentityColumn, EventSource source, boolean shouldDelayIdentityInserts) { if ( useIdentityColumn ) { ReactiveEntityIdentityInsertAction insert = new ReactiveEntityIdentityInsertAction( values, entity, persister, false, source, shouldDelayIdentityInserts ); return source.unwrap(ReactiveSession.class) .getReactiveActionQueue() .addAction( insert ) .thenApply( v -> insert ); } else { Object version = Versioning.getVersion( values, persister ); ReactiveEntityRegularInsertAction insert = new ReactiveEntityRegularInsertAction( id, values, entity, version, persister, false, source ); return source.unwrap(ReactiveSession.class) .getReactiveActionQueue() .addAction( insert ) .thenApply( v -> insert ); } } /** * Handles the calls needed to perform pre-save cascades for the given entity. * * @param source The session from whcih the save event originated. * @param persister The entity's persister instance. * @param entity The entity to be saved. * @param context Generally cascade-specific data */ protected CompletionStage<Void> cascadeBeforeSave( EventSource source, EntityPersister persister, Object entity, C context) { // cascade-save to many-to-one BEFORE the parent is saved return new Cascade<>( getCascadeReactiveAction(), CascadePoint.BEFORE_INSERT_AFTER_DELETE, persister, entity, context, source ).cascade(); } /** * Handles to calls needed to perform post-save cascades. * * @param source The session from which the event originated. * @param persister The entity's persister instance. * @param entity The entity beng saved. * @param context Generally cascade-specific data */ protected CompletionStage<Void> cascadeAfterSave( EventSource source, EntityPersister persister, Object entity, C context) { // cascade-save to collections AFTER the collection owner was saved return new Cascade<>( getCascadeReactiveAction(), CascadePoint.AFTER_INSERT_BEFORE_DELETE, persister, entity, context, source ).cascade(); } protected abstract CascadingAction<C> getCascadeReactiveAction(); /** * Perform any property value substitution that is necessary * (interceptor callback, version initialization...) * * @param entity The entity * @param id The entity identifier * @param values The snapshot entity state * @param persister The entity persister * @param source The originating session * * @return True if the snapshot state changed such that * reinjection of the values into the entity is required. */ protected boolean substituteValuesIfNecessary( Object entity, Serializable id, Object[] values, EntityPersister persister, SessionImplementor source) { boolean substitute = source.getInterceptor().onSave( entity, id, values, persister.getPropertyNames(), persister.getPropertyTypes() ); //keep the existing version number in the case of replicate! if ( persister.isVersioned() ) { substitute = Versioning.seedVersion( values, persister.getVersionProperty(), persister.getVersionType(), source ) || substitute; } return substitute; } protected boolean visitCollectionsBeforeSave( Object entity, Serializable id, Object[] values, Type[] types, EventSource source) { WrapVisitor visitor = new WrapVisitor( entity, id, source ); // substitutes into values by side-effect visitor.processEntityPropertyValues( values, types ); return visitor.isSubstitutionRequired(); } }