diff --git a/.gitignore b/.gitignore index 8016117ce..750319d47 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,3 @@ bin # Vim *.swp *.swo - -# Release scripts downloaded from hibernate/hibernate-release-scripts -.release diff --git a/.release/.gitignore b/.release/.gitignore new file mode 100644 index 000000000..cdd9a17d6 --- /dev/null +++ b/.release/.gitignore @@ -0,0 +1,3 @@ +# The folder into which we checkout our release scripts into +* +!.gitignore \ No newline at end of file diff --git a/build.gradle b/build.gradle index 1ad0a24c7..33e79e8fd 100644 --- a/build.gradle +++ b/build.gradle @@ -30,13 +30,6 @@ ext { logger.lifecycle "Vert.x SQL Client Version: " + project.vertxSqlClientVersion } -// Publishing to Sonatype (Maven Central): -nexusPublishing { - repositories { - sonatype() - } -} - subprojects { apply plugin: 'java-library' apply plugin: 'com.diffplug.spotless' diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile index 80b89bf9f..f4c001431 100644 --- a/ci/release/Jenkinsfile +++ b/ci/release/Jenkinsfile @@ -119,8 +119,10 @@ pipeline { echo "Release was triggered automatically" // Avoid doing an automatic release for commits from a release - def lastCommitter = sh(script: 'git show -s --format=\'%an\'', returnStdout: true) - def secondLastCommitter = sh(script: 'git show -s --format=\'%an\' HEAD~1', returnStdout: true) + def lastCommitter = sh(script: 'git show -s --format=\'%an\'', returnStdout: true).trim() + def secondLastCommitter = sh(script: 'git show -s --format=\'%an\' HEAD~1', returnStdout: true).trim() + echo "Last two commits were performed by '${lastCommitter}'/'${secondLastCommitter}'." + if (lastCommitter == 'Hibernate-CI' && secondLastCommitter == 'Hibernate-CI') { print "INFO: Automatic release skipped because last commits were for the previous release" currentBuild.result = 'ABORTED' @@ -149,6 +151,7 @@ pipeline { env.DEVELOPMENT_VERSION = developmentVersion.toString() // Dry run is not supported at the moment env.SCRIPT_OPTIONS = params.RELEASE_DRY_RUN ? "-d" : "" + env.JRELEASER_DRY_RUN = params.RELEASE_DRY_RUN // Determine version id to check if Jira version exists // This step doesn't work for Hibernate Reactive (the project has been created with a different type on JIRA) @@ -172,11 +175,10 @@ pipeline { // tags the version // changes the version to the provided development version withEnv([ - "BRANCH=${env.GIT_BRANCH}", // Increase the amount of memory for this part since asciidoctor doc rendering consumes a lot of metaspace "GRADLE_OPTS=-Dorg.gradle.jvmargs='-Dlog4j2.disableJmx -Xmx4g -XX:MaxMetaspaceSize=768m -XX:+HeapDumpOnOutOfMemoryError -Duser.language=en -Duser.country=US -Duser.timezone=UTC -Dfile.encoding=UTF-8'" ]) { - sh ".release/scripts/prepare-release.sh ${env.PROJECT} ${env.RELEASE_VERSION} ${env.DEVELOPMENT_VERSION}" + sh ".release/scripts/prepare-release.sh -b ${env.GIT_BRANCH} -d ${env.DEVELOPMENT_VERSION} ${env.PROJECT} ${env.RELEASE_VERSION}" } } } @@ -193,11 +195,17 @@ pipeline { configFile(fileId: 'release.config.ssh.knownhosts', targetLocation: "${env.HOME}/.ssh/known_hosts") ]) { withCredentials([ - // https://github.com/gradle-nexus/publish-plugin#publishing-to-maven-central-via-sonatype-ossrh - usernamePassword(credentialsId: 'ossrh.sonatype.org', passwordVariable: 'ORG_GRADLE_PROJECT_sonatypePassword', usernameVariable: 'ORG_GRADLE_PROJECT_sonatypeUsername'), - file(credentialsId: 'release.gpg.private-key', variable: 'SIGNING_GPG_PRIVATE_KEY_PATH'), - string(credentialsId: 'release.gpg.passphrase', variable: 'SIGNING_GPG_PASSPHRASE'), - gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default') + // TODO: Once we switch to maven-central publishing (from nexus2) we need to add a new credentials + // to use the following env variable names to set the user/password: + // - JRELEASER_MAVENCENTRAL_USERNAME + // - JRELEASER_MAVENCENTRAL_TOKEN + // Also use the new `credentialsId` for Maven Central, e.g.: + // usernamePassword(credentialsId: '???????', passwordVariable: 'JRELEASER_MAVENCENTRAL_TOKEN', usernameVariable: 'JRELEASER_MAVENCENTRAL_USERNAME'), + usernamePassword(credentialsId: 'ossrh.sonatype.org', passwordVariable: 'JRELEASER_NEXUS2_PASSWORD', usernameVariable: 'JRELEASER_NEXUS2_USERNAME'), + gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default'), + file(credentialsId: 'release.gpg.private-key', variable: 'RELEASE_GPG_PRIVATE_KEY_PATH'), + string(credentialsId: 'release.gpg.passphrase', variable: 'JRELEASER_GPG_PASSPHRASE'), + string(credentialsId: 'Hibernate-CI.github.com', variable: 'JRELEASER_GITHUB_TOKEN') ]) { sshagent(['ed25519.Hibernate-CI.github.com', 'hibernate.filemgmt.jboss.org', 'hibernate-ci.frs.sourceforge.net']) { // performs documentation upload and Sonatype release @@ -217,4 +225,4 @@ pipeline { } } } -} \ No newline at end of file +} diff --git a/ci/snapshot-publish.Jenkinsfile b/ci/snapshot-publish.Jenkinsfile index c9e19ca4c..de6a030f2 100644 --- a/ci/snapshot-publish.Jenkinsfile +++ b/ci/snapshot-publish.Jenkinsfile @@ -10,6 +10,14 @@ if (currentBuild.getBuildCauses().toString().contains('BranchIndexingCause')) { return } +def checkoutReleaseScripts() { + dir('.release/scripts') { + checkout scmGit(branches: [[name: '*/main']], extensions: [], + userRemoteConfigs: [[credentialsId: 'ed25519.Hibernate-CI.github.com', + url: 'https://github.com/hibernate/hibernate-release-scripts.git']]) + } +} + pipeline { agent { label 'Release' @@ -30,13 +38,23 @@ pipeline { } stage('Publish') { steps { - withCredentials([ - // https://github.com/gradle-nexus/publish-plugin#publishing-to-maven-central-via-sonatype-ossrh - usernamePassword(credentialsId: 'ossrh.sonatype.org', usernameVariable: 'ORG_GRADLE_PROJECT_sonatypeUsername', passwordVariable: 'ORG_GRADLE_PROJECT_sonatypePassword'), - file(credentialsId: 'release.gpg.private-key', variable: 'SIGNING_GPG_PRIVATE_KEY_PATH'), - string(credentialsId: 'release.gpg.passphrase', variable: 'SIGNING_GPG_PASSPHRASE') - ]) { - sh "./gradlew clean publish --no-scan" + script { + withCredentials([ + // https://github.com/gradle-nexus/publish-plugin#publishing-to-maven-central-via-sonatype-ossrh + // TODO: Once we switch to maven-central publishing (from nexus2) we need to update credentialsId: + // https://docs.gradle.org/current/samples/sample_publishing_credentials.html#:~:text=via%20environment%20variables + usernamePassword(credentialsId: 'ossrh.sonatype.org', passwordVariable: 'ORG_GRADLE_PROJECT_snapshotsPassword', usernameVariable: 'ORG_GRADLE_PROJECT_snapshotsUsername'), + gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default'), + string(credentialsId: 'Hibernate-CI.github.com', variable: 'JRELEASER_GITHUB_TOKEN') + ]) { + checkoutReleaseScripts() + def version = sh( + script: ".release/scripts/determine-current-version.sh reactive", + returnStdout: true + ).trim() + echo "Current version: '${version}'" + sh "bash -xe .release/scripts/snapshot-deploy.sh reactive ${version}" + } } } } @@ -48,4 +66,4 @@ pipeline { } } } -} \ No newline at end of file +} diff --git a/gradle/version.properties b/gradle/version.properties index 3992a0cf7..c96d0ad0c 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=2.4.6.Final \ No newline at end of file +projectVersion=2.4.7.Final \ No newline at end of file diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/context/impl/ContextualDataStorage.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/context/impl/ContextualDataStorage.java deleted file mode 100644 index 295b92f4d..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/context/impl/ContextualDataStorage.java +++ /dev/null @@ -1,25 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.context.impl; - -import java.util.concurrent.ConcurrentMap; - -import io.vertx.core.impl.VertxBuilder; -import io.vertx.core.spi.VertxServiceProvider; -import io.vertx.core.spi.context.storage.ContextLocal; - -/** - * SPI Implementation for {@link ContextLocal} storage. - */ -public class ContextualDataStorage implements VertxServiceProvider { - - @SuppressWarnings("rawtypes") - static ContextLocal CONTEXTUAL_DATA_KEY = ContextLocal.registerLocal( ConcurrentMap.class ); - - @Override - public void init(VertxBuilder vertxBuilder) { - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/context/impl/VertxContext.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/context/impl/VertxContext.java index 937dc3b1b..4448c9b6c 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/context/impl/VertxContext.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/context/impl/VertxContext.java @@ -6,12 +6,9 @@ package org.hibernate.reactive.context.impl; import java.lang.invoke.MethodHandles; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import io.vertx.core.Vertx; import io.vertx.core.impl.ContextInternal; -import io.vertx.core.spi.context.storage.AccessMode; import org.hibernate.reactive.context.Context; import org.hibernate.reactive.logging.impl.Log; @@ -39,10 +36,10 @@ public void injectServices(ServiceRegistryImplementor serviceRegistry) { @Override public void put(Key key, T instance) { - final ContextInternal context = ContextInternal.current(); + final io.vertx.core.Context context = Vertx.currentContext(); if ( context != null ) { if ( trace ) LOG.tracef( "Putting key,value in context: [%1$s, %2$s]", key, instance ); - VertxContext.contextualDataMap( context ).put( key, instance ); + context.putLocal( key, instance ); } else { if ( trace ) LOG.tracef( "Context is null for key,value: [%1$s, %2$s]", key, instance ); @@ -52,9 +49,9 @@ public void put(Key key, T instance) { @Override public T get(Key key) { - final ContextInternal context = ContextInternal.current(); + final io.vertx.core.Context context = Vertx.currentContext(); if ( context != null ) { - T local = VertxContext.contextualDataMap( context ).get( key ); + T local = context.getLocal( key ); if ( trace ) LOG.tracef( "Getting value %2$s from context for key %1$s", key, local ); return local; } @@ -66,9 +63,9 @@ public T get(Key key) { @Override public void remove(Key key) { - final ContextInternal context = ContextInternal.current(); + final io.vertx.core.Context context = Vertx.currentContext(); if ( context != null ) { - boolean removed = contextualDataMap( context ).remove( key ) != null; + boolean removed = context.removeLocal( key ); if ( trace ) LOG.tracef( "Key %s removed from context: %s", key, removed ); } else { @@ -95,12 +92,4 @@ public void execute(Runnable runnable) { } } - @SuppressWarnings({ "unchecked" }) - private static ConcurrentMap, T> contextualDataMap(ContextInternal vertxContext) { - return vertxContext.getLocal( - ContextualDataStorage.CONTEXTUAL_DATA_KEY, - AccessMode.CONCURRENT, - ConcurrentHashMap::new - ); - } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveDeleteEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveDeleteEventListener.java index 66151f03b..6b2392e1e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveDeleteEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveDeleteEventListener.java @@ -48,6 +48,7 @@ import org.hibernate.reactive.event.ReactiveDeleteEventListener; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; +import org.hibernate.reactive.session.ReactiveQueryProducer; import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.type.CollectionType; import org.hibernate.type.CompositeType; @@ -199,7 +200,7 @@ private CompletionStage fetchAndDelete(DeleteEvent event, DeleteContext tr } //Object entity = persistenceContext.unproxyAndReassociate( event.getObject() ); - return ( (ReactiveSession) source ) + return ( (ReactiveQueryProducer) source ) .reactiveFetch( objectEvent, true ) .thenCompose( entity -> delete( event, transientEntities, entity ) ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLockEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLockEventListener.java index 7832298e0..a75ac1194 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLockEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLockEventListener.java @@ -35,6 +35,7 @@ import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister; +import org.hibernate.reactive.session.ReactiveQueryProducer; import org.hibernate.reactive.session.ReactiveSession; import static org.hibernate.pretty.MessageHelper.infoString; @@ -76,7 +77,7 @@ public CompletionStage reactiveOnLock(LockEvent event) throws HibernateExc //TODO: if object was an uninitialized proxy, this is inefficient, // resulting in two SQL selects - return ( (ReactiveSession) source ).reactiveFetch( event.getObject(), true ) + return ( (ReactiveQueryProducer) source ).reactiveFetch( event.getObject(), true ) .thenCompose( entity -> reactiveOnLock( event, entity ) ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java index f5e5eb1b4..ba3edda4a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java @@ -37,7 +37,7 @@ import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.persister.entity.impl.ReactiveAbstractEntityPersister; -import org.hibernate.reactive.session.ReactiveSession; +import org.hibernate.reactive.session.ReactiveQueryProducer; import org.hibernate.type.CollectionType; import org.hibernate.type.CompositeType; import org.hibernate.type.Type; @@ -84,7 +84,7 @@ public CompletionStage reactiveOnRefresh(RefreshEvent event, RefreshContex // Hibernate Reactive doesn't support detached instances in refresh() throw new IllegalArgumentException( "Unmanaged instance passed to refresh()" ); } - return ( (ReactiveSession) source ) + return ( (ReactiveQueryProducer) source ) .reactiveFetch( event.getObject(), true ) .thenCompose( entity -> reactiveOnRefresh( event, refreshedAlready, entity ) ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java index fef169431..d8f77be0f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java @@ -32,8 +32,8 @@ public interface Log extends BasicLogger { @LogMessage(level = INFO) - @Message(id = 1, value = "Hibernate Reactive") - void startHibernateReactive(); + @Message(id = 1, value = "Hibernate Reactive version %s") + void startHibernateReactive(String version); @LogMessage(level = INFO) @Message(id = 2, value = "Vert.x not detected, creating a new instance") diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Version.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Version.java new file mode 100644 index 000000000..2ed6d6b9d --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Version.java @@ -0,0 +1,32 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.logging.impl; + +/** + * Information about the version of Hibernate Reactive. + * + * @author Steve Ebersole + */ +public final class Version { + + private static final String VERSION; + static { + final String version = Version.class.getPackage().getImplementationVersion(); + VERSION = version != null ? version : "[WORKING]"; + } + + private Version() { + } + + /** + * Access to the Hibernate Reactive version. + * + * @return The version + */ + public static String getVersionString() { + return VERSION; + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java index 65b0ad906..90972f9c5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java @@ -2279,6 +2279,31 @@ default Uni withStatelessTransaction(Function> w */ Statistics getStatistics(); + /** + * Return the current instance of {@link Session}, if any. + * A current session exists only when this method is called + * from within an invocation of {@link #withSession(Function)} + * or {@link #withTransaction(Function)}. + * + * @return the current instance, if any, or {@code null} + * + * @since 2.4.7 + */ + Session getCurrentSession(); + + /** + * Return the current instance of {@link Session}, if any. + * A current session exists only when this method is called + * from within an invocation of + * {@link #withStatelessSession(Function)} or + * {@link #withStatelessTransaction(Function)}. + * + * @return the current instance, if any, or {@code null} + * + * @since 2.4.7 + */ + StatelessSession getCurrentStatelessSession(); + /** * Destroy the session factory and clean up its connection pool. */ diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/delegation/MutinySessionDelegator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/delegation/MutinySessionDelegator.java new file mode 100644 index 000000000..09d85913a --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/delegation/MutinySessionDelegator.java @@ -0,0 +1,341 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.mutiny.delegation; + +import io.smallrye.mutiny.Uni; +import jakarta.persistence.CacheRetrieveMode; +import jakarta.persistence.CacheStoreMode; +import jakarta.persistence.EntityGraph; +import jakarta.persistence.FlushModeType; +import jakarta.persistence.LockModeType; +import jakarta.persistence.criteria.CriteriaDelete; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.CriteriaUpdate; +import jakarta.persistence.metamodel.Attribute; +import org.hibernate.CacheMode; +import org.hibernate.Filter; +import org.hibernate.FlushMode; +import org.hibernate.Incubating; +import org.hibernate.LockMode; +import org.hibernate.reactive.common.AffectedEntities; +import org.hibernate.reactive.common.Identifier; +import org.hibernate.reactive.common.ResultSetMapping; +import org.hibernate.reactive.mutiny.Mutiny; + +import java.util.List; +import java.util.function.Function; + +/** + * Wraps a {@linkplain #delegate} session. + * + * @author Gavin King + */ +public abstract class MutinySessionDelegator implements Mutiny.Session { + + public abstract Mutiny.Session delegate(); + + public Uni find(Class entityClass, Object id) { + return delegate().find(entityClass, id); + } + + public Uni find(Class entityClass, Object id, LockModeType lockModeType) { + return delegate().find(entityClass, id, lockModeType); + } + + public Uni find(Class entityClass, Object id, LockMode lockMode) { + return delegate().find(entityClass, id, lockMode); + } + + public Uni find(EntityGraph entityGraph, Object id) { + return delegate().find(entityGraph, id); + } + + public Uni> find(Class entityClass, Object... ids) { + return delegate().find(entityClass, ids); + } + + public Mutiny.SelectionQuery createNamedQuery(String queryName, Class resultType) { + return delegate().createNamedQuery(queryName, resultType); + } + + public Mutiny.SelectionQuery createQuery(String queryString, Class resultType) { + return delegate().createQuery(queryString, resultType); + } + + public boolean isReadOnly(Object entityOrProxy) { + return delegate().isReadOnly(entityOrProxy); + } + + public Mutiny.SelectionQuery createNativeQuery(String queryString, Class resultType, AffectedEntities affectedEntities) { + return delegate().createNativeQuery(queryString, resultType, affectedEntities); + } + + public boolean isDefaultReadOnly() { + return delegate().isDefaultReadOnly(); + } + + public Uni unproxy(T association) { + return delegate().unproxy(association); + } + + public Mutiny.MutationQuery createMutationQuery(String queryString) { + return delegate().createMutationQuery(queryString); + } + + public Uni close() { + return delegate().close(); + } + + public Mutiny.Session disableFetchProfile(String name) { + return delegate().disableFetchProfile(name); + } + + public EntityGraph getEntityGraph(Class rootType, String graphName) { + return delegate().getEntityGraph(rootType, graphName); + } + + public Mutiny.SelectionQuery createSelectionQuery(String queryString, Class resultType) { + return delegate().createSelectionQuery(queryString, resultType); + } + + public Uni refresh(Object entity, LockModeType lockModeType) { + return delegate().refresh(entity, lockModeType); + } + + public Uni lock(Object entity, LockModeType lockModeType) { + return delegate().lock(entity, lockModeType); + } + + public Mutiny.SelectionQuery createNativeQuery(String queryString, ResultSetMapping resultSetMapping, AffectedEntities affectedEntities) { + return delegate().createNativeQuery(queryString, resultSetMapping, affectedEntities); + } + + public Uni lock(Object entity, LockMode lockMode) { + return delegate().lock(entity, lockMode); + } + + @Incubating + public Uni find(Class entityClass, Identifier naturalId) { + return delegate().find(entityClass, naturalId); + } + + public Uni withTransaction(Function> work) { + return delegate().withTransaction(work); + } + + public Mutiny.SelectionQuery createNativeQuery(String queryString, ResultSetMapping resultSetMapping) { + return delegate().createNativeQuery(queryString, resultSetMapping); + } + + public EntityGraph createEntityGraph(Class rootType, String graphName) { + return delegate().createEntityGraph(rootType, graphName); + } + + public Mutiny.Transaction currentTransaction() { + return delegate().currentTransaction(); + } + + public Mutiny.Session detach(Object entity) { + return delegate().detach(entity); + } + + public Mutiny.Session setCacheStoreMode(CacheStoreMode cacheStoreMode) { + return delegate().setCacheStoreMode(cacheStoreMode); + } + + public FlushMode getFlushMode() { + return delegate().getFlushMode(); + } + + public LockMode getLockMode(Object entity) { + return delegate().getLockMode(entity); + } + + public Mutiny.Query createNamedQuery(String queryName) { + return delegate().createNamedQuery(queryName); + } + + public Mutiny.SessionFactory getFactory() { + return delegate().getFactory(); + } + + public Mutiny.SelectionQuery createNativeQuery(String queryString, Class resultType) { + return delegate().createNativeQuery(queryString, resultType); + } + + public Mutiny.Session setSubselectFetchingEnabled(boolean enabled) { + return delegate().setSubselectFetchingEnabled(enabled); + } + + public Mutiny.Session setFlushMode(FlushMode flushMode) { + return delegate().setFlushMode(flushMode); + } + + public Uni remove(Object entity) { + return delegate().remove(entity); + } + + public Mutiny.Session setCacheMode(CacheMode cacheMode) { + return delegate().setCacheMode(cacheMode); + } + + public Filter enableFilter(String filterName) { + return delegate().enableFilter(filterName); + } + + public Mutiny.Query createNativeQuery(String queryString, AffectedEntities affectedEntities) { + return delegate().createNativeQuery(queryString, affectedEntities); + } + + public Mutiny.Session setReadOnly(Object entityOrProxy, boolean readOnly) { + return delegate().setReadOnly(entityOrProxy, readOnly); + } + + public EntityGraph createEntityGraph(Class rootType) { + return delegate().createEntityGraph(rootType); + } + + public Uni refreshAll(Object... entities) { + return delegate().refreshAll(entities); + } + + public Integer getBatchSize() { + return delegate().getBatchSize(); + } + + public Uni refresh(Object entity, LockMode lockMode) { + return delegate().refresh(entity, lockMode); + } + + public T getReference(Class entityClass, Object id) { + return delegate().getReference(entityClass, id); + } + + public T getReference(T entity) { + return delegate().getReference(entity); + } + + public Mutiny.Session setBatchSize(Integer batchSize) { + return delegate().setBatchSize(batchSize); + } + + public Uni refresh(Object entity) { + return delegate().refresh(entity); + } + + public CacheMode getCacheMode() { + return delegate().getCacheMode(); + } + + public Uni mergeAll(Object... entities) { + return delegate().mergeAll(entities); + } + + public Uni persist(Object object) { + return delegate().persist(object); + } + + public boolean contains(Object entity) { + return delegate().contains(entity); + } + + public int getFetchBatchSize() { + return delegate().getFetchBatchSize(); + } + + public Mutiny.Session setDefaultReadOnly(boolean readOnly) { + return delegate().setDefaultReadOnly(readOnly); + } + + public Mutiny.Session clear() { + return delegate().clear(); + } + + public Uni fetch(E entity, Attribute field) { + return delegate().fetch(entity, field); + } + + public Mutiny.SelectionQuery createQuery(CriteriaQuery criteriaQuery) { + return delegate().createQuery(criteriaQuery); + } + + public Mutiny.Session setCacheRetrieveMode(CacheRetrieveMode cacheRetrieveMode) { + return delegate().setCacheRetrieveMode(cacheRetrieveMode); + } + + public Uni removeAll(Object... entities) { + return delegate().removeAll(entities); + } + + public Filter getEnabledFilter(String filterName) { + return delegate().getEnabledFilter(filterName); + } + + public void disableFilter(String filterName) { + delegate().disableFilter(filterName); + } + + public Mutiny.MutationQuery createQuery(CriteriaDelete criteriaDelete) { + return delegate().createQuery(criteriaDelete); + } + + public Mutiny.Session enableFetchProfile(String name) { + return delegate().enableFetchProfile(name); + } + + public ResultSetMapping getResultSetMapping(Class resultType, String mappingName) { + return delegate().getResultSetMapping(resultType, mappingName); + } + + public Uni flush() { + return delegate().flush(); + } + + public Mutiny.Query createNativeQuery(String queryString) { + return delegate().createNativeQuery(queryString); + } + + public boolean isFetchProfileEnabled(String name) { + return delegate().isFetchProfileEnabled(name); + } + + public Uni merge(T entity) { + return delegate().merge(entity); + } + + public boolean isSubselectFetchingEnabled() { + return delegate().isSubselectFetchingEnabled(); + } + + public Mutiny.MutationQuery createQuery(CriteriaUpdate criteriaUpdate) { + return delegate().createQuery(criteriaUpdate); + } + + public Mutiny.Session setFetchBatchSize(int batchSize) { + return delegate().setFetchBatchSize(batchSize); + } + + public Uni fetch(T association) { + return delegate().fetch(association); + } + + public Uni persistAll(Object... entities) { + return delegate().persistAll(entities); + } + + @Deprecated + public Mutiny.Query createQuery(String queryString) { + return delegate().createQuery(queryString); + } + + public Mutiny.Session setFlushMode(FlushModeType flushModeType) { + return delegate().setFlushMode(flushModeType); + } + + public boolean isOpen() { + return delegate().isOpen(); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/delegation/MutinyStatelessSessionDelegator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/delegation/MutinyStatelessSessionDelegator.java new file mode 100644 index 000000000..152140006 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/delegation/MutinyStatelessSessionDelegator.java @@ -0,0 +1,191 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.mutiny.delegation; + +import io.smallrye.mutiny.Uni; +import jakarta.persistence.EntityGraph; +import jakarta.persistence.LockModeType; +import jakarta.persistence.criteria.CriteriaQuery; +import org.hibernate.Incubating; +import org.hibernate.LockMode; +import org.hibernate.reactive.common.ResultSetMapping; +import org.hibernate.reactive.mutiny.Mutiny; + +import java.util.function.Function; + +/** + * Wraps a {@linkplain #delegate} stateless session. + * + * @author Gavin King + */ +public abstract class MutinyStatelessSessionDelegator implements Mutiny.StatelessSession { + + public abstract Mutiny.StatelessSession delegate(); + + public Uni get(Class entityClass, Object id) { + return delegate().get(entityClass, id); + } + + @Deprecated + public Mutiny.Query createQuery(String queryString) { + return delegate().createQuery(queryString); + } + + public Uni get(EntityGraph entityGraph, Object id) { + return delegate().get(entityGraph, id); + } + + public Mutiny.SelectionQuery createNativeQuery(String queryString, ResultSetMapping resultSetMapping) { + return delegate().createNativeQuery(queryString, resultSetMapping); + } + + public Uni get(Class entityClass, Object id, LockMode lockMode) { + return delegate().get(entityClass, id, lockMode); + } + + public boolean isOpen() { + return delegate().isOpen(); + } + + public Uni insertAll(Object... entities) { + return delegate().insertAll(entities); + } + + public Uni updateAll(int batchSize, Object... entities) { + return delegate().updateAll(batchSize, entities); + } + + public EntityGraph createEntityGraph(Class rootType, String graphName) { + return delegate().createEntityGraph(rootType, graphName); + } + + public EntityGraph getEntityGraph(Class rootType, String graphName) { + return delegate().getEntityGraph(rootType, graphName); + } + + public Uni get(Class entityClass, Object id, LockModeType lockModeType) { + return delegate().get(entityClass, id, lockModeType); + } + + public Uni update(Object entity) { + return delegate().update(entity); + } + + public Uni refreshAll(int batchSize, Object... entities) { + return delegate().refreshAll(batchSize, entities); + } + + public Mutiny.SelectionQuery createQuery(String queryString, Class resultType) { + return delegate().createQuery(queryString, resultType); + } + + public Uni delete(Object entity) { + return delegate().delete(entity); + } + + public Uni refresh(Object entity, LockModeType lockModeType) { + return delegate().refresh(entity, lockModeType); + } + + public Mutiny.SessionFactory getFactory() { + return delegate().getFactory(); + } + + public Mutiny.SelectionQuery createNativeQuery(String queryString, Class resultType) { + return delegate().createNativeQuery(queryString, resultType); + } + + public Uni deleteAll(Object... entities) { + return delegate().deleteAll(entities); + } + + public Mutiny.SelectionQuery createSelectionQuery(String queryString, Class resultType) { + return delegate().createSelectionQuery(queryString, resultType); + } + + @Incubating + public Uni upsert(Object entity) { + return delegate().upsert(entity); + } + + public Mutiny.MutationQuery createMutationQuery(String queryString) { + return delegate().createMutationQuery(queryString); + } + + public Uni refresh(Object entity) { + return delegate().refresh(entity); + } + + public ResultSetMapping getResultSetMapping(Class resultType, String mappingName) { + return delegate().getResultSetMapping(resultType, mappingName); + } + + public Uni insertAll(int batchSize, Object... entities) { + return delegate().insertAll(batchSize, entities); + } + + public Mutiny.Query createNativeQuery(String queryString) { + return delegate().createNativeQuery(queryString); + } + + public Mutiny.Query createNamedQuery(String queryName) { + return delegate().createNamedQuery(queryName); + } + + public Mutiny.SelectionQuery createNamedQuery(String queryName, Class resultType) { + return delegate().createNamedQuery(queryName, resultType); + } + + public Uni refreshAll(Object... entities) { + return delegate().refreshAll(entities); + } + + public Uni close() { + return delegate().close(); + } + + public Uni updateAll(Object... entities) { + return delegate().updateAll(entities); + } + + public Mutiny.SelectionQuery createQuery(CriteriaQuery criteriaQuery) { + return delegate().createQuery(criteriaQuery); + } + + public Uni withTransaction(Function> work) { + return delegate().withTransaction(work); + } + + public Uni fetch(T association) { + return delegate().fetch(association); + } + + @Incubating + public Uni upsert(String entityName, Object entity) { + return delegate().upsert(entityName, entity); + } + + @Incubating + public Mutiny.Transaction currentTransaction() { + return delegate().currentTransaction(); + } + + public EntityGraph createEntityGraph(Class rootType) { + return delegate().createEntityGraph(rootType); + } + + public Uni insert(Object entity) { + return delegate().insert(entity); + } + + public Uni refresh(Object entity, LockMode lockMode) { + return delegate().refresh(entity, lockMode); + } + + public Uni deleteAll(int batchSize, Object... entities) { + return delegate().deleteAll(batchSize, entities); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java index aecc99e37..23feaba57 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java @@ -148,6 +148,16 @@ private CompletionStage connection(String tenantId) { : connectionPool.getConnection( tenantId ); } + @Override + public Mutiny.Session getCurrentSession() { + return context.get( contextKeyForSession ); + } + + @Override + public Mutiny.StatelessSession getCurrentStatelessSession() { + return context.get( contextKeyForStatelessSession ); + } + @Override public Uni withSession(Function> work) { Objects.requireNonNull( work, "parameter 'work' is required" ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveIntegrator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveIntegrator.java index 0d1876320..cda361d5a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveIntegrator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveIntegrator.java @@ -28,6 +28,7 @@ import org.hibernate.reactive.event.impl.DefaultReactiveResolveNaturalIdEventListener; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; +import org.hibernate.reactive.logging.impl.Version; import org.hibernate.service.ServiceRegistry; import org.hibernate.service.spi.SessionFactoryServiceRegistry; @@ -54,7 +55,7 @@ public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactor private void attachEventContextManagingListenersIfRequired(ServiceRegistry serviceRegistry) { if ( ReactiveModeCheck.isReactiveRegistry( serviceRegistry ) ) { - LOG.startHibernateReactive(); + LOG.startHibernateReactive( Version.getVersionString() ); EventListenerRegistry eventListenerRegistry = serviceRegistry.getService( EventListenerRegistry.class ); eventListenerRegistry.addDuplicationStrategy( ReplacementDuplicationStrategy.INSTANCE ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java index d1ea9026f..a75872f43 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java @@ -30,8 +30,8 @@ import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; import org.hibernate.query.sqm.tree.expression.SqmParameter; +import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveAbstractMutationHandler; -import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; import org.hibernate.sql.ast.SqlAstTranslator; @@ -179,7 +179,7 @@ public MappingModelExpressible getResolvedMappingModelType(SqmParameter StandardReactiveSelectExecutor.INSTANCE.list( select, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java index aaf1be3e9..86f0a8bea 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java @@ -43,10 +43,10 @@ import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement; import org.hibernate.query.sqm.tree.insert.SqmValues; +import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveHandler; -import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; import org.hibernate.spi.NavigablePath; @@ -575,7 +575,7 @@ public MappingModelExpressible getResolvedMappingModelType(SqmParameter StandardReactiveSelectExecutor.INSTANCE.list( select, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java index a00287eab..d19728fb1 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java @@ -25,7 +25,7 @@ import org.hibernate.persister.entity.EntityPersister; import org.hibernate.proxy.LazyInitializer; import org.hibernate.proxy.map.MapProxy; -import org.hibernate.reactive.session.ReactiveSession; +import org.hibernate.reactive.session.ReactiveQueryProducer; import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; import org.hibernate.sql.results.graph.AssemblerCreationState; @@ -532,7 +532,7 @@ protected CompletionStage reactiveResolveEntityInstance(ReactiveEntityIn // If this initializer owns the entity, we have to remove the entity holder, // because the subsequent loading process will claim the entity session.getPersistenceContextInternal().removeEntityHolder( data.getEntityKey() ); - return ( (ReactiveSession) session ).reactiveInternalLoad( + return ( (ReactiveQueryProducer) session ).reactiveInternalLoad( data.getConcreteDescriptor().getEntityName(), data.getEntityKey().getIdentifier(), true, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializer.java index 8f848d01b..6d5ea5418 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializer.java @@ -23,7 +23,7 @@ import org.hibernate.proxy.LazyInitializer; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.reactive.session.ReactiveSession; +import org.hibernate.reactive.session.ReactiveQueryProducer; import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.results.graph.AssemblerCreationState; @@ -154,7 +154,7 @@ else if ( data.getInstance() == null ) { data.setState( State.INITIALIZED ); final String entityName = concreteDescriptor.getEntityName(); - return ( (ReactiveSession) session ).reactiveInternalLoad( + return ( (ReactiveQueryProducer) session ).reactiveInternalLoad( entityName, data.getEntityIdentifier(), true, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java index 47da9f6cf..3a6164a39 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java @@ -2314,6 +2314,31 @@ default CompletionStage withStatelessTransaction(Function connection(String tenantId) { : connectionPool.getConnection( tenantId ); } + @Override + public Stage.Session getCurrentSession() { + return context.get( contextKeyForSession ); + } + + @Override + public Stage.StatelessSession getCurrentStatelessSession() { + return context.get( contextKeyForStatelessSession ); + } + @Override public CompletionStage withSession(Function> work) { Objects.requireNonNull( work, "parameter 'work' is required" ); diff --git a/hibernate-reactive-core/src/main/resources/META-INF/services/io.vertx.core.spi.VertxServiceProvider b/hibernate-reactive-core/src/main/resources/META-INF/services/io.vertx.core.spi.VertxServiceProvider deleted file mode 100644 index 80ffcb31c..000000000 --- a/hibernate-reactive-core/src/main/resources/META-INF/services/io.vertx.core.spi.VertxServiceProvider +++ /dev/null @@ -1 +0,0 @@ -org.hibernate.reactive.context.impl.ContextualDataStorage \ No newline at end of file diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinyStatelessSessionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinyStatelessSessionTest.java new file mode 100644 index 000000000..5d325b5f6 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinyStatelessSessionTest.java @@ -0,0 +1,250 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive; + +import io.vertx.junit5.Timeout; +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.*; +import jakarta.persistence.criteria.*; +import org.junit.jupiter.api.Test; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.junit.jupiter.api.Assertions.*; + +@Timeout(value = 10, timeUnit = MINUTES) + +public class MutinyStatelessSessionTest extends BaseReactiveTest { + + @Override + protected Collection> annotatedEntities() { + return List.of( GuineaPig.class ); + } + + @Test + public void testStatelessSession(VertxTestContext context) { + GuineaPig pig = new GuineaPig( "Aloi" ); + test( context, getMutinySessionFactory().withStatelessSession( ss -> ss + .insert( pig ) + .chain( v -> ss.createSelectionQuery( "from GuineaPig where name=:n", GuineaPig.class ) + .setParameter( "n", pig.name ) + .getResultList() ) + .invoke( list -> { + assertFalse( list.isEmpty() ); + assertEquals( 1, list.size() ); + assertThatPigsAreEqual( pig, list.get( 0 ) ); + } ) + .chain( v -> ss.get( GuineaPig.class, pig.id ) ) + .chain( p -> { + assertThatPigsAreEqual( pig, p ); + p.name = "X"; + return ss.update( p ); + } ) + .chain( v -> ss.refresh( pig ) ) + .invoke( v -> assertEquals( pig.name, "X" ) ) + .chain( v -> ss.createMutationQuery( "update GuineaPig set name='Y'" ).executeUpdate() ) + .chain( v -> ss.refresh( pig ) ) + .invoke( v -> assertEquals( pig.name, "Y" ) ) + .chain( v -> ss.delete( pig ) ) + .chain( v -> ss.createSelectionQuery( "from GuineaPig", GuineaPig.class ).getResultList() ) + .invoke( list -> assertTrue( list.isEmpty() ) ) ) + ); + } + + @Test + public void testStatelessSessionWithNamed(VertxTestContext context) { + GuineaPig pig = new GuineaPig( "Aloi" ); + test( context, getMutinySessionFactory().withStatelessSession( ss -> ss + .insert( pig ) + .chain( v -> ss.createNamedQuery( "findbyname", GuineaPig.class ) + .setParameter( "n", pig.name ) + .getResultList() ) + .invoke( list -> { + assertFalse( list.isEmpty() ); + assertEquals( 1, list.size() ); + assertThatPigsAreEqual( pig, list.get( 0 ) ); + } ) + .chain( v -> ss.get( GuineaPig.class, pig.id ) ) + .chain( p -> { + assertThatPigsAreEqual( pig, p ); + p.name = "X"; + return ss.update( p ); + } ) + .chain( v -> ss.refresh( pig ) ) + .invoke( v -> assertEquals( pig.name, "X" ) ) + .chain( v -> ss.createNamedQuery( "updatebyname" ).executeUpdate() ) + .chain( v -> ss.refresh( pig ) ) + .invoke( v -> assertEquals( pig.name, "Y" ) ) + .chain( v -> ss.delete( pig ) ) + .chain( v -> ss.createNamedQuery( "findall" ).getResultList() ) + .invoke( list -> assertTrue( list.isEmpty() ) ) ) + ); + } + + @Test + public void testStatelessSessionWithNative(VertxTestContext context) { + GuineaPig pig = new GuineaPig( "Aloi" ); + test( context, getMutinySessionFactory().openStatelessSession() + .chain( ss -> ss.insert( pig ) + .chain( v -> ss + .createNativeQuery( "select * from Piggy where name=:n", GuineaPig.class ) + .setParameter( "n", pig.name ) + .getResultList() ) + .invoke( list -> { + assertFalse( list.isEmpty() ); + assertEquals( 1, list.size() ); + assertThatPigsAreEqual( pig, list.get( 0 ) ); + } ) + .chain( v -> ss.get( GuineaPig.class, pig.id ) ) + .chain( p -> { + assertThatPigsAreEqual( pig, p ); + p.name = "X"; + return ss.update( p ); + } ) + .chain( v -> ss.refresh( pig ) ) + .invoke( v -> assertEquals( pig.name, "X" ) ) + .chain( v -> ss.createNativeQuery( "update Piggy set name='Y'" ) + .executeUpdate() ) + .invoke( rows -> assertEquals( 1, rows ) ) + .chain( v -> ss.refresh( pig ) ) + .invoke( v -> assertEquals( pig.name, "Y" ) ) + .chain( v -> ss.delete( pig ) ) + .chain( v -> ss.createNativeQuery( "select id from Piggy" ).getResultList() ) + .invoke( list -> assertTrue( list.isEmpty() ) ) + .chain( v -> ss.close() ) ) + ); + } + + @Test + public void testStatelessSessionCriteria(VertxTestContext context) { + GuineaPig pig = new GuineaPig( "Aloi" ); + GuineaPig mate = new GuineaPig("Aloina"); + pig.mate = mate; + + CriteriaBuilder cb = getSessionFactory().getCriteriaBuilder(); + + CriteriaQuery query = cb.createQuery( GuineaPig.class ); + Root gp = query.from( GuineaPig.class ); + query.where( cb.equal( gp.get( "name" ), cb.parameter( String.class, "n" ) ) ); + query.orderBy( cb.asc( gp.get( "name" ) ) ); + + test( context, getMutinySessionFactory().openStatelessSession() + .chain( ss -> ss.insert(mate) + .chain( v -> ss.insert(pig) ) + .chain( v -> ss.createQuery( query ) + .setParameter( "n", pig.name ) + .getResultList() ) + .invoke( list -> { + assertFalse( list.isEmpty() ); + assertEquals( 1, list.size() ); + assertThatPigsAreEqual( pig, list.get( 0 ) ); + } ) + .chain( v -> ss.close() ) ) + ); + } + + @Test + public void testTransactionPropagation(VertxTestContext context) { + test( context, getMutinySessionFactory().withStatelessSession( + session -> session.withTransaction( transaction -> session.createSelectionQuery( "from GuineaPig", GuineaPig.class ) + .getResultList() + .chain( list -> { + assertNotNull( session.currentTransaction() ); + assertFalse( session.currentTransaction().isMarkedForRollback() ); + session.currentTransaction().markForRollback(); + assertTrue( session.currentTransaction().isMarkedForRollback() ); + assertTrue( transaction.isMarkedForRollback() ); + return session.withTransaction( t -> { + assertTrue( t.isMarkedForRollback() ); + return session.createSelectionQuery( "from GuineaPig", GuineaPig.class ).getResultList(); + } ); + } ) ) + ) ); + } + + @Test + public void testSessionPropagation(VertxTestContext context) { + test( context, getMutinySessionFactory().withStatelessSession( + session -> session.createSelectionQuery( "from GuineaPig", GuineaPig.class ).getResultList() + .chain( list -> getMutinySessionFactory().withStatelessSession( s -> { + assertEquals( session, s ); + return s.createSelectionQuery( "from GuineaPig", GuineaPig.class ).getResultList(); + } ) ) + ) ); + } + + private void assertThatPigsAreEqual( GuineaPig expected, GuineaPig actual) { + assertNotNull( actual ); + assertEquals( expected.getId(), actual.getId() ); + assertEquals( expected.getName(), actual.getName() ); + } + + @NamedQuery(name = "findbyname", query = "from GuineaPig where name=:n") + @NamedQuery(name = "updatebyname", query = "update GuineaPig set name='Y'") + @NamedQuery(name = "findall", query = "from GuineaPig") + + @Entity(name = "GuineaPig") + @Table(name = "Piggy") + public static class GuineaPig { + @Id + @GeneratedValue + private Integer id; + private String name; + @Version + private int version; + + @ManyToOne + private GuineaPig mate; + + public GuineaPig() { + } + + public GuineaPig(String name) { + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return id + ": " + name; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + GuineaPig guineaPig = (GuineaPig) o; + return Objects.equals( name, guineaPig.name ); + } + + @Override + public int hashCode() { + return Objects.hash( name ); + } + } +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/delegation/ConcreteSessionDelegator.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/delegation/ConcreteSessionDelegator.java new file mode 100644 index 000000000..2c93d9226 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/delegation/ConcreteSessionDelegator.java @@ -0,0 +1,17 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.delegation; + +import org.hibernate.reactive.mutiny.Mutiny; +import org.hibernate.reactive.mutiny.delegation.MutinySessionDelegator; + +@SuppressWarnings("unused") +class ConcreteSessionDelegator extends MutinySessionDelegator { + @Override + public Mutiny.Session delegate() { + throw new UnsupportedOperationException(); + } +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/delegation/ConcreteStatelessSessionDelegator.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/delegation/ConcreteStatelessSessionDelegator.java new file mode 100644 index 000000000..e275d0180 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/delegation/ConcreteStatelessSessionDelegator.java @@ -0,0 +1,17 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.delegation; + +import org.hibernate.reactive.mutiny.Mutiny; +import org.hibernate.reactive.mutiny.delegation.MutinyStatelessSessionDelegator; + +@SuppressWarnings("unused") +class ConcreteStatelessSessionDelegator extends MutinyStatelessSessionDelegator { + @Override + public Mutiny.StatelessSession delegate() { + throw new UnsupportedOperationException(); + } +} diff --git a/jreleaser.yml b/jreleaser.yml new file mode 100644 index 000000000..e39594d95 --- /dev/null +++ b/jreleaser.yml @@ -0,0 +1,39 @@ +project: + languages: + java: + groupId: org.hibernate.reactive + +release: + github: + skipTag: true + skipRelease: true + tagName: '{{projectVersion}}' + +# File signing is always active +signing: + mode: COMMAND + active: RELEASE + armored: true + +deploy: + maven: + # TODO: Remove the entire nexus2 section when switching to maven central publishing (see below): + nexus2: + maven-central: + active: RELEASE + url: https://oss.sonatype.org/service/local + snapshotUrl: https://oss.sonatype.org/content/repositories/snapshots/ + closeRepository: true + releaseRepository: true + stagingRepositories: + - build/staging-deploy/maven + mavenCentral: + maven-central: + # TODO: Change to RELEASE once switching to Maven-Central: + # Note, this is an untested configuration, hence might need further adjustments + active: NEVER + url: https://central.sonatype.com/api/v1/publisher + snapshotSupported: false + applyMavenCentralRules: true + stagingRepositories: + - build/staging-deploy/maven diff --git a/publish.gradle b/publish.gradle index 977092d9a..f77b26959 100644 --- a/publish.gradle +++ b/publish.gradle @@ -1,6 +1,5 @@ apply plugin: 'java' apply plugin: 'maven-publish' -apply plugin: 'signing' // Java / publishing @@ -75,97 +74,17 @@ publishing { } } } -} - - -// Signing - -var signingExtension = project.getExtensions().getByType(SigningExtension) as SigningExtension - -var publishingExtension = project.getExtensions().getByType(PublishingExtension) as PublishingExtension -signingExtension.sign publishingExtension.publications.publishedArtifacts - -var signingKey = resolveSigningKey() -var signingPassphrase = resolveSigningPassphrase() -signingExtension.useInMemoryPgpKeys(signingKey, signingPassphrase) - -gradle.taskGraph.whenReady { TaskExecutionGraph graph -> - boolean wasPublishingRequested = false - - graph.allTasks.each {task -> - if ( task instanceof PublishToMavenRepository ) { - logger.lifecycle( "Found PublishToMavenRepository task : {}", task.path ) - wasPublishingRequested = true + repositories { + maven { + name = "staging" + url = rootProject.layout.buildDirectory.dir("staging-deploy${File.separator}maven") } - } - - if ( wasPublishingRequested ) { - def publishUser = resolvePublishUser() - def publishPass = resolvePublishPass() - if ( publishUser == null || publishPass == null ) { - throw new RuntimeException( "Cannot perform publishing to OSSRH without credentials." ) + maven { + name = 'snapshots' + url = "https://oss.sonatype.org/content/repositories/snapshots/" + // So that Gradle uses the `ORG_GRADLE_PROJECT_snapshotsPassword` / `ORG_GRADLE_PROJECT_snapshotsUsername` + // env variables to read the username/password for the `snapshots` repository publishing: + credentials(PasswordCredentials) } - - logger.lifecycle "Publishing {} : {} : {}", project.group, project.name, project.version - - // require signing if publishing to OSSRH - signingExtension.required = true - } - else if ( signingKey == null || signingPassphrase == null ) { - tasks.withType( Sign ).each { t-> t.enabled = false } - } -} - - -static String resolveSigningKey() { - var key = System.getenv().get( "SIGNING_GPG_PRIVATE_KEY" ) - if ( key != null ) { - return key - } - - var keyFile = System.getenv().get( "SIGNING_GPG_PRIVATE_KEY_PATH" ) - if ( keyFile != null ) { - return new File( keyFile ).text - } - - return null -} - -static String resolveSigningPassphrase() { - return System.getenv().get( "SIGNING_GPG_PASSPHRASE" ) -} - -String resolvePublishUser() { - var envVar = System.getenv().get( "ORG_GRADLE_PROJECT_sonatypeUsername" ) - if ( envVar != null ) { - return envVar - } - - def projectProp = projectPropOrNull( "sonatypeUsername" ) - if ( projectProp != null ) { - return projectProp } - - return null } - -String resolvePublishPass() { - var envVar = System.getenv().get( "ORG_GRADLE_PROJECT_sonatypePassword" ) - if ( envVar != null ) { - return envVar - } - - def projectProp = projectPropOrNull( "sonatypePassword" ) - if ( projectProp != null ) { - return projectProp - } - - return null -} - -String projectPropOrNull(String name) { - if ( project.hasProperty( name ) ) { - return project.findProperty( name ) - } - return null; -} \ No newline at end of file diff --git a/release/build.gradle b/release/build.gradle index 2b507a9a2..690534750 100644 --- a/release/build.gradle +++ b/release/build.gradle @@ -236,7 +236,6 @@ void updateVersionFile(var version) { def publishReleaseArtifactsTask = tasks.register( 'publishReleaseArtifacts' ) { dependsOn uploadDocumentationTask - dependsOn ":hibernate-reactive-core:publishToSonatype" mustRunAfter gitPreparationForReleaseTask }