/* 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.session.impl; import org.hibernate.CacheMode; import org.hibernate.FlushMode; import org.hibernate.HibernateException; import org.hibernate.JDBCException; import org.hibernate.LazyInitializationException; import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.MappingException; import org.hibernate.ObjectDeletedException; import org.hibernate.ObjectNotFoundException; import org.hibernate.TypeMismatchException; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.dialect.Dialect; import org.hibernate.engine.ResultSetMappingDefinition; import org.hibernate.engine.internal.StatefulPersistenceContext; import org.hibernate.engine.query.spi.HQLQueryPlan; import org.hibernate.engine.query.spi.QueryPlanCache; import org.hibernate.engine.query.spi.sql.NativeSQLQueryCollectionReturn; import org.hibernate.engine.query.spi.sql.NativeSQLQueryConstructorReturn; import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn; import org.hibernate.engine.query.spi.sql.NativeSQLQueryRootReturn; import org.hibernate.engine.query.spi.sql.NativeSQLQueryScalarReturn; import org.hibernate.engine.query.spi.sql.NativeSQLQuerySpecification; import org.hibernate.engine.spi.NamedQueryDefinition; import org.hibernate.engine.spi.NamedSQLQueryDefinition; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.event.internal.MergeContext; import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.spi.AutoFlushEvent; import org.hibernate.event.spi.DeleteEvent; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.EventType; import org.hibernate.event.spi.FlushEvent; import org.hibernate.event.spi.InitializeCollectionEvent; import org.hibernate.event.spi.LoadEvent; import org.hibernate.event.spi.LoadEventListener; import org.hibernate.event.spi.LockEvent; import org.hibernate.event.spi.MergeEvent; import org.hibernate.event.spi.PersistEvent; import org.hibernate.event.spi.RefreshEvent; import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.RootGraph; import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.internal.SessionCreationOptions; import org.hibernate.internal.SessionFactoryImpl; import org.hibernate.internal.SessionImpl; import org.hibernate.internal.util.LockModeConverter; import org.hibernate.internal.util.collections.IdentitySet; import org.hibernate.jpa.QueryHints; import org.hibernate.jpa.spi.CriteriaQueryTupleTransformer; import org.hibernate.jpa.spi.NativeQueryTupleTransformer; import org.hibernate.loader.custom.CustomQuery; import org.hibernate.loader.custom.sql.SQLCustomQuery; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.MultiLoadOptions; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; import org.hibernate.query.ParameterMetadata; import org.hibernate.query.Query; import org.hibernate.query.internal.ParameterMetadataImpl; import org.hibernate.reactive.common.ResultSetMapping; import org.hibernate.reactive.engine.impl.ReactivePersistenceContextAdapter; import org.hibernate.reactive.engine.ReactiveActionQueue; import org.hibernate.reactive.event.impl.DefaultReactiveAutoFlushEventListener; import org.hibernate.reactive.event.impl.DefaultReactiveInitializeCollectionEventListener; import org.hibernate.reactive.event.ReactiveDeleteEventListener; import org.hibernate.reactive.event.ReactiveFlushEventListener; import org.hibernate.reactive.event.ReactiveLoadEventListener; import org.hibernate.reactive.event.ReactiveLockEventListener; import org.hibernate.reactive.event.ReactiveMergeEventListener; import org.hibernate.reactive.event.ReactivePersistEventListener; import org.hibernate.reactive.event.ReactiveRefreshEventListener; import org.hibernate.reactive.loader.custom.impl.ReactiveCustomLoader; import org.hibernate.reactive.mutiny.Mutiny; import org.hibernate.reactive.mutiny.impl.MutinySessionImpl; import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister; import org.hibernate.reactive.pool.ReactiveConnection; import org.hibernate.reactive.session.Criteria; import org.hibernate.reactive.session.CriteriaQueryOptions; import org.hibernate.reactive.session.ReactiveNativeQuery; import org.hibernate.reactive.session.ReactiveQuery; import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.reactive.stage.Stage; import org.hibernate.reactive.stage.impl.StageSessionImpl; import org.hibernate.reactive.util.impl.CompletionStages; import javax.persistence.EntityNotFoundException; import javax.persistence.Tuple; import javax.persistence.metamodel.Attribute; import java.io.Serializable; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletionStage; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; /** * An {@link ReactiveSession} implemented by extension of * the {@link SessionImpl} in Hibernate core. Extension was * preferred to delegation because there are places where * Hibernate core compares the identity of session instances. */ public class ReactiveSessionImpl extends SessionImpl implements ReactiveSession, EventSource { private transient ReactiveActionQueue reactiveActionQueue = new ReactiveActionQueue( this ); private final ReactiveConnection reactiveConnection; public ReactiveSessionImpl(SessionFactoryImpl delegate, SessionCreationOptions options, ReactiveConnection connection) { super( delegate, options ); reactiveConnection = connection; } @Override public SharedSessionContractImplementor getSharedContract() { return this; } @Override public Dialect getDialect() { return getJdbcServices().getDialect(); } @Override protected StatefulPersistenceContext createPersistenceContext() { return new ReactivePersistenceContextAdapter( this ); } @Override public ReactiveActionQueue getReactiveActionQueue() { return reactiveActionQueue; } @Override public Object immediateLoad(String entityName, Serializable id) throws HibernateException { throw new LazyInitializationException("reactive sessions do not support transparent lazy fetching" + " - use Session.fetch() (entity '" + entityName + "' with id '" + id + "' was not loaded)"); } @Override public <T> CompletionStage<T> reactiveFetch(T association, boolean unproxy) { checkOpen(); if ( association instanceof HibernateProxy ) { LazyInitializer initializer = ((HibernateProxy) association).getHibernateLazyInitializer(); //TODO: is this correct? // SessionImpl doesn't use IdentifierLoadAccessImpl for initializing proxies String entityName = initializer.getEntityName(); Serializable identifier = initializer.getIdentifier(); return new ReactiveIdentifierLoadAccessImpl<T>( entityName ) .fetch( identifier ) .thenApply( SessionUtil.checkEntityFound( this, entityName, identifier ) ) .thenApply( entity -> { initializer.setSession( this ); initializer.setImplementation( entity ); return unproxy ? entity : association; } ); } else if ( association instanceof PersistentCollection ) { PersistentCollection persistentCollection = (PersistentCollection) association; return reactiveInitializeCollection( persistentCollection, false ) .thenApply( pc -> association ); } else { return CompletionStages.completedFuture( association ); } } @Override public <E,T> CompletionStage<T> reactiveFetch(E entity, Attribute<E,T> field) { return ( (ReactiveEntityPersister) getEntityPersister( null, entity ) ) .reactiveInitializeLazyProperty( field, entity, this ); } @Override public <T> ReactiveNativeQueryImpl<T> createReactiveNativeQuery(String sqlString) { return getReactiveNativeQueryImplementor( sqlString, false ); } @Override public <T> ReactiveNativeQuery<T> createReactiveNativeQuery(String sqlString, String resultSetMapping) { checkOpen(); pulseTransactionCoordinator(); delayedAfterCompletion(); try { ReactiveNativeQuery<T> query = createReactiveNativeQuery( sqlString ); query.setResultSetMapping( resultSetMapping ); return query; } catch ( RuntimeException he ) { throw getExceptionConverter().convert( he ); } } @Override public <T> ResultSetMapping<T> getResultSetMapping(Class<T> resultType, String mappingName) { ResultSetMappingDefinition mapping = getFactory().getNamedQueryRepository() .getResultSetMappingDefinition( mappingName ); if (mapping==null) { throw new IllegalArgumentException("result set mapping does not exist: " + mappingName); } if ( resultType!=null ) { Class<?> mappedResultType = getResultType(mapping); if ( !resultType.equals(mappedResultType) ) { throw new IllegalArgumentException("incorrect result type for result set mapping: " + mappingName + " has type " + mappedResultType.getName() ); } } return new ResultSetMapping<T>() { @Override public String getName() { return mappingName; } @Override public Class<T> getResultType() { return resultType; } }; } private Class<?> getResultType(ResultSetMappingDefinition mapping) { Class<?> mappedResultType = null; for ( NativeSQLQueryReturn queryReturn: mapping.getQueryReturns() ) { if (queryReturn instanceof NativeSQLQueryScalarReturn) { if (mappedResultType != null) { return Object[].class; } else { mappedResultType = ((NativeSQLQueryScalarReturn) queryReturn).getType().getReturnedClass(); } } else if (queryReturn instanceof NativeSQLQueryRootReturn) { if (mappedResultType != null) { return Object[].class; } else { NativeSQLQueryRootReturn entityReturn = (NativeSQLQueryRootReturn) queryReturn; String entityName = entityReturn.getReturnEntityName(); mappedResultType = getFactory().getMetamodel().entityPersister(entityName).getMappedClass(); } } else if (queryReturn instanceof NativeSQLQueryCollectionReturn) { if (mappedResultType != null) { return Object[].class; } else { NativeSQLQueryCollectionReturn collectionReturn = (NativeSQLQueryCollectionReturn) queryReturn; String entityName = collectionReturn.getOwnerEntityName(); String propertyName = collectionReturn.getOwnerProperty(); String role = entityName + '.' + propertyName; mappedResultType = getFactory().getMetamodel().collectionPersister(role).getElementClass(); } } else if (queryReturn instanceof NativeSQLQueryConstructorReturn) { if (mappedResultType != null) { return Object[].class; } else { mappedResultType = ((NativeSQLQueryConstructorReturn) queryReturn).getTargetClass(); } } } return mappedResultType; } @Override public <T> ReactiveQuery<T> createReactiveNativeQuery(String sqlString, Class<T> resultClass) { checkOpen(); pulseTransactionCoordinator(); delayedAfterCompletion(); try { ReactiveNativeQuery<T> query = createReactiveNativeQuery( sqlString ); handleNativeQueryResult( query, resultClass ); return query; } catch ( RuntimeException he ) { throw getExceptionConverter().convert( he ); } } private <T> void handleNativeQueryResult(ReactiveNativeQuery<T> query, Class<T> resultClass) { if ( Tuple.class.equals( resultClass ) ) { query.setResultTransformer( new NativeQueryTupleTransformer() ); } else { query.addEntity( "alias1", resultClass.getName(), LockMode.READ ); } } private <T> ReactiveNativeQueryImpl<T> getReactiveNativeQueryImplementor( String queryString, boolean isOrdinalParameterZeroBased) { checkOpen(); pulseTransactionCoordinator(); delayedAfterCompletion(); try { ParameterMetadata params = getFactory().getQueryPlanCache() .getSQLParameterMetadata( queryString, isOrdinalParameterZeroBased ); ReactiveNativeQueryImpl<T> query = new ReactiveNativeQueryImpl<>( queryString, false, this, params ); query.setComment( "dynamic native SQL query" ); applyQuerySettingsAndHints( query ); return query; } catch ( RuntimeException he ) { throw getExceptionConverter().convert( he ); } } private static QueryType queryType(String queryString) { queryString = queryString.trim().toLowerCase(); return queryString.startsWith("insert") || queryString.startsWith("update") || queryString.startsWith("delete") ? QueryType.INSERT_UPDATE_DELETE : QueryType.SELECT; } @Override public <R> ReactiveQueryImpl<R> createReactiveQuery(String queryString) { checkOpen(); pulseTransactionCoordinator(); delayedAfterCompletion(); try { ParameterMetadataImpl paramMetadata = getQueryPlan( queryString, false ).getParameterMetadata(); ReactiveQueryImpl<R> query = new ReactiveQueryImpl<>( this, paramMetadata, queryString, queryType(queryString) ); applyQuerySettingsAndHints( query ); query.setComment( queryString ); return query; } catch (RuntimeException e) { markForRollbackOnly(); throw getExceptionConverter().convert( e ); } } @Override public <R> ReactiveQuery<R> createReactiveQuery(String queryString, Class<R> resultType) { checkOpen(); pulseTransactionCoordinator(); delayedAfterCompletion(); try { // do the translation final ReactiveQueryImpl<R> query = createReactiveQuery( queryString ); resultClassChecking( resultType, query.unwrap( Query.class ) ); return query; } catch (RuntimeException e) { throw getExceptionConverter().convert( e ); } } /** * @deprecated use {@link #reactiveInitializeCollection(PersistentCollection, boolean)} instead */ @Deprecated @Override public void initializeCollection(PersistentCollection collection, boolean writing) { throw getExceptionConverter().convert( new UnsupportedOperationException( "ReactiveSessionImpl#initializeCollection not supported, use reactiveInitializeCollection instead" ) ); } public CompletionStage<Void> reactiveInitializeCollection(PersistentCollection collection, boolean writing) { checkOpenOrWaitingForAutoClose(); pulseTransactionCoordinator(); InitializeCollectionEvent event = new InitializeCollectionEvent( collection, this ); return fire( event, EventType.INIT_COLLECTION, (DefaultReactiveInitializeCollectionEventListener l) -> l::onReactiveInitializeCollection ) .handle( (v, e) -> { delayedAfterCompletion(); if ( e instanceof MappingException ) { throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage() ) ); } else if ( e instanceof RuntimeException ) { throw getExceptionConverter().convert( (RuntimeException) e ); } return CompletionStages.returnNullorRethrow( e ); } ); } @Override protected ReactiveHQLQueryPlan getQueryPlan(String query, boolean shallow) throws HibernateException { QueryPlanCache queryPlanCache = getFactory().getQueryPlanCache(); return (ReactiveHQLQueryPlan) queryPlanCache.getHQLQueryPlan( query, shallow, getLoadQueryInfluencers().getEnabledFilters() ); } //TODO: parameterize the SessionFactory constructor by ReactiveNativeSQLQueryPlan::new // @Override // protected ReactiveNativeSQLQueryPlan getNativeQueryPlan(NativeSQLQuerySpecification spec) throws HibernateException { // QueryPlanCache queryPlanCache = getFactory().getQueryPlanCache(); // return (ReactiveNativeSQLQueryPlan) queryPlanCache.getNativeSQLQueryPlan( spec ); // } protected CompletionStage<Void> reactiveAutoFlushIfRequired(Set<?> querySpaces) throws HibernateException { checkOpen(); // if ( !isTransactionInProgress() ) { // do not auto-flush while outside a transaction // return CompletionStages.nullFuture(); // } AutoFlushEvent event = new AutoFlushEvent( querySpaces, this ); return fire( event, EventType.AUTO_FLUSH, (DefaultReactiveAutoFlushEventListener l) -> l::reactiveOnAutoFlush ); } @Override public <T> CompletionStage<List<T>> reactiveList(String query, QueryParameters parameters) throws HibernateException { checkOpenOrWaitingForAutoClose(); pulseTransactionCoordinator(); parameters.validateParameters(); HQLQueryPlan plan = parameters.getQueryPlan(); if ( plan == null ) { plan = getQueryPlan( query, false ); } ReactiveHQLQueryPlan reactivePlan = (ReactiveHQLQueryPlan) plan; return reactiveAutoFlushIfRequired( plan.getQuerySpaces() ) // FIXME: I guess I can fix this as a separate issue // dontFlushFromFind++; //stops flush being called multiple times if this method is recursively called .thenCompose( v -> reactivePlan.performReactiveList(parameters, this ) ) .whenComplete( (list, x) -> { // dontFlushFromFind--; afterOperation( x == null ); delayedAfterCompletion(); } ) //TODO: this typecast is rubbish .thenApply( list -> (List<T>) list ); } @Override public <T> CompletionStage<List<T>> reactiveList(NativeSQLQuerySpecification spec, QueryParameters parameters) { return listReactiveCustomQuery( getNativeQueryPlan( spec ).getCustomQuery(), parameters) //TODO: this typecast is rubbish .thenApply( list -> (List<T>) list ); } private CompletionStage<List<Object>> listReactiveCustomQuery(CustomQuery customQuery, QueryParameters parameters) { checkOpenOrWaitingForAutoClose(); // checkTransactionSynchStatus(); ReactiveCustomLoader loader = new ReactiveCustomLoader( customQuery, getFactory() ); // autoFlushIfRequired( loader.getQuerySpaces() ); // dontFlushFromFind++; // boolean success = false; return loader.reactiveList( this, parameters ) .whenComplete( (r, e) -> delayedAfterCompletion() ); // success = true; // dontFlushFromFind--; // afterOperation( success ); } @Override public <R> ReactiveQuery<R> createReactiveNamedQuery(String name) { return buildReactiveQueryFromName( name, null ); } @Override public <R> ReactiveQuery<R> createReactiveNamedQuery(String name, Class<R> resultClass) { return buildReactiveQueryFromName( name, resultClass ); } private <T> ReactiveQuery<T> buildReactiveQueryFromName(String name, Class<T> resultType) { checkOpen(); try { pulseTransactionCoordinator(); delayedAfterCompletion(); // todo : apply stored setting at the JPA Query level too final NamedQueryDefinition namedQueryDefinition = getFactory().getNamedQueryRepository().getNamedQueryDefinition( name ); if ( namedQueryDefinition != null ) { return createReactiveQuery( namedQueryDefinition, resultType ); } final NamedSQLQueryDefinition nativeQueryDefinition = getFactory().getNamedQueryRepository().getNamedSQLQueryDefinition( name ); if ( nativeQueryDefinition != null ) { return createReactiveNativeQuery( nativeQueryDefinition, resultType ); } throw getExceptionConverter().convert( new IllegalArgumentException( "No query defined for that name [" + name + "]" ) ); } catch (RuntimeException e) { throw !( e instanceof IllegalArgumentException ) ? new IllegalArgumentException( e ) : e; } } private <T> ReactiveQuery<T> createReactiveQuery(NamedQueryDefinition namedQueryDefinition, Class<T> resultType) { final ReactiveQuery<T> query = createReactiveQuery( namedQueryDefinition ); if ( resultType != null ) { resultClassChecking( resultType, createQuery( namedQueryDefinition ) ); } return query; } private <T> ReactiveQuery<T> createReactiveQuery(NamedQueryDefinition queryDefinition) { String queryString = queryDefinition.getQueryString(); ParameterMetadataImpl paramMetadata = getQueryPlan( queryString, false ).getParameterMetadata(); ReactiveQueryImpl<T> query = new ReactiveQueryImpl<>( this, paramMetadata, queryString, queryType( queryString ) ); applyQuerySettingsAndHints( query ); query.setHibernateFlushMode( queryDefinition.getFlushMode() ); query.setComment( queryDefinition.getComment() != null ? queryDefinition.getComment() : queryDefinition.getName() ); if ( queryDefinition.getLockOptions() != null ) { query.setLockOptions( queryDefinition.getLockOptions() ); } initQueryFromNamedDefinition( query, queryDefinition ); return query; } private <T> ReactiveNativeQuery<T> createReactiveNativeQuery(NamedSQLQueryDefinition queryDefinition, Class<T> resultType) { if ( resultType != null && !Tuple.class.equals( resultType ) && !Object[].class.equals( resultType ) ) { resultClassChecking( resultType, queryDefinition ); } final ReactiveNativeQueryImpl<T> query = new ReactiveNativeQueryImpl<>( queryDefinition, this, getFactory().getQueryPlanCache().getSQLParameterMetadata( queryDefinition.getQueryString(), false ) ); if ( Tuple.class.equals( resultType ) ) { query.setResultTransformer( new NativeQueryTupleTransformer() ); } applyQuerySettingsAndHints( query ); query.setHibernateFlushMode( queryDefinition.getFlushMode() ); query.setComment( queryDefinition.getComment() != null ? queryDefinition.getComment() : queryDefinition.getName() ); if ( queryDefinition.getLockOptions() != null ) { query.setLockOptions( queryDefinition.getLockOptions() ); } initQueryFromNamedDefinition( query, queryDefinition ); return query; } @Override public <R> ReactiveQuery<R> createReactiveQuery(Criteria<R> criteria) { try { criteria.validate(); } catch (IllegalStateException ise) { throw new IllegalArgumentException( "Error occurred validating the Criteria", ise ); } return criteria.build( newRenderingContext(), this ); } private CriteriaQueryRenderingContext newRenderingContext() { return new CriteriaQueryRenderingContext( getFactory() ); } @Override public <T> ReactiveQuery<T> createReactiveCriteriaQuery(String jpaqlString, Class<T> resultClass, CriteriaQueryOptions queryOptions) { try { ReactiveQuery<T> query = createReactiveQuery( jpaqlString ); query.setParameterMetadata( queryOptions.getParameterMetadata() ); boolean hasValueHandlers = queryOptions.getValueHandlers() != null; boolean hasTupleElements = Tuple.class.equals( resultClass ); if ( !hasValueHandlers ) { queryOptions.validate( query.getReturnTypes() ); } // determine if we need a result transformer if ( hasValueHandlers || hasTupleElements ) { query.setResultTransformer( new CriteriaQueryTupleTransformer( queryOptions.getValueHandlers(), hasTupleElements ? queryOptions.getSelection().getCompoundSelectionItems() : null ) ); } return query; } catch ( RuntimeException e ) { throw getExceptionConverter().convert( e ); } } @Override public CompletionStage<Integer> executeReactiveUpdate(String query, QueryParameters parameters) { checkOpenOrWaitingForAutoClose(); pulseTransactionCoordinator(); parameters.validateParameters(); ReactiveHQLQueryPlan reactivePlan = getQueryPlan( query, false ); return reactiveAutoFlushIfRequired( reactivePlan.getQuerySpaces() ) .thenAccept( v -> verifyImmutableEntityUpdate( reactivePlan ) ) .thenCompose( v -> reactivePlan.performExecuteReactiveUpdate( parameters, this ) ) .whenComplete( (count, x) -> { afterOperation( x == null ); delayedAfterCompletion(); } ); } @Override public CompletionStage<Integer> executeReactiveUpdate(NativeSQLQuerySpecification specification, QueryParameters parameters) { checkOpenOrWaitingForAutoClose(); pulseTransactionCoordinator(); parameters.validateParameters(); ReactiveNativeSQLQueryPlan reactivePlan = //getNativeQueryPlan( specification ); new ReactiveNativeSQLQueryPlan( specification.getQueryString(), new SQLCustomQuery( specification.getQueryString(), specification.getQueryReturns(), specification.getQuerySpaces(), getFactory() ) ); return reactiveAutoFlushIfRequired( reactivePlan.getCustomQuery().getQuerySpaces() ) .thenCompose( v -> reactivePlan.performExecuteReactiveUpdate( parameters, this ) ) .whenComplete( (count, x) -> { afterOperation( x == null ); delayedAfterCompletion(); } ); } @Override public CompletionStage<Void> reactivePersist(Object entity) { checkOpen(); return firePersist( new PersistEvent( null, entity, this ) ); } @Override public CompletionStage<Void> reactivePersist(Object object, IdentitySet copiedAlready) { checkOpenOrWaitingForAutoClose(); return firePersist( copiedAlready, new PersistEvent( null, object, this ) ); } // Should be similar to firePersist private CompletionStage<Void> firePersist(PersistEvent event) { checkTransactionSynchStatus(); checkNoUnresolvedActionsBeforeOperation(); return fire(event, EventType.PERSIST, (ReactivePersistEventListener l) -> l::reactiveOnPersist) .handle( (v, e) -> { checkNoUnresolvedActionsAfterOperation(); if (e instanceof MappingException) { throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage() ) ); } else if (e instanceof RuntimeException) { throw getExceptionConverter().convert( (RuntimeException) e ); } return CompletionStages.returnNullorRethrow( e ); }); } private CompletionStage<Void> firePersist(IdentitySet copiedAlready, PersistEvent event) { pulseTransactionCoordinator(); return fire(event, copiedAlready, EventType.PERSIST, (ReactivePersistEventListener l) -> l::reactiveOnPersist) .handle( (v, e) -> { delayedAfterCompletion(); if (e instanceof MappingException) { throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage() ) ); } else if (e instanceof RuntimeException) { throw getExceptionConverter().convert( (RuntimeException) e ); } return CompletionStages.returnNullorRethrow( e ); }); } @Override public CompletionStage<Void> reactivePersistOnFlush(Object entity, IdentitySet copiedAlready) { checkOpenOrWaitingForAutoClose(); return firePersistOnFlush( copiedAlready, new PersistEvent( null, entity, this ) ); } private CompletionStage<Void> firePersistOnFlush(IdentitySet copiedAlready, PersistEvent event) { pulseTransactionCoordinator(); return fire(event, copiedAlready, EventType.PERSIST_ONFLUSH, (ReactivePersistEventListener l) -> l::reactiveOnPersist) .whenComplete( (v, e) -> delayedAfterCompletion() ); } @Override public CompletionStage<Void> reactiveRemove(Object entity) { checkOpen(); return fireRemove( new DeleteEvent( null, entity, this ) ); } @Override public CompletionStage<Void> reactiveRemove(Object object, boolean isCascadeDeleteEnabled, IdentitySet transientEntities) throws HibernateException { checkOpenOrWaitingForAutoClose(); return fireRemove( new DeleteEvent( null, object, isCascadeDeleteEnabled, ((ReactivePersistenceContextAdapter) getPersistenceContextInternal()) .isRemovingOrphanBeforeUpates(), this ), transientEntities ); } // Should be similar to fireRemove private CompletionStage<Void> fireRemove(DeleteEvent event) { pulseTransactionCoordinator(); return fire(event, EventType.DELETE, (ReactiveDeleteEventListener l) -> l::reactiveOnDelete) .handle( (v, e) -> { delayedAfterCompletion(); if ( e instanceof ObjectDeletedException ) { throw getExceptionConverter().convert( new IllegalArgumentException( e ) ); } else if ( e instanceof MappingException ) { throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage(), e ) ); } else if ( e instanceof RuntimeException ) { //including HibernateException throw getExceptionConverter().convert( (RuntimeException) e ); } return CompletionStages.returnNullorRethrow( e ); }); } private CompletionStage<Void> fireRemove(DeleteEvent event, IdentitySet transientEntities) { pulseTransactionCoordinator(); return fire(event, transientEntities, EventType.DELETE, (ReactiveDeleteEventListener l) -> l::reactiveOnDelete) .handle( (v, e) -> { delayedAfterCompletion(); if ( e instanceof ObjectDeletedException ) { throw getExceptionConverter().convert( new IllegalArgumentException( e ) ); } else if ( e instanceof MappingException ) { throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage(), e ) ); } else if ( e instanceof RuntimeException ) { //including HibernateException throw getExceptionConverter().convert( (RuntimeException) e ); } return CompletionStages.returnNullorRethrow( e ); }); } @Override public <T> CompletionStage<T> reactiveMerge(T object) throws HibernateException { checkOpen(); return fireMerge( new MergeEvent( null, object, this )); } @Override public CompletionStage<Void> reactiveMerge(Object object, MergeContext copiedAlready) throws HibernateException { checkOpenOrWaitingForAutoClose(); return fireMerge( copiedAlready, new MergeEvent( null, object, this ) ); } @SuppressWarnings("unchecked") private <T> CompletionStage<T> fireMerge(MergeEvent event) { checkTransactionSynchStatus(); checkNoUnresolvedActionsBeforeOperation(); return fire(event, EventType.MERGE, (ReactiveMergeEventListener l) -> l::reactiveOnMerge) .handle( (v,e) -> { checkNoUnresolvedActionsAfterOperation(); if (e instanceof ObjectDeletedException) { throw getExceptionConverter().convert( new IllegalArgumentException( e ) ); } else if (e instanceof MappingException) { throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage(), e ) ); } else if (e instanceof RuntimeException) { //including HibernateException throw getExceptionConverter().convert( (RuntimeException) e ); } return CompletionStages.returnOrRethrow( e, (T) event.getResult() ); }); } private CompletionStage<Void> fireMerge(MergeContext copiedAlready, MergeEvent event) { pulseTransactionCoordinator(); return fire(event, copiedAlready, EventType.MERGE, (ReactiveMergeEventListener l) -> l::reactiveOnMerge) .handle( (v,e) -> { delayedAfterCompletion(); if (e instanceof ObjectDeletedException) { throw getExceptionConverter().convert( new IllegalArgumentException( e ) ); } else if (e instanceof MappingException) { throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage(), e ) ); } else if (e instanceof RuntimeException) { //including HibernateException throw getExceptionConverter().convert( (RuntimeException) e ); } return CompletionStages.returnNullorRethrow( e ); }); } @Override public CompletionStage<Void> reactiveFlush() { checkOpen(); return doFlush(); } @Override public CompletionStage<Void> reactiveAutoflush() { return getHibernateFlushMode().lessThan( FlushMode.COMMIT ) ? CompletionStages.nullFuture() : doFlush(); } private CompletionStage<Void> doFlush() { checkTransactionNeededForUpdateOperation( "no transaction is in progress" ); pulseTransactionCoordinator(); if ( getPersistenceContextInternal().getCascadeLevel() > 0 ) { throw new HibernateException( "Flush during cascade is dangerous" ); } return fire(new FlushEvent( this ), EventType.FLUSH, (ReactiveFlushEventListener l) -> l::reactiveOnFlush) .handle( (v, e) -> { delayedAfterCompletion(); if ( e instanceof RuntimeException ) { throw getExceptionConverter().convert( (RuntimeException) e ); } return CompletionStages.returnNullorRethrow( e ); } ); } @Override public CompletionStage<Void> reactiveRefresh(Object entity, LockMode lockMode) { checkOpen(); return fireRefresh( new RefreshEvent( entity, lockMode, this ) ); } @Override public CompletionStage<Void> reactiveRefresh(Object object, IdentitySet refreshedAlready) { checkOpenOrWaitingForAutoClose(); return fireRefresh( refreshedAlready, new RefreshEvent( null, object, this ) ); } CompletionStage<Void> fireRefresh(RefreshEvent event) { if ( !getSessionFactory().getSessionFactoryOptions().isAllowRefreshDetachedEntity() ) { if ( event.getEntityName() != null ) { if ( !contains( event.getEntityName(), event.getObject() ) ) { throw new IllegalArgumentException( "Entity not managed" ); } } else { if ( !contains( event.getObject() ) ) { throw new IllegalArgumentException( "Entity not managed" ); } } } pulseTransactionCoordinator(); return fire(event, EventType.REFRESH, (ReactiveRefreshEventListener l) -> l::reactiveOnRefresh) .handle( (v, e) -> { delayedAfterCompletion(); if (e instanceof RuntimeException) { if ( !getSessionFactory().getSessionFactoryOptions().isJpaBootstrap() ) { if ( e instanceof HibernateException ) { return CompletionStages.rethrow(e); } } //including HibernateException throw getExceptionConverter().convert( (RuntimeException) e ); } return CompletionStages.returnNullorRethrow( e ); }); } private CompletionStage<Void> fireRefresh(IdentitySet refreshedAlready, RefreshEvent event) { pulseTransactionCoordinator(); return fire(event, refreshedAlready, EventType.REFRESH, (ReactiveRefreshEventListener l) -> l::reactiveOnRefresh) .handle( (v, e) -> { delayedAfterCompletion(); if (e instanceof RuntimeException) { throw getExceptionConverter().convert( (RuntimeException) e ); } return CompletionStages.returnNullorRethrow( e ); }); } @Override public CompletionStage<Void> reactiveLock(Object object, LockMode lockMode) { checkOpen(); return fireLock( new LockEvent( object, lockMode, this ) ); } private CompletionStage<Void> fireLock(LockEvent event) { pulseTransactionCoordinator(); return fire( event, EventType.LOCK, (ReactiveLockEventListener l) -> l::reactiveOnLock ) .handle( (v, e) -> { delayedAfterCompletion(); if (e instanceof RuntimeException) { throw getExceptionConverter().convert( (RuntimeException) e ); } return CompletionStages.returnNullorRethrow( e ); }); } @Override public <T> CompletionStage<T> reactiveGet( Class<T> entityClass, Serializable id) { return new ReactiveIdentifierLoadAccessImpl<>( entityClass ).load( id ); } @Override public <T> CompletionStage<T> reactiveFind( Class<T> entityClass, Object id, LockMode lockMode, Map<String, Object> properties) { checkOpen(); getLoadQueryInfluencers().getEffectiveEntityGraph().applyConfiguredGraph( properties ); Boolean readOnly = properties == null ? null : (Boolean) properties.get( QueryHints.HINT_READONLY ); getLoadQueryInfluencers().setReadOnly( readOnly ); final ReactiveIdentifierLoadAccessImpl<T> loadAccess = new ReactiveIdentifierLoadAccessImpl<>(entityClass) .with( determineAppropriateLocalCacheMode( properties ) ); LockOptions lockOptions; if ( lockMode != null ) { // if ( !LockModeType.NONE.equals( lockModeType) ) { // checkTransactionNeededForUpdateOperation(); // } lockOptions = buildLockOptions( LockModeConverter.convertToLockModeType(lockMode), properties ); loadAccess.with( lockOptions ); } else { lockOptions = null; } return loadAccess.load( (Serializable) id ) .handle( (result, e) -> { if ( e instanceof EntityNotFoundException) { // DefaultLoadEventListener.returnNarrowedProxy may throw ENFE (see HHH-7861 for details), // which find() should not throw. Find() should return null if the entity was not found. // if ( log.isDebugEnabled() ) { // String entityName = entityClass != null ? entityClass.getName(): null; // String identifierValue = id != null ? id.toString() : null ; // log.ignoringEntityNotFound( entityName, identifierValue ); // } return null; } if ( e instanceof ObjectDeletedException) { //the spec is silent about people doing remove() find() on the same PC return null; } if ( e instanceof ObjectNotFoundException) { //should not happen on the entity itself with get throw new IllegalArgumentException( e.getMessage(), e ); } if ( e instanceof MappingException || e instanceof TypeMismatchException || e instanceof ClassCastException ) { throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage(), e ) ); } if ( e instanceof JDBCException ) { // if ( accessTransaction().getRollbackOnly() ) { // // assume this is the similar to the WildFly / IronJacamar "feature" described under HHH-12472 // return null; // } throw getExceptionConverter().convert( (JDBCException) e, lockOptions ); } if ( e instanceof RuntimeException ) { throw getExceptionConverter().convert( (RuntimeException) e, lockOptions ); } return result; } ) .whenComplete( (v, e) -> getLoadQueryInfluencers().getEffectiveEntityGraph().clear() ); } @Override public <T> CompletionStage<List<T>> reactiveFind(Class<T> entityClass, Object... ids) { return new ReactiveMultiIdentifierLoadAccessImpl<>(entityClass).multiLoad(ids); //TODO: copy/paste the exception handling from immediately above? } @SuppressWarnings("unchecked") private <E, L, RL, T> CompletionStage<T> fire( E event, EventType<L> eventType, Function<RL, Function<E, CompletionStage<T>>> fun) { CompletionStage<T> ret = CompletionStages.nullFuture(); for ( L listener : eventListeners( eventType ) ) { //to preserve atomicity of the Session methods //call apply() from within the arg of thenCompose() ret = ret.thenCompose( v -> fun.apply((RL) listener).apply(event) ); } return ret; } @SuppressWarnings("unchecked") private <E,L,RL,P> CompletionStage<Void> fire(E event, P extra, EventType<L> eventType, Function<RL, BiFunction<E, P, CompletionStage<Void>>> fun) { CompletionStage<Void> ret = CompletionStages.nullFuture(); for ( L listener : eventListeners(eventType) ) { //to preserve atomicity of the Session methods //call apply() from within the arg of thenCompose() ret = ret.thenCompose( v -> fun.apply((RL) listener).apply(event, extra) ); } return ret; } @SuppressWarnings("deprecation") private <T> Iterable<T> eventListeners(EventType<T> type) { return getFactory().unwrap( SessionFactoryImplementor.class ) .getServiceRegistry().getService( EventListenerRegistry.class ) .getEventListenerGroup( type ) .listeners(); } private CompletionStage<Void> fireLoad(LoadEvent event, LoadEventListener.LoadType loadType) { checkOpenOrWaitingForAutoClose(); return fireLoadNoChecks( event, loadType ) .whenComplete( (v, e) -> delayedAfterCompletion() ); } private CompletionStage<Void> fireLoadNoChecks(LoadEvent event, LoadEventListener.LoadType loadType) { pulseTransactionCoordinator(); return fire(event, loadType, EventType.LOAD, (ReactiveLoadEventListener l) -> l::reactiveOnLoad); } @Override protected void delayedAfterCompletion() { //disable for now, but figure out what to do here } public void afterOperation(boolean success) { //disable for now, but figure out what to do here } @Override public void checkTransactionNeededForUpdateOperation(String exceptionMessage) { //no-op because we don't support transactions } private class ReactiveIdentifierLoadAccessImpl<T> { private final EntityPersister entityPersister; private LockOptions lockOptions; private CacheMode cacheMode; //Note that entity graphs aren't supported at all //because we're not using the EntityLoader from //the plan package, so this stuff is useless private RootGraphImplementor<T> rootGraph; private GraphSemantic graphSemantic; public ReactiveIdentifierLoadAccessImpl(EntityPersister entityPersister) { this.entityPersister = entityPersister; } public ReactiveIdentifierLoadAccessImpl(String entityName) { this( getFactory().getMetamodel().locateEntityPersister( entityName ) ); } public ReactiveIdentifierLoadAccessImpl(Class<T> entityClass) { this( getFactory().getMetamodel().locateEntityPersister( entityClass ) ); } public final ReactiveIdentifierLoadAccessImpl<T> with(LockOptions lockOptions) { this.lockOptions = lockOptions; return this; } public ReactiveIdentifierLoadAccessImpl<T> with(CacheMode cacheMode) { this.cacheMode = cacheMode; return this; } public ReactiveIdentifierLoadAccessImpl<T> with(RootGraph<T> graph, GraphSemantic semantic) { rootGraph = (RootGraphImplementor<T>) graph; graphSemantic = semantic; return this; } public final CompletionStage<T> getReference(Serializable id) { return perform( () -> doGetReference( id ) ); } protected CompletionStage<T> perform(Supplier<CompletionStage<T>> executor) { if ( graphSemantic != null ) { if ( rootGraph == null ) { throw new IllegalArgumentException( "Graph semantic specified, but no RootGraph was supplied" ); } } CacheMode sessionCacheMode = getCacheMode(); boolean cacheModeChanged = false; if ( cacheMode != null ) { // naive check for now... // todo : account for "conceptually equal" if ( cacheMode != sessionCacheMode ) { setCacheMode( cacheMode ); cacheModeChanged = true; } } if ( graphSemantic != null ) { getLoadQueryInfluencers().getEffectiveEntityGraph().applyGraph( rootGraph, graphSemantic ); } boolean finalCacheModeChanged = cacheModeChanged; return executor.get() .whenComplete( (v, x) -> { if ( graphSemantic != null ) { getLoadQueryInfluencers().getEffectiveEntityGraph().clear(); } if ( finalCacheModeChanged ) { // change it back setCacheMode( sessionCacheMode ); } } ); } @SuppressWarnings("unchecked") protected CompletionStage<T> doGetReference(Serializable id) { if ( lockOptions != null ) { LoadEvent event = new LoadEvent(id, entityPersister.getEntityName(), lockOptions, ReactiveSessionImpl.this, getReadOnlyFromLoadQueryInfluencers()); return fireLoad( event, LoadEventListener.LOAD ).thenApply( v -> (T) event.getResult() ); } LoadEvent event = new LoadEvent(id, entityPersister.getEntityName(), false, ReactiveSessionImpl.this, getReadOnlyFromLoadQueryInfluencers()); return fireLoad( event, LoadEventListener.LOAD ) .thenApply( v -> { if ( event.getResult() == null ) { getFactory().getEntityNotFoundDelegate().handleEntityNotFound( entityPersister.getEntityName(), id ); } return (T) event.getResult(); } ).whenComplete( (v, x) -> afterOperation( x != null ) ); } public final CompletionStage<T> load(Serializable id) { return perform( () -> doLoad( id, LoadEventListener.GET) ); } public final CompletionStage<T> fetch(Serializable id) { return perform( () -> doLoad( id, LoadEventListener.IMMEDIATE_LOAD) ); } private Boolean getReadOnlyFromLoadQueryInfluencers() { return getLoadQueryInfluencers().getReadOnly(); } @SuppressWarnings("unchecked") protected final CompletionStage<T> doLoad(Serializable id, LoadEventListener.LoadType loadType) { if ( lockOptions != null ) { LoadEvent event = new LoadEvent(id, entityPersister.getEntityName(), lockOptions, ReactiveSessionImpl.this, getReadOnlyFromLoadQueryInfluencers()); return fireLoad( event, loadType ).thenApply( v -> (T) event.getResult() ); } LoadEvent event = new LoadEvent(id, entityPersister.getEntityName(), false, ReactiveSessionImpl.this, getReadOnlyFromLoadQueryInfluencers()); return fireLoad( event, loadType ) .whenComplete( (v, t) -> afterOperation( t != null ) ) .thenApply( v -> (T) event.getResult() ); } } private class ReactiveMultiIdentifierLoadAccessImpl<T> implements MultiLoadOptions { private final EntityPersister entityPersister; private LockOptions lockOptions; private CacheMode cacheMode; private RootGraphImplementor<T> rootGraph; private GraphSemantic graphSemantic; private Integer batchSize; private boolean sessionCheckingEnabled; private boolean returnOfDeletedEntitiesEnabled; private boolean orderedReturnEnabled = true; public ReactiveMultiIdentifierLoadAccessImpl(EntityPersister entityPersister) { this.entityPersister = entityPersister; } public ReactiveMultiIdentifierLoadAccessImpl(Class<T> entityClass) { this( getFactory().getMetamodel().locateEntityPersister( entityClass ) ); } @Override public LockOptions getLockOptions() { return lockOptions; } public final ReactiveMultiIdentifierLoadAccessImpl<T> with(LockOptions lockOptions) { this.lockOptions = lockOptions; return this; } public ReactiveMultiIdentifierLoadAccessImpl<T> with(CacheMode cacheMode) { this.cacheMode = cacheMode; return this; } public ReactiveMultiIdentifierLoadAccessImpl<T> with(RootGraph<T> graph, GraphSemantic semantic) { this.rootGraph = (RootGraphImplementor<T>) graph; this.graphSemantic = semantic; return this; } @Override public Integer getBatchSize() { return batchSize; } public ReactiveMultiIdentifierLoadAccessImpl<T> withBatchSize(int batchSize) { if ( batchSize < 1 ) { this.batchSize = null; } else { this.batchSize = batchSize; } return this; } @Override public boolean isSessionCheckingEnabled() { return sessionCheckingEnabled; } @Override public boolean isSecondLevelCacheCheckingEnabled() { return cacheMode == CacheMode.NORMAL || cacheMode == CacheMode.GET; } public ReactiveMultiIdentifierLoadAccessImpl<T> enableSessionCheck(boolean enabled) { this.sessionCheckingEnabled = enabled; return this; } @Override public boolean isReturnOfDeletedEntitiesEnabled() { return returnOfDeletedEntitiesEnabled; } public ReactiveMultiIdentifierLoadAccessImpl<T> enableReturnOfDeletedEntities(boolean enabled) { this.returnOfDeletedEntitiesEnabled = enabled; return this; } @Override public boolean isOrderReturnEnabled() { return orderedReturnEnabled; } public ReactiveMultiIdentifierLoadAccessImpl<T> enableOrderedReturn(boolean enabled) { this.orderedReturnEnabled = enabled; return this; } @SuppressWarnings("unchecked") public CompletionStage<List<T>> multiLoad(Object... ids) { Serializable[] sids = new Serializable[ids.length]; System.arraycopy(ids, 0, sids, 0, ids.length); return perform( () -> (CompletionStage) ((ReactiveEntityPersister) entityPersister).reactiveMultiLoad( sids, ReactiveSessionImpl.this, this ) ); } public CompletionStage<List<T>> perform(Supplier<CompletionStage<List<T>>> executor) { CacheMode sessionCacheMode = getCacheMode(); boolean cacheModeChanged = false; if ( cacheMode != null ) { // naive check for now... // todo : account for "conceptually equal" if ( cacheMode != sessionCacheMode ) { setCacheMode( cacheMode ); cacheModeChanged = true; } } if ( graphSemantic != null ) { if ( rootGraph == null ) { throw new IllegalArgumentException( "Graph semantic specified, but no RootGraph was supplied" ); } getLoadQueryInfluencers().getEffectiveEntityGraph().applyGraph( rootGraph, graphSemantic ); } boolean finalCacheModeChanged = cacheModeChanged; return executor.get() .whenComplete( (v, x) -> { if ( graphSemantic != null ) { getLoadQueryInfluencers().getEffectiveEntityGraph().clear(); } if ( finalCacheModeChanged ) { // change it back setCacheMode( sessionCacheMode ); } } ); } @SuppressWarnings("unchecked") public <K extends Serializable> CompletionStage<List<T>> multiLoad(List<K> ids) { return perform( () -> (CompletionStage<List<T>>) entityPersister.multiLoad( ids.toArray(new Serializable[0]), ReactiveSessionImpl.this, this ) ); } } @Override public <T> T unwrap(Class<T> clazz) { if ( ReactiveSession.class.isAssignableFrom( clazz ) ) { return clazz.cast(this); } if ( Stage.Session.class.isAssignableFrom( clazz ) ) { return clazz.cast( new StageSessionImpl( this ) ); } if ( Mutiny.Session.class.isAssignableFrom( clazz ) ) { return clazz.cast( new MutinySessionImpl( this ) ); } return super.unwrap( clazz ); } public ReactiveConnection getReactiveConnection() { return reactiveConnection; } @Override public void close() throws HibernateException { if ( reactiveConnection != null ) { reactiveConnection.close(); } super.close(); } @Override @SuppressWarnings("unchecked") public <T> RootGraphImplementor<T> createEntityGraph(Class<T> entity, String name) { RootGraphImplementor<?> entityGraph = super.createEntityGraph(name); if ( !entityGraph.getGraphedType().getJavaType().equals(entity) ) { throw new HibernateException("wrong entity type"); } return (RootGraphImplementor<T>) entityGraph; } public <T> RootGraphImplementor<T> createEntityGraph(Class<T> entity) { return super.createEntityGraph( entity ); } @Override @SuppressWarnings("unchecked") public <T> RootGraphImplementor<T> getEntityGraph(Class<T> entity, String name) { RootGraphImplementor<?> entityGraph = super.getEntityGraph(name); if ( !entityGraph.getGraphedType().getJavaType().equals(entity) ) { throw new HibernateException("wrong entity type"); } return (RootGraphImplementor<T>) entityGraph; } }