From 02a48f839222ad691747fd54cf6157efd13516ba Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Tue, 26 Apr 2022 13:42:12 +0100 Subject: [PATCH 1/4] Porting to SDK-integrated version of transactions The transactions logic exists in the Java SDK as of 3.3.0, with a slightly different API. This is the first effort at the port, which literally just compiles. It will not run as crucial code has been commented and todo-ed. There is work remaining to figure out how to complete the port, as some crucial parts (such as ctx.commit() and ctx.rollback()) have been intentionally removed. --- pom.xml | 27 +- .../com/couchbase/client/java/Cluster.java | 11 + .../AttemptContextReactiveAccessor.java | 101 +- .../transactions/TransactionsReactive.java | 1506 ++++++++--------- .../demo/CouchbaseTransactionManager.java | 92 +- .../demo/CouchbaseTransactionalTemplate.java | 26 +- .../demo/SpringTransactionGetResult.java | 2 +- .../couchbase/CouchbaseClientFactory.java | 7 +- .../ReactiveCouchbaseClientFactory.java | 11 +- .../SimpleCouchbaseClientFactory.java | 20 +- .../SimpleReactiveCouchbaseClientFactory.java | 38 +- .../AbstractCouchbaseConfiguration.java | 34 +- .../ReactiveFindByIdOperationSupport.java | 14 +- .../ReactiveFindByQueryOperationSupport.java | 16 +- .../ReactiveInsertByIdOperationSupport.java | 22 +- .../ReactiveRemoveByIdOperationSupport.java | 16 +- ...ReactiveRemoveByQueryOperationSupport.java | 6 +- .../ReactiveReplaceByIdOperationSupport.java | 12 +- .../ReactiveUpsertByIdOperationSupport.java | 2 +- .../couchbase/core/query/OptionsBuilder.java | 23 +- .../transaction/SDCouchbaseTransactions.java | 76 - .../repository/TransactionResult.java | 1 + .../support/TransactionResultHolder.java | 22 +- .../couchbase/transaction/ClientSession.java | 22 +- .../transaction/ClientSessionImpl.java | 156 +- .../transaction/ClientSessionOptions.java | 3 +- .../CouchbaseAttemptContextReactive.java | 10 +- .../CouchbaseCallbackTransactionManager.java | 51 +- .../transaction/CouchbaseStuffHandle.java | 29 +- .../CouchbaseTransactionDefinition.java | 14 +- .../CouchbaseTransactionManager.java | 27 +- ...hbaseTransactionalOperatorNonReactive.save | 8 +- .../ReactiveCouchbaseTransactionManager.java | 42 +- .../transaction/TransactionOptions.java | 19 - .../transaction/TransactionsWrapper.java | 160 +- .../internal/AsyncClientSession.java | 2 +- ...basePersonTransactionIntegrationTests.java | 75 +- ...onTransactionReactiveIntegrationTests.java | 21 +- ...uchbaseReactiveTransactionNativeTests.java | 46 +- ...eTemplateTransaction2IntegrationTests.java | 14 +- ...seTemplateTransactionIntegrationTests.java | 10 - .../CouchbaseTransactionNativeTests.save | 34 +- 42 files changed, 1262 insertions(+), 1566 deletions(-) delete mode 100644 src/main/java/org/springframework/data/couchbase/core/transaction/SDCouchbaseTransactions.java delete mode 100644 src/main/java/org/springframework/data/couchbase/transaction/TransactionOptions.java diff --git a/pom.xml b/pom.xml index 99a817e2b..eb583ef70 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,6 @@ 3.2.5 3.2.5 2.7.0-SNAPSHOT - 1.2.2 spring.data.couchbase - - com.couchbase.client - couchbase-transactions - ${couchbase-transactions} - - - com.couchbase.client - java-client - - - - org.springframework spring-context-support @@ -244,18 +231,6 @@ 4.0.3 test - - com.couchbase.client - couchbase-transactions - ${couchbase-transactions} - compile - - - com.couchbase.client - java-client - - - org.testcontainers testcontainers @@ -264,7 +239,7 @@ com.couchbase.client java-client - 3.2.5 + 3.3.0-SNAPSHOT diff --git a/src/main/java/com/couchbase/client/java/Cluster.java b/src/main/java/com/couchbase/client/java/Cluster.java index 907b58241..0d3b3601c 100644 --- a/src/main/java/com/couchbase/client/java/Cluster.java +++ b/src/main/java/com/couchbase/client/java/Cluster.java @@ -44,6 +44,7 @@ import com.couchbase.client.java.search.SearchOptions; import com.couchbase.client.java.search.SearchQuery; import com.couchbase.client.java.search.result.SearchResult; +import com.couchbase.client.java.transactions.Transactions; import java.time.Duration; import java.util.Map; @@ -102,6 +103,7 @@ * The SDK will only work against Couchbase Server 5.0 and later, because RBAC (role-based access control) is a first * class concept since 3.0 and therefore required. */ +// todo gp is this required? public class Cluster implements ClusterInterface { /** @@ -574,5 +576,14 @@ public void waitUntilReady(final Duration timeout, final WaitUntilReadyOptions o block(asyncCluster.waitUntilReady(timeout, options)); } + /** + * Allows access to transactions. + * + * @return the {@link Transactions} interface. + */ + @Stability.Uncommitted + public Transactions transactions() { + return new Transactions(core(), environment().jsonSerializer()); + } } diff --git a/src/main/java/com/couchbase/transactions/AttemptContextReactiveAccessor.java b/src/main/java/com/couchbase/transactions/AttemptContextReactiveAccessor.java index f59d110ed..5f2a0559f 100644 --- a/src/main/java/com/couchbase/transactions/AttemptContextReactiveAccessor.java +++ b/src/main/java/com/couchbase/transactions/AttemptContextReactiveAccessor.java @@ -17,12 +17,10 @@ package com.couchbase.transactions; import com.couchbase.client.core.annotation.Stability; -import com.couchbase.transactions.config.MergedTransactionConfig; -import com.couchbase.transactions.config.PerTransactionConfig; -import com.couchbase.transactions.config.PerTransactionConfigBuilder; -import com.couchbase.transactions.config.TransactionConfig; -import com.couchbase.transactions.forwards.Supported; -import com.couchbase.transactions.log.TransactionLogger; +import com.couchbase.client.core.transaction.log.CoreTransactionLogger; +import com.couchbase.client.java.transactions.ReactiveTransactionAttemptContext; +import com.couchbase.client.java.transactions.TransactionAttemptContext; +import org.springframework.transaction.reactive.TransactionContext; import java.time.Duration; import java.time.temporal.ChronoUnit; @@ -30,61 +28,70 @@ import java.util.UUID; /** - * To access the AttemptContextReactive held by AttemptContext + * To access the ReactiveTransactionAttemptContext held by TransactionAttemptContext * * @author Michael Reiche */ public class AttemptContextReactiveAccessor { - public static AttemptContextReactive getACR(AttemptContext attemptContext) { - return attemptContext.ctx(); + public static ReactiveTransactionAttemptContext getACR(TransactionAttemptContext attemptContext) { + // return attemptContext.ctx(); + // todo gp is this access needed. Could hold the raw CoreTransactionAttemptContext instead. + return null; } - public static AttemptContext from(AttemptContextReactive attemptContextReactive) { - return new AttemptContext(attemptContextReactive); + public static TransactionAttemptContext from(ReactiveTransactionAttemptContext attemptContextReactive) { + // todo gp needed? + return null; +// return new TransactionAttemptContext(attemptContextReactive); } - public static TransactionLogger getLogger(AttemptContextReactive attemptContextReactive){ - return attemptContextReactive.LOGGER; - } - @Stability.Internal - public static AttemptContextReactive newAttemptContextReactive(TransactionsReactive transactions){ - PerTransactionConfig perConfig = PerTransactionConfigBuilder.create().build(); - MergedTransactionConfig merged = new MergedTransactionConfig(transactions.config(), Optional.of(perConfig)); - - TransactionContext overall = new TransactionContext( - transactions.cleanup().clusterData().cluster().environment().requestTracer(), - transactions.cleanup().clusterData().cluster().environment().eventBus(), - UUID.randomUUID().toString(), now(), Duration.ZERO, merged); - - String txnId = UUID.randomUUID().toString(); - overall.LOGGER.info(configDebug(transactions.config(), perConfig)); - return transactions.createAttemptContext(overall, merged, txnId); + public static CoreTransactionLogger getLogger(ReactiveTransactionAttemptContext attemptContextReactive){ + // todo gp needed? + return null; + //return attemptContextReactive; } + // todo gp needed? +// @Stability.Internal +// public static ReactiveTransactionAttemptContext newAttemptContextReactive(TransactionsReactive transactions){ +// return null; +// PerTransactionConfig perConfig = PerTransactionConfigBuilder.create().build(); +// MergedTransactionConfig merged = new MergedTransactionConfig(transactions.config(), Optional.of(perConfig)); +// +// TransactionContext overall = new TransactionContext( +// transactions.cleanup().clusterData().cluster().environment().requestTracer(), +// transactions.cleanup().clusterData().cluster().environment().eventBus(), +// UUID.randomUUID().toString(), now(), Duration.ZERO, merged); +// +// String txnId = UUID.randomUUID().toString(); +// overall.LOGGER.info(configDebug(transactions.config(), perConfig)); +// return transactions.createAttemptContext(overall, merged, txnId); +// } private static Duration now() { return Duration.of(System.nanoTime(), ChronoUnit.NANOS); } - static private String configDebug(TransactionConfig config, PerTransactionConfig perConfig) { - StringBuilder sb = new StringBuilder(); - sb.append("library version: "); - sb.append(TransactionsReactive.class.getPackage().getImplementationVersion()); - sb.append(" config: "); - sb.append("atrs="); - sb.append(config.numAtrs()); - sb.append(", metadataCollection="); - sb.append(config.metadataCollection()); - sb.append(", expiry="); - sb.append(perConfig.expirationTime().orElse(config.transactionExpirationTime()).toMillis()); - sb.append("msecs durability="); - sb.append(config.durabilityLevel()); - sb.append(" per-txn config="); - sb.append(" durability="); - sb.append(perConfig.durabilityLevel()); - sb.append(", supported="); - sb.append(Supported.SUPPORTED); - return sb.toString(); - } + // todo gp if needed let's expose in the SDK +// static private String configDebug(TransactionConfig config, PerTransactionConfig perConfig) { +// StringBuilder sb = new StringBuilder(); +// sb.append("library version: "); +// sb.append(TransactionsReactive.class.getPackage().getImplementationVersion()); +// sb.append(" config: "); +// sb.append("atrs="); +// sb.append(config.numAtrs()); +// sb.append(", metadataCollection="); +// sb.append(config.metadataCollection()); +// sb.append(", expiry="); +// sb.append(perConfig.expirationTime().orElse(config.transactionExpirationTime()).toMillis()); +// sb.append("msecs durability="); +// sb.append(config.durabilityLevel()); +// sb.append(" per-txn config="); +// sb.append(" durability="); +// sb.append(perConfig.durabilityLevel()); +// sb.append(", supported="); +// sb.append(Supported.SUPPORTED); +// return sb.toString(); +// } } diff --git a/src/main/java/com/couchbase/transactions/TransactionsReactive.java b/src/main/java/com/couchbase/transactions/TransactionsReactive.java index 1e64e803e..352135ead 100644 --- a/src/main/java/com/couchbase/transactions/TransactionsReactive.java +++ b/src/main/java/com/couchbase/transactions/TransactionsReactive.java @@ -1,753 +1,753 @@ -/* - * Copyright 2021 Couchbase, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.couchbase.transactions; - -import com.couchbase.client.core.annotation.Stability; -import com.couchbase.client.core.cnc.EventBus; -import com.couchbase.client.core.retry.reactor.DefaultRetry; -import com.couchbase.client.core.retry.reactor.Jitter; -import com.couchbase.client.core.retry.reactor.RetryContext; -import com.couchbase.client.java.Cluster; -import com.couchbase.client.java.ReactiveCollection; -import com.couchbase.client.java.ReactiveScope; -import com.couchbase.client.java.json.JsonObject; -import com.couchbase.client.java.query.ReactiveQueryResult; -import com.couchbase.transactions.cleanup.ClusterData; -import com.couchbase.transactions.cleanup.TransactionsCleanup; -import com.couchbase.transactions.components.ATR; -import com.couchbase.transactions.components.ActiveTransactionRecord; -import com.couchbase.transactions.config.MergedTransactionConfig; -import com.couchbase.transactions.config.PerTransactionConfig; -import com.couchbase.transactions.config.PerTransactionConfigBuilder; -import com.couchbase.transactions.config.SingleQueryTransactionConfig; -import com.couchbase.transactions.config.SingleQueryTransactionConfigBuilder; -import com.couchbase.transactions.config.TransactionConfig; -import com.couchbase.transactions.deferred.TransactionSerializedContext; -import com.couchbase.transactions.error.TransactionCommitAmbiguous; -import com.couchbase.transactions.error.TransactionExpired; -import com.couchbase.transactions.error.TransactionFailed; -import com.couchbase.transactions.error.internal.ErrorClasses; -import com.couchbase.transactions.error.external.TransactionOperationFailed; -import com.couchbase.transactions.forwards.Supported; -import com.couchbase.transactions.log.EventBusPersistedLogger; -import com.couchbase.transactions.log.PersistedLogWriter; -import com.couchbase.transactions.log.TransactionLogEvent; -import com.couchbase.transactions.support.AttemptContextFactory; -import com.couchbase.transactions.support.AttemptStates; -import com.couchbase.transactions.support.OptionsWrapperUtil; -import com.couchbase.transactions.util.DebugUtil; -import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; - -import java.time.Duration; -import java.time.temporal.ChronoUnit; -import java.util.Objects; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Predicate; - -import static com.couchbase.transactions.error.internal.TransactionOperationFailedBuilder.createError; -import static com.couchbase.transactions.log.PersistedLogWriter.MAX_LOG_ENTRIES_DEFAULT; -import static com.couchbase.transactions.support.SpanWrapperUtil.DB_COUCHBASE_TRANSACTIONS; - -/** - * An asynchronous version of {@link Transactions}, allowing transactions to be created and run in an asynchronous - * manner. - *

- * The main method to run transactions is {@link TransactionsReactive#run}. - */ -public class TransactionsReactive { - static final int MAX_ATTEMPTS = 1000; - private final TransactionsCleanup cleanup; - private final TransactionConfig config; - private AttemptContextFactory attemptContextFactory; - private EventBusPersistedLogger persistedLogger; - - /** - * This is package-private. Applications should create a {@link Transactions} object instead, and then call {@link - * Transactions#reactive}. - */ - static TransactionsReactive create(Cluster cluster, TransactionConfig config) { - return new TransactionsReactive(cluster, config); - } - - private TransactionsReactive(Cluster cluster, TransactionConfig config) { - Objects.requireNonNull(cluster); - Objects.requireNonNull(config); - - ClusterData clusterData = new ClusterData(cluster); - this.config = config; - this.attemptContextFactory = config.attemptContextFactory(); - MergedTransactionConfig merged = new MergedTransactionConfig(config, Optional.empty()); - cleanup = new TransactionsCleanup(merged, clusterData); - - config.persistentLoggingCollection().ifPresent(collection -> { - PersistedLogWriter persistedLogWriter = new PersistedLogWriter(collection, MAX_LOG_ENTRIES_DEFAULT); - persistedLogger = new EventBusPersistedLogger(cluster.environment().eventBus(), persistedLogWriter, merged); - }); - } - - - /** - * The main transactions 'engine', responsible for attempting the transaction logic as many times as required, - * until the transaction commits, is explicitly rolled back, or expires. - */ - // TODO: changed from private to public. package-protected plus an accessor would be ok to - public Mono executeTransaction(MergedTransactionConfig config, - TransactionContext overall, - Mono transactionLogic) { - AtomicReference startTime = new AtomicReference<>(); - - return Mono.just(overall) - - .subscribeOn(reactor.core.scheduler.Schedulers.elastic()) - - .doOnSubscribe(v -> { - if (startTime.get() == null) startTime.set(System.nanoTime()); - }) - - // Where the magic happens: execute the app's transaction logic - // A AttemptContextReactive gets created in here. Rollback requires one of these (so it knows what - // to rollback), so only errors thrown inside this block can trigger rollback. - // So, expiry checks only get done inside this block. - .then(transactionLogic) - - .flatMap(this::executeImplicitCommit) - - // Track an attempt if non-error, and request that the attempt be cleaned up. Similar logic is also - // done in executeHandleErrorsPreRetry. - .doOnNext(ctx -> executeAddAttemptAndCleanupRequest(config, overall, ctx)) - - // Track an attempt if error, and perform rollback if needed. - // All errors reaching here must be a `TransactionOperationFailed`. - .onErrorResume(err -> executeHandleErrorsPreRetry(config, overall, err)) - - // This is the main place to retry txns. Feed all errors up to this centralised point. - // All errors reaching here must be a `TransactionOperationFailed`. - .retryWhen(executeCreateRetryWhen(overall)) - - // If we're here, then we've hit an error that we don't want to retry. - // Either raise some derivative of TransactionFailed to the app, or return an AttemptContextReactive - // to return success (some errors result in success, e.g. TRANSACTION_FAILED_POST_COMMIT) - // All errors reaching here must be an `ErrorWrapper`. - .onErrorResume(err -> executeHandleErrorsPostRetry(overall, err)) - - .doOnError(err -> { - if (config.logOnFailure() && !config.logDirectly()) { - EventBus eventBus = cleanup.clusterData().cluster().environment().eventBus(); - overall.LOGGER.logs().forEach(log -> { - eventBus.publish(new TransactionLogEvent(config.logOnFailureLevel(), - TransactionLogEvent.DEFAULT_CATEGORY, log.toString())); - }); - } - }) - - // If we get here, success - .doOnSuccess(v -> - overall.LOGGER.info("finished txn in %dus", - TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - startTime.get())) - ) - - // Safe to do single() as there will only ever be 1 result - .single() - .map(v -> createResultFromContext(overall)); - } - - private reactor.util.retry.Retry executeCreateRetryWhen(TransactionContext overall) { - Predicate> predicate = context -> { - Throwable exception = context.exception(); - - if (!(exception instanceof TransactionOperationFailed)) { - // A bug. Only TransactionOperationFailed is allowed to reach here. - throw new IllegalStateException("Non-TransactionOperationFailed '" + DebugUtil.dbg(exception) + "' received during retry, this is a bug", exception); - } - - TransactionOperationFailed e = (TransactionOperationFailed) exception; - - overall.LOGGER.info("TransactionOperationFailed retryTransaction=%s", e.retryTransaction()); - - return e.retryTransaction(); - }; - - return DefaultRetry.create(predicate) - - .exponentialBackoff(Duration.of(1, ChronoUnit.MILLIS), - Duration.of(2, ChronoUnit.MILLIS)) - - .doOnRetry(v -> overall.LOGGER.info("<>", "retrying transaction after backoff %dmillis", v.backoff().toMillis())) - - // Add some jitter so two txns don't livelock each other - .jitter(Jitter.random()) - - // Really, this is a safety-guard. The txn will be aborted when it expires. - .retryMax(MAX_ATTEMPTS) - - .toReactorRetry(); - } - - private Mono executeHandleErrorsPreRetry(MergedTransactionConfig config, - TransactionContext overall, Throwable err) { - if (!(err instanceof TransactionOperationFailed)) { - // A bug. Only TransactionOperationFailed is allowed to reach here. - overall.LOGGER.warn("<>", "received non-TransactionOperationFailed error %s, unable to rollback as don't have " + - "context", DebugUtil.dbg(err)); - return Mono.error(new IllegalStateException("received non-TransactionOperationFailed error " + err.getClass().getName() + " in pre-retry", err)); - } - - Mono autoRollback = Mono.empty(); - Mono cleanupReq = Mono.empty(); - - TransactionOperationFailed e = (TransactionOperationFailed) err; - AttemptContextReactive ctx = e.context(); - - overall.LOGGER.info("<>", "finishing attempt off after error '%s'", e); - - if (e.autoRollbackAttempt()) { - // In queryMode we always ROLLBACK, as there is possibly delta table state to cleanup, and there may be an - // ATR - we don't know - if (ctx.state() == AttemptStates.NOT_STARTED && !ctx.queryMode()) { - // This is a better way of doing [RETRY-ERR-NOATR] and likely means that the older logic for - // handling that won't trigger now - ctx.LOGGER.info(ctx.attemptId(), "told to auto-rollback but in NOT_STARTED state, so nothing to do - skipping rollback"); - } - else { - ctx.LOGGER.info(ctx.attemptId(), "auto-rolling-back on error"); - - autoRollback = ctx.rollbackInternal(false); - } - } else { - ctx.LOGGER.info(ctx.attemptId(), "has been told to skip auto-rollback"); - } - - if (!config.runRegularAttemptsCleanupThread()) { - // Don't add a request to a queue that no-one will be processing - ctx.LOGGER.trace(ctx.attemptId(), "skipping addition of cleanup request on failure as regular cleanup disabled"); - } - else { - cleanupReq = Mono.fromRunnable(() -> addCleanupRequestForContext(ctx)); - } - - Mono addAttempt = Mono.fromRunnable(() -> { - TransactionAttempt ta = TransactionAttempt.createFromContext(ctx, Optional.of(err)); - overall.addAttempt(ta); - ctx.LOGGER.info(ctx.attemptId(), "added attempt %s after error", ta); - }); - - final Mono cleanupReqForLambda = cleanupReq; - - return autoRollback - // See [Primary Operations] section in design document - .onErrorResume(er -> { - overall.LOGGER.info("<>", "rollback failed with %s, raising original error but with retryTransaction turned off", - DebugUtil.dbg(er)); - - // Still want to add attempt and cleanup request - return cleanupReqForLambda - .then(addAttempt) - .then(Mono.error(createError(e.context(), e.causingErrorClass()) - .raiseException(e.toRaise()) - .cause(e.getCause()) - .build())); - }) - .then(cleanupReqForLambda) - // Only want to add the attempt after doing the rollback, so the attempt has the correct state (hopefully - // ROLLED_BACK) - .then(addAttempt) - .then(Mono.defer(() -> { - if (e.retryTransaction() && overall.hasExpiredClientSide()) { - overall.LOGGER.info("<>", "original error planned to retry transaction, but it has subsequently expired"); - return Mono.error(createError(ctx, ErrorClasses.FAIL_EXPIRY) - .doNotRollbackAttempt() - .raiseException(TransactionOperationFailed.FinalErrorToRaise.TRANSACTION_EXPIRED) - .build()); - } - else { - // Raise the error up the stack so the logic later can decide whether to retry the transaction - overall.LOGGER.info("<>", "reraising original exception %s", DebugUtil.dbg(err)); - return Mono.error(err); - } - })) - .doFinally(v -> ctx.span().failWith(e)) - .thenReturn(ctx); - } - - private Mono executeHandleErrorsPostRetry(TransactionContext overall, Throwable err) { - if (!(err instanceof TransactionOperationFailed)) { - // A bug. Only TransactionOperationFailed is allowed to reach here. - return Mono.error(new IllegalStateException("Non-TransactionOperationFailed '" + DebugUtil.dbg(err) + "' received, this is a bug")); - } - - TransactionResult result = createResultFromContext(overall); - TransactionOperationFailed e = (TransactionOperationFailed) err; - - if (e.toRaise() == TransactionOperationFailed.FinalErrorToRaise.TRANSACTION_FAILED_POST_COMMIT) { - e.context().LOGGER.info(e.context().attemptId(), "converted TRANSACTION_FAILED_POST_COMMIT to success, unstagingComplete() will be false"); - - return Mono.just(e.context()); - } - else { - TransactionFailed ret; - - switch (e.toRaise()) { - case TRANSACTION_EXPIRED: { - String msg = "Transaction has expired configured timeout of " + overall.expirationTime().toMillis() + "msecs. The transaction is not committed."; - ret = new TransactionExpired(e.getCause(), result, msg); - break; - } - case TRANSACTION_COMMIT_AMBIGUOUS: { - String msg = "It is ambiguous whether the transaction committed"; - ret = new TransactionCommitAmbiguous(e.getCause(), result, msg); - break; - } - default: - ret = new TransactionFailed(e.getCause(), result); - break; - } - - e.context().LOGGER.info(e.context().attemptId(), "converted TransactionOperationFailed %s to final error %s", - e.toRaise(), ret); - - return Mono.error(ret); - } - } - - private void executeAddAttemptAndCleanupRequest(MergedTransactionConfig config, TransactionContext overall, - AttemptContextReactive ctx) { - TransactionAttempt ta = TransactionAttempt.createFromContext(ctx, Optional.empty()); - overall.addAttempt(ta); - ctx.LOGGER.info(ctx.attemptId(), "added attempt %s after success", ta); - - if (config.runRegularAttemptsCleanupThread()) { - addCleanupRequestForContext(ctx); - } else { - ctx.LOGGER.trace(ctx.attemptId(), "skipping addition of cleanup request on success"); - } - - ctx.span().finish(); - } - - private Mono executeImplicitCommit(AttemptContextReactive ctx) { - return Mono.defer(() -> { - // If app has not explicitly performed a commit, assume they want to do so anyway - if (!ctx.isDone()) { - if (ctx.serialized().isPresent()) { - return Mono.just(ctx); - } else { - ctx.LOGGER.trace(ctx.attemptId(), "doing implicit commit"); - - return ctx.commit() - .then(Mono.just(ctx)) - .onErrorResume(err -> Mono.error(TransactionOperationFailed.convertToOperationFailedIfNeeded(err, ctx))); - } - } else { - return Mono.just(ctx); - } - }); - } - - // TODO: changed from package-protected to public (could have just used an accessor class in same package) - public AttemptContextReactive createAttemptContext(TransactionContext overall, - MergedTransactionConfig config, - String attemptId) { - // null only happens in testing with Mockito, harmless - if (overall != null) { - return attemptContextFactory.create(overall, config, attemptId, this, Optional.of(overall.span())); - } else { - return null; - } - } - - /** - * Runs the supplied transactional logic until success or failure. - *

- * This is the asynchronous version of {@link Transactions#run}, so to cover the differences: - *

    - *
  • The transaction logic is supplied with a {@link AttemptContextReactive}, which contains asynchronous - * methods to allow it to read, mutate, insert and delete documents, as well as commit or rollback the - * transactions.
  • - *
  • The transaction logic should run these methods as a Reactor chain.
  • - *
  • The transaction logic should return a Mono{@literal <}Void{@literal >}. Any - * Flux or Mono can be converted to a Mono{@literal <}Void{@literal >} by - * calling .then() on it.
  • - *
  • This method returns a Mono{@literal <}TransactionResult{@literal >}, which should be handled - * as a normal Reactor Mono.
  • - *
- * - * @param transactionLogic the application's transaction logic - * @param perConfig the configuration to use for this transaction - * @return there is no need to check the returned {@link TransactionResult}, as success is implied by the lack of a - * thrown exception. It contains information useful only for debugging and logging. - * @throws TransactionFailed or a derived exception if the transaction fails to commit for any reason, possibly - * after multiple retries. The exception contains further details of the error. Not - */ - public Mono run(Function> transactionLogic, - PerTransactionConfig perConfig) { - return Mono.defer(() -> { - MergedTransactionConfig merged = new MergedTransactionConfig(config, Optional.of(perConfig)); - - TransactionContext overall = - new TransactionContext(cleanup.clusterData().cluster().environment().requestTracer(), - cleanup.clusterData().cluster().environment().eventBus(), - UUID.randomUUID().toString(), - now(), - Duration.ZERO, - merged); - AtomicReference startTime = new AtomicReference<>(0L); - - Mono ob = Mono.fromCallable(() -> { - String txnId = UUID.randomUUID().toString(); - overall.LOGGER.info(configDebug(config, perConfig)); - return createAttemptContext(overall, merged, txnId); - }).flatMap(ctx -> { - ctx.LOGGER.info("starting attempt %d/%s/%s", - overall.numAttempts(), ctx.transactionId(), ctx.attemptId()); - Mono result = transactionLogic.apply(ctx); - return result - .onErrorResume(err -> { - ctx.LOGGER.info(ctx.attemptId(), "caught exception '%s' in async, rethrowing", err); - logElidedStacktrace(ctx, err); - - return Mono.error(TransactionOperationFailed.convertToOperationFailedIfNeeded(err, ctx)); - }) - .thenReturn(ctx); - }).doOnSubscribe(v -> startTime.set(System.nanoTime())) - .doOnNext(v -> v.LOGGER.trace(v.attemptId(), "finished attempt %d in %sms", - overall.numAttempts(), (System.nanoTime() - startTime.get()) / 1_000_000)); - - return executeTransaction(merged, overall, ob) - .doOnNext(v -> overall.span().finish()) - .doOnError(err -> overall.span().failWith(err)); - }); - } - - // Printing the stacktrace is expensive in terms of log noise, but has been a life saver on many debugging - // encounters. Strike a balance by eliding the more useless elements. - // TODO: changed from private to public - public void logElidedStacktrace(AttemptContextReactive ctx, Throwable err) { - DebugUtil.fetchElidedStacktrace(err, (s) -> ctx.LOGGER.info(ctx.attemptId(), " " + s.toString())); - } - - // TODO: changed from private to public - public static String configDebug(TransactionConfig config, PerTransactionConfig perConfig) { - StringBuilder sb = new StringBuilder(); - sb.append("library version: "); - sb.append(TransactionsReactive.class.getPackage().getImplementationVersion()); - sb.append(" config: "); - sb.append("atrs="); - sb.append(config.numAtrs()); - sb.append(", metadataCollection="); - sb.append(config.metadataCollection()); - sb.append(", expiry="); - sb.append(perConfig.expirationTime().orElse(config.transactionExpirationTime()).toMillis()); - sb.append("msecs durability="); - sb.append(config.durabilityLevel()); - sb.append(" per-txn config="); - sb.append(" durability="); - sb.append(perConfig.durabilityLevel()); - sb.append(", supported="); - sb.append(Supported.SUPPORTED); - return sb.toString(); - } - - /** - * Convenience overload that runs {@link TransactionsReactive#run} with a default PerTransactionConfig. - */ - public Mono run(Function> transactionLogic) { - return run(transactionLogic, PerTransactionConfigBuilder.create().build()); - } - - @Stability.Volatile - public Mono commit(TransactionSerializedContext serialized, PerTransactionConfig perConfig) { - return deferred(serialized, - perConfig, - // Nothing to actually do, just want the implicit commit - (ctx) -> Mono.empty()); - } - - @Stability.Volatile - public Mono rollback(TransactionSerializedContext serialized, PerTransactionConfig perConfig) { - return deferred(serialized, - perConfig, - (ctx) -> ctx.rollback()); - } - - @Stability.Volatile - private Mono deferred(TransactionSerializedContext serialized, - PerTransactionConfig perConfig, - Function> initial) { - MergedTransactionConfig merged = new MergedTransactionConfig(config, Optional.of(perConfig)); - JsonObject hydrated = JsonObject.fromJson(serialized.encodeAsString()); - - String atrBucket = hydrated.getString("atrBucket"); - String atrScope = hydrated.getString("atrScope"); - String atrCollectionName = hydrated.getString("atrCollection"); - String atrId = hydrated.getString("atrId"); - ReactiveCollection atrCollection = cleanup.clusterData() - .getBucketFromName(atrBucket) - .scope(atrScope) - .collection(atrCollectionName); - - return ActiveTransactionRecord.getAtr(atrCollection, - atrId, - OptionsWrapperUtil.kvTimeoutNonMutating(merged, atrCollection.core()), - null) - - .flatMap(atrOpt -> { - if (!atrOpt.isPresent()) { - return Mono.error(new IllegalStateException(String.format("ATR %s/%s could not be found", - atrBucket, atrId))); - } - else { - ATR atr = atrOpt.get(); - - // Note startTimeServerMillis is written with ${Mutation.CAS} while currentTimeServer - // could have come from $vbucket.HLC and is hence one-second granularity. So, this is a - // somewhat imperfect comparison. - Duration currentTimeServer = Duration.ofNanos(atr.cas()); - Duration startTimeServer = Duration.ofMillis(hydrated.getLong("startTimeServerMillis")); - - // This includes the time elapsed during the first part of the transaction, plus any time - // elapsed during the period the transaction was expired. Total time since the transaction - // began, basically. - Duration timeElapsed = currentTimeServer.minus(startTimeServer); - - TransactionContext overall = - new TransactionContext(cleanup.clusterData().cluster().environment().requestTracer(), - cleanup.clusterData().cluster().environment().eventBus(), - UUID.randomUUID().toString(), - Duration.ofNanos(System.nanoTime()), - timeElapsed, - merged); - AtomicReference startTime = new AtomicReference<>(0L); - - overall.LOGGER.info("elapsed time = %dmsecs (ATR start time %dmsecs, current ATR time %dmsecs)", - timeElapsed.toMillis(), startTimeServer.toMillis(), currentTimeServer.toMillis()); - - Mono ob = Mono.defer(() -> { - AttemptContextReactive ctx = attemptContextFactory.createFrom(hydrated, overall, merged, this); - ctx.LOGGER.info("starting attempt %d/%s/%s", - overall.numAttempts(), ctx.transactionId(), ctx.attemptId()); - ctx.LOGGER.info(configDebug(config, perConfig)); - - return initial.apply(ctx) - - // TXNJ-50: Make sure we run user's blocking logic on a scheduler that can take it - .subscribeOn(Schedulers.elastic()) - - .onErrorResume(err -> { - ctx.LOGGER.info(ctx.attemptId(), "caught exception '%s' in deferred, rethrowing", - err); - - logElidedStacktrace(ctx, err); - - return Mono.error(TransactionOperationFailed.convertToOperationFailedIfNeeded(err, ctx)); - }) - - .doOnSubscribe(v -> startTime.set(System.nanoTime())) - - .doOnNext(v -> { - ctx.LOGGER.trace(ctx.attemptId(), "finished attempt %d in %sms", - overall.numAttempts(), (System.nanoTime() - startTime.get()) / 1_000_000); - }) - - .thenReturn(ctx); - }); - - return executeTransaction(merged, overall, ob) - .doOnNext(v -> overall.span().attribute(DB_COUCHBASE_TRANSACTIONS + "retries", overall.numAttempts()).finish()) - .doOnError(err -> overall.span().attribute(DB_COUCHBASE_TRANSACTIONS + "retries", overall.numAttempts()).failWith(err)); - } - }); - } - - Mono runBlocking(Consumer txnLogic, PerTransactionConfig perConfig) { - return Mono.defer(() -> { - MergedTransactionConfig merged = new MergedTransactionConfig(config, Optional.of(perConfig)); - TransactionContext overall = - new TransactionContext(cleanup.clusterData().cluster().environment().requestTracer(), - cleanup.clusterData().cluster().environment().eventBus(), - UUID.randomUUID().toString(), - now(), - Duration.ZERO, - merged); - AtomicReference startTime = new AtomicReference<>(0L); - overall.LOGGER.info(configDebug(config, perConfig)); - - Mono ob = Mono.defer(() -> { - String txnId = UUID.randomUUID().toString(); - AttemptContextReactive ctx = createAttemptContext(overall, merged, txnId); - AttemptContext ctxBlocking = new AttemptContext(ctx); - ctx.LOGGER.info("starting attempt %d/%s/%s", - overall.numAttempts(), ctx.transactionId(), ctx.attemptId()); - - return Mono.fromRunnable(() -> txnLogic.accept(ctxBlocking)) - - // TXNJ-50: Make sure we run user's blocking logic on a scheduler that can take it - .subscribeOn(Schedulers.elastic()) - - .onErrorResume(err -> { - ctx.LOGGER.info(ctx.attemptId(), "caught exception '%s' in runBlocking, rethrowing", err); - - logElidedStacktrace(ctx, err); - - return Mono.error(TransactionOperationFailed.convertToOperationFailedIfNeeded(err, ctx)); - }) - - .doOnSubscribe(v -> startTime.set(System.nanoTime())) - - .doOnNext(v -> { - ctx.LOGGER.trace(ctx.attemptId(), "finished attempt %d in %sms", - overall.numAttempts(), (System.nanoTime() - startTime.get()) / 1_000_000); - }) - - .thenReturn(ctx); - }); - - return executeTransaction(merged, overall, ob) - .doOnNext(v -> overall.span().attribute(DB_COUCHBASE_TRANSACTIONS + "retries", overall.numAttempts()).finish()) - .doOnError(err -> overall.span().attribute(DB_COUCHBASE_TRANSACTIONS + "retries", overall.numAttempts()).failWith(err)); - }); - } - - public TransactionConfig config() { - return config; - } - - private static Duration now() { - return Duration.of(System.nanoTime(), ChronoUnit.NANOS); - } - - TransactionsCleanup cleanup() { - return cleanup; - } - - private void addCleanupRequestForContext(AttemptContextReactive ctx) { - // Whether the txn was successful or not, still want to clean it up - if (ctx.queryMode()) { - ctx.LOGGER.info(ctx.attemptId(), "Skipping cleanup request as in query mode"); - } - else if (ctx.serialized().isPresent()) { - ctx.LOGGER.info(ctx.attemptId(), "Skipping cleanup request as deferred transaction"); - } - else if (ctx.atrId().isPresent() && ctx.atrCollection().isPresent()) { - switch (ctx.state()) { - case NOT_STARTED: - case COMPLETED: - case ROLLED_BACK: - ctx.LOGGER.trace(ctx.attemptId(), "Skipping addition of cleanup request in state %s", ctx.state()); - break; - default: - ctx.LOGGER.trace(ctx.attemptId(), "Adding cleanup request for %s/%s", - ctx.atrCollection().get().name(), ctx.atrId().get()); - - cleanup.add(ctx.createCleanupRequest()); - } - } else { - // No ATR entry to remove - ctx.LOGGER.trace(ctx.attemptId(), "Skipping cleanup request as no ATR entry to remove (due to no " + - "mutations)"); - } - } - - private static TransactionResult createResultFromContext(TransactionContext overall) { - return new TransactionResult(overall.attempts(), - overall.LOGGER, - Duration.of(System.nanoTime() - overall.startTimeClient().toNanos(), ChronoUnit.NANOS), - overall.transactionId(), - overall.serialized()); - } - - /** - * Performs a single query transaction, with default configuration. - * - * @param statement the statement to execute. - * @return a ReactiveSingleQueryTransactionResult - */ - @Stability.Uncommitted - public Mono query(String statement) { - return query(null, statement, SingleQueryTransactionConfigBuilder.create().build()); - } - - /** - * Performs a single query transaction, with a custom configuration. - * - * @param statement the statement to execute. - * @param queryOptions configuration options. - * @return a ReactiveSingleQueryTransactionResult - */ - @Stability.Uncommitted - public Mono query(String statement, SingleQueryTransactionConfig queryOptions) { - return query(null, statement, queryOptions); - } - - /** - * Performs a single query transaction, with a scope context and default configuration. - * - * @param statement the statement to execute. - * @param scope the query will be executed in the context of this scope, so it can refer to a collection on this scope - * rather than needed to provide the full keyspace. - * @return a ReactiveSingleQueryTransactionResult - */ - @Stability.Uncommitted - public Mono query(ReactiveScope scope, String statement) { - return query(scope, statement, SingleQueryTransactionConfigBuilder.create().build()); - } - - /** - * Performs a single query transaction, with a scope context and custom configuration. - * - * @param statement the statement to execute. - * @param scope the query will be executed in the context of this scope, so it can refer to a collection on this scope - * rather than needed to provide the full keyspace. - * @param queryOptions configuration options. - * @return a ReactiveSingleQueryTransactionResult - */ - @Stability.Uncommitted - public Mono query(ReactiveScope scope, String statement, SingleQueryTransactionConfig queryOptions) { - return Mono.defer(() -> { - AtomicReference queryResult = new AtomicReference<>(); - return run((ctx) -> ctx.query(scope, statement, queryOptions.queryOptions(), true) - .doOnNext(qr -> queryResult.set(qr)) - .then(), queryOptions.convert()) - .map(result -> new ReactiveSingleQueryTransactionResult(result.log(), queryResult.get())); - }); - } - - @Stability.Internal - @Deprecated // Prefer setting TransactionConfigBuilder#testFactories now - public void setAttemptContextFactory(AttemptContextFactory factory) { - this.attemptContextFactory = factory; - } - public AttemptContextReactive newAttemptContextReactive(){ - PerTransactionConfig perConfig = PerTransactionConfigBuilder.create().build(); - MergedTransactionConfig merged = new MergedTransactionConfig(config, Optional.of(perConfig)); - - TransactionContext overall = new TransactionContext( - cleanup().clusterData().cluster().environment().requestTracer(), - cleanup().clusterData().cluster().environment().eventBus(), - UUID.randomUUID().toString(), now(), Duration.ZERO, merged); - - String txnId = UUID.randomUUID().toString(); - overall.LOGGER.info(configDebug(config, perConfig)); - return createAttemptContext(overall, merged, txnId); - } - -} +///* +// * Copyright 2021 Couchbase, Inc. +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +// +//package com.couchbase.transactions; +// +//import com.couchbase.client.core.annotation.Stability; +//import com.couchbase.client.core.cnc.EventBus; +//import com.couchbase.client.core.retry.reactor.DefaultRetry; +//import com.couchbase.client.core.retry.reactor.Jitter; +//import com.couchbase.client.core.retry.reactor.RetryContext; +//import com.couchbase.client.java.Cluster; +//import com.couchbase.client.java.ReactiveCollection; +//import com.couchbase.client.java.ReactiveScope; +//import com.couchbase.client.java.json.JsonObject; +//import com.couchbase.client.java.query.ReactiveQueryResult; +//import com.couchbase.transactions.cleanup.ClusterData; +//import com.couchbase.transactions.cleanup.TransactionsCleanup; +//import com.couchbase.transactions.components.ATR; +//import com.couchbase.transactions.components.ActiveTransactionRecord; +//import com.couchbase.transactions.config.MergedTransactionConfig; +//import com.couchbase.transactions.config.PerTransactionConfig; +//import com.couchbase.transactions.config.PerTransactionConfigBuilder; +//import com.couchbase.transactions.config.SingleQueryTransactionConfig; +//import com.couchbase.transactions.config.SingleQueryTransactionConfigBuilder; +//import com.couchbase.transactions.config.TransactionConfig; +//import com.couchbase.transactions.deferred.TransactionSerializedContext; +//import com.couchbase.transactions.error.TransactionCommitAmbiguous; +//import com.couchbase.transactions.error.TransactionExpired; +//import com.couchbase.transactions.error.TransactionFailedException; +//import com.couchbase.transactions.error.internal.ErrorClasses; +//import com.couchbase.transactions.error.external.TransactionOperationFailed; +//import com.couchbase.transactions.forwards.Supported; +//import com.couchbase.transactions.log.EventBusPersistedLogger; +//import com.couchbase.transactions.log.PersistedLogWriter; +//import com.couchbase.transactions.log.TransactionLogEvent; +//import com.couchbase.transactions.support.AttemptContextFactory; +//import com.couchbase.transactions.support.AttemptState; +//import com.couchbase.transactions.support.OptionsWrapperUtil; +//import com.couchbase.transactions.util.DebugUtil; +//import reactor.core.publisher.Mono; +//import reactor.core.scheduler.Schedulers; +// +//import java.time.Duration; +//import java.time.temporal.ChronoUnit; +//import java.util.Objects; +//import java.util.Optional; +//import java.util.UUID; +//import java.util.concurrent.TimeUnit; +//import java.util.concurrent.atomic.AtomicReference; +//import java.util.function.Consumer; +//import java.util.function.Function; +//import java.util.function.Predicate; +// +//import static com.couchbase.transactions.error.internal.TransactionOperationFailedBuilder.createError; +//import static com.couchbase.transactions.log.PersistedLogWriter.MAX_LOG_ENTRIES_DEFAULT; +//import static com.couchbase.transactions.support.SpanWrapperUtil.DB_COUCHBASE_TRANSACTIONS; +// +///** +// * An asynchronous version of {@link Transactions}, allowing transactions to be created and run in an asynchronous +// * manner. +// *

+// * The main method to run transactions is {@link TransactionsReactive#run}. +// */ +//public class TransactionsReactive { +// static final int MAX_ATTEMPTS = 1000; +// private final TransactionsCleanup cleanup; +// private final TransactionConfig config; +// private AttemptContextFactory attemptContextFactory; +// private EventBusPersistedLogger persistedLogger; +// +// /** +// * This is package-private. Applications should create a {@link Transactions} object instead, and then call {@link +// * Transactions#reactive}. +// */ +// static TransactionsReactive create(Cluster cluster, TransactionConfig config) { +// return new TransactionsReactive(cluster, config); +// } +// +// private TransactionsReactive(Cluster cluster, TransactionConfig config) { +// Objects.requireNonNull(cluster); +// Objects.requireNonNull(config); +// +// ClusterData clusterData = new ClusterData(cluster); +// this.config = config; +// this.attemptContextFactory = config.attemptContextFactory(); +// MergedTransactionConfig merged = new MergedTransactionConfig(config, Optional.empty()); +// cleanup = new TransactionsCleanup(merged, clusterData); +// +// config.persistentLoggingCollection().ifPresent(collection -> { +// PersistedLogWriter persistedLogWriter = new PersistedLogWriter(collection, MAX_LOG_ENTRIES_DEFAULT); +// persistedLogger = new EventBusPersistedLogger(cluster.environment().eventBus(), persistedLogWriter, merged); +// }); +// } +// +// +// /** +// * The main transactions 'engine', responsible for attempting the transaction logic as many times as required, +// * until the transaction commits, is explicitly rolled back, or expires. +// */ +// // TODO: changed from private to public. package-protected plus an accessor would be ok to +// public Mono executeTransaction(MergedTransactionConfig config, +// TransactionContext overall, +// Mono transactionLogic) { +// AtomicReference startTime = new AtomicReference<>(); +// +// return Mono.just(overall) +// +// .subscribeOn(reactor.core.scheduler.Schedulers.elastic()) +// +// .doOnSubscribe(v -> { +// if (startTime.get() == null) startTime.set(System.nanoTime()); +// }) +// +// // Where the magic happens: execute the app's transaction logic +// // A ReactiveTransactionAttemptContext gets created in here. Rollback requires one of these (so it knows what +// // to rollback), so only errors thrown inside this block can trigger rollback. +// // So, expiry checks only get done inside this block. +// .then(transactionLogic) +// +// .flatMap(this::executeImplicitCommit) +// +// // Track an attempt if non-error, and request that the attempt be cleaned up. Similar logic is also +// // done in executeHandleErrorsPreRetry. +// .doOnNext(ctx -> executeAddAttemptAndCleanupRequest(config, overall, ctx)) +// +// // Track an attempt if error, and perform rollback if needed. +// // All errors reaching here must be a `TransactionOperationFailed`. +// .onErrorResume(err -> executeHandleErrorsPreRetry(config, overall, err)) +// +// // This is the main place to retry txns. Feed all errors up to this centralised point. +// // All errors reaching here must be a `TransactionOperationFailed`. +// .retryWhen(executeCreateRetryWhen(overall)) +// +// // If we're here, then we've hit an error that we don't want to retry. +// // Either raise some derivative of TransactionFailedException to the app, or return an ReactiveTransactionAttemptContext +// // to return success (some errors result in success, e.g. TRANSACTION_FAILED_POST_COMMIT) +// // All errors reaching here must be an `ErrorWrapper`. +// .onErrorResume(err -> executeHandleErrorsPostRetry(overall, err)) +// +// .doOnError(err -> { +// if (config.logOnFailure() && !config.logDirectly()) { +// EventBus eventBus = cleanup.clusterData().cluster().environment().eventBus(); +// overall.LOGGER.logs().forEach(log -> { +// eventBus.publish(new TransactionLogEvent(config.logOnFailureLevel(), +// TransactionLogEvent.DEFAULT_CATEGORY, log.toString())); +// }); +// } +// }) +// +// // If we get here, success +// .doOnSuccess(v -> +// overall.LOGGER.info("finished txn in %dus", +// TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - startTime.get())) +// ) +// +// // Safe to do single() as there will only ever be 1 result +// .single() +// .map(v -> createResultFromContext(overall)); +// } +// +// private reactor.util.retry.Retry executeCreateRetryWhen(TransactionContext overall) { +// Predicate> predicate = context -> { +// Throwable exception = context.exception(); +// +// if (!(exception instanceof TransactionOperationFailed)) { +// // A bug. Only TransactionOperationFailed is allowed to reach here. +// throw new IllegalStateException("Non-TransactionOperationFailed '" + DebugUtil.dbg(exception) + "' received during retry, this is a bug", exception); +// } +// +// TransactionOperationFailed e = (TransactionOperationFailed) exception; +// +// overall.LOGGER.info("TransactionOperationFailed retryTransaction=%s", e.retryTransaction()); +// +// return e.retryTransaction(); +// }; +// +// return DefaultRetry.create(predicate) +// +// .exponentialBackoff(Duration.of(1, ChronoUnit.MILLIS), +// Duration.of(2, ChronoUnit.MILLIS)) +// +// .doOnRetry(v -> overall.LOGGER.info("<>", "retrying transaction after backoff %dmillis", v.backoff().toMillis())) +// +// // Add some jitter so two txns don't livelock each other +// .jitter(Jitter.random()) +// +// // Really, this is a safety-guard. The txn will be aborted when it expires. +// .retryMax(MAX_ATTEMPTS) +// +// .toReactorRetry(); +// } +// +// private Mono executeHandleErrorsPreRetry(MergedTransactionConfig config, +// TransactionContext overall, Throwable err) { +// if (!(err instanceof TransactionOperationFailed)) { +// // A bug. Only TransactionOperationFailed is allowed to reach here. +// overall.LOGGER.warn("<>", "received non-TransactionOperationFailed error %s, unable to rollback as don't have " + +// "context", DebugUtil.dbg(err)); +// return Mono.error(new IllegalStateException("received non-TransactionOperationFailed error " + err.getClass().getName() + " in pre-retry", err)); +// } +// +// Mono autoRollback = Mono.empty(); +// Mono cleanupReq = Mono.empty(); +// +// TransactionOperationFailed e = (TransactionOperationFailed) err; +// ReactiveTransactionAttemptContext ctx = e.context(); +// +// overall.LOGGER.info("<>", "finishing attempt off after error '%s'", e); +// +// if (e.autoRollbackAttempt()) { +// // In queryMode we always ROLLBACK, as there is possibly delta table state to cleanup, and there may be an +// // ATR - we don't know +// if (ctx.state() == AttemptState.NOT_STARTED && !ctx.queryMode()) { +// // This is a better way of doing [RETRY-ERR-NOATR] and likely means that the older logic for +// // handling that won't trigger now +// ctx.LOGGER.info(ctx.attemptId(), "told to auto-rollback but in NOT_STARTED state, so nothing to do - skipping rollback"); +// } +// else { +// ctx.LOGGER.info(ctx.attemptId(), "auto-rolling-back on error"); +// +// autoRollback = ctx.rollbackInternal(false); +// } +// } else { +// ctx.LOGGER.info(ctx.attemptId(), "has been told to skip auto-rollback"); +// } +// +// if (!config.runRegularAttemptsCleanupThread()) { +// // Don't add a request to a queue that no-one will be processing +// ctx.LOGGER.trace(ctx.attemptId(), "skipping addition of cleanup request on failure as regular cleanup disabled"); +// } +// else { +// cleanupReq = Mono.fromRunnable(() -> addCleanupRequestForContext(ctx)); +// } +// +// Mono addAttempt = Mono.fromRunnable(() -> { +// TransactionAttempt ta = TransactionAttempt.createFromContext(ctx, Optional.of(err)); +// overall.addAttempt(ta); +// ctx.LOGGER.info(ctx.attemptId(), "added attempt %s after error", ta); +// }); +// +// final Mono cleanupReqForLambda = cleanupReq; +// +// return autoRollback +// // See [Primary Operations] section in design document +// .onErrorResume(er -> { +// overall.LOGGER.info("<>", "rollback failed with %s, raising original error but with retryTransaction turned off", +// DebugUtil.dbg(er)); +// +// // Still want to add attempt and cleanup request +// return cleanupReqForLambda +// .then(addAttempt) +// .then(Mono.error(createError(e.context(), e.causingErrorClass()) +// .raiseException(e.toRaise()) +// .cause(e.getCause()) +// .build())); +// }) +// .then(cleanupReqForLambda) +// // Only want to add the attempt after doing the rollback, so the attempt has the correct state (hopefully +// // ROLLED_BACK) +// .then(addAttempt) +// .then(Mono.defer(() -> { +// if (e.retryTransaction() && overall.hasExpiredClientSide()) { +// overall.LOGGER.info("<>", "original error planned to retry transaction, but it has subsequently expired"); +// return Mono.error(createError(ctx, ErrorClasses.FAIL_EXPIRY) +// .doNotRollbackAttempt() +// .raiseException(TransactionOperationFailed.FinalErrorToRaise.TRANSACTION_EXPIRED) +// .build()); +// } +// else { +// // Raise the error up the stack so the logic later can decide whether to retry the transaction +// overall.LOGGER.info("<>", "reraising original exception %s", DebugUtil.dbg(err)); +// return Mono.error(err); +// } +// })) +// .doFinally(v -> ctx.span().failWith(e)) +// .thenReturn(ctx); +// } +// +// private Mono executeHandleErrorsPostRetry(TransactionContext overall, Throwable err) { +// if (!(err instanceof TransactionOperationFailed)) { +// // A bug. Only TransactionOperationFailed is allowed to reach here. +// return Mono.error(new IllegalStateException("Non-TransactionOperationFailed '" + DebugUtil.dbg(err) + "' received, this is a bug")); +// } +// +// TransactionResult result = createResultFromContext(overall); +// TransactionOperationFailed e = (TransactionOperationFailed) err; +// +// if (e.toRaise() == TransactionOperationFailed.FinalErrorToRaise.TRANSACTION_FAILED_POST_COMMIT) { +// e.context().LOGGER.info(e.context().attemptId(), "converted TRANSACTION_FAILED_POST_COMMIT to success, unstagingComplete() will be false"); +// +// return Mono.just(e.context()); +// } +// else { +// TransactionFailedException ret; +// +// switch (e.toRaise()) { +// case TRANSACTION_EXPIRED: { +// String msg = "Transaction has expired configured timeout of " + overall.expirationTime().toMillis() + "msecs. The transaction is not committed."; +// ret = new TransactionExpired(e.getCause(), result, msg); +// break; +// } +// case TRANSACTION_COMMIT_AMBIGUOUS: { +// String msg = "It is ambiguous whether the transaction committed"; +// ret = new TransactionCommitAmbiguous(e.getCause(), result, msg); +// break; +// } +// default: +// ret = new TransactionFailedException(e.getCause(), result); +// break; +// } +// +// e.context().LOGGER.info(e.context().attemptId(), "converted TransactionOperationFailed %s to final error %s", +// e.toRaise(), ret); +// +// return Mono.error(ret); +// } +// } +// +// private void executeAddAttemptAndCleanupRequest(MergedTransactionConfig config, TransactionContext overall, +// ReactiveTransactionAttemptContext ctx) { +// TransactionAttempt ta = TransactionAttempt.createFromContext(ctx, Optional.empty()); +// overall.addAttempt(ta); +// ctx.LOGGER.info(ctx.attemptId(), "added attempt %s after success", ta); +// +// if (config.runRegularAttemptsCleanupThread()) { +// addCleanupRequestForContext(ctx); +// } else { +// ctx.LOGGER.trace(ctx.attemptId(), "skipping addition of cleanup request on success"); +// } +// +// ctx.span().finish(); +// } +// +// private Mono executeImplicitCommit(ReactiveTransactionAttemptContext ctx) { +// return Mono.defer(() -> { +// // If app has not explicitly performed a commit, assume they want to do so anyway +// if (!ctx.isDone()) { +// if (ctx.serialized().isPresent()) { +// return Mono.just(ctx); +// } else { +// ctx.LOGGER.trace(ctx.attemptId(), "doing implicit commit"); +// +// return ctx.commit() +// .then(Mono.just(ctx)) +// .onErrorResume(err -> Mono.error(TransactionOperationFailed.convertToOperationFailedIfNeeded(err, ctx))); +// } +// } else { +// return Mono.just(ctx); +// } +// }); +// } +// +// // TODO: changed from package-protected to public (could have just used an accessor class in same package) +// public ReactiveTransactionAttemptContext createAttemptContext(TransactionContext overall, +// MergedTransactionConfig config, +// String attemptId) { +// // null only happens in testing with Mockito, harmless +// if (overall != null) { +// return attemptContextFactory.create(overall, config, attemptId, this, Optional.of(overall.span())); +// } else { +// return null; +// } +// } +// +// /** +// * Runs the supplied transactional logic until success or failure. +// *

+// * This is the asynchronous version of {@link Transactions#run}, so to cover the differences: +// *

    +// *
  • The transaction logic is supplied with a {@link ReactiveTransactionAttemptContext}, which contains asynchronous +// * methods to allow it to read, mutate, insert and delete documents, as well as commit or rollback the +// * transactions.
  • +// *
  • The transaction logic should run these methods as a Reactor chain.
  • +// *
  • The transaction logic should return a Mono{@literal <}Void{@literal >}. Any +// * Flux or Mono can be converted to a Mono{@literal <}Void{@literal >} by +// * calling .then() on it.
  • +// *
  • This method returns a Mono{@literal <}TransactionResult{@literal >}, which should be handled +// * as a normal Reactor Mono.
  • +// *
+// * +// * @param transactionLogic the application's transaction logic +// * @param perConfig the configuration to use for this transaction +// * @return there is no need to check the returned {@link TransactionResult}, as success is implied by the lack of a +// * thrown exception. It contains information useful only for debugging and logging. +// * @throws TransactionFailedException or a derived exception if the transaction fails to commit for any reason, possibly +// * after multiple retries. The exception contains further details of the error. Not +// */ +// public Mono run(Function> transactionLogic, +// PerTransactionConfig perConfig) { +// return Mono.defer(() -> { +// MergedTransactionConfig merged = new MergedTransactionConfig(config, Optional.of(perConfig)); +// +// TransactionContext overall = +// new TransactionContext(cleanup.clusterData().cluster().environment().requestTracer(), +// cleanup.clusterData().cluster().environment().eventBus(), +// UUID.randomUUID().toString(), +// now(), +// Duration.ZERO, +// merged); +// AtomicReference startTime = new AtomicReference<>(0L); +// +// Mono ob = Mono.fromCallable(() -> { +// String txnId = UUID.randomUUID().toString(); +// overall.LOGGER.info(configDebug(config, perConfig)); +// return createAttemptContext(overall, merged, txnId); +// }).flatMap(ctx -> { +// ctx.LOGGER.info("starting attempt %d/%s/%s", +// overall.numAttempts(), ctx.transactionId(), ctx.attemptId()); +// Mono result = transactionLogic.apply(ctx); +// return result +// .onErrorResume(err -> { +// ctx.LOGGER.info(ctx.attemptId(), "caught exception '%s' in async, rethrowing", err); +// logElidedStacktrace(ctx, err); +// +// return Mono.error(TransactionOperationFailed.convertToOperationFailedIfNeeded(err, ctx)); +// }) +// .thenReturn(ctx); +// }).doOnSubscribe(v -> startTime.set(System.nanoTime())) +// .doOnNext(v -> v.LOGGER.trace(v.attemptId(), "finished attempt %d in %sms", +// overall.numAttempts(), (System.nanoTime() - startTime.get()) / 1_000_000)); +// +// return executeTransaction(merged, overall, ob) +// .doOnNext(v -> overall.span().finish()) +// .doOnError(err -> overall.span().failWith(err)); +// }); +// } +// +// // Printing the stacktrace is expensive in terms of log noise, but has been a life saver on many debugging +// // encounters. Strike a balance by eliding the more useless elements. +// // TODO: changed from private to public +// public void logElidedStacktrace(ReactiveTransactionAttemptContext ctx, Throwable err) { +// DebugUtil.fetchElidedStacktrace(err, (s) -> ctx.LOGGER.info(ctx.attemptId(), " " + s.toString())); +// } +// +// // TODO: changed from private to public +// public static String configDebug(TransactionConfig config, PerTransactionConfig perConfig) { +// StringBuilder sb = new StringBuilder(); +// sb.append("library version: "); +// sb.append(TransactionsReactive.class.getPackage().getImplementationVersion()); +// sb.append(" config: "); +// sb.append("atrs="); +// sb.append(config.numAtrs()); +// sb.append(", metadataCollection="); +// sb.append(config.metadataCollection()); +// sb.append(", expiry="); +// sb.append(perConfig.expirationTime().orElse(config.transactionExpirationTime()).toMillis()); +// sb.append("msecs durability="); +// sb.append(config.durabilityLevel()); +// sb.append(" per-txn config="); +// sb.append(" durability="); +// sb.append(perConfig.durabilityLevel()); +// sb.append(", supported="); +// sb.append(Supported.SUPPORTED); +// return sb.toString(); +// } +// +// /** +// * Convenience overload that runs {@link TransactionsReactive#run} with a default PerTransactionConfig. +// */ +// public Mono run(Function> transactionLogic) { +// return run(transactionLogic, PerTransactionConfigBuilder.create().build()); +// } +// +// @Stability.Volatile +// public Mono commit(TransactionSerializedContext serialized, PerTransactionConfig perConfig) { +// return deferred(serialized, +// perConfig, +// // Nothing to actually do, just want the implicit commit +// (ctx) -> Mono.empty()); +// } +// +// @Stability.Volatile +// public Mono rollback(TransactionSerializedContext serialized, PerTransactionConfig perConfig) { +// return deferred(serialized, +// perConfig, +// (ctx) -> ctx.rollback()); +// } +// +// @Stability.Volatile +// private Mono deferred(TransactionSerializedContext serialized, +// PerTransactionConfig perConfig, +// Function> initial) { +// MergedTransactionConfig merged = new MergedTransactionConfig(config, Optional.of(perConfig)); +// JsonObject hydrated = JsonObject.fromJson(serialized.encodeAsString()); +// +// String atrBucket = hydrated.getString("atrBucket"); +// String atrScope = hydrated.getString("atrScope"); +// String atrCollectionName = hydrated.getString("atrCollection"); +// String atrId = hydrated.getString("atrId"); +// ReactiveCollection atrCollection = cleanup.clusterData() +// .getBucketFromName(atrBucket) +// .scope(atrScope) +// .collection(atrCollectionName); +// +// return ActiveTransactionRecord.getAtr(atrCollection, +// atrId, +// OptionsWrapperUtil.kvTimeoutNonMutating(merged, atrCollection.core()), +// null) +// +// .flatMap(atrOpt -> { +// if (!atrOpt.isPresent()) { +// return Mono.error(new IllegalStateException(String.format("ATR %s/%s could not be found", +// atrBucket, atrId))); +// } +// else { +// ATR atr = atrOpt.get(); +// +// // Note startTimeServerMillis is written with ${Mutation.CAS} while currentTimeServer +// // could have come from $vbucket.HLC and is hence one-second granularity. So, this is a +// // somewhat imperfect comparison. +// Duration currentTimeServer = Duration.ofNanos(atr.cas()); +// Duration startTimeServer = Duration.ofMillis(hydrated.getLong("startTimeServerMillis")); +// +// // This includes the time elapsed during the first part of the transaction, plus any time +// // elapsed during the period the transaction was expired. Total time since the transaction +// // began, basically. +// Duration timeElapsed = currentTimeServer.minus(startTimeServer); +// +// TransactionContext overall = +// new TransactionContext(cleanup.clusterData().cluster().environment().requestTracer(), +// cleanup.clusterData().cluster().environment().eventBus(), +// UUID.randomUUID().toString(), +// Duration.ofNanos(System.nanoTime()), +// timeElapsed, +// merged); +// AtomicReference startTime = new AtomicReference<>(0L); +// +// overall.LOGGER.info("elapsed time = %dmsecs (ATR start time %dmsecs, current ATR time %dmsecs)", +// timeElapsed.toMillis(), startTimeServer.toMillis(), currentTimeServer.toMillis()); +// +// Mono ob = Mono.defer(() -> { +// ReactiveTransactionAttemptContext ctx = attemptContextFactory.createFrom(hydrated, overall, merged, this); +// ctx.LOGGER.info("starting attempt %d/%s/%s", +// overall.numAttempts(), ctx.transactionId(), ctx.attemptId()); +// ctx.LOGGER.info(configDebug(config, perConfig)); +// +// return initial.apply(ctx) +// +// // TXNJ-50: Make sure we run user's blocking logic on a scheduler that can take it +// .subscribeOn(Schedulers.elastic()) +// +// .onErrorResume(err -> { +// ctx.LOGGER.info(ctx.attemptId(), "caught exception '%s' in deferred, rethrowing", +// err); +// +// logElidedStacktrace(ctx, err); +// +// return Mono.error(TransactionOperationFailed.convertToOperationFailedIfNeeded(err, ctx)); +// }) +// +// .doOnSubscribe(v -> startTime.set(System.nanoTime())) +// +// .doOnNext(v -> { +// ctx.LOGGER.trace(ctx.attemptId(), "finished attempt %d in %sms", +// overall.numAttempts(), (System.nanoTime() - startTime.get()) / 1_000_000); +// }) +// +// .thenReturn(ctx); +// }); +// +// return executeTransaction(merged, overall, ob) +// .doOnNext(v -> overall.span().attribute(DB_COUCHBASE_TRANSACTIONS + "retries", overall.numAttempts()).finish()) +// .doOnError(err -> overall.span().attribute(DB_COUCHBASE_TRANSACTIONS + "retries", overall.numAttempts()).failWith(err)); +// } +// }); +// } +// +// Mono runBlocking(Consumer txnLogic, PerTransactionConfig perConfig) { +// return Mono.defer(() -> { +// MergedTransactionConfig merged = new MergedTransactionConfig(config, Optional.of(perConfig)); +// TransactionContext overall = +// new TransactionContext(cleanup.clusterData().cluster().environment().requestTracer(), +// cleanup.clusterData().cluster().environment().eventBus(), +// UUID.randomUUID().toString(), +// now(), +// Duration.ZERO, +// merged); +// AtomicReference startTime = new AtomicReference<>(0L); +// overall.LOGGER.info(configDebug(config, perConfig)); +// +// Mono ob = Mono.defer(() -> { +// String txnId = UUID.randomUUID().toString(); +// ReactiveTransactionAttemptContext ctx = createAttemptContext(overall, merged, txnId); +// TransactionAttemptContext ctxBlocking = new TransactionAttemptContext(ctx); +// ctx.LOGGER.info("starting attempt %d/%s/%s", +// overall.numAttempts(), ctx.transactionId(), ctx.attemptId()); +// +// return Mono.fromRunnable(() -> txnLogic.accept(ctxBlocking)) +// +// // TXNJ-50: Make sure we run user's blocking logic on a scheduler that can take it +// .subscribeOn(Schedulers.elastic()) +// +// .onErrorResume(err -> { +// ctx.LOGGER.info(ctx.attemptId(), "caught exception '%s' in runBlocking, rethrowing", err); +// +// logElidedStacktrace(ctx, err); +// +// return Mono.error(TransactionOperationFailed.convertToOperationFailedIfNeeded(err, ctx)); +// }) +// +// .doOnSubscribe(v -> startTime.set(System.nanoTime())) +// +// .doOnNext(v -> { +// ctx.LOGGER.trace(ctx.attemptId(), "finished attempt %d in %sms", +// overall.numAttempts(), (System.nanoTime() - startTime.get()) / 1_000_000); +// }) +// +// .thenReturn(ctx); +// }); +// +// return executeTransaction(merged, overall, ob) +// .doOnNext(v -> overall.span().attribute(DB_COUCHBASE_TRANSACTIONS + "retries", overall.numAttempts()).finish()) +// .doOnError(err -> overall.span().attribute(DB_COUCHBASE_TRANSACTIONS + "retries", overall.numAttempts()).failWith(err)); +// }); +// } +// +// public TransactionConfig config() { +// return config; +// } +// +// private static Duration now() { +// return Duration.of(System.nanoTime(), ChronoUnit.NANOS); +// } +// +// TransactionsCleanup cleanup() { +// return cleanup; +// } +// +// private void addCleanupRequestForContext(ReactiveTransactionAttemptContext ctx) { +// // Whether the txn was successful or not, still want to clean it up +// if (ctx.queryMode()) { +// ctx.LOGGER.info(ctx.attemptId(), "Skipping cleanup request as in query mode"); +// } +// else if (ctx.serialized().isPresent()) { +// ctx.LOGGER.info(ctx.attemptId(), "Skipping cleanup request as deferred transaction"); +// } +// else if (ctx.atrId().isPresent() && ctx.atrCollection().isPresent()) { +// switch (ctx.state()) { +// case NOT_STARTED: +// case COMPLETED: +// case ROLLED_BACK: +// ctx.LOGGER.trace(ctx.attemptId(), "Skipping addition of cleanup request in state %s", ctx.state()); +// break; +// default: +// ctx.LOGGER.trace(ctx.attemptId(), "Adding cleanup request for %s/%s", +// ctx.atrCollection().get().name(), ctx.atrId().get()); +// +// cleanup.add(ctx.createCleanupRequest()); +// } +// } else { +// // No ATR entry to remove +// ctx.LOGGER.trace(ctx.attemptId(), "Skipping cleanup request as no ATR entry to remove (due to no " + +// "mutations)"); +// } +// } +// +// private static TransactionResult createResultFromContext(TransactionContext overall) { +// return new TransactionResult(overall.attempts(), +// overall.LOGGER, +// Duration.of(System.nanoTime() - overall.startTimeClient().toNanos(), ChronoUnit.NANOS), +// overall.transactionId(), +// overall.serialized()); +// } +// +// /** +// * Performs a single query transaction, with default configuration. +// * +// * @param statement the statement to execute. +// * @return a ReactiveSingleQueryTransactionResult +// */ +// @Stability.Uncommitted +// public Mono query(String statement) { +// return query(null, statement, SingleQueryTransactionConfigBuilder.create().build()); +// } +// +// /** +// * Performs a single query transaction, with a custom configuration. +// * +// * @param statement the statement to execute. +// * @param queryOptions configuration options. +// * @return a ReactiveSingleQueryTransactionResult +// */ +// @Stability.Uncommitted +// public Mono query(String statement, SingleQueryTransactionConfig queryOptions) { +// return query(null, statement, queryOptions); +// } +// +// /** +// * Performs a single query transaction, with a scope context and default configuration. +// * +// * @param statement the statement to execute. +// * @param scope the query will be executed in the context of this scope, so it can refer to a collection on this scope +// * rather than needed to provide the full keyspace. +// * @return a ReactiveSingleQueryTransactionResult +// */ +// @Stability.Uncommitted +// public Mono query(ReactiveScope scope, String statement) { +// return query(scope, statement, SingleQueryTransactionConfigBuilder.create().build()); +// } +// +// /** +// * Performs a single query transaction, with a scope context and custom configuration. +// * +// * @param statement the statement to execute. +// * @param scope the query will be executed in the context of this scope, so it can refer to a collection on this scope +// * rather than needed to provide the full keyspace. +// * @param queryOptions configuration options. +// * @return a ReactiveSingleQueryTransactionResult +// */ +// @Stability.Uncommitted +// public Mono query(ReactiveScope scope, String statement, SingleQueryTransactionConfig queryOptions) { +// return Mono.defer(() -> { +// AtomicReference queryResult = new AtomicReference<>(); +// return run((ctx) -> ctx.query(scope, statement, queryOptions.queryOptions(), true) +// .doOnNext(qr -> queryResult.set(qr)) +// .then(), queryOptions.convert()) +// .map(result -> new ReactiveSingleQueryTransactionResult(result.log(), queryResult.get())); +// }); +// } +// +// @Stability.Internal +// @Deprecated // Prefer setting TransactionConfigBuilder#testFactories now +// public void setAttemptContextFactory(AttemptContextFactory factory) { +// this.attemptContextFactory = factory; +// } +// public ReactiveTransactionAttemptContext newAttemptContextReactive(){ +// PerTransactionConfig perConfig = PerTransactionConfigBuilder.create().build(); +// MergedTransactionConfig merged = new MergedTransactionConfig(config, Optional.of(perConfig)); +// +// TransactionContext overall = new TransactionContext( +// cleanup().clusterData().cluster().environment().requestTracer(), +// cleanup().clusterData().cluster().environment().eventBus(), +// UUID.randomUUID().toString(), now(), Duration.ZERO, merged); +// +// String txnId = UUID.randomUUID().toString(); +// overall.LOGGER.info(configDebug(config, perConfig)); +// return createAttemptContext(overall, merged, txnId); +// } +// +//} diff --git a/src/main/java/com/example/demo/CouchbaseTransactionManager.java b/src/main/java/com/example/demo/CouchbaseTransactionManager.java index 41f62bf26..967fd06fd 100644 --- a/src/main/java/com/example/demo/CouchbaseTransactionManager.java +++ b/src/main/java/com/example/demo/CouchbaseTransactionManager.java @@ -2,7 +2,9 @@ import java.util.concurrent.atomic.AtomicReference; -import com.couchbase.transactions.AttemptContextReactive; +import com.couchbase.client.java.transactions.ReactiveTransactionAttemptContext; +import com.couchbase.client.java.transactions.TransactionAttemptContext; +import com.couchbase.client.java.transactions.TransactionResult; import com.couchbase.transactions.AttemptContextReactiveAccessor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,25 +26,17 @@ import org.springframework.transaction.support.TransactionSynchronizationUtils; import org.springframework.util.Assert; -import com.couchbase.transactions.AttemptContext; -import com.couchbase.transactions.TransactionResult; -import com.couchbase.transactions.Transactions; -import com.couchbase.transactions.config.TransactionConfig; - +// todo gp why is there separate CouchbaseCallbackTransactionManager if this class also extends CallbackPreferringPlatformTransactionManager? +// todo gp there is another CouchbaseTransactionManager in another package, which is valid? public class CouchbaseTransactionManager extends AbstractPlatformTransactionManager implements DisposableBean, ResourceTransactionManager, CallbackPreferringPlatformTransactionManager { private static final Logger LOGGER = LoggerFactory.getLogger(CouchbaseTransactionManager.class); private final CouchbaseTemplate template; - private final Transactions transactions; - public CouchbaseTransactionManager(CouchbaseTemplate template, TransactionConfig transactionConfig) { + public CouchbaseTransactionManager(CouchbaseTemplate template) { this.template = template; - this.transactions = Transactions.create( - template.getCouchbaseClientFactory().getCluster(), - transactionConfig - ); } public CouchbaseTransactionalTemplate template() { @@ -52,35 +46,36 @@ public CouchbaseTransactionalTemplate template() { @Override public T execute(TransactionDefinition definition, TransactionCallback callback) throws TransactionException { final AtomicReference result = new AtomicReference<>(); - TransactionResult txnResult = transactions.run(attemptContext -> { - - if (TransactionSynchronizationManager.hasResource(template.getCouchbaseClientFactory())) { - ((CouchbaseResourceHolder) TransactionSynchronizationManager - .getResource(template.reactive().getCouchbaseClientFactory())) - .setAttemptContext(attemptContext); - } else { - TransactionSynchronizationManager.bindResource( - template.reactive().getCouchbaseClientFactory(), - new CouchbaseResourceHolder(attemptContext) - ); - } - - try { - // Since we are on a different thread now transparently, at least make sure - // that the original method invocation is synchronized. - synchronized (this) { - result.set(callback.doInTransaction(null)); - } - } catch (RuntimeException e) { - System.err.println("RuntimeException: "+e+" instanceof RuntimeException "+(e instanceof RuntimeException)); - throw e; - } catch (Throwable e) { - System.err.println("RuntimeException: "+e+" instanceof "+(e instanceof Throwable)); - throw new RuntimeException(e); - } - }); - - LOGGER.debug("Completed Couchbase Transaction with Result: " + txnResult); + // todo gp like CouchbaseCallbackTransactionManager, it needs access to CouchbaseClientFactory here (Cluster) +// TransactionResult txnResult = transactions.run(attemptContext -> { +// +// if (TransactionSynchronizationManager.hasResource(template.getCouchbaseClientFactory())) { +// ((CouchbaseResourceHolder) TransactionSynchronizationManager +// .getResource(template.reactive().getCouchbaseClientFactory())) +// .setAttemptContext(attemptContext); +// } else { +// TransactionSynchronizationManager.bindResource( +// template.reactive().getCouchbaseClientFactory(), +// new CouchbaseResourceHolder(attemptContext) +// ); +// } +// +// try { +// // Since we are on a different thread now transparently, at least make sure +// // that the original method invocation is synchronized. +// synchronized (this) { +// result.set(callback.doInTransaction(null)); +// } +// } catch (RuntimeException e) { +// System.err.println("RuntimeException: "+e+" instanceof RuntimeException "+(e instanceof RuntimeException)); +// throw e; +// } catch (Throwable e) { +// System.err.println("RuntimeException: "+e+" instanceof "+(e instanceof Throwable)); +// throw new RuntimeException(e); +// } +// }); + +// LOGGER.debug("Completed Couchbase Transaction with Result: " + txnResult); return result.get(); } @@ -118,7 +113,6 @@ protected void doCleanupAfterCompletion(Object transaction) { @Override public void destroy() { - transactions.close(); } @Override @@ -136,26 +130,26 @@ private static CouchbaseTransactionObject extractTransaction(Object transaction) public static class CouchbaseResourceHolder extends ResourceHolderSupport { - private volatile AttemptContext attemptContext; - private volatile AttemptContextReactive attemptContextReactive; + private volatile TransactionAttemptContext attemptContext; + private volatile ReactiveTransactionAttemptContext attemptContextReactive; private volatile ClientSession session = new ClientSessionImpl(); - public CouchbaseResourceHolder(AttemptContext attemptContext) { + public CouchbaseResourceHolder(TransactionAttemptContext attemptContext) { this.attemptContext = attemptContext; } - public AttemptContext getAttemptContext() { + public TransactionAttemptContext getAttemptContext() { return attemptContext; } - public void setAttemptContext(AttemptContext attemptContext) { + public void setAttemptContext(TransactionAttemptContext attemptContext) { this.attemptContext = attemptContext; } - public AttemptContextReactive getAttemptContextReactive() { + public ReactiveTransactionAttemptContext getAttemptContextReactive() { return attemptContext!= null ? AttemptContextReactiveAccessor.getACR(attemptContext) : attemptContextReactive; } - public void setAttemptContextReactive(AttemptContextReactive attemptContextReactive) { + public void setAttemptContextReactive(ReactiveTransactionAttemptContext attemptContextReactive) { this.attemptContextReactive = attemptContextReactive; } diff --git a/src/main/java/com/example/demo/CouchbaseTransactionalTemplate.java b/src/main/java/com/example/demo/CouchbaseTransactionalTemplate.java index 70116952c..4fa7cecfc 100644 --- a/src/main/java/com/example/demo/CouchbaseTransactionalTemplate.java +++ b/src/main/java/com/example/demo/CouchbaseTransactionalTemplate.java @@ -1,15 +1,14 @@ package com.example.demo; -import com.couchbase.transactions.AttemptContextReactive; +import com.couchbase.client.java.transactions.ReactiveTransactionAttemptContext; +import com.couchbase.client.java.transactions.TransactionAttemptContext; +import com.couchbase.client.java.transactions.TransactionGetResult; import org.springframework.data.couchbase.core.CouchbaseTemplate; import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate; import org.springframework.data.couchbase.transaction.ClientSession; import org.springframework.data.couchbase.transaction.CouchbaseResourceHolder; import org.springframework.transaction.support.TransactionSynchronizationManager; -import com.couchbase.transactions.AttemptContext; -import com.couchbase.transactions.TransactionGetResult; - public class CouchbaseTransactionalTemplate { private final CouchbaseTemplate template; @@ -20,10 +19,11 @@ public CouchbaseTransactionalTemplate(CouchbaseTemplate template) { public SpringTransactionGetResult findById(String id, Class domainType) { try { - AttemptContext ctx = getContext(); + TransactionAttemptContext ctx = getContext(); TransactionGetResult getResult = ctx.get(template.getCouchbaseClientFactory().getDefaultCollection(), id); - T t = template.support().decodeEntity(id, getResult.contentAsObject().toString(), getResult.cas(), domainType, + // todo gp getResult.cas() is no longer exposed - required? + T t = template.support().decodeEntity(id, getResult.contentAsObject().toString(), 0, domainType, null, null, null); return new SpringTransactionGetResult<>(t, getResult); } catch (Exception e) { @@ -34,36 +34,36 @@ public SpringTransactionGetResult findById(String id, Class domainType } public void replaceById(TransactionGetResult getResult, T entity) { - AttemptContext ctx = getContext(); + TransactionAttemptContext ctx = getContext(); ctx.replace(getResult, template.support().encodeEntity(entity).getContent()); } - private AttemptContext getContext() { + private TransactionAttemptContext getContext() { CouchbaseTransactionManager.CouchbaseResourceHolder resource = (CouchbaseTransactionManager.CouchbaseResourceHolder) TransactionSynchronizationManager .getResource(template.getCouchbaseClientFactory()); - AttemptContext atr; + TransactionAttemptContext atr; if (resource != null) { atr = resource.getAttemptContext(); } else { CouchbaseResourceHolder holder = (CouchbaseResourceHolder) TransactionSynchronizationManager .getResource(template.getCouchbaseClientFactory().getCluster()); - atr = holder.getSession().getAttemptContext(); + atr = holder.getSession().getTransactionAttemptContext(); } return atr; } - public static AttemptContextReactive getContextReactive(ReactiveCouchbaseTemplate template) { + public static ReactiveTransactionAttemptContext getContextReactive(ReactiveCouchbaseTemplate template) { CouchbaseTransactionManager.CouchbaseResourceHolder resource = (CouchbaseTransactionManager.CouchbaseResourceHolder) TransactionSynchronizationManager .getResource(template.getCouchbaseClientFactory()); - AttemptContextReactive atr = null; + ReactiveTransactionAttemptContext atr = null; if (resource != null) { atr = resource.getAttemptContextReactive(); } else { CouchbaseResourceHolder holder = (CouchbaseResourceHolder) TransactionSynchronizationManager .getResource(template.getCouchbaseClientFactory().getCluster()); if (holder != null && holder.getSession() != null) { - atr = holder.getSession().getAttemptContextReactive(); + atr = holder.getSession().getReactiveTransactionAttemptContext(); } } return atr; diff --git a/src/main/java/com/example/demo/SpringTransactionGetResult.java b/src/main/java/com/example/demo/SpringTransactionGetResult.java index 365751fc9..6e02d4d98 100644 --- a/src/main/java/com/example/demo/SpringTransactionGetResult.java +++ b/src/main/java/com/example/demo/SpringTransactionGetResult.java @@ -1,6 +1,6 @@ package com.example.demo; -import com.couchbase.transactions.TransactionGetResult; +import com.couchbase.client.java.transactions.TransactionGetResult; public class SpringTransactionGetResult { diff --git a/src/main/java/org/springframework/data/couchbase/CouchbaseClientFactory.java b/src/main/java/org/springframework/data/couchbase/CouchbaseClientFactory.java index f4203bcf2..4136c24a1 100644 --- a/src/main/java/org/springframework/data/couchbase/CouchbaseClientFactory.java +++ b/src/main/java/org/springframework/data/couchbase/CouchbaseClientFactory.java @@ -18,9 +18,7 @@ import java.io.Closeable; -import com.couchbase.transactions.AttemptContextReactive; -import com.couchbase.transactions.Transactions; -import com.couchbase.transactions.config.TransactionConfig; +import com.couchbase.client.java.transactions.ReactiveTransactionAttemptContext; import org.springframework.dao.support.PersistenceExceptionTranslator; import com.couchbase.client.java.Bucket; @@ -79,8 +77,7 @@ public interface CouchbaseClientFactory extends Closeable { */ PersistenceExceptionTranslator getExceptionTranslator(); - ClientSession getSession(ClientSessionOptions options, Transactions transactions, - TransactionConfig config , AttemptContextReactive atr); + ClientSession getSession(ClientSessionOptions options, ReactiveTransactionAttemptContext atr); //CouchbaseClientFactory with(CouchbaseStuffHandle txOp); diff --git a/src/main/java/org/springframework/data/couchbase/ReactiveCouchbaseClientFactory.java b/src/main/java/org/springframework/data/couchbase/ReactiveCouchbaseClientFactory.java index cfeab6cef..2d18a2013 100644 --- a/src/main/java/org/springframework/data/couchbase/ReactiveCouchbaseClientFactory.java +++ b/src/main/java/org/springframework/data/couchbase/ReactiveCouchbaseClientFactory.java @@ -20,9 +20,7 @@ import com.couchbase.client.java.ClusterInterface; import com.couchbase.client.java.Collection; import com.couchbase.client.java.Scope; -import com.couchbase.transactions.AttemptContextReactive; -import com.couchbase.transactions.Transactions; -import com.couchbase.transactions.config.TransactionConfig; +import com.couchbase.client.java.transactions.ReactiveTransactionAttemptContext; import org.springframework.data.couchbase.transaction.ClientSession; import org.springframework.data.couchbase.transaction.ClientSessionOptions; import org.springframework.data.couchbase.transaction.CouchbaseStuffHandle; @@ -88,7 +86,7 @@ public interface ReactiveCouchbaseClientFactory /*extends CodecRegistryProvider* */ PersistenceExceptionTranslator getExceptionTranslator(); - Mono getSession(ClientSessionOptions options, Transactions transactions, TransactionConfig config); + Mono getSession(ClientSessionOptions options); String getBucketName(); @@ -96,10 +94,7 @@ public interface ReactiveCouchbaseClientFactory /*extends CodecRegistryProvider* void close() throws IOException; - Mono getSession(ClientSessionOptions options); - - ClientSession getSession(ClientSessionOptions options, Transactions transactions, TransactionConfig config, - AttemptContextReactive atr); + ClientSession getSession(ClientSessionOptions options, ReactiveTransactionAttemptContext ctx); /* * (non-Javadoc) diff --git a/src/main/java/org/springframework/data/couchbase/SimpleCouchbaseClientFactory.java b/src/main/java/org/springframework/data/couchbase/SimpleCouchbaseClientFactory.java index 0080196a7..41a40a2b0 100644 --- a/src/main/java/org/springframework/data/couchbase/SimpleCouchbaseClientFactory.java +++ b/src/main/java/org/springframework/data/couchbase/SimpleCouchbaseClientFactory.java @@ -19,6 +19,7 @@ import java.time.temporal.ChronoUnit; import java.util.function.Supplier; +import com.couchbase.client.java.transactions.ReactiveTransactionAttemptContext; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.data.couchbase.core.CouchbaseExceptionTranslator; import org.springframework.data.couchbase.transaction.ClientSession; @@ -34,11 +35,7 @@ import com.couchbase.client.java.Collection; import com.couchbase.client.java.Scope; import com.couchbase.client.java.env.ClusterEnvironment; -import com.couchbase.transactions.AttemptContext; -import com.couchbase.transactions.AttemptContextReactive; import com.couchbase.transactions.AttemptContextReactiveAccessor; -import com.couchbase.transactions.Transactions; -import com.couchbase.transactions.config.TransactionConfig; /** * The default implementation of a {@link CouchbaseClientFactory}. @@ -127,14 +124,15 @@ public PersistenceExceptionTranslator getExceptionTranslator() { } @Override - public ClientSession getSession(ClientSessionOptions options, Transactions transactions, TransactionConfig config, - AttemptContextReactive atr) { - // can't we just use AttemptContextReactive everywhere? Instead of creating AttemptContext(atr), then + public ClientSession getSession(ClientSessionOptions options, ReactiveTransactionAttemptContext atr) { + // todo gp needed? + return null; + // can't we just use ReactiveTransactionAttemptContext everywhere? Instead of creating TransactionAttemptContext(atr), then // accessing at.getACR() ? - AttemptContext at = AttemptContextReactiveAccessor - .from(atr != null ? atr : AttemptContextReactiveAccessor.newAttemptContextReactive(transactions.reactive())); - - return new ClientSessionImpl(this, transactions, config, at); +// TransactionAttemptContext at = AttemptContextReactiveAccessor +// .from(atr != null ? atr : AttemptContextReactiveAccessor.newAttemptContextReactive(transactions.reactive())); +// +// return new ClientSessionImpl(this, transactions, config, at); } // @Override diff --git a/src/main/java/org/springframework/data/couchbase/SimpleReactiveCouchbaseClientFactory.java b/src/main/java/org/springframework/data/couchbase/SimpleReactiveCouchbaseClientFactory.java index 46159bf33..90dd31d88 100644 --- a/src/main/java/org/springframework/data/couchbase/SimpleReactiveCouchbaseClientFactory.java +++ b/src/main/java/org/springframework/data/couchbase/SimpleReactiveCouchbaseClientFactory.java @@ -4,6 +4,7 @@ import static com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_SCOPE; import com.couchbase.client.java.ClusterInterface; +import com.couchbase.client.java.transactions.ReactiveTransactionAttemptContext; import com.couchbase.transactions.AttemptContextReactiveAccessor; import org.springframework.data.couchbase.transaction.CouchbaseStuffHandle; import reactor.core.publisher.Mono; @@ -24,9 +25,6 @@ import com.couchbase.client.java.Cluster; import com.couchbase.client.java.Collection; import com.couchbase.client.java.Scope; -import com.couchbase.transactions.AttemptContextReactive; -import com.couchbase.transactions.Transactions; -import com.couchbase.transactions.config.TransactionConfig; public class SimpleReactiveCouchbaseClientFactory implements ReactiveCouchbaseClientFactory { final Mono cluster; @@ -108,13 +106,6 @@ public PersistenceExceptionTranslator getExceptionTranslator() { return exceptionTranslator; } - @Override - public Mono getSession(ClientSessionOptions options, Transactions transactions, - TransactionConfig config /*, AttemptContextReactive atr*/) { - throw new RuntimeException("TODO: maybe not used"); - // return Mono.just(new ClientSessionImpl(this, transactions, config)); - } - @Override public void close() { cluster.block().disconnect(); @@ -122,16 +113,16 @@ public void close() { @Override public Mono getSession(ClientSessionOptions options) { // hopefully this gets filled in later - return Mono.from(Mono.just(new ClientSessionImpl(this, null, null, null))); // .startSession(options)); + return Mono.from(Mono.just(new ClientSessionImpl(this, null))); // .startSession(options)); } @Override - public ClientSession getSession(ClientSessionOptions options, Transactions transactions, TransactionConfig config, - AttemptContextReactive atr) { - - AttemptContextReactive at = atr != null ? atr : AttemptContextReactiveAccessor.newAttemptContextReactive(transactions.reactive()); - - return new ClientSessionImpl(this, transactions, config, at); + public ClientSession getSession(ClientSessionOptions options, ReactiveTransactionAttemptContext atr) { + // todo gp needed? + return null; +// ReactiveTransactionAttemptContext at = atr != null ? atr : AttemptContextReactiveAccessor.newAttemptContextReactive(transactions.reactive()); +// +// return new ClientSessionImpl(this, at); } @Override @@ -234,12 +225,6 @@ public PersistenceExceptionTranslator getExceptionTranslator() { return delegate.getExceptionTranslator(); } - @Override - public Mono getSession(ClientSessionOptions options, Transactions transactions, - TransactionConfig config) { - return Mono.just(session); - } - @Override public String getBucketName() { return delegate.getBucketName(); @@ -261,13 +246,12 @@ public void close() throws IOException { */ @Override public Mono getSession(ClientSessionOptions options) { - return getSession(options, null, null); + return Mono.just(getSession(options, null)); } @Override - public ClientSession getSession(ClientSessionOptions options, Transactions transactions, TransactionConfig config, - AttemptContextReactive atr) { - return delegate.getSession(options, transactions, config, atr); + public ClientSession getSession(ClientSessionOptions options, ReactiveTransactionAttemptContext atr) { + return delegate.getSession(options, atr); } /* diff --git a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java index 127044c1d..67d13c01d 100644 --- a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java +++ b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java @@ -67,10 +67,6 @@ import com.couchbase.client.java.env.ClusterEnvironment; import com.couchbase.client.java.json.JacksonTransformers; import com.couchbase.client.java.json.JsonValueModule; -import com.couchbase.transactions.TransactionDurabilityLevel; -import com.couchbase.transactions.Transactions; -import com.couchbase.transactions.config.TransactionConfig; -import com.couchbase.transactions.config.TransactionConfigBuilder; import com.fasterxml.jackson.databind.ObjectMapper; /** @@ -334,40 +330,32 @@ public ObjectMapper couchbaseObjectMapper() { return mapper; } - @Bean(COUCHBASE_TRANSACTIONS) - public Transactions getTransactions(Cluster cluster, TransactionConfig transactionConfig) { - return Transactions.create(cluster, transactionConfig); - } - - @Bean - public TransactionConfig transactionConfig() { - return TransactionConfigBuilder.create().logDirectly(Event.Severity.INFO).logOnFailure(true, Event.Severity.ERROR) - .expirationTime(Duration.ofSeconds(10)).durabilityLevel(TransactionDurabilityLevel.MAJORITY).build(); - } + // todo gp how to DI this into the Cluster creation esp. as it creates a CoreTransactionConfig +// @Bean +// public TransactionsConfig transactionConfig() { +// return TransactionsConfig.builder().build(); +// } @Bean(BeanNames.REACTIVE_COUCHBASE_TRANSACTION_MANAGER) ReactiveCouchbaseTransactionManager reactiveTransactionManager( - ReactiveCouchbaseClientFactory reactiveCouchbaseClientFactory, Transactions transactions) { - return new ReactiveCouchbaseTransactionManager(reactiveCouchbaseClientFactory, transactions); + ReactiveCouchbaseClientFactory reactiveCouchbaseClientFactory) { + return new ReactiveCouchbaseTransactionManager(reactiveCouchbaseClientFactory); } @Bean(BeanNames.COUCHBASE_TRANSACTION_MANAGER) - CouchbaseTransactionManager transactionManager(CouchbaseClientFactory couchbaseClientFactory, - Transactions transactions) { - return new CouchbaseTransactionManager(couchbaseClientFactory, transactions); + CouchbaseTransactionManager transactionManager(CouchbaseClientFactory couchbaseClientFactory) { + return new CouchbaseTransactionManager(couchbaseClientFactory); } /** * Blocking Transaction Manager * * @param couchbaseTemplate - * @param transactionConfig * @return */ @Bean(BeanNames.COUCHBASE_CALLBACK_TRANSACTION_MANAGER) - CouchbaseCallbackTransactionManager callbackTransactionManager(CouchbaseTemplate couchbaseTemplate, ReactiveCouchbaseTemplate couchbaseReactiveTemplate, - TransactionConfig transactionConfig) { - return new CouchbaseCallbackTransactionManager(couchbaseTemplate, couchbaseReactiveTemplate, transactionConfig); + CouchbaseCallbackTransactionManager callbackTransactionManager(CouchbaseTemplate couchbaseTemplate, ReactiveCouchbaseTemplate couchbaseReactiveTemplate) { + return new CouchbaseCallbackTransactionManager(couchbaseTemplate, couchbaseReactiveTemplate); } /** diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java index 89ad24ea6..2e947d71d 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java @@ -17,12 +17,7 @@ import static com.couchbase.client.java.kv.GetAndTouchOptions.getAndTouchOptions; -import com.couchbase.client.java.query.QueryOptions; -import com.couchbase.transactions.AttemptContextReactive; -import com.couchbase.transactions.TransactionQueryOptions; -import com.example.demo.CouchbaseTransactionalTemplate; import org.springframework.data.couchbase.repository.support.TransactionResultHolder; -import org.springframework.data.couchbase.transaction.ClientSession; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -95,12 +90,12 @@ public Mono one(final String id) { .getCollection(pArgs.getCollection()).block().reactive(); Mono tmpl = template.doGetTemplate(); - //AttemptContextReactive ctx = CouchbaseTransactionalTemplate.getContextReactive(template); + //ReactiveTransactionAttemptContext ctx = CouchbaseTransactionalTemplate.getContextReactive(template); //ClientSession session = CouchbaseTransactionalTemplate.getSession(template); Mono reactiveEntity = tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getSession(null) .flatMap(s -> { - if ( s == null || s.getAttemptContextReactive() == null ) { + if ( s == null || s.getReactiveTransactionAttemptContext() == null ) { if (pArgs.getOptions() instanceof GetAndTouchOptions) { return rc.getAndTouch(id, expiryToUse(), (GetAndTouchOptions) pArgs.getOptions()).flatMap( result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), domainType, pArgs.getScope(), pArgs.getCollection(), null)); @@ -109,8 +104,9 @@ public Mono one(final String id) { result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), domainType, pArgs.getScope(), pArgs.getCollection(), null)); } } else { - return s.getAttemptContextReactive().get(rc, id) - .flatMap(result -> support.decodeEntity(id, result.contentAsObject().toString(), result.cas(), + return s.getReactiveTransactionAttemptContext().get(rc, id) + // todo gp no cas + .flatMap(result -> support.decodeEntity(id, result.contentAsObject().toString(), 0, domainType, pArgs.getScope(), pArgs.getCollection(), new TransactionResultHolder(result), s)); } })); diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java index 5c53a8a4c..6bc8dc1b0 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java @@ -15,6 +15,8 @@ */ package org.springframework.data.couchbase.core; +import com.couchbase.client.java.transactions.TransactionQueryOptions; +import com.couchbase.client.java.transactions.TransactionQueryResult; import org.springframework.data.couchbase.ReactiveCouchbaseClientFactory; import org.springframework.data.couchbase.transaction.CouchbaseStuffHandle; import reactor.core.publisher.Flux; @@ -22,7 +24,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.data.couchbase.CouchbaseClientFactory; import org.springframework.data.couchbase.core.query.OptionsBuilder; import org.springframework.data.couchbase.core.query.Query; import org.springframework.data.couchbase.core.support.PseudoArgs; @@ -34,7 +35,6 @@ import com.couchbase.client.java.query.QueryOptions; import com.couchbase.client.java.query.QueryScanConsistency; import com.couchbase.client.java.query.ReactiveQueryResult; -import com.couchbase.transactions.TransactionQueryOptions; /** * {@link ReactiveFindByQueryOperation} implementations for Couchbase. @@ -207,14 +207,16 @@ public Flux all() { //if (pArgs.getTxOp() == null && txOp == null) { // too early to find TxOp - transactional() has not yet been called allResult = tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getSession(null) .flatMap(s -> { - if ( s == null || s.getAttemptContextReactive() == null ) { + if ( s == null || s.getReactiveTransactionAttemptContext() == null ) { QueryOptions opts = buildOptions(pArgs.getOptions()); return pArgs.getScope() == null ? clientFactory.getCluster().block().reactive().query(statement, opts) : rs.query(statement, opts); } else { TransactionQueryOptions opts = buildTransactionOptions(pArgs.getOptions()); - return s.getAttemptContextReactive() + Mono tqr = s.getReactiveTransactionAttemptContext() .query(statement, opts); + // todo gp do something with tqr + return Mono.empty(); } })); Mono finalAllResult = allResult; @@ -279,14 +281,16 @@ public Mono count() { */ countResult = tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getSession(null) .flatMap(s -> { - if ( s == null || s.getAttemptContextReactive() == null ) { + if ( s == null || s.getReactiveTransactionAttemptContext() == null ) { QueryOptions opts = buildOptions(pArgs.getOptions()); return pArgs.getScope() == null ? clientFactory.getCluster().block().reactive().query(statement, opts) : rs.query(statement, opts); } else { TransactionQueryOptions opts = buildTransactionOptions(pArgs.getOptions()); - return s.getAttemptContextReactive() + Mono tqr = s.getReactiveTransactionAttemptContext() .query(statement, opts); + // todo gp do something with tqr + return Mono.empty(); } })); Mono finalCountResult = countResult; diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java index 8622f75e9..88fdc4c16 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java @@ -15,13 +15,7 @@ */ package org.springframework.data.couchbase.core; -import com.couchbase.client.java.Cluster; -import com.example.demo.CouchbaseTransactionalTemplate; -import org.springframework.data.couchbase.ReactiveCouchbaseClientFactory; import org.springframework.data.couchbase.repository.support.TransactionResultHolder; -import org.springframework.data.couchbase.transaction.ClientSession; -import org.springframework.transaction.reactive.TransactionContext; -import org.springframework.transaction.reactive.TransactionContextManager; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -30,7 +24,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.data.couchbase.CouchbaseClientFactory; import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; import org.springframework.data.couchbase.core.query.OptionsBuilder; import org.springframework.data.couchbase.core.support.PseudoArgs; @@ -39,11 +32,9 @@ import org.springframework.util.Assert; import com.couchbase.client.core.msg.kv.DurabilityLevel; -import com.couchbase.client.java.ReactiveCollection; import com.couchbase.client.java.kv.InsertOptions; import com.couchbase.client.java.kv.PersistTo; import com.couchbase.client.java.kv.ReplicateTo; -import com.couchbase.transactions.TransactionInsertOptions; import static com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_COLLECTION; import static com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_SCOPE; @@ -135,7 +126,7 @@ public Mono one(T object) { //ClientSession session = CouchbaseTransactionalTemplate.getSession(template); Mono reactiveEntity = support.encodeEntity(object) .flatMap(converted -> tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getSession(null).flatMap(s -> { - if (s == null || s.getAttemptContextReactive() == null) { + if (s == null || s.getReactiveTransactionAttemptContext() == null) { return template.getCouchbaseClientFactory().withScope(pArgs.getScope()) .getCollection(pArgs.getCollection()) .flatMap(collection -> collection.reactive() @@ -143,13 +134,14 @@ public Mono one(T object) { .flatMap( result -> support.applyResult(object, converted, converted.getId(), result.cas(), null))); } else { - return s.getAttemptContextReactive() + return s.getReactiveTransactionAttemptContext() .insert( tp.doGetDatabase().block().bucket(tp.getBucketName()).reactive() .scope(pArgs.getScope() != null ? pArgs.getScope() : DEFAULT_SCOPE) .collection(pArgs.getCollection() != null ? pArgs.getCollection() : DEFAULT_COLLECTION), - converted.getId(), converted.getContent(), buildTxOptions(pArgs.getOptions(), converted)) - .flatMap(result -> support.applyResult(object, converted, converted.getId(), result.cas(), new TransactionResultHolder(result), s)); + converted.getId(), converted.getContent()) + // todo gp don't have result.cas() anymore - needed? + .flatMap(result -> support.applyResult(object, converted, converted.getId(), 0L, new TransactionResultHolder(result), s)); } }))); // .flatMap(converted ->/* rc */tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getCluster().flatMap( cl -> @@ -190,10 +182,6 @@ public InsertOptions buildOptions(InsertOptions options, CouchbaseDocument doc) return OptionsBuilder.buildInsertOptions(options, persistTo, replicateTo, durabilityLevel, expiry, doc); } - private TransactionInsertOptions buildTxOptions(InsertOptions buildOptions, CouchbaseDocument doc) { - return OptionsBuilder.buildTxInsertOptions(buildOptions(buildOptions, doc)); - } - @Override public TerminatingInsertById withOptions(final InsertOptions options) { Assert.notNull(options, "Options must not be null."); diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java index 815679dee..8bc0b70a0 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java @@ -34,8 +34,6 @@ import com.couchbase.client.java.kv.PersistTo; import com.couchbase.client.java.kv.RemoveOptions; import com.couchbase.client.java.kv.ReplicateTo; -import com.couchbase.transactions.TransactionGetResult; -import com.couchbase.transactions.components.TransactionLinks; public class ReactiveRemoveByIdOperationSupport implements ReactiveRemoveByIdOperation { @@ -71,10 +69,6 @@ static class ReactiveRemoveByIdSupport implements ReactiveRemoveById { private final Long cas; private final CouchbaseStuffHandle txCtx; - private final TransactionLinks tl = new TransactionLinks(Optional.empty(), Optional.empty(), Optional.empty(), - Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), - Optional.empty(), Optional.empty(), false, Optional.empty(), Optional.empty()); - ReactiveRemoveByIdSupport(final ReactiveCouchbaseTemplate template, final Class domainType, final String scope, final String collection, final RemoveOptions options, final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel, Long cas, CouchbaseStuffHandle txCtx) { @@ -101,9 +95,13 @@ public Mono one(final String id) { removeResult = rc.remove(id, buildRemoveOptions(pArgs.getOptions())).map(r -> RemoveResult.from(id, r)); } else { Transcoder transcoder = template.getCouchbaseClientFactory().getCluster().block().environment().transcoder(); - TransactionGetResult doc = new TransactionGetResult(id, null, 0, rc, tl, null, Optional.empty(), transcoder, - null); - removeResult = pArgs.getTxOp().getAttemptContextReactive().remove(doc).map(r -> new RemoveResult(id, 0, null)); + // todo gp we definitely don't want to be creating TransactionGetResult. It's essential that this is passed + // from a previous ctx.get(). So we know if this doc is in a transaction and can safely detect + // write-write conflicts. This will be a blocker. + // Looks like replace is solving this with a getTransactionHolder? +// TransactionGetResult doc = new TransactionGetResult(id, null, 0, rc, tl, null, Optional.empty(), transcoder, +// null); + removeResult = pArgs.getTxOp().getAttemptContextReactive().remove(null).map(r -> new RemoveResult(id, 0, null)); } return removeResult.onErrorMap(throwable -> { if (throwable instanceof RuntimeException) { diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java index 2df3a5aef..1e3295e9c 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java @@ -15,6 +15,8 @@ */ package org.springframework.data.couchbase.core; +import com.couchbase.client.java.transactions.TransactionQueryOptions; +import com.couchbase.client.java.transactions.TransactionQueryResult; import org.springframework.data.couchbase.ReactiveCouchbaseClientFactory; import org.springframework.data.couchbase.transaction.CouchbaseStuffHandle; import reactor.core.publisher.Flux; @@ -34,7 +36,6 @@ import com.couchbase.client.java.query.QueryOptions; import com.couchbase.client.java.query.QueryScanConsistency; import com.couchbase.client.java.query.ReactiveQueryResult; -import com.couchbase.transactions.TransactionQueryOptions; public class ReactiveRemoveByQueryOperationSupport implements ReactiveRemoveByQueryOperation { @@ -90,7 +91,8 @@ public Flux all() { : rs.query(statement, opts); } else { TransactionQueryOptions opts = buildTransactionOptions(buildQueryOptions(pArgs.getOptions())); - allResult = pArgs.getScope() == null ? pArgs.getTxOp().getAttemptContextReactive().query(statement, opts) : pArgs.getTxOp().getAttemptContextReactive().query(rs, statement, opts); + Mono tqr = pArgs.getScope() == null ? pArgs.getTxOp().getAttemptContextReactive().query(statement, opts) : pArgs.getTxOp().getAttemptContextReactive().query(rs, statement, opts); + // todo gp do something with tqr } Mono finalAllResult = allResult; return Flux.defer(() -> finalAllResult.onErrorMap(throwable -> { diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java index 242cb3f9d..3ef25c78c 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java @@ -36,7 +36,6 @@ import com.couchbase.client.java.kv.PersistTo; import com.couchbase.client.java.kv.ReplaceOptions; import com.couchbase.client.java.kv.ReplicateTo; -import com.couchbase.transactions.components.TransactionLinks; public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdOperation { @@ -68,10 +67,6 @@ static class ReactiveReplaceByIdSupport implements ReactiveReplaceById { private final CouchbaseStuffHandle txCtx; private final ReactiveTemplateSupport support; - private final TransactionLinks tl = new TransactionLinks(Optional.empty(), Optional.empty(), Optional.empty(), - Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), - Optional.empty(), Optional.empty(), false, Optional.empty(), Optional.empty()); - ReactiveReplaceByIdSupport(final ReactiveCouchbaseTemplate template, final Class domainType, final String scope, final String collection, final ReplaceOptions options, final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel, final Duration expiry, final CouchbaseStuffHandle txCtx, @@ -127,7 +122,7 @@ public Mono one(T object) { CouchbaseDocument converted = support.encodeEntity(object).block(); reactiveEntity = tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getSession(null).flatMap(s -> { - if (s == null || s.getAttemptContextReactive() == null) { + if (s == null || s.getReactiveTransactionAttemptContext() == null) { System.err.println("ReactiveReplaceById: not"); Mono op = template.getCouchbaseClientFactory() .withScope(pArgs.getScope()).getCollection(pArgs.getCollection()); @@ -137,10 +132,11 @@ public Mono one(T object) { .flatMap(result -> support.applyResult(object, converted, converted.getId(), result.cas(), null))); } else { System.err.println("ReactiveReplaceById: transaction"); - return s.getAttemptContextReactive() + return s.getReactiveTransactionAttemptContext() .replace(s.transactionResultHolder(getTransactionHolder(object)).transactionGetResult(), converted.getContent()) - .flatMap(result -> support.applyResult(object, converted, converted.getId(), result.cas(), + // todo gp no CAS + .flatMap(result -> support.applyResult(object, converted, converted.getId(), 0L, new TransactionResultHolder(result), s)); } })); diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java index fc112537e..6dadac473 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java @@ -85,7 +85,7 @@ public Mono one(T object) { Mono tmpl = template.doGetTemplate(); Mono reactiveEntity = support.encodeEntity(object) .flatMap(converted -> tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getSession(null).flatMap(s -> { - if (s == null || s.getAttemptContextReactive() == null) { + if (s == null || s.getReactiveTransactionAttemptContext() == null) { return tp.getCouchbaseClientFactory().withScope(pArgs.getScope()) .getCollection(pArgs.getCollection()).flatMap(collection -> collection.reactive() .upsert(converted.getId(), converted.export(), buildUpsertOptions(pArgs.getOptions(), converted)) diff --git a/src/main/java/org/springframework/data/couchbase/core/query/OptionsBuilder.java b/src/main/java/org/springframework/data/couchbase/core/query/OptionsBuilder.java index e7ce86357..aacc2a4f0 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/OptionsBuilder.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/OptionsBuilder.java @@ -27,6 +27,7 @@ import java.util.Map; import java.util.Optional; +import com.couchbase.client.java.transactions.TransactionQueryOptions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.AnnotatedElementUtils; @@ -50,9 +51,6 @@ import com.couchbase.client.java.kv.UpsertOptions; import com.couchbase.client.java.query.QueryOptions; import com.couchbase.client.java.query.QueryScanConsistency; -import com.couchbase.transactions.TransactionInsertOptions; -import com.couchbase.transactions.TransactionQueryOptions; -import com.couchbase.transactions.TransactionReplaceOptions; public class OptionsBuilder { @@ -134,13 +132,6 @@ public static InsertOptions buildInsertOptions(InsertOptions options, PersistTo return options; } - public static TransactionInsertOptions buildTxInsertOptions(InsertOptions options) { - options = options != null ? options : InsertOptions.insertOptions(); - InsertOptions.Built built = options.build(); - TransactionInsertOptions txOptions = TransactionInsertOptions.insertOptions(); - return txOptions; - } - public static UpsertOptions buildUpsertOptions(UpsertOptions options, PersistTo persistTo, ReplicateTo replicateTo, DurabilityLevel durabilityLevel, Duration expiry, CouchbaseDocument doc) { options = options != null ? options : UpsertOptions.upsertOptions(); @@ -182,18 +173,6 @@ public static ReplaceOptions buildReplaceOptions(ReplaceOptions options, Persist return options; } - public static Object buildTransactionReplaceOptions(ReplaceOptions options) { - ReplaceOptions.Built built = options.build(); - TransactionReplaceOptions txOptions = TransactionReplaceOptions.replaceOptions(); - return txOptions; - } - - public static TransactionReplaceOptions buildTransactionUpsertOptions(ReplaceOptions options) { - ReplaceOptions.Built built = options.build(); - TransactionReplaceOptions txOptions = TransactionReplaceOptions.replaceOptions(); - return txOptions; - } - public static RemoveOptions buildRemoveOptions(RemoveOptions options, PersistTo persistTo, ReplicateTo replicateTo, DurabilityLevel durabilityLevel, Long cas) { options = options != null ? options : RemoveOptions.removeOptions(); diff --git a/src/main/java/org/springframework/data/couchbase/core/transaction/SDCouchbaseTransactions.java b/src/main/java/org/springframework/data/couchbase/core/transaction/SDCouchbaseTransactions.java deleted file mode 100644 index 4b26edc4a..000000000 --- a/src/main/java/org/springframework/data/couchbase/core/transaction/SDCouchbaseTransactions.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2021 the original author or authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.couchbase.core.transaction; - -import com.couchbase.transactions.AttemptContextReactive; -import com.couchbase.transactions.TransactionGetResult; -import com.couchbase.transactions.TransactionResult; -import com.couchbase.transactions.Transactions; -import com.couchbase.transactions.TransactionsReactive; -import com.couchbase.transactions.config.PerTransactionConfigBuilder; -import org.springframework.data.couchbase.repository.support.TransactionResultHolder; -import reactor.core.publisher.Mono; - -import java.util.HashMap; -import java.util.Map; -import java.util.function.Function; - -public class SDCouchbaseTransactions { - - Transactions transactions; - Map getResultMap = new HashMap<>(); - private AttemptContextReactive ctx; - - public SDCouchbaseTransactions(Transactions transactions) { - this.transactions = transactions; - } - - public TransactionsReactive reactive(){ - return transactions.reactive(); - } - - public AttemptContextReactive getCtx(){ - return ctx; - } - - // public Mono reactive(Function> transactionLogic) { - // return reactive(transactionLogic, true); - // } - /** - * A convenience wrapper around {@link TransactionsReactive#run}, that provides a default PerTransactionConfig. - */ - public Mono reactive(Function> transactionLogic/*, boolean commit*/) { - return transactions.reactive((ctx) -> { - setAttemptTransactionReactive(ctx); - return transactionLogic.apply(ctx); }, PerTransactionConfigBuilder.create().build()/*, commit*/); - - } - - public TransactionResultHolder transactionGetResult(Integer key){ - return getResultMap.get(key); - } - - public TransactionResultHolder transactionGetResult(TransactionGetResult result){ - TransactionResultHolder holder = new TransactionResultHolder(result); - getResultMap.put(System.identityHashCode(holder), holder); - return holder; - } - - public void setAttemptTransactionReactive(AttemptContextReactive ctx) { - this.ctx = ctx; - } - -} diff --git a/src/main/java/org/springframework/data/couchbase/repository/TransactionResult.java b/src/main/java/org/springframework/data/couchbase/repository/TransactionResult.java index 6e9be67da..d2236d520 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/TransactionResult.java +++ b/src/main/java/org/springframework/data/couchbase/repository/TransactionResult.java @@ -28,6 +28,7 @@ * * @author Michael Reiche */ +// todo gp can we give this a different name since there is an existing TransactionResult @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD }) @Documented diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/TransactionResultHolder.java b/src/main/java/org/springframework/data/couchbase/repository/support/TransactionResultHolder.java index c3282a489..31676d11b 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/TransactionResultHolder.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/TransactionResultHolder.java @@ -16,8 +16,9 @@ package org.springframework.data.couchbase.repository.support; -import com.couchbase.transactions.SingleQueryTransactionResult; -import com.couchbase.transactions.TransactionGetResult; +import com.couchbase.client.java.query.QueryResult; +import com.couchbase.client.java.transactions.TransactionGetResult; +import reactor.util.annotation.Nullable; /** * Holds previously obtained Transaction*Result @@ -26,26 +27,29 @@ */ public class TransactionResultHolder { - TransactionGetResult getResult; - SingleQueryTransactionResult singleQueryResult; + private final @Nullable TransactionGetResult getResult; + // todo gp needed? + private final @Nullable QueryResult singleQueryResult; public TransactionResultHolder(TransactionGetResult getResult) { // we don't need the content and we don't have access to the transcoder an txnMeta (and we don't need them either). - this.getResult = new TransactionGetResult(getResult.id(), null, getResult.cas(), getResult.collection(), - getResult.links(), getResult.status(), getResult.documentMetadata(), null, null); + // todo gp will need to expose a copy ctor if a copy is really needed + this.getResult = getResult; +// this.getResult = new TransactionGetResult(getResult.id(), null, getResult.cas(), getResult.collection(), +// getResult.links(), getResult.status(), getResult.documentMetadata(), null, null); this.singleQueryResult = null; } - public TransactionResultHolder(SingleQueryTransactionResult singleQueryResult) { + public TransactionResultHolder(QueryResult singleQueryResult) { this.getResult = null; this.singleQueryResult = singleQueryResult; } - public TransactionGetResult transactionGetResult() { + public @Nullable TransactionGetResult transactionGetResult() { return getResult; } - public SingleQueryTransactionResult singleQueryResult() { + public @Nullable QueryResult singleQueryResult() { return singleQueryResult(); } } diff --git a/src/main/java/org/springframework/data/couchbase/transaction/ClientSession.java b/src/main/java/org/springframework/data/couchbase/transaction/ClientSession.java index f3f79ac63..4905ee247 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/ClientSession.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/ClientSession.java @@ -3,21 +3,17 @@ import com.couchbase.client.java.AsyncCluster; import com.couchbase.client.java.Scope; -import com.couchbase.transactions.AttemptContext; -import com.couchbase.transactions.AttemptContextReactive; -import com.couchbase.transactions.TransactionGetResult; -import com.couchbase.transactions.TransactionQueryOptions; -import com.couchbase.transactions.config.TransactionConfig; +import com.couchbase.client.java.transactions.ReactiveTransactionAttemptContext; +import com.couchbase.client.java.transactions.TransactionAttemptContext; +import com.couchbase.client.java.transactions.config.TransactionOptions; import org.reactivestreams.Publisher; import org.springframework.data.couchbase.repository.support.TransactionResultHolder; import reactor.core.publisher.Mono; -import java.util.Map; - /** * ClientSession. There is only one implementation - ClientSessionImpl * The SpringTransaction framework relies on the client session to perform commit() and abort() - * and therefore it has an AttemptContextReactive + * and therefore it has a ReactiveTransactionAttemptContext * * @author Michael Reiche */ @@ -33,9 +29,9 @@ public interface ClientSession /*extends com.mongodb.session.ClientSession*/ { void notifyOperationInitiated(Object var1); - //void setAttemptContextReactive(AttemptContextReactive atr); + //void setAttemptContextReactive(ReactiveTransactionAttemptContext atr); - AttemptContextReactive getAttemptContextReactive(); + ReactiveTransactionAttemptContext getReactiveTransactionAttemptContext(); TransactionOptions getTransactionOptions(); @@ -43,8 +39,6 @@ public interface ClientSession /*extends com.mongodb.session.ClientSession*/ { void startTransaction(); - void startTransaction(TransactionConfig var1); - Publisher commitTransaction(); Publisher abortTransaction(); @@ -61,7 +55,7 @@ public interface ClientSession /*extends com.mongodb.session.ClientSession*/ { TransactionResultHolder transactionResultHolder(Integer key); - AttemptContext getAttemptContext(); + TransactionAttemptContext getTransactionAttemptContext(); - //ClientSession with(AttemptContextReactive atr); + //ClientSession with(ReactiveTransactionAttemptContext atr); } diff --git a/src/main/java/org/springframework/data/couchbase/transaction/ClientSessionImpl.java b/src/main/java/org/springframework/data/couchbase/transaction/ClientSessionImpl.java index b9db58c1e..eaae9bf68 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/ClientSessionImpl.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/ClientSessionImpl.java @@ -1,11 +1,14 @@ package org.springframework.data.couchbase.transaction; -import com.couchbase.transactions.AttemptContext; -import com.couchbase.transactions.AttemptContextReactiveAccessor; -import com.couchbase.transactions.error.external.TransactionOperationFailed; +import com.couchbase.client.core.transaction.support.AttemptState; +import com.couchbase.client.java.transactions.ReactiveTransactionAttemptContext; +import com.couchbase.client.java.transactions.TransactionAttemptContext; +import com.couchbase.client.java.transactions.Transactions; +import com.couchbase.client.java.transactions.config.TransactionOptions; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.transaction.reactive.AbstractReactiveTransactionManager; +import org.springframework.transaction.reactive.TransactionContext; import reactor.core.publisher.Mono; import reactor.core.publisher.MonoSink; @@ -27,14 +30,6 @@ import com.couchbase.client.java.AsyncCluster; import com.couchbase.client.java.Scope; import com.couchbase.client.java.env.ClusterEnvironment; -import com.couchbase.transactions.AttemptContextReactive; -import com.couchbase.transactions.TransactionContext; -import com.couchbase.transactions.TransactionGetResult; -import com.couchbase.transactions.Transactions; -import com.couchbase.transactions.config.MergedTransactionConfig; -import com.couchbase.transactions.config.TransactionConfig; -import com.couchbase.transactions.config.TransactionConfigBuilder; -import com.couchbase.transactions.support.AttemptStates; public class ClientSessionImpl implements ClientSession { @@ -44,47 +39,45 @@ public class ClientSessionImpl implements ClientSession { Scope scope; boolean commitInProgress = false; boolean messageSentInCurrentTransaction = true; // needs to be true for commit - AttemptStates transactionState = AttemptStates.NOT_STARTED; + // todo gp probably should not be duplicating CoreTransactionAttemptContext state outside of it + AttemptState transactionState = AttemptState.NOT_STARTED; TransactionOptions transactionOptions; - Transactions transactions; TransactionContext ctx; - TransactionConfig config; - AttemptContextReactive atr = null; - AttemptContext at = null; + ReactiveTransactionAttemptContext atr = null; + TransactionAttemptContext at = null; Map getResultMap = new HashMap<>(); public ClientSessionImpl(){} - public ClientSessionImpl(ReactiveCouchbaseClientFactory couchbaseClientFactory, Transactions transactions, - TransactionConfig config, AttemptContextReactive atr) { - this.transactions = transactions; + public ClientSessionImpl(ReactiveCouchbaseClientFactory couchbaseClientFactory, ReactiveTransactionAttemptContext atr) { scopeRx = couchbaseClientFactory.getScope(); - this.config = config == null - ? TransactionConfigBuilder.create().expirationTime(Duration.ofSeconds(120)).build() - : config; - MergedTransactionConfig merged = new MergedTransactionConfig(this.config, Optional.empty()); - ClusterEnvironment environment = couchbaseClientFactory.getCluster().block().environment(); - ctx = new TransactionContext(environment.requestTracer(), environment.eventBus(), UUID.randomUUID().toString(), - now(), Duration.ZERO, merged); - // does this not need an non-reactive AttemptContext? + // todo gp hopefully none of this is needed +// this.config = config == null +// ? TransactionConfigBuilder.create().expirationTime(Duration.ofSeconds(120)).build() +// : config; +// MergedTransactionConfig merged = new MergedTransactionConfig(this.config, Optional.empty()); +// ClusterEnvironment environment = couchbaseClientFactory.getCluster().block().environment(); +// ctx = new TransactionContext(environment.requestTracer(), environment.eventBus(), UUID.randomUUID().toString(), +// now(), Duration.ZERO, merged); + // does this not need an non-reactive TransactionAttemptContext? this.atr = atr; } - public ClientSessionImpl(CouchbaseClientFactory couchbaseClientFactory, Transactions transactions, - TransactionConfig config, AttemptContext at) { - this.transactions = transactions; + public ClientSessionImpl(CouchbaseClientFactory couchbaseClientFactory, TransactionAttemptContext at) { + // todo gp hopefully none of this is needed +// this.transactions = transactions; scope = couchbaseClientFactory.getScope(); - this.config = config == null - ? TransactionConfigBuilder.create().expirationTime(Duration.ofSeconds(120)).build() - : config; - MergedTransactionConfig merged = new MergedTransactionConfig(this.config, Optional.empty()); +// this.config = config == null +// ? TransactionConfigBuilder.create().expirationTime(Duration.ofSeconds(120)).build() +// : config; +// MergedTransactionConfig merged = new MergedTransactionConfig(this.config, Optional.empty()); ClusterEnvironment environment = couchbaseClientFactory.getCluster().environment(); - ctx = new TransactionContext(environment.requestTracer(), environment.eventBus(), UUID.randomUUID().toString(), - now(), Duration.ZERO, merged); +// ctx = new TransactionContext(environment.requestTracer(), environment.eventBus(), UUID.randomUUID().toString(), +// now(), Duration.ZERO, merged); this.at = at; - if(at != null){ - this.atr = AttemptContextReactiveAccessor.getACR(at); - } +// if(at != null){ +// this.atr = AttemptContextReactiveAccessor.getACR(at); +// } } @Override @@ -108,24 +101,24 @@ public void notifyOperationInitiated(Object var1) { } //@Override - //public void setAttemptContextReactive(AttemptContextReactive atr){ + //public void setAttemptContextReactive(ReactiveTransactionAttemptContext atr){ // this.atr = atr; //} @Override - public AttemptContextReactive getAttemptContextReactive(){ + public ReactiveTransactionAttemptContext getReactiveTransactionAttemptContext(){ return atr; } @Override - public AttemptContext getAttemptContext(){ + public TransactionAttemptContext getTransactionAttemptContext(){ return at; } // setter that returns `this` //@Override - //public ClientSession with(AttemptContextReactive atr){ + //public ClientSession with(ReactiveTransactionAttemptContext atr){ // setAttemptContextReactive(atr); // return this; //} @@ -140,38 +133,35 @@ public AsyncCluster getWrapped() { return null; } + // todo gp @Override public void startTransaction() { - transactionState = AttemptStates.PENDING; - } - - @Override - public void startTransaction(TransactionConfig var1) { - startTransaction(); + transactionState = AttemptState.PENDING; } + // todo gp @Override public Publisher commitTransaction() { - if (this.transactionState == AttemptStates.ABORTED) { + if (this.transactionState == AttemptState.ABORTED) { throw new IllegalStateException("Cannot call commitTransaction after calling abortTransaction"); - } else if (this.transactionState == AttemptStates.NOT_STARTED) { + } else if (this.transactionState == AttemptState.NOT_STARTED) { throw new IllegalStateException("There is no transaction started"); } else if (!this.messageSentInCurrentTransaction) { // seems there should have been a messageSent. We just do nothing(?) - this.cleanupTransaction(AttemptStates.COMMITTED); + this.cleanupTransaction(AttemptState.COMMITTED); return Mono.create(MonoSink::success); } else { /*ReadConcern readConcern = this.transactionOptions.getReadConcern(); */ if (0 == 1/* readConcern == null*/) { throw new CouchbaseException("Invariant violated. Transaction options read concern can not be null"); } else { - boolean alreadyCommitted = this.commitInProgress || this.transactionState == AttemptStates.COMMITTED; + boolean alreadyCommitted = this.commitInProgress || this.transactionState == AttemptState.COMMITTED; this.commitInProgress = true; // this will fail with ctx.serialized() being Optional.empty() // how does the commit happen in transactions.reactive().run() ? /* return transactions.reactive().commit(ctx.serialized().get(), null).then().doOnSuccess(x -> { commitInProgress = false; - this.transactionState = AttemptStates.COMMITTED; + this.transactionState = AttemptState.COMMITTED; }).doOnError(CouchbaseException.class, this::clearTransactionContextOnError); */ // TODO MSR @@ -180,7 +170,7 @@ public Publisher commitTransaction() { /* return this.executor.execute((new CommitTransactionOperation(this.transactionOptions.getWriteConcern(), alreadyCommitted)).recoveryToken(this.getRecoveryToken()).maxCommitTime(this.transactionOptions.getMaxCommitTime(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS), readConcern, this).doOnTerminate(() -> { this.commitInProgress = false; - this.transactionState = AttemptStates.COMMITTED; + this.transactionState = AttemptState.COMMITTED; }).doOnError(CouchbaseException.class, this::clearTransactionContextOnError); */ @@ -189,7 +179,7 @@ public Publisher commitTransaction() { } } - public Mono executeImplicitCommit(AttemptContextReactive ctx) { + public Mono executeImplicitCommit(ReactiveTransactionAttemptContext ctx) { return Mono.defer(() -> { if (logger.isDebugEnabled()) { logger.debug(String.format("About to commit ctx %s", ctx)); @@ -201,19 +191,21 @@ public Mono executeImplicitCommit(AttemptContextReactive } else { //System.err.println(ctx.attemptId()+ " doing implicit commit"); // ctx.LOGGER.trace(); System.err.println("doing implicit commit"); - if(ctx != null) { - return ctx.commit() - .then(Mono.just(ctx)) - .onErrorResume(err -> Mono.error(TransactionOperationFailed.convertToOperationFailedIfNeeded(err, - ctx))); - } else { - at.commit(); - return Mono.empty(); - } + // todo gp ctx.commit() has gone in the SDK integration. Do we need this logic though? + return Mono.empty(); +// if(ctx != null) { +// return ctx.commit() +// .then(Mono.just(ctx)) +// .onErrorResume(err -> Mono.error(TransactionOperationFailed.convertToOperationFailedIfNeeded(err, +// ctx))); +// } else { +// at.commit(); +// return Mono.empty(); +// } } } else { System.err.println("Transaction already done"); - System.err.println(ctx.attemptId()+" Transaction already done"); // // ctx.LOGGER.trace(); + //System.err.println(ctx.attemptId()+" Transaction already done"); // // ctx.LOGGER.trace(); return Mono.just(ctx); } }); @@ -224,24 +216,26 @@ public Mono executeImplicitCommit(AttemptContextReactive @Override public Publisher abortTransaction() { System.err.println("**** abortTransaction ****"); - Assert.notNull(transactions, "transactions"); +// Assert.notNull(transactions, "transactions"); Assert.notNull(ctx, "ctx"); - Assert.notNull(ctx.serialized(), "ctx.serialized()"); - if (ctx.serialized().isPresent()) { - Assert.notNull(ctx.serialized().get(), "ctx.serialized().get()"); - return transactions.reactive().rollback(ctx.serialized().get(), null).then(); - } else { +// Assert.notNull(ctx.serialized(), "ctx.serialized()"); +// if (ctx.serialized().isPresent()) { +// Assert.notNull(ctx.serialized().get(), "ctx.serialized().get()"); +// return transactions.reactive().rollback(ctx.serialized().get(), null).then(); +// } else { return executeExplicitRollback(atr).then(); - } +// } } - private Mono executeExplicitRollback(AttemptContextReactive atr) { - if(at != null){ - at.rollback(); - return Mono.empty(); - } else { - return atr.rollback(); - } + private Mono executeExplicitRollback(ReactiveTransactionAttemptContext atr) { + // todo gp ctx.rollback() is removed + return Mono.empty(); +// if(at != null){ +// at.rollback(); +// return Mono.empty(); +// } else { +// return atr.rollback(); +// } } @Override @@ -264,7 +258,7 @@ public Object isCausallyConsistent() { return null; } - private void cleanupTransaction(AttemptStates attempState) {} + private void cleanupTransaction(AttemptState attempState) {} private void clearTransactionContext() {} diff --git a/src/main/java/org/springframework/data/couchbase/transaction/ClientSessionOptions.java b/src/main/java/org/springframework/data/couchbase/transaction/ClientSessionOptions.java index b145f0715..c5aa3417a 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/ClientSessionOptions.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/ClientSessionOptions.java @@ -2,12 +2,11 @@ import java.util.Objects; +import com.couchbase.client.java.transactions.TransactionQueryOptions; import org.springframework.data.annotation.Immutable; import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import com.couchbase.transactions.TransactionQueryOptions; - @Immutable public final class ClientSessionOptions { private final Boolean causallyConsistent; diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseAttemptContextReactive.java b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseAttemptContextReactive.java index 9f5b3f7f5..ee9facf31 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseAttemptContextReactive.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseAttemptContextReactive.java @@ -25,11 +25,11 @@ import org.springframework.transaction.reactive.TransactionalOperator; import com.couchbase.client.core.error.CouchbaseException; -// import com.couchbase.transactions.AttemptContextReactive; +// import com.couchbase.transactions.ReactiveTransactionAttemptContext; /** - * This is a proxy for AttemptContextReactive that also has the transactionalOperator, so that it can provide the + * This is a proxy for ReactiveTransactionAttemptContext that also has the transactionalOperator, so that it can provide the * transactionalOperator to the repository and templates used within the transaction lambda via ctx.template(templ) and * ctx.repository(repo) */ @@ -39,7 +39,7 @@ public interface CouchbaseAttemptContextReactive { ReactiveCouchbaseTemplate template(ReactiveCouchbaseTemplate template); - static CouchbaseAttemptContextReactive proxyFor(/*AttemptContextReactive acr,*/ TransactionalOperator txOperator) { + static CouchbaseAttemptContextReactive proxyFor(/*ReactiveTransactionAttemptContext acr,*/ TransactionalOperator txOperator) { Class[] interfaces = new Class[] { /* AttemptContextReactiveInterface.class, */ CouchbaseAttemptContextReactive.class }; CouchbaseAttemptContextReactive proxyInstance = (CouchbaseAttemptContextReactive) Proxy.newProxyInstance( @@ -50,10 +50,10 @@ static CouchbaseAttemptContextReactive proxyFor(/*AttemptContextReactive acr,*/ class ACRInvocationHandler implements InvocationHandler { - // final AttemptContextReactive acr; + // final ReactiveTransactionAttemptContext acr; final TransactionalOperator txOperator; - public ACRInvocationHandler(/*AttemptContextReactive acr,*/ TransactionalOperator txOperator) { + public ACRInvocationHandler(/*ReactiveTransactionAttemptContext acr,*/ TransactionalOperator txOperator) { // this.acr = acr; this.txOperator = txOperator; } diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseCallbackTransactionManager.java b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseCallbackTransactionManager.java index 27bbd7c94..591db5ae1 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseCallbackTransactionManager.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseCallbackTransactionManager.java @@ -15,6 +15,9 @@ */ package org.springframework.data.couchbase.transaction; +import com.couchbase.client.core.error.transaction.TransactionOperationFailedException; +import com.couchbase.client.java.transactions.ReactiveTransactionAttemptContext; +import com.couchbase.client.java.transactions.TransactionResult; import reactor.core.publisher.Mono; import java.time.Duration; @@ -41,15 +44,6 @@ import org.springframework.transaction.support.TransactionSynchronizationUtils; import org.springframework.util.Assert; -import com.couchbase.client.java.Cluster; -import com.couchbase.transactions.AttemptContextReactive; -import com.couchbase.transactions.AttemptContextReactiveAccessor; -import com.couchbase.transactions.TransactionResult; -import com.couchbase.transactions.Transactions; -import com.couchbase.transactions.config.TransactionConfig; -import com.couchbase.transactions.error.external.TransactionOperationFailed; -import reactor.util.context.ContextView; - /** * Blocking TransactionManager * @@ -64,18 +58,14 @@ public class CouchbaseCallbackTransactionManager extends AbstractPlatformTransac private final CouchbaseTemplate template; private final ReactiveCouchbaseTemplate reactiveTemplate; - private Transactions transactions; private final ReactiveCouchbaseClientFactory reactiveCouchbaseClientFactory; private final CouchbaseClientFactory couchbaseClientFactory; private ReactiveCouchbaseTransactionManager.ReactiveCouchbaseTransactionObject transaction; - public CouchbaseCallbackTransactionManager(CouchbaseTemplate template, ReactiveCouchbaseTemplate reactiveTemplate, - TransactionConfig transactionConfig) { + public CouchbaseCallbackTransactionManager(CouchbaseTemplate template, ReactiveCouchbaseTemplate reactiveTemplate) { this.template = template; this.reactiveTemplate = reactiveTemplate; - this.transactions = Transactions.create((Cluster) (template().getCouchbaseClientFactory().getCluster().block()), - transactionConfig); this.reactiveCouchbaseClientFactory = this.reactiveTemplate.getCouchbaseClientFactory(); this.couchbaseClientFactory = this.template.getCouchbaseClientFactory(); } @@ -85,12 +75,12 @@ public ReactiveCouchbaseTemplate template() { } private CouchbaseResourceHolder newResourceHolder(TransactionDefinition definition, ClientSessionOptions options, - AttemptContextReactive atr) { + ReactiveTransactionAttemptContext atr) { CouchbaseClientFactory databaseFactory = template.getCouchbaseClientFactory(); CouchbaseResourceHolder resourceHolder = new CouchbaseResourceHolder( - databaseFactory.getSession(options, transactions, null, atr), databaseFactory); + databaseFactory.getSession(options, atr), databaseFactory); return resourceHolder; } @@ -99,10 +89,11 @@ public T execute(TransactionDefinition definition, TransactionCallback ca final AtomicReference execResult = new AtomicReference<>(); AtomicReference startTime = new AtomicReference<>(0L); - Mono txnResult = transactions.reactive().run(ctx -> { + Mono txnResult = couchbaseClientFactory.getCluster().reactive().transactions().run(ctx -> { /* begin spring-data-couchbase transaction 1/2 */ ClientSession clientSession = reactiveCouchbaseClientFactory // couchbaseClientFactory - .getSession(ClientSessionOptions.builder().causallyConsistent(true).build(), transactions, null, ctx); + .getSession(ClientSessionOptions.builder().causallyConsistent(true).build()) + .block(); ReactiveCouchbaseResourceHolder reactiveResourceHolder = new ReactiveCouchbaseResourceHolder(clientSession, reactiveCouchbaseClientFactory); @@ -149,11 +140,14 @@ public T execute(TransactionDefinition definition, TransactionCallback ca }).contextWrite(TransactionContextManager.getOrCreateContext()) // this doesn't create a context on the desired publisher .contextWrite(TransactionContextManager.getOrCreateContextHolder()).then(); - result.onErrorResume(err -> { - AttemptContextReactiveAccessor.getLogger(ctx).info(ctx.attemptId(), - "caught exception '%s' in async, rethrowing", err); - return Mono.error(TransactionOperationFailed.convertToOperationFailedIfNeeded(err, ctx)); - }).thenReturn(ctx); + // todo gp this isn't part of the chain (no `result = result.onErrorResume...`) so isn't called + // and presumably isn't needed? +// result.onErrorResume(err -> { +// AttemptContextReactiveAccessor.getLogger(ctx).info(ctx.attemptId(), +// "caught exception '%s' in async, rethrowing", err); +// return Mono.error(ctx.TransactionOperationFailedException.convertToOperationFailedIfNeeded(err, ctx)); +// }).thenReturn(ctx); + return result.then(Mono.just(synchronizationManager)); }); /* begin spring-data-couchbase transaction 2/2 */ // this doesn't create a context on the desired publisher @@ -168,7 +162,7 @@ public T execute(TransactionDefinition definition, TransactionCallback ca } private void setTransaction(ReactiveCouchbaseTransactionManager.ReactiveCouchbaseTransactionObject transaction) { - this.transactions = transactions; + this.transaction = transaction; } @Override @@ -214,7 +208,6 @@ protected void doCleanupAfterCompletion(Object transaction) { @Override public void destroy() { - transactions.close(); } @Override @@ -232,18 +225,18 @@ private static CouchbaseTransactionObject extractTransaction(Object transaction) /* public class CouchbaseResourceHolder extends ResourceHolderSupport { - private volatile AttemptContextReactive attemptContext; + private volatile ReactiveTransactionAttemptContext attemptContext; //private volatile TransactionResultMap resultMap = new TransactionResultMap(template); - public CouchbaseResourceHolder(AttemptContextReactive attemptContext) { + public CouchbaseResourceHolder(ReactiveTransactionAttemptContext attemptContext) { this.attemptContext = attemptContext; } - public AttemptContextReactive getAttemptContext() { + public ReactiveTransactionAttemptContext getAttemptContext() { return attemptContext; } - public void setAttemptContext(AttemptContextReactive attemptContext) { + public void setAttemptContext(ReactiveTransactionAttemptContext attemptContext) { this.attemptContext = attemptContext; } diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseStuffHandle.java b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseStuffHandle.java index aeccf9f4e..14f0d441a 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseStuffHandle.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseStuffHandle.java @@ -1,5 +1,8 @@ package org.springframework.data.couchbase.transaction; +import com.couchbase.client.java.transactions.ReactiveTransactionAttemptContext; +import com.couchbase.client.java.transactions.TransactionGetResult; +import com.couchbase.client.java.transactions.TransactionResult; import org.springframework.lang.Nullable; import org.springframework.transaction.support.TransactionSynchronizationManager; import reactor.core.publisher.Mono; @@ -20,10 +23,6 @@ import org.springframework.util.Assert; import com.couchbase.client.core.error.CouchbaseException; -import com.couchbase.transactions.AttemptContextReactive; -import com.couchbase.transactions.TransactionGetResult; -import com.couchbase.transactions.TransactionResult; -import com.couchbase.transactions.TransactionsReactive; public class CouchbaseStuffHandle { @@ -33,7 +32,7 @@ public class CouchbaseStuffHandle { private final TransactionDefinition transactionDefinition; Map getResultMap = new HashMap<>(); - private AttemptContextReactive attemptContextReactive; + private ReactiveTransactionAttemptContext attemptContextReactive; public CouchbaseStuffHandle() { transactionManager = null; @@ -61,13 +60,15 @@ public Mono reactive(FunctionPerTransactionConfig. */ public Mono reactive(Function> transactionLogic, - boolean commit) { - return ((ReactiveCouchbaseTransactionManager) transactionManager).getTransactions().reactive((ctx) -> { - setAttemptContextReactive(ctx); // for getTxOp().getCtx() in Reactive*OperationSupport - // for transactional(), transactionDefinition.setAtr(ctx) is called at the beginning of that method - // and is eventually added to the ClientSession in transactionManager.doBegin() via newResourceHolder() - return transactionLogic.apply(this); - }/*, commit*/); + boolean commit) { + // todo gp this needs access to a Cluster + return Mono.empty(); +// return ((ReactiveCouchbaseTransactionManager) transactionManager).getTransactions().reactive((ctx) -> { +// setAttemptContextReactive(ctx); // for getTxOp().getCtx() in Reactive*OperationSupport +// // for transactional(), transactionDefinition.setAtr(ctx) is called at the beginning of that method +// // and is eventually added to the ClientSession in transactionManager.doBegin() via newResourceHolder() +// return transactionLogic.apply(this); +// }/*, commit*/); } public TransactionResultHolder transactionResultHolder(Integer key) { @@ -80,7 +81,7 @@ public TransactionResultHolder transactionResultHolder(TransactionGetResult resu return holder; } - public void setAttemptContextReactive(AttemptContextReactive attemptContextReactive) { + public void setAttemptContextReactive(ReactiveTransactionAttemptContext attemptContextReactive) { this.attemptContextReactive = attemptContextReactive; // see ReactiveCouchbaseTransactionManager.doBegin() // transactionManager.getReactiveTransaction(new CouchbaseTransactionDefinition()).block(); @@ -94,7 +95,7 @@ public void setAttemptContextReactive(AttemptContextReactive attemptContextReact */ } - public AttemptContextReactive getAttemptContextReactive() { + public ReactiveTransactionAttemptContext getAttemptContextReactive() { return attemptContextReactive; } diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionDefinition.java b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionDefinition.java index 006df028f..e87aacfa4 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionDefinition.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionDefinition.java @@ -1,14 +1,14 @@ package org.springframework.data.couchbase.transaction; -import com.couchbase.transactions.AttemptContext; -import com.couchbase.transactions.AttemptContextReactive; +import com.couchbase.client.java.transactions.ReactiveTransactionAttemptContext; +import com.couchbase.client.java.transactions.TransactionAttemptContext; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.support.DefaultTransactionDefinition; public class CouchbaseTransactionDefinition extends DefaultTransactionDefinition { - AttemptContextReactive atr; - AttemptContext at; + ReactiveTransactionAttemptContext atr; + TransactionAttemptContext at; public CouchbaseTransactionDefinition(){ super(); @@ -22,15 +22,15 @@ public CouchbaseTransactionDefinition(int propagationBehavior) { super(propagationBehavior); } - public void setAttemptContextReactive(AttemptContextReactive atr){ + public void setAttemptContextReactive(ReactiveTransactionAttemptContext atr){ this.atr = atr; } - public AttemptContextReactive getAttemptContextReactive(){ + public ReactiveTransactionAttemptContext getAttemptContextReactive(){ return atr; } - public void setAttemptContext(AttemptContext attemptContext) { + public void setAttemptContext(TransactionAttemptContext attemptContext) { at = attemptContext; } } diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionManager.java b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionManager.java index 840903624..7314a53d9 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionManager.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionManager.java @@ -16,6 +16,8 @@ package org.springframework.data.couchbase.transaction; +import com.couchbase.client.java.transactions.ReactiveTransactionAttemptContext; +import com.couchbase.client.java.transactions.config.TransactionOptions; import org.springframework.beans.factory.InitializingBean; import org.springframework.data.couchbase.CouchbaseClientFactory; import org.springframework.lang.Nullable; @@ -32,9 +34,6 @@ import org.springframework.util.ClassUtils; import com.couchbase.client.core.error.CouchbaseException; -import com.couchbase.transactions.AttemptContextReactive; -import com.couchbase.transactions.Transactions; -import com.couchbase.transactions.config.TransactionConfig; import reactor.core.publisher.Mono; /** @@ -67,8 +66,6 @@ public class CouchbaseTransactionManager extends AbstractPlatformTransactionMana implements ResourceTransactionManager, InitializingBean { private @Nullable CouchbaseClientFactory databaseFactory; - private @Nullable Transactions transactions; // This is the com.couchbase.transactions object - private @Nullable TransactionConfig config; private @Nullable TransactionOptions options; /** @@ -86,28 +83,18 @@ public class CouchbaseTransactionManager extends AbstractPlatformTransactionMana */ public CouchbaseTransactionManager() {} - /** - * Create a new {@link CouchbaseTransactionManager} obtaining sessions from the given {@link CouchbaseClientFactory}. - * - * @param databaseFactory must not be {@literal null}. - */ - public CouchbaseTransactionManager(CouchbaseClientFactory databaseFactory) { - this(databaseFactory, null); - } - /** * Create a new {@link CouchbaseTransactionManager} obtaining sessions from the given {@link CouchbaseClientFactory} * applying the given {@link TransactionOptions options}, if present, when starting a new transaction. * * @param databaseFactory must not be {@literal null}. @//param options can be {@literal null}. */ - public CouchbaseTransactionManager(CouchbaseClientFactory databaseFactory, @Nullable Transactions transactions) { + public CouchbaseTransactionManager(CouchbaseClientFactory databaseFactory) { Assert.notNull(databaseFactory, "DbFactory must not be null!"); System.err.println(this); System.err.println(databaseFactory.getCluster()); this.databaseFactory = databaseFactory; - this.transactions = transactions; } /* @@ -351,12 +338,12 @@ public void afterPropertiesSet() { } private CouchbaseResourceHolder newResourceHolder(TransactionDefinition definition, ClientSessionOptions options, - AttemptContextReactive atr) { + ReactiveTransactionAttemptContext atr) { CouchbaseClientFactory databaseFactory = getResourceFactory(); CouchbaseResourceHolder resourceHolder = new CouchbaseResourceHolder( - databaseFactory.getSession(options, transactions, null, atr), databaseFactory); + databaseFactory.getSession(options, atr), databaseFactory); // TODO resourceHolder.setTimeoutIfNotDefaulted(determineTimeout(definition)); return resourceHolder; @@ -427,10 +414,6 @@ public CouchbaseClientFactory getDatabaseFactory() { return databaseFactory; } - public Transactions getTransactions() { - return transactions; - } - /** * MongoDB specific transaction object, representing a {@link CouchbaseResourceHolder}. Used as transaction object by * {@link CouchbaseTransactionManager}. diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionalOperatorNonReactive.save b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionalOperatorNonReactive.save index aaab01580..200463ede 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionalOperatorNonReactive.save +++ b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionalOperatorNonReactive.save @@ -15,7 +15,7 @@ */ package org.springframework.data.couchbase.transaction; -import com.couchbase.transactions.AttemptContext; +import com.couchbase.transactions.TransactionAttemptContext; import org.springframework.data.couchbase.core.CouchbaseOperations; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionManager; @@ -53,7 +53,7 @@ public class CouchbaseTransactionalOperatorNonReactive implements TransactionalO private final TransactionDefinition transactionDefinition; Map getResultMap = new HashMap<>(); - private AttemptContext attemptContext; + private TransactionAttemptContext attemptContext; public CouchbaseTransactionalOperatorNonReactive(CouchbaseTransactionManager transactionManager) { this(transactionManager, new CouchbaseTransactionDefinition()); @@ -95,11 +95,11 @@ public class CouchbaseTransactionalOperatorNonReactive implements TransactionalO return holder; } - public void setAttemptContext(AttemptContext attemptContext) { + public void setAttemptContext(TransactionAttemptContext attemptContext) { this.attemptContext = attemptContext; } - public AttemptContext getAttemptContext() { + public TransactionAttemptContext getAttemptContext() { return attemptContext; } diff --git a/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseTransactionManager.java b/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseTransactionManager.java index b390c8151..cb8705fd1 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseTransactionManager.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseTransactionManager.java @@ -33,10 +33,6 @@ import org.springframework.util.ClassUtils; import com.couchbase.client.core.error.CouchbaseException; -import com.couchbase.transactions.AttemptContextReactive; -import com.couchbase.transactions.TransactionQueryOptions; -import com.couchbase.transactions.Transactions; -import com.couchbase.transactions.config.TransactionConfig; /** * A {@link org.springframework.transaction.ReactiveTransactionManager} implementation that manages @@ -69,8 +65,6 @@ public class ReactiveCouchbaseTransactionManager extends AbstractReactiveTransac implements InitializingBean { private @Nullable ReactiveCouchbaseClientFactory databaseFactory; // (why) does this need to be reactive? - private @Nullable Transactions transactions; // This is the com.couchbase.transactions object - private @Nullable TransactionConfig config; /** * Create a new {@link ReactiveCouchbaseTransactionManager} for bean-style usage. @@ -92,14 +86,11 @@ public ReactiveCouchbaseTransactionManager() {} * starting a new transaction. * * @param databaseFactory must not be {@literal null}. - * @param transactions - couchbase Transactions object */ - public ReactiveCouchbaseTransactionManager(ReactiveCouchbaseClientFactory databaseFactory, - @Nullable Transactions transactions) { + public ReactiveCouchbaseTransactionManager(ReactiveCouchbaseClientFactory databaseFactory) { Assert.notNull(databaseFactory, "DatabaseFactory must not be null!"); this.databaseFactory = databaseFactory; // should be a clone? TransactionSynchronizationManager binds objs to it - this.transactions = transactions; - System.err.println("ReactiveCouchbaseTransactionManager : created Transactions: " + transactions); + System.err.println("ReactiveCouchbaseTransactionManager : created"); } /* @@ -111,10 +102,6 @@ public ReactiveCouchbaseTransactionManager(CouchbaseClientFactory databaseFactor System.err.println("ReactiveCouchbaseTransactionManager : created Transactions: " + transactions); } */ - public Transactions getTransactions() { - System.err.println("ReactiveCouchbaseTransactionManager.getTransactions() : " + transactions); - return transactions; - } /* * (non-Javadoc) @@ -168,7 +155,7 @@ protected Mono doBegin(TransactionSynchronizationManager synchronizationMa }).doOnNext(resourceHolder -> { - couchbaseTransactionObject.startTransaction(config); + couchbaseTransactionObject.startTransaction(); if (logger.isDebugEnabled()) { logger.debug(String.format("Started transaction for session %s.", debugString(resourceHolder.getSession()))); @@ -330,15 +317,6 @@ public void setDatabaseFactory(ReactiveCouchbaseClientFactory databaseFactory) { this.databaseFactory = databaseFactory; } - /** - * Set the {@link TransactionConfig} to be applied when starting transactions. - * - * @param config can be {@literal null}. - */ - public void setConfig(@Nullable TransactionConfig config) { - this.config = config; - } - /** * Get the {@link CouchbaseClientFactory} that this instance manages transactions for. * @@ -363,9 +341,7 @@ private Mono newResourceHolder(TransactionDefin ReactiveCouchbaseClientFactory dbFactory = getRequiredDatabaseFactory(); // TODO MSR : config should be derived from config that was used for `transactions` - getTransactions().reactive(); - TransactionConfig config = transactions.reactive().config(); - Mono sess = Mono.just(dbFactory.getSession(options, transactions, config , null/* TODO */)); + Mono sess = dbFactory.getSession(options); return sess.map(session -> new ReactiveCouchbaseResourceHolder(session, dbFactory)); } @@ -463,17 +439,13 @@ final boolean hasResourceHolder() { /** * Start a XxxxxxXX transaction optionally given {@link TransactionQueryOptions}. - * + * todo gp how to expose TransactionOptions * @param options can be {@literal null} */ - void startTransaction(@Nullable TransactionConfig options) { + void startTransaction() { ClientSession session = getRequiredSession(); - if (options != null) { - session.startTransaction(options); - } else { - session.startTransaction(); - } + session.startTransaction(); } /** diff --git a/src/main/java/org/springframework/data/couchbase/transaction/TransactionOptions.java b/src/main/java/org/springframework/data/couchbase/transaction/TransactionOptions.java deleted file mode 100644 index 86b23d9c0..000000000 --- a/src/main/java/org/springframework/data/couchbase/transaction/TransactionOptions.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2021 the original author or authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.couchbase.transaction; - -public class TransactionOptions { -} diff --git a/src/main/java/org/springframework/data/couchbase/transaction/TransactionsWrapper.java b/src/main/java/org/springframework/data/couchbase/transaction/TransactionsWrapper.java index e3db6bd1a..2f86a7563 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/TransactionsWrapper.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/TransactionsWrapper.java @@ -1,18 +1,9 @@ package org.springframework.data.couchbase.transaction; import com.couchbase.client.java.env.ClusterEnvironment; -import com.couchbase.transactions.AttemptContextReactive; -import com.couchbase.transactions.AttemptContextReactiveAccessor; -import com.couchbase.transactions.TransactionContext; -import com.couchbase.transactions.TransactionResult; -import com.couchbase.transactions.Transactions; -import com.couchbase.transactions.TransactionsReactive; -import com.couchbase.transactions.config.MergedTransactionConfig; -import com.couchbase.transactions.config.PerTransactionConfig; -import com.couchbase.transactions.config.PerTransactionConfigBuilder; -import com.couchbase.transactions.config.TransactionConfig; -import com.couchbase.transactions.config.TransactionConfigBuilder; -import com.couchbase.transactions.error.external.TransactionOperationFailed; +import com.couchbase.client.java.transactions.ReactiveTransactionAttemptContext; +import com.couchbase.client.java.transactions.TransactionResult; +import com.couchbase.client.java.transactions.config.TransactionOptions; import org.springframework.data.couchbase.CouchbaseClientFactory; import org.springframework.data.couchbase.ReactiveCouchbaseClientFactory; import org.springframework.transaction.ReactiveTransaction; @@ -28,94 +19,95 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; +// todo gp needed now Transactions has gone? public class TransactionsWrapper { - Transactions transactions; ReactiveCouchbaseClientFactory reactiveCouchbaseClientFactory; - public TransactionsWrapper(Transactions transactions, ReactiveCouchbaseClientFactory reactiveCouchbaseClientFactory){ - this.transactions = transactions; + public TransactionsWrapper(ReactiveCouchbaseClientFactory reactiveCouchbaseClientFactory){ this.reactiveCouchbaseClientFactory = reactiveCouchbaseClientFactory; } /** * A convenience wrapper around {@link TransactionsReactive#run}, that provides a default PerTransactionConfig. */ - public Mono reactive(Function> transactionLogic) { + public Mono reactive(Function> transactionLogic) { // TODO long duration for debugger Duration duration = Duration.ofMinutes(20); System.err.println("tx duration of "+duration); - return run(transactionLogic, PerTransactionConfigBuilder.create().expirationTime(duration).build()); + return run(transactionLogic, TransactionOptions.transactionOptions().timeout(duration)); } - public Mono run(Function> transactionLogic, - PerTransactionConfig perConfig) { - TransactionConfig config = TransactionConfigBuilder.create().build(); - - ClusterEnvironment env = ClusterEnvironment.builder().build(); - return Mono.defer(() -> { - MergedTransactionConfig merged = new MergedTransactionConfig(config, Optional.of(perConfig)); - - TransactionContext overall = - new TransactionContext(env.requestTracer(), - env.eventBus(), - UUID.randomUUID().toString(), - now(), - Duration.ZERO, - merged); - AtomicReference startTime = new AtomicReference<>(0L); - - Mono ob = Mono.fromCallable(() -> { - String txnId = UUID.randomUUID().toString(); - overall.LOGGER.info(configDebug(config, perConfig)); - return transactions.reactive().createAttemptContext(overall, merged, txnId); - }).flatMap(ctx -> { - - AttemptContextReactiveAccessor.getLogger(ctx).info("starting attempt %d/%s/%s", - overall.numAttempts(), ctx.transactionId(), ctx.attemptId()); - -/* begin spring-data-couchbase transaction 1/2 */ - ClientSession clientSession = reactiveCouchbaseClientFactory // couchbaseClientFactory - .getSession(ClientSessionOptions.builder().causallyConsistent(true).build(), transactions, null, ctx); - ReactiveCouchbaseResourceHolder resourceHolder = new ReactiveCouchbaseResourceHolder(clientSession, - reactiveCouchbaseClientFactory); - Mono sync = TransactionContextManager.currentContext() - .map(TransactionSynchronizationManager::new).flatMap(synchronizationManager -> { - synchronizationManager.bindResource(reactiveCouchbaseClientFactory.getCluster().block(), resourceHolder); - prepareSynchronization(synchronizationManager, null, new CouchbaseTransactionDefinition()); -/* end spring-data-couchbase transaction 1/2 */ - Mono result = transactionLogic.apply(ctx); - result - .onErrorResume(err -> { - AttemptContextReactiveAccessor.getLogger(ctx).info(ctx.attemptId(), "caught exception '%s' in async, rethrowing", err); - logElidedStacktrace(ctx, err); - - return Mono.error(TransactionOperationFailed.convertToOperationFailedIfNeeded(err, ctx)); - }) - .thenReturn(ctx); - return result.then(Mono.just(synchronizationManager)); - }); - /* begin spring-data-couchbase transaction 2/2 */ - return sync.contextWrite(TransactionContextManager.getOrCreateContext()) - .contextWrite(TransactionContextManager.getOrCreateContextHolder()).then(Mono.just(ctx)); -/* end spring-data-couchbase transaction 2/2 */ - }).doOnSubscribe(v -> startTime.set(System.nanoTime())) - .doOnNext(v -> AttemptContextReactiveAccessor.getLogger(v).trace(v.attemptId(), "finished attempt %d in %sms", - overall.numAttempts(), (System.nanoTime() - startTime.get()) / 1_000_000)); - - return transactions.reactive().executeTransaction(merged, overall, ob) - .doOnNext(v -> overall.span().finish()) - .doOnError(err -> overall.span().failWith(err)); - }); - } - - private void logElidedStacktrace(AttemptContextReactive ctx, Throwable err) { - transactions.reactive().logElidedStacktrace(ctx, err); - } - - private String configDebug(TransactionConfig config, PerTransactionConfig perConfig) { - return transactions.reactive().configDebug(config, perConfig); + public Mono run(Function> transactionLogic, + TransactionOptions perConfig) { + // todo gp this is duplicating a lot of logic from the core loop, and is hopefully not needed.. + return Mono.empty(); +// TransactionConfig config = TransactionConfigBuilder.create().build(); +// +// ClusterEnvironment env = ClusterEnvironment.builder().build(); +// return Mono.defer(() -> { +// MergedTransactionConfig merged = new MergedTransactionConfig(config, Optional.of(perConfig)); +// +// TransactionContext overall = +// new TransactionContext(env.requestTracer(), +// env.eventBus(), +// UUID.randomUUID().toString(), +// now(), +// Duration.ZERO, +// merged); +// AtomicReference startTime = new AtomicReference<>(0L); +// +// Mono ob = Mono.fromCallable(() -> { +// String txnId = UUID.randomUUID().toString(); +// overall.LOGGER.info(configDebug(config, perConfig)); +// return transactions.reactive().createAttemptContext(overall, merged, txnId); +// }).flatMap(ctx -> { +// +// AttemptContextReactiveAccessor.getLogger(ctx).info("starting attempt %d/%s/%s", +// overall.numAttempts(), ctx.transactionId(), ctx.attemptId()); +// +///* begin spring-data-couchbase transaction 1/2 */ +// ClientSession clientSession = reactiveCouchbaseClientFactory // couchbaseClientFactory +// .getSession(ClientSessionOptions.builder().causallyConsistent(true).build(), transactions, null, ctx); +// ReactiveCouchbaseResourceHolder resourceHolder = new ReactiveCouchbaseResourceHolder(clientSession, +// reactiveCouchbaseClientFactory); +// Mono sync = TransactionContextManager.currentContext() +// .map(TransactionSynchronizationManager::new).flatMap(synchronizationManager -> { +// synchronizationManager.bindResource(reactiveCouchbaseClientFactory.getCluster().block(), resourceHolder); +// prepareSynchronization(synchronizationManager, null, new CouchbaseTransactionDefinition()); +///* end spring-data-couchbase transaction 1/2 */ +// Mono result = transactionLogic.apply(ctx); +// result +// .onErrorResume(err -> { +// AttemptContextReactiveAccessor.getLogger(ctx).info(ctx.attemptId(), "caught exception '%s' in async, rethrowing", err); +// logElidedStacktrace(ctx, err); +// +// return Mono.error(TransactionOperationFailed.convertToOperationFailedIfNeeded(err, ctx)); +// }) +// .thenReturn(ctx); +// return result.then(Mono.just(synchronizationManager)); +// }); +// /* begin spring-data-couchbase transaction 2/2 */ +// return sync.contextWrite(TransactionContextManager.getOrCreateContext()) +// .contextWrite(TransactionContextManager.getOrCreateContextHolder()).then(Mono.just(ctx)); +///* end spring-data-couchbase transaction 2/2 */ +// }).doOnSubscribe(v -> startTime.set(System.nanoTime())) +// .doOnNext(v -> AttemptContextReactiveAccessor.getLogger(v).trace(v.attemptId(), "finished attempt %d in %sms", +// overall.numAttempts(), (System.nanoTime() - startTime.get()) / 1_000_000)); +// +// return transactions.reactive().executeTransaction(merged, overall, ob) +// .doOnNext(v -> overall.span().finish()) +// .doOnError(err -> overall.span().failWith(err)); +// }); } +// private void logElidedStacktrace(ReactiveTransactionAttemptContext ctx, Throwable err) { +// transactions.reactive().logElidedStacktrace(ctx, err); +// } +// +// private String configDebug(TransactionConfig config, PerTransactionConfig perConfig) { +// return transactions.reactive().configDebug(config, perConfig); +// } +// private static Duration now() { return Duration.of(System.nanoTime(), ChronoUnit.NANOS); } diff --git a/src/main/java/org/springframework/data/couchbase/transaction/internal/AsyncClientSession.java b/src/main/java/org/springframework/data/couchbase/transaction/internal/AsyncClientSession.java index a82cdf333..c32711b60 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/internal/AsyncClientSession.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/internal/AsyncClientSession.java @@ -6,8 +6,8 @@ package org.springframework.data.couchbase.transaction.internal; +import com.couchbase.client.java.transactions.config.TransactionOptions; import org.springframework.data.couchbase.transaction.ClientSession; -import org.springframework.data.couchbase.transaction.TransactionOptions; public interface AsyncClientSession extends ClientSession { boolean hasActiveTransaction(); diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionIntegrationTests.java index 7ee1c3862..2b2fbfb3f 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionIntegrationTests.java @@ -22,6 +22,9 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.couchbase.client.core.error.transaction.TransactionOperationFailedException; +import com.couchbase.client.java.transactions.TransactionResult; +import com.couchbase.client.java.transactions.error.TransactionFailedException; import lombok.Data; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -95,13 +98,6 @@ import com.couchbase.client.java.Collection; import com.couchbase.client.java.ReactiveCollection; import com.couchbase.client.java.kv.RemoveOptions; -import com.couchbase.transactions.TransactionDurabilityLevel; -import com.couchbase.transactions.TransactionResult; -import com.couchbase.transactions.Transactions; -import com.couchbase.transactions.config.TransactionConfig; -import com.couchbase.transactions.config.TransactionConfigBuilder; -import com.couchbase.transactions.error.TransactionFailed; -import com.couchbase.transactions.error.external.TransactionOperationFailed; /** * Tests for com.couchbase.transactions using @@ -123,7 +119,6 @@ public class CouchbasePersonTransactionIntegrationTests extends JavaIntegrationT @Autowired PersonRepository repo; @Autowired CouchbaseTemplate cbTmpl; @Autowired ReactiveCouchbaseTemplate rxCBTmpl; - @Autowired Transactions transactions; /* DO NOT @Autowired - it will result in no @Transactional annotation behavior */ PersonService personService; @Autowired CouchbaseTemplate operations; @@ -160,7 +155,7 @@ public void beforeEachTest() { } /* Not used in this class. The class itself is not @Transaction - + List>> assertionList; @BeforeTransaction @@ -276,7 +271,8 @@ public void emitMultipleElementsDuringTransaction() { @Test public void errorAfterTxShouldNotAffectPreviousStep() { Person p = personService.savePerson(new Person(null, "Walter", "White")); - assertThrows(TransactionOperationFailed.class, () -> personService.savePerson(p)); + // todo gp user shouldn't be getting exposed to TransactionOperationFailedException + assertThrows(TransactionOperationFailedException.class, () -> personService.savePerson(p)); Long count = operations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count(); assertEquals(1, count, "should have saved and found 1"); } @@ -289,9 +285,9 @@ public void errorAfterTxShouldNotAffectPreviousStep() { public void replacePersonCBTransactionsRxTmpl() { Person person = new Person(1, "Walter", "White"); cbTmpl.insertById(Person.class).one(person); - Mono result = transactions.reactive(ctx -> { // get the ctx + Mono result = this.couchbaseClientFactory.getCluster().reactive().transactions().run(ctx -> { // get the ctx ClientSession clientSession = couchbaseClientFactory - .getSession(ClientSessionOptions.builder().causallyConsistent(true).build(), transactions, null, ctx); + .getSession(ClientSessionOptions.builder().causallyConsistent(true).build(), ctx); ReactiveCouchbaseResourceHolder resourceHolder = new ReactiveCouchbaseResourceHolder(clientSession, reactiveCouchbaseClientFactory); Mono sync = TransactionContextManager.currentContext() @@ -299,11 +295,6 @@ public void replacePersonCBTransactionsRxTmpl() { synchronizationManager.bindResource(reactiveCouchbaseClientFactory.getCluster().block(), resourceHolder); prepareSynchronization(synchronizationManager, null, new CouchbaseTransactionDefinition()); return rxCBTmpl.findById(Person.class).one(person.getId().toString()) // - .flatMap((pp) -> { - System.err.println("==================================== ATTEMPT : " + ctx.attemptId() - + " ======================================"); - return Mono.just(pp); - }) // .flatMap((pp) -> rxCBTmpl.replaceById(Person.class).one(pp)) // .then(Mono.just(synchronizationManager)); // tx }); @@ -321,11 +312,11 @@ public void insertPersonCBTransactionsRxTmplRollback() { Person person = new Person(1, "Walter", "White"); try { rxCBTmpl.removeById(Person.class).one(person.getId().toString()); - } catch (DocumentNotFoundException dnfe) {} - Mono result = transactions.reactive(ctx -> { // get the ctx + } catch(DocumentNotFoundException dnfe){} + Mono result = couchbaseClientFactory.getCluster().reactive().transactions().run(ctx -> { // get the ctx ClientSession clientSession = couchbaseClientFactory - .getSession(ClientSessionOptions.builder().causallyConsistent(true).build(), transactions, null, ctx); + .getSession(ClientSessionOptions.builder().causallyConsistent(true).build(), ctx); ReactiveCouchbaseResourceHolder resourceHolder = new ReactiveCouchbaseResourceHolder(clientSession, reactiveCouchbaseClientFactory); Mono sync = TransactionContextManager.currentContext() @@ -339,7 +330,7 @@ public void insertPersonCBTransactionsRxTmplRollback() { .contextWrite(TransactionContextManager.getOrCreateContextHolder()).then(); }); - assertThrowsCause(TransactionFailed.class, SimulateFailureException.class, (ignore) -> { + assertThrowsCause(TransactionFailedException.class, SimulateFailureException.class, (ignore) -> { result.block(); return null; }); @@ -351,9 +342,9 @@ public void insertPersonCBTransactionsRxTmplRollback() { public void insertTwicePersonCBTransactionsRxTmplRollback() { Person person = new Person(1, "Walter", "White"); sleepMs(1000); - Mono result = transactions.reactive(ctx -> { // get the ctx + Mono result = couchbaseClientFactory.getCluster().reactive().transactions().run(ctx -> { // get the ctx ClientSession clientSession = couchbaseClientFactory - .getSession(ClientSessionOptions.builder().causallyConsistent(true).build(), transactions, null, ctx); + .getSession(ClientSessionOptions.builder().causallyConsistent(true).build(), ctx); ReactiveCouchbaseResourceHolder resourceHolder = new ReactiveCouchbaseResourceHolder(clientSession, reactiveCouchbaseClientFactory); Mono sync = TransactionContextManager.currentContext() @@ -367,7 +358,7 @@ public void insertTwicePersonCBTransactionsRxTmplRollback() { return sync.contextWrite(TransactionContextManager.getOrCreateContext()) .contextWrite(TransactionContextManager.getOrCreateContextHolder()).then(); }); - assertThrowsCause(TransactionFailed.class, DuplicateKeyException.class, (ignore) -> { + assertThrowsCause(TransactionFailedException.class, DuplicateKeyException.class, (ignore) -> { result.block(); return null; }); @@ -385,10 +376,10 @@ public void replaceWithCasConflictResolvedViaRetry() { cbTmpl.insertById(Person.class).one(person); AtomicInteger tryCount = new AtomicInteger(0); - Mono result = transactions.reactive(ctx -> { // get the ctx + Mono result = couchbaseClientFactory.getCluster().reactive().transactions().run(ctx -> { // get the ctx // see TransactionalOperatorImpl.transactional(). ClientSession clientSession = couchbaseClientFactory - .getSession(ClientSessionOptions.builder().causallyConsistent(true).build(), transactions, null, ctx); + .getSession(ClientSessionOptions.builder().causallyConsistent(true).build(), ctx); ReactiveCouchbaseResourceHolder resourceHolder = new ReactiveCouchbaseResourceHolder(clientSession, reactiveCouchbaseClientFactory); Mono sync = TransactionContextManager.currentContext() @@ -398,7 +389,7 @@ public void replaceWithCasConflictResolvedViaRetry() { return rxCBTmpl.findById(Person.class).one(person.getId().toString()) // .flatMap((ppp) -> { tryCount.getAndIncrement(); - System.err.println("===== ATTEMPT : " + tryCount.get() + " " + ctx.attemptId() + " ====="); + System.err.println("===== ATTEMPT : " + tryCount.get() + " ====="); return Mono.just(ppp); })// .flatMap((ppp) -> rxCBTmpl.replaceById(Person.class).one(ppp)) // @@ -429,7 +420,7 @@ public void wrapperReplaceWithCasConflictResolvedViaRetry() { t.start(); cbTmpl.insertById(Person.class).one(person); tryCount.set(0); - TransactionsWrapper transactionsWrapper = new TransactionsWrapper(transactions, reactiveCouchbaseClientFactory); + TransactionsWrapper transactionsWrapper = new TransactionsWrapper(reactiveCouchbaseClientFactory); Mono result = transactionsWrapper.reactive(ctx -> { System.err.println("try: " + tryCount.incrementAndGet()); return rxCBTmpl.findById(Person.class).one(person.getId().toString()) // @@ -546,15 +537,15 @@ public void replacePersonCBTransactionsRxTmplRollback() { rxCBTmpl.insertById(Person.class).one(person).block(); sleepMs(1000); Mono result = transactions.reactive(((ctx) -> { // get the ctx - // can we take the AttemptContextReactive ctx and save it in the context? + // can we take the ReactiveTransactionAttemptContext ctx and save it in the context? ClientSession clientSession = couchbaseClientFactory - .getSession(ClientSessionOptions.builder().causallyConsistent(true).build(), transactions, null, ctx); + .getSession(ClientSessionOptions.builder().causallyConsistent(true).build(), ctx); CouchbaseResourceHolder resourceHolder = new CouchbaseResourceHolder(clientSession, couchbaseClientFactory); - // I think this needs to happen within the transactions.reactive() call - or equivalent. + // I think this needs to happen within the couchbaseClientFactory.getCluster().reactive().transactions().run() call - or equivalent. // this currentContext() call is going to create a new ctx, and store the acr. Will it get uses in syncFlatMap() - // below? Should the ctx be created in the above call to transactions.reactive()? + // below? Should the ctx be created in the above call to couchbaseClientFactory.getCluster().reactive().transactions().run()? // How does this work in savePerson etc? // is there means for just getting the currentContext() without creating it? Mono sync = TransactionContextManager.currentContext().map(TransactionSynchronizationManager::new) @@ -575,7 +566,7 @@ public void replacePersonCBTransactionsRxTmplRollback() { result.block(); - // assertThrows(TransactionFailed.class, () -> result.block()); + // assertThrows(TransactionFailedException.class, () -> result.block()); Person pFound = rxCBTmpl.findById(Person.class).one(person.getId().toString()).block(); System.err.println(pFound); assertEquals(person.getFirstname(), pFound.getFirstname()); @@ -624,7 +615,7 @@ public void deletePersonCBTransactionsRxTmplFail() { .then(rxCBTmpl.removeById(Person.class).inCollection(cName).transaction(ctx).one(person.getId().toString())) .then(); })); - assertThrows(TransactionFailed.class, result::block); + assertThrows(TransactionFailedException.class, result::block); Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); assertEquals(pFound, person, "Should have found " + person); } @@ -655,7 +646,7 @@ public void deletePersonCBTransactionsRxRepoFail() { return rxRepo.withCollection(cName).withTransaction(ctx).deleteById(person.getId().toString()) .then(rxRepo.withCollection(cName).withTransaction(ctx).deleteById(person.getId().toString())).then(); })); - assertThrows(TransactionFailed.class, result::block); + assertThrows(TransactionFailedException.class, result::block); Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); assertEquals(pFound, person, "Should have found " + person); } @@ -694,7 +685,7 @@ public void insertPersonRbCBTransactions() { try { result.block(); - } catch (TransactionFailed e) { + } catch (TransactionFailedException e) { e.printStackTrace(); if (e.getCause() instanceof PoofException) { Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); @@ -704,7 +695,7 @@ public void insertPersonRbCBTransactions() { e.printStackTrace(); } } - throw new RuntimeException("Should have been a TransactionFailed exception with a cause of PoofException"); + throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); } @Test @@ -723,7 +714,7 @@ public void replacePersonRbCBTransactions() { try { result.block(); - } catch (TransactionFailed e) { + } catch (TransactionFailedException e) { if (e.getCause() instanceof PoofException) { Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); assertEquals(person, pFound, "Should have found " + person); @@ -732,7 +723,7 @@ public void replacePersonRbCBTransactions() { e.printStackTrace(); } } - throw new RuntimeException("Should have been a TransactionFailed exception with a cause of PoofException"); + throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); } @Test @@ -835,12 +826,6 @@ public String getBucketName() { return bucketName(); } - @Override - public TransactionConfig transactionConfig() { - // expirationTime 20 minutes for stepping with the debugger - return TransactionConfigBuilder.create().logDirectly(Event.Severity.INFO).logOnFailure(true, Event.Severity.ERROR) - .expirationTime(Duration.ofMinutes(20)).durabilityLevel(TransactionDurabilityLevel.MAJORITY).build(); - } /* beforeAll creates a PersonService bean in the applicationContext diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionReactiveIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionReactiveIntegrationTests.java index 9da5bbb73..bea9b357c 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionReactiveIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionReactiveIntegrationTests.java @@ -69,9 +69,6 @@ import com.couchbase.client.java.Collection; import com.couchbase.client.java.ReactiveCollection; import com.couchbase.client.java.kv.RemoveOptions; -import com.couchbase.transactions.TransactionDurabilityLevel; -import com.couchbase.transactions.config.TransactionConfig; -import com.couchbase.transactions.config.TransactionConfigBuilder; import static com.couchbase.client.java.query.QueryScanConsistency.REQUEST_PLUS; import static org.assertj.core.api.Assertions.assertThat; @@ -394,7 +391,7 @@ public void deletePersonCBTransactionsRxTmplFail() { .then(rxCBTmpl.removeById(Person.class).inCollection(cName).transaction(ctx).one(person.getId().toString())) .then(); })); - assertThrows(TransactionFailed.class, result::block); + assertThrows(TransactionFailedException.class, result::block); Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); assertEquals(pFound, person, "Should have found " + person); } @@ -425,7 +422,7 @@ public void deletePersonCBTransactionsRxRepoFail() { return rxRepo.withCollection(cName).withTransaction(ctx).deleteById(person.getId().toString()) .then(rxRepo.withCollection(cName).withTransaction(ctx).deleteById(person.getId().toString())).then(); })); - assertThrows(TransactionFailed.class, result::block); + assertThrows(TransactionFailedException.class, result::block); Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); assertEquals(pFound, person, "Should have found " + person); } @@ -464,7 +461,7 @@ public void insertPersonRbCBTransactions() { try { result.block(); - } catch (TransactionFailed e) { + } catch (TransactionFailedException e) { e.printStackTrace(); if (e.getCause() instanceof PoofException) { Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); @@ -474,7 +471,7 @@ public void insertPersonRbCBTransactions() { e.printStackTrace(); } } - throw new RuntimeException("Should have been a TransactionFailed exception with a cause of PoofException"); + throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); } @Test @@ -493,7 +490,7 @@ public void replacePersonRbCBTransactions() { try { result.block(); - } catch (TransactionFailed e) { + } catch (TransactionFailedException e) { if (e.getCause() instanceof PoofException) { Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); assertEquals(person, pFound, "Should have found " + person); @@ -502,7 +499,7 @@ public void replacePersonRbCBTransactions() { e.printStackTrace(); } } - throw new RuntimeException("Should have been a TransactionFailed exception with a cause of PoofException"); + throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); } @Test @@ -575,12 +572,6 @@ public String getBucketName() { return bucketName(); } - @Override - public TransactionConfig transactionConfig() { - return TransactionConfigBuilder.create().logDirectly(Event.Severity.INFO).logOnFailure(true, Event.Severity.ERROR) - .expirationTime(Duration.ofMinutes(10)).durabilityLevel(TransactionDurabilityLevel.MAJORITY).build(); - } - @Bean public Cluster couchbaseCluster() { return Cluster.connect("10.144.220.101", "Administrator", "password"); diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseReactiveTransactionNativeTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseReactiveTransactionNativeTests.java index 7ca0847d1..3c6ddfd36 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseReactiveTransactionNativeTests.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseReactiveTransactionNativeTests.java @@ -19,6 +19,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import com.couchbase.client.java.transactions.TransactionResult; +import com.couchbase.client.java.transactions.error.TransactionFailedException; import org.junit.jupiter.api.Disabled; import reactor.core.publisher.Mono; @@ -55,11 +57,6 @@ import com.couchbase.client.java.Collection; import com.couchbase.client.java.ReactiveCollection; import com.couchbase.client.java.kv.RemoveOptions; -import com.couchbase.transactions.TransactionDurabilityLevel; -import com.couchbase.transactions.TransactionResult; -import com.couchbase.transactions.config.TransactionConfig; -import com.couchbase.transactions.config.TransactionConfigBuilder; -import com.couchbase.transactions.error.TransactionFailed; /** * Tests for com.couchbase.transactions without using the spring data transactions framework @@ -115,7 +112,7 @@ public void replacePersonTemplate() { try { result.block(); - } catch (TransactionFailed e) { + } catch (TransactionFailedException e) { if (e.getCause() instanceof SimulateFailureException) { Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); assertEquals(person, pFound, "Should have found " + person); @@ -125,7 +122,7 @@ public void replacePersonTemplate() { } Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt"); - // throw new RuntimeException("Should have been a TransactionFailed exception with a cause of PoofException"); + // throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); } @Test @@ -142,7 +139,7 @@ public void replacePersonRbTemplate() { try { result.block(); - } catch (TransactionFailed e) { + } catch (TransactionFailedException e) { if (e.getCause() instanceof SimulateFailureException) { Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); assertEquals(person, pFound, "Should have found " + person); @@ -152,7 +149,7 @@ public void replacePersonRbTemplate() { } // Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); // assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt"); - throw new RuntimeException("Should have been a TransactionFailed exception with a cause of PoofException"); + throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); } @Test @@ -168,7 +165,7 @@ public void insertPersonTemplate() { try { result.block(); - } catch (TransactionFailed e) { + } catch (TransactionFailedException e) { if (e.getCause() instanceof SimulateFailureException) { Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); assertNull(pFound, "Should NOT have found " + pFound); @@ -178,7 +175,7 @@ public void insertPersonTemplate() { } Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt"); - // throw new RuntimeException("Should have been a TransactionFailed exception with a cause of PoofException"); + // throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); } @Test @@ -193,7 +190,7 @@ public void insertPersonRbTemplate() { try { result.block(); - } catch (TransactionFailed e) { + } catch (TransactionFailedException e) { if (e.getCause() instanceof SimulateFailureException) { Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); assertNull(pFound, "Should NOT have found " + pFound); @@ -203,7 +200,7 @@ public void insertPersonRbTemplate() { } // Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); // assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt"); - throw new RuntimeException("Should have been a TransactionFailed exception with a cause of PoofException"); + throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); } @Test @@ -220,7 +217,7 @@ public void replacePersonRbRepo() { try { result.block(); - } catch (TransactionFailed e) { + } catch (TransactionFailedException e) { if (e.getCause() instanceof SimulateFailureException) { Person pFound = rxRepo.withCollection(cName).findById(person.getId().toString()).block(); assertEquals(person, pFound, "Should have found " + person); @@ -230,7 +227,7 @@ public void replacePersonRbRepo() { } // Person pFound = rxRepo.withCollection(cName).findById(person.getId().toString()).block(); // assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt"); - throw new RuntimeException("Should have been a TransactionFailed exception with a cause of PoofException"); + throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); } @Test @@ -246,7 +243,7 @@ public void insertPersonRbRepo() { try { result.block(); - } catch (TransactionFailed e) { + } catch (TransactionFailedException e) { if (e.getCause() instanceof SimulateFailureException) { Person pFound = rxRepo.withCollection(cName).findById(person.getId().toString()).block(); assertNull(pFound, "Should NOT have found " + pFound); @@ -256,7 +253,7 @@ public void insertPersonRbRepo() { } // Person pFound = rxRepo.withCollection(cName).findById(person.getId().toString()).block(); // assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt"); - throw new RuntimeException("Should have been a TransactionFailed exception with a cause of PoofException"); + throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); } @Test @@ -273,7 +270,7 @@ public void insertPersonRepo() { try { result.block(); - } catch (TransactionFailed e) { + } catch (TransactionFailedException e) { if (e.getCause() instanceof SimulateFailureException) { Person pFound = rxRepo.withCollection(cName).findById(person.getId().toString()).block(); assertNull(pFound, "Should NOT have found " + pFound); @@ -283,7 +280,7 @@ public void insertPersonRepo() { } Person pFound = rxRepo.withCollection(cName).findById(person.getId().toString()).block(); assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt"); - // throw new RuntimeException("Should have been a TransactionFailed exception with a cause of PoofException"); + // throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); } /* @@ -312,7 +309,7 @@ public void replacePersonRbSpringTransactional() { try { result.block(); - } catch (TransactionFailed e) { + } catch (TransactionFailedException e) { if (e.getCause() instanceof PoofException) { Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); assertEquals(person, pFound, "Should have found " + person); @@ -322,7 +319,7 @@ public void replacePersonRbSpringTransactional() { } Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt"); - // throw new RuntimeException("Should have been a TransactionFailed exception with a cause of PoofException"); + // throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); } */ @Test @@ -427,13 +424,6 @@ public String getPassword() { public String getBucketName() { return bucketName(); } - - @Override - public TransactionConfig transactionConfig() { - return TransactionConfigBuilder.create().logDirectly(Event.Severity.INFO).logOnFailure(true, Event.Severity.ERROR) - .expirationTime(Duration.ofMinutes(10)).durabilityLevel(TransactionDurabilityLevel.MAJORITY).build(); - } - } } diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTemplateTransaction2IntegrationTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTemplateTransaction2IntegrationTests.java index 547959cec..7a6fcf809 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTemplateTransaction2IntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTemplateTransaction2IntegrationTests.java @@ -53,9 +53,6 @@ import org.springframework.transaction.annotation.Transactional; import com.couchbase.client.core.cnc.Event; -import com.couchbase.transactions.TransactionDurabilityLevel; -import com.couchbase.transactions.config.TransactionConfig; -import com.couchbase.transactions.config.TransactionConfigBuilder; import com.example.demo.CouchbaseTransactionManager; import com.example.demo.CouchbaseTransactionalTemplate; @@ -98,16 +95,9 @@ public String getBucketName() { return bucketName(); } - @Override - public TransactionConfig transactionConfig() { - return TransactionConfigBuilder.create().logDirectly(Event.Severity.INFO).logOnFailure(true, Event.Severity.ERROR) - .expirationTime(Duration.ofMinutes(10)).durabilityLevel(TransactionDurabilityLevel.NONE).build(); - } - @Bean - public CouchbaseTransactionManager transactionManager(@Autowired CouchbaseTemplate template, - @Autowired TransactionConfig transactionConfig) { - return new CouchbaseTransactionManager(template, transactionConfig); + public CouchbaseTransactionManager transactionManager(@Autowired CouchbaseTemplate template) { + return new CouchbaseTransactionManager(template); } @Bean diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTemplateTransactionIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTemplateTransactionIntegrationTests.java index 32786ee67..ffd19e851 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTemplateTransactionIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTemplateTransactionIntegrationTests.java @@ -55,9 +55,6 @@ import org.springframework.transaction.annotation.Transactional; import com.couchbase.client.core.cnc.Event; -import com.couchbase.transactions.TransactionDurabilityLevel; -import com.couchbase.transactions.config.TransactionConfig; -import com.couchbase.transactions.config.TransactionConfigBuilder; /** * @author Christoph Strobl @@ -96,13 +93,6 @@ public String getPassword() { public String getBucketName() { return bucketName(); } - - @Override - public TransactionConfig transactionConfig() { - return TransactionConfigBuilder.create().logDirectly(Event.Severity.INFO).logOnFailure(true, Event.Severity.ERROR) - .expirationTime(Duration.ofMinutes(10)).durabilityLevel(TransactionDurabilityLevel.NONE).build(); - } - } @Autowired CouchbaseTemplate template; diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionNativeTests.save b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionNativeTests.save index b6f263b54..894cf8594 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionNativeTests.save +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionNativeTests.save @@ -56,7 +56,7 @@ import com.couchbase.transactions.TransactionDurabilityLevel; import com.couchbase.transactions.TransactionResult; import com.couchbase.transactions.config.TransactionConfig; import com.couchbase.transactions.config.TransactionConfigBuilder; -import com.couchbase.transactions.error.TransactionFailed; +import com.couchbase.transactions.error.TransactionFailedException; /** * Tests for com.couchbase.transactions without using the spring data transactions framework @@ -112,7 +112,7 @@ public class CouchbaseTransactionNativeTests extends JavaIntegrationTests { return null; }); - } catch (TransactionFailed e) { + } catch (TransactionFailedException e) { if (e.getCause() instanceof PoofException) { Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); assertEquals(person, pFound, "Should have found " + person); @@ -122,7 +122,7 @@ public class CouchbaseTransactionNativeTests extends JavaIntegrationTests { } Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt"); - // throw new RuntimeException("Should have been a TransactionFailed exception with a cause of PoofException"); + // throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); } @Test @@ -138,7 +138,7 @@ public class CouchbaseTransactionNativeTests extends JavaIntegrationTests { .flatMap(p -> ctx.template(cbTmpl).replaceById(Person.class).one(p.withFirstName("Walt"))) .flatMap(it -> Mono.error(new PoofException())).then()); - } catch (TransactionFailed e) { + } catch (TransactionFailedException e) { if (e.getCause() instanceof PoofException) { Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); assertEquals(person, pFound, "Should have found " + person); @@ -148,7 +148,7 @@ public class CouchbaseTransactionNativeTests extends JavaIntegrationTests { } // Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); // assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt"); - throw new RuntimeException("Should have been a TransactionFailed exception with a cause of PoofException"); + throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); } @Test @@ -164,7 +164,7 @@ public class CouchbaseTransactionNativeTests extends JavaIntegrationTests { // .flatMap(it -> Mono.error(new PoofException())) .then()); - } catch (TransactionFailed e) { + } catch (TransactionFailedException e) { if (e.getCause() instanceof PoofException) { Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); assertNull(pFound, "Should NOT have found " + pFound); @@ -174,7 +174,7 @@ public class CouchbaseTransactionNativeTests extends JavaIntegrationTests { } Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt"); - // throw new RuntimeException("Should have been a TransactionFailed exception with a cause of PoofException"); + // throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); } @Test @@ -189,7 +189,7 @@ public class CouchbaseTransactionNativeTests extends JavaIntegrationTests { .one(person).flatMap(p -> ctx.template(cbTmpl).replaceById(Person.class).one(p.withFirstName("Walt"))) .flatMap(it -> Mono.error(new PoofException())).then()); - } catch (TransactionFailed e) { + } catch (TransactionFailedException e) { if (e.getCause() instanceof PoofException) { Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); assertNull(pFound, "Should NOT have found " + pFound); @@ -199,7 +199,7 @@ public class CouchbaseTransactionNativeTests extends JavaIntegrationTests { } // Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); // assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt"); - throw new RuntimeException("Should have been a TransactionFailed exception with a cause of PoofException"); + throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); } @Test @@ -215,7 +215,7 @@ public class CouchbaseTransactionNativeTests extends JavaIntegrationTests { .flatMap(p -> ctx.repository(repo).withCollection(cName).save(p.withFirstName("Walt"))) .flatMap(it -> Mono.error(new PoofException())).then()); - } catch (TransactionFailed e) { + } catch (TransactionFailedException e) { if (e.getCause() instanceof PoofException) { Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); assertEquals(person, pFound, "Should have found " + person); @@ -225,7 +225,7 @@ public class CouchbaseTransactionNativeTests extends JavaIntegrationTests { } // Person pFound = repo.withCollection(cName).findById(person.getId().toString()); // assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt"); - throw new RuntimeException("Should have been a TransactionFailed exception with a cause of PoofException"); + throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); } @Test @@ -239,7 +239,7 @@ try { .flatMap(p -> ctx.repository(repo).withCollection(cName).save(p.withFirstName("Walt"))) // replace .flatMap(it -> Mono.error(new PoofException())).then()); - } catch (TransactionFailed e) { + } catch (TransactionFailedException e) { if (e.getCause() instanceof PoofException) { Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); assertNull(pFound, "Should NOT have found " + pFound); @@ -249,7 +249,7 @@ try { } // Person pFound = repo.withCollection(cName).findById(person.getId().toString()); // assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt"); - throw new RuntimeException("Should have been a TransactionFailed exception with a cause of PoofException"); + throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); } @Test @@ -265,7 +265,7 @@ try { .then()); - } catch (TransactionFailed e) { + } catch (TransactionFailedException e) { if (e.getCause() instanceof PoofException) { Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); assertNull(pFound, "Should NOT have found " + pFound); @@ -275,7 +275,7 @@ try { } Person pFound = repo.withCollection(cName).findById(person.getId().toString()); assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt"); - // throw new RuntimeException("Should have been a TransactionFailed exception with a cause of PoofException"); + // throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); } @Test @@ -293,7 +293,7 @@ try { .as(txOperator::transactional).then(); }, false); - } catch (TransactionFailed e) { + } catch (TransactionFailedException e) { if (e.getCause() instanceof PoofException) { Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); assertEquals(person, pFound, "Should have found " + person); @@ -303,7 +303,7 @@ try { } Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt"); - // throw new RuntimeException("Should have been a TransactionFailed exception with a cause of PoofException"); + // throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); } void remove(Collection col, String id) { From a778c6d594b6de182af3a56654287ef54db598ec Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Thu, 28 Apr 2022 13:58:16 +0100 Subject: [PATCH 2/4] Continuing work to get the ExtSDKIntegration port working Trying to transition to CallbackPreferring manager. --- .../config/AbstractCouchbaseConfiguration.java | 17 +++++++++++------ .../ReactiveInsertByIdOperationSupport.java | 1 + .../support/TransactionResultHolder.java | 2 +- .../couchbase/transaction/ClientSession.java | 1 + .../CouchbaseCallbackTransactionManager.java | 6 ++++++ .../transaction/CouchbaseResourceHolder.java | 2 +- .../CouchbaseTransactionDefinition.java | 1 + .../CouchbaseTransactionManager.java | 1 + .../ReactiveCouchbaseResourceHolder.java | 1 + ...chbasePersonTransactionIntegrationTests.java | 8 ++++---- ...rsonTransactionReactiveIntegrationTests.java | 9 +++++---- 11 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java index 67d13c01d..598de221d 100644 --- a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java +++ b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java @@ -342,9 +342,14 @@ ReactiveCouchbaseTransactionManager reactiveTransactionManager( return new ReactiveCouchbaseTransactionManager(reactiveCouchbaseClientFactory); } +// @Bean(BeanNames.COUCHBASE_TRANSACTION_MANAGER) +// CouchbaseTransactionManager transactionManager(CouchbaseClientFactory couchbaseClientFactory) { +// return new CouchbaseTransactionManager(couchbaseClientFactory); +// } + @Bean(BeanNames.COUCHBASE_TRANSACTION_MANAGER) - CouchbaseTransactionManager transactionManager(CouchbaseClientFactory couchbaseClientFactory) { - return new CouchbaseTransactionManager(couchbaseClientFactory); + CouchbaseCallbackTransactionManager transactionManager(CouchbaseTemplate couchbaseTemplate, ReactiveCouchbaseTemplate couchbaseReactiveTemplate) { + return new CouchbaseCallbackTransactionManager(couchbaseTemplate, couchbaseReactiveTemplate); } /** @@ -353,10 +358,10 @@ CouchbaseTransactionManager transactionManager(CouchbaseClientFactory couchbaseC * @param couchbaseTemplate * @return */ - @Bean(BeanNames.COUCHBASE_CALLBACK_TRANSACTION_MANAGER) - CouchbaseCallbackTransactionManager callbackTransactionManager(CouchbaseTemplate couchbaseTemplate, ReactiveCouchbaseTemplate couchbaseReactiveTemplate) { - return new CouchbaseCallbackTransactionManager(couchbaseTemplate, couchbaseReactiveTemplate); - } +// @Bean(BeanNames.COUCHBASE_CALLBACK_TRANSACTION_MANAGER) +// CouchbaseCallbackTransactionManager callbackTransactionManager(CouchbaseTemplate couchbaseTemplate, ReactiveCouchbaseTemplate couchbaseReactiveTemplate) { +// return new CouchbaseCallbackTransactionManager(couchbaseTemplate, couchbaseReactiveTemplate); +// } /** * Configure whether to automatically create indices for domain types by deriving the from the entity or not. diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java index 88fdc4c16..0f1834451 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java @@ -208,6 +208,7 @@ public InsertByIdInScope withDurability(final DurabilityLevel durabilityLevel durabilityLevel, expiry, txCtx, support); } + // todo gp need to figure out how to handle options re transactions. E.g. many non-transactional insert options, like this, aren't supported @Override public InsertByIdInScope withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) { Assert.notNull(persistTo, "PersistTo must not be null."); diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/TransactionResultHolder.java b/src/main/java/org/springframework/data/couchbase/repository/support/TransactionResultHolder.java index 31676d11b..55cc2dc3f 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/TransactionResultHolder.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/TransactionResultHolder.java @@ -33,7 +33,7 @@ public class TransactionResultHolder { public TransactionResultHolder(TransactionGetResult getResult) { // we don't need the content and we don't have access to the transcoder an txnMeta (and we don't need them either). - // todo gp will need to expose a copy ctor if a copy is really needed + // todo gp will need to expose a copy ctor if a copy is needed this.getResult = getResult; // this.getResult = new TransactionGetResult(getResult.id(), null, getResult.cas(), getResult.collection(), // getResult.links(), getResult.status(), getResult.documentMetadata(), null, null); diff --git a/src/main/java/org/springframework/data/couchbase/transaction/ClientSession.java b/src/main/java/org/springframework/data/couchbase/transaction/ClientSession.java index 4905ee247..ca968753a 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/ClientSession.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/ClientSession.java @@ -17,6 +17,7 @@ * * @author Michael Reiche */ +// todo gp understand why this is needed public interface ClientSession /*extends com.mongodb.session.ClientSession*/ { Mono getScope(); diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseCallbackTransactionManager.java b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseCallbackTransactionManager.java index 591db5ae1..904c2ec47 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseCallbackTransactionManager.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseCallbackTransactionManager.java @@ -105,6 +105,7 @@ public T execute(TransactionDefinition definition, TransactionCallback ca . flatMap(synchronizationManager -> { System.err.println("CallbackTransactionManager: " + this); System.err.println("bindResource: " + reactiveCouchbaseClientFactory.getCluster().block()); + // todo gp not sure why we bind, unbind, bind again? synchronizationManager.bindResource(reactiveCouchbaseClientFactory.getCluster().block(), reactiveResourceHolder); org.springframework.transaction.support.TransactionSynchronizationManager @@ -115,9 +116,13 @@ public T execute(TransactionDefinition definition, TransactionCallback ca reactiveResourceHolder); setTransaction(transaction); + synchronizationManager.bindResource(ReactiveTransactionAttemptContext.class, ctx); + /* end spring-data-couchbase transaction 1/2 */ + // todo gp do we need TransactionSynchronizationManager.forCurrentTransaction()? as we already have synchronizationManager Mono result = TransactionSynchronizationManager.forCurrentTransaction().flatMap((sm) -> { + // todo gp not sure why re-binding again? sm.unbindResourceIfPossible(reactiveCouchbaseClientFactory.getCluster().block()); sm.bindResource(reactiveCouchbaseClientFactory.getCluster().block(), reactiveResourceHolder); @@ -129,6 +134,7 @@ public T execute(TransactionDefinition definition, TransactionCallback ca // Since we are on a different thread now transparently, at least make sure // that the original method invocation is synchronized. synchronized (this) { + // todo gp this will execute the lambda, and so we likely don't want that to be inside a synchronized block execResult.set(callback.doInTransaction(status)); } } catch (RuntimeException e) { diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseResourceHolder.java b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseResourceHolder.java index 43a4b159c..16ab949a7 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseResourceHolder.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseResourceHolder.java @@ -33,7 +33,7 @@ * @see CouchbaseTransactionManager * @see CouchbaseTemplate */ - +// todo gp understand why this is needed - can we not just hold ctx in Mono context? public class CouchbaseResourceHolder extends ResourceHolderSupport { private @Nullable ClientSession session; // which holds the atr diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionDefinition.java b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionDefinition.java index e87aacfa4..d46d73bc7 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionDefinition.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionDefinition.java @@ -12,6 +12,7 @@ public class CouchbaseTransactionDefinition extends DefaultTransactionDefinition public CouchbaseTransactionDefinition(){ super(); + setIsolationLevel(ISOLATION_READ_COMMITTED); } public CouchbaseTransactionDefinition(TransactionDefinition that) { diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionManager.java b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionManager.java index 7314a53d9..e9fc558a3 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionManager.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionManager.java @@ -62,6 +62,7 @@ * @see MongoDB Transaction Documentation * @see MongoDatabaseUtils#getDatabase(CouchbaseClientFactory, SessionSynchronization) */ +// todo gp is this needed, or can we only have the CallbackPreferring one? public class CouchbaseTransactionManager extends AbstractPlatformTransactionManager implements ResourceTransactionManager, InitializingBean { diff --git a/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseResourceHolder.java b/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseResourceHolder.java index f7c76ff2a..d73ba2057 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseResourceHolder.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseResourceHolder.java @@ -33,6 +33,7 @@ * @see ReactiveCouchbaseTransactionManager * @see ReactiveCouchbaseTemplate */ +// todo gp understand why this is needed public class ReactiveCouchbaseResourceHolder extends ResourceHolderSupport { private @Nullable ClientSession session; // which holds the atr diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionIntegrationTests.java index 2b2fbfb3f..189491bf1 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionIntegrationTests.java @@ -114,7 +114,7 @@ public class CouchbasePersonTransactionIntegrationTests extends JavaIntegrationT @Autowired CouchbaseClientFactory couchbaseClientFactory; @Autowired ReactiveCouchbaseClientFactory reactiveCouchbaseClientFactory; @Autowired ReactiveCouchbaseTransactionManager reactiveCouchbaseTransactionManager; - @Autowired CouchbaseTransactionManager couchbaseTransactionManager; + //@Autowired CouchbaseTransactionManager couchbaseTransactionManager; @Autowired ReactivePersonRepository rxRepo; @Autowired PersonRepository repo; @Autowired CouchbaseTemplate cbTmpl; @@ -915,16 +915,16 @@ public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor( class PersonService { final CouchbaseOperations personOperations; - final CouchbaseTransactionManager manager; // final ReactiveCouchbaseTransactionManager manager; + final CouchbaseCallbackTransactionManager manager; // final ReactiveCouchbaseTransactionManager manager; final ReactiveCouchbaseOperations personOperationsRx; final ReactiveCouchbaseTransactionManager managerRx; - public PersonService(CouchbaseOperations ops, CouchbaseTransactionManager mgr, ReactiveCouchbaseOperations opsRx, + public PersonService(CouchbaseOperations ops, CouchbaseCallbackTransactionManager mgr, ReactiveCouchbaseOperations opsRx, ReactiveCouchbaseTransactionManager mgrRx) { personOperations = ops; manager = mgr; System.err.println("operations cluster : " + personOperations.getCouchbaseClientFactory().getCluster()); - System.err.println("manager cluster : " + manager.getDatabaseFactory().getCluster()); +// System.err.println("manager cluster : " + manager.getDatabaseFactory().getCluster()); System.err.println("manager Manager : " + manager); personOperationsRx = opsRx; diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionReactiveIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionReactiveIntegrationTests.java index bea9b357c..062139ea4 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionReactiveIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionReactiveIntegrationTests.java @@ -22,6 +22,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.data.couchbase.config.BeanNames; import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.couchbase.transaction.CouchbaseCallbackTransactionManager; import org.springframework.data.couchbase.transaction.CouchbaseTransactionManager; import org.springframework.data.domain.Persistable; import org.springframework.test.context.transaction.AfterTransaction; @@ -85,7 +86,7 @@ public class CouchbasePersonTransactionReactiveIntegrationTests extends JavaInte @Autowired CouchbaseClientFactory couchbaseClientFactory; @Autowired ReactiveCouchbaseTransactionManager reactiveCouchbaseTransactionManager; - @Autowired CouchbaseTransactionManager couchbaseTransactionManager; + @Autowired CouchbaseCallbackTransactionManager couchbaseTransactionManager; @Autowired ReactivePersonRepository rxRepo; @Autowired PersonRepository repo; @Autowired ReactiveCouchbaseTemplate rxCBTmpl; @@ -269,14 +270,14 @@ static class PersonService { final ReactiveCouchbaseOperations personOperationsRx; final ReactiveCouchbaseTransactionManager managerRx; final CouchbaseOperations personOperations; - final CouchbaseTransactionManager manager; + final CouchbaseCallbackTransactionManager manager; - public PersonService(CouchbaseOperations ops, CouchbaseTransactionManager mgr, ReactiveCouchbaseOperations opsRx, + public PersonService(CouchbaseOperations ops, CouchbaseCallbackTransactionManager mgr, ReactiveCouchbaseOperations opsRx, ReactiveCouchbaseTransactionManager mgrRx) { personOperations = ops; manager = mgr; System.err.println("operations cluster : " + personOperations.getCouchbaseClientFactory().getCluster()); - System.err.println("manager cluster : " + manager.getDatabaseFactory().getCluster()); +// System.err.println("manager cluster : " + manager.getDatabaseFactory().getCluster()); System.err.println("manager Manager : " + manager); personOperationsRx = opsRx; From 5012c4e471dd19b183db4836e099a91ad8864a4b Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Fri, 29 Apr 2022 17:38:56 +0100 Subject: [PATCH 3/4] Added CouchbaseSimpleCallbackTransactionManager, the simplest possible implementation of CallbackPreferringTransactionManager, combined with a simpler approach to ThreadLocal storage in ReactiveInsertByIdSupport. Test 'commitShouldPersistTxEntriesOfTxAnnotatedMethod' is now passing. --- .../SimpleCouchbaseClientFactory.java | 10 +- .../AbstractCouchbaseConfiguration.java | 7 +- .../ReactiveInsertByIdOperationSupport.java | 34 ++++--- .../CouchbaseCallbackTransactionManager.java | 2 + ...hbaseSimpleCallbackTransactionManager.java | 81 ++++++++++++++++ .../CouchbaseSimpleTransactionManager.java | 49 ++++++++++ ...basePersonTransactionIntegrationTests.java | 95 ++++++++++--------- .../util/ClusterAwareIntegrationTests.java | 7 +- 8 files changed, 220 insertions(+), 65 deletions(-) create mode 100644 src/main/java/org/springframework/data/couchbase/transaction/CouchbaseSimpleCallbackTransactionManager.java create mode 100644 src/main/java/org/springframework/data/couchbase/transaction/CouchbaseSimpleTransactionManager.java diff --git a/src/main/java/org/springframework/data/couchbase/SimpleCouchbaseClientFactory.java b/src/main/java/org/springframework/data/couchbase/SimpleCouchbaseClientFactory.java index 41a40a2b0..6c095735a 100644 --- a/src/main/java/org/springframework/data/couchbase/SimpleCouchbaseClientFactory.java +++ b/src/main/java/org/springframework/data/couchbase/SimpleCouchbaseClientFactory.java @@ -20,6 +20,9 @@ import java.util.function.Supplier; import com.couchbase.client.java.transactions.ReactiveTransactionAttemptContext; +import com.couchbase.client.java.transactions.TransactionAttemptContext; +import com.couchbase.client.java.transactions.config.TransactionsCleanupConfig; +import com.couchbase.client.java.transactions.config.TransactionsConfig; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.data.couchbase.core.CouchbaseExceptionTranslator; import org.springframework.data.couchbase.transaction.ClientSession; @@ -57,8 +60,9 @@ public SimpleCouchbaseClientFactory(final String connectionString, final Authent public SimpleCouchbaseClientFactory(final String connectionString, final Authenticator authenticator, final String bucketName, final String scopeName) { - this(new OwnedSupplier<>(Cluster.connect(connectionString, ClusterOptions.clusterOptions(authenticator))), - bucketName, scopeName); + this(new OwnedSupplier<>(Cluster.connect(connectionString, ClusterOptions.clusterOptions(authenticator) + // todo gp disabling cleanupLostAttempts to simplify output during development + .environment(env -> env.transactionsConfig(TransactionsConfig.cleanupConfig(TransactionsCleanupConfig.cleanupLostAttempts(false)))))), bucketName, scopeName); } public SimpleCouchbaseClientFactory(final String connectionString, final Authenticator authenticator, @@ -132,7 +136,7 @@ public ClientSession getSession(ClientSessionOptions options, ReactiveTransactio // TransactionAttemptContext at = AttemptContextReactiveAccessor // .from(atr != null ? atr : AttemptContextReactiveAccessor.newAttemptContextReactive(transactions.reactive())); // -// return new ClientSessionImpl(this, transactions, config, at); +// return new ClientSessionImpl(this, at); } // @Override diff --git a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java index 598de221d..f7a1d63e5 100644 --- a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java +++ b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java @@ -47,6 +47,7 @@ import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOperationsMapping; import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; import org.springframework.data.couchbase.transaction.CouchbaseCallbackTransactionManager; +import org.springframework.data.couchbase.transaction.CouchbaseSimpleCallbackTransactionManager; import org.springframework.data.couchbase.transaction.CouchbaseTransactionManager; import org.springframework.data.couchbase.transaction.ReactiveCouchbaseTransactionManager; import org.springframework.data.mapping.model.CamelCaseAbbreviatingFieldNamingStrategy; @@ -347,9 +348,11 @@ ReactiveCouchbaseTransactionManager reactiveTransactionManager( // return new CouchbaseTransactionManager(couchbaseClientFactory); // } + // todo gp experimenting with making CouchbaseSimpleCallbackTransactionManager the default - but it doesn't play + // nice with MR's changes to insert CouchbaseTransactionInterceptor @Bean(BeanNames.COUCHBASE_TRANSACTION_MANAGER) - CouchbaseCallbackTransactionManager transactionManager(CouchbaseTemplate couchbaseTemplate, ReactiveCouchbaseTemplate couchbaseReactiveTemplate) { - return new CouchbaseCallbackTransactionManager(couchbaseTemplate, couchbaseReactiveTemplate); + CouchbaseSimpleCallbackTransactionManager transactionManager(CouchbaseClientFactory clientFactory) { + return new CouchbaseSimpleCallbackTransactionManager(clientFactory); } /** diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java index 0f1834451..63e9a9094 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java @@ -15,12 +15,15 @@ */ package org.springframework.data.couchbase.core; +import com.couchbase.client.java.transactions.TransactionAttemptContext; +import com.couchbase.client.java.transactions.TransactionGetResult; import org.springframework.data.couchbase.repository.support.TransactionResultHolder; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.time.Duration; import java.util.Collection; +import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -122,11 +125,12 @@ public Mono one(T object) { PseudoArgs pArgs = new PseudoArgs(template, scope, collection, options, txCtx, domainType); LOG.trace("insertById {}", pArgs); - Mono tmpl = template.doGetTemplate(); - //ClientSession session = CouchbaseTransactionalTemplate.getSession(template); + Optional ctxr = Optional.ofNullable((TransactionAttemptContext) + org.springframework.transaction.support.TransactionSynchronizationManager.getResource(TransactionAttemptContext.class)); + Mono reactiveEntity = support.encodeEntity(object) - .flatMap(converted -> tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getSession(null).flatMap(s -> { - if (s == null || s.getReactiveTransactionAttemptContext() == null) { + .flatMap(converted -> { + if (!ctxr.isPresent()) { return template.getCouchbaseClientFactory().withScope(pArgs.getScope()) .getCollection(pArgs.getCollection()) .flatMap(collection -> collection.reactive() @@ -134,16 +138,20 @@ public Mono one(T object) { .flatMap( result -> support.applyResult(object, converted, converted.getId(), result.cas(), null))); } else { - return s.getReactiveTransactionAttemptContext() - .insert( - tp.doGetDatabase().block().bucket(tp.getBucketName()).reactive() - .scope(pArgs.getScope() != null ? pArgs.getScope() : DEFAULT_SCOPE) - .collection(pArgs.getCollection() != null ? pArgs.getCollection() : DEFAULT_COLLECTION), - converted.getId(), converted.getContent()) - // todo gp don't have result.cas() anymore - needed? - .flatMap(result -> support.applyResult(object, converted, converted.getId(), 0L, new TransactionResultHolder(result), s)); + return template.doGetTemplate() + // todo gp this runnable probably not great + .flatMap(tp -> Mono.defer(() -> { +// ReactiveTransactionAttemptContext ctx = (ReactiveTransactionAttemptContext) ctxr.get(); + TransactionGetResult result = ctxr.get().insert( + tp.doGetDatabase().block().bucket(tp.getBucketName()) + .scope(pArgs.getScope() != null ? pArgs.getScope() : DEFAULT_SCOPE) + .collection(pArgs.getCollection() != null ? pArgs.getCollection() : DEFAULT_COLLECTION), + converted.getId(), converted.getContent()); + // todo gp don't have result.cas() anymore - needed? + return support.applyResult(object, converted, converted.getId(), 0L, new TransactionResultHolder(result), null); + })); } - }))); + }); // .flatMap(converted ->/* rc */tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getCluster().flatMap( cl -> // cl.bucket("my_bucket").reactive() // .defaultCollection() diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseCallbackTransactionManager.java b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseCallbackTransactionManager.java index 904c2ec47..74d7f6fba 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseCallbackTransactionManager.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseCallbackTransactionManager.java @@ -116,6 +116,8 @@ public T execute(TransactionDefinition definition, TransactionCallback ca reactiveResourceHolder); setTransaction(transaction); + // todo gp experimenting with replacing the ClientSession, the ReactiveCouchbaseTransactionObject, + // the resource holders etc., with just storing the TransactionAttemptContext. synchronizationManager.bindResource(ReactiveTransactionAttemptContext.class, ctx); /* end spring-data-couchbase transaction 1/2 */ diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseSimpleCallbackTransactionManager.java b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseSimpleCallbackTransactionManager.java new file mode 100644 index 000000000..35048cb27 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseSimpleCallbackTransactionManager.java @@ -0,0 +1,81 @@ +/* + * Copyright 2021 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.transaction; + +import com.couchbase.client.java.transactions.ReactiveTransactionAttemptContext; +import com.couchbase.client.java.transactions.TransactionAttemptContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.couchbase.CouchbaseClientFactory; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionException; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.reactive.TransactionContextManager; +import org.springframework.transaction.support.CallbackPreferringPlatformTransactionManager; +import org.springframework.transaction.support.TransactionCallback; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import reactor.core.publisher.Mono; + +import java.util.concurrent.atomic.AtomicReference; + +// todo gp experimenting with simplest possible CallbackPreferringPlatformTransactionManager, extending PlatformTransactionManager +// not AbstractPlatformTransactionManager +public class CouchbaseSimpleCallbackTransactionManager implements CallbackPreferringPlatformTransactionManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(CouchbaseTransactionManager.class); + + private final CouchbaseClientFactory couchbaseClientFactory; + + public CouchbaseSimpleCallbackTransactionManager(CouchbaseClientFactory couchbaseClientFactory) { + this.couchbaseClientFactory = couchbaseClientFactory; + } + + @Override + public T execute(TransactionDefinition definition, TransactionCallback callback) throws TransactionException { + final AtomicReference execResult = new AtomicReference<>(); + + couchbaseClientFactory.getCluster().transactions().run(ctx -> { + CouchbaseTransactionStatus status = new CouchbaseTransactionStatus(null, true, false, false, true, null, null); + + // Setting ThreadLocal storage + TransactionSynchronizationManager.setActualTransactionActive(true); + TransactionSynchronizationManager.initSynchronization(); + TransactionSynchronizationManager.bindResource(TransactionAttemptContext.class, ctx); + + execResult.set(callback.doInTransaction(status)); + }); + + TransactionSynchronizationManager.clear(); + + return execResult.get(); + } + + @Override + public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException { + return null; + } + + @Override + public void commit(TransactionStatus status) throws TransactionException { + System.out.println("commit"); + } + + @Override + public void rollback(TransactionStatus status) throws TransactionException { + System.out.println("rollback"); + } +} diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseSimpleTransactionManager.java b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseSimpleTransactionManager.java new file mode 100644 index 000000000..1947cb7a2 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseSimpleTransactionManager.java @@ -0,0 +1,49 @@ +/* + * Copyright 2018-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.couchbase.transaction; + +import org.springframework.data.couchbase.CouchbaseClientFactory; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionException; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.TransactionSystemException; + +// todo gp experimenting with the simplest possible class, extending PlatformTransactionManager not AbstractPlatformTransactionManager +public class CouchbaseSimpleTransactionManager implements PlatformTransactionManager { + + private final CouchbaseClientFactory clientFactory; + + public CouchbaseSimpleTransactionManager(CouchbaseClientFactory clientFactory) { + this.clientFactory = clientFactory; + } + + @Override + public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException { + return null; + } + + @Override + public void commit(TransactionStatus status) throws TransactionException { + // todo gp what here - do we need to re-allow explicit commit? how to handle retries of this part? + } + + @Override + public void rollback(TransactionStatus status) throws TransactionException { + // todo gp same as commit() + } +} diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionIntegrationTests.java index 189491bf1..05a70cc3e 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionIntegrationTests.java @@ -26,6 +26,7 @@ import com.couchbase.client.java.transactions.TransactionResult; import com.couchbase.client.java.transactions.error.TransactionFailedException; import lombok.Data; +import org.springframework.data.couchbase.transaction.CouchbaseSimpleCallbackTransactionManager; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -128,7 +129,8 @@ public class CouchbasePersonTransactionIntegrationTests extends JavaIntegrationT public static void beforeAll() { callSuperBeforeAll(new Object() {}); context = new AnnotationConfigApplicationContext(CouchbasePersonTransactionIntegrationTests.Config.class, - PersonService.class, CouchbasePersonTransactionIntegrationTests.TransactionInterception.class); +// PersonService.class, CouchbasePersonTransactionIntegrationTests.TransactionInterception.class); + PersonService.class); } @AfterAll @@ -869,43 +871,44 @@ public String toString() { } } - @Configuration(proxyBeanMethods = false) - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - static class TransactionInterception { - - @Bean - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource, - CouchbaseTransactionManager txManager) { - TransactionInterceptor interceptor = new CouchbaseTransactionInterceptor(); - interceptor.setTransactionAttributeSource(transactionAttributeSource); - if (txManager != null) { - interceptor.setTransactionManager(txManager); - } - return interceptor; - } - - @Bean - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - public TransactionAttributeSource transactionAttributeSource() { - return new AnnotationTransactionAttributeSource(); - } - - @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME) - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor( - TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) { - - BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor(); - advisor.setTransactionAttributeSource(transactionAttributeSource); - advisor.setAdvice(transactionInterceptor); - // if (this.enableTx != null) { - // advisor.setOrder(this.enableTx.getNumber("order")); - // } - return advisor; - } - - } + // todo gp disabled while trying to get alternative method of CouchbaseCallbackTransactionManager working +// @Configuration(proxyBeanMethods = false) +// @Role(BeanDefinition.ROLE_INFRASTRUCTURE) +// static class TransactionInterception { +// +// @Bean +// @Role(BeanDefinition.ROLE_INFRASTRUCTURE) +// public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource, +// CouchbaseTransactionManager txManager) { +// TransactionInterceptor interceptor = new CouchbaseTransactionInterceptor(); +// interceptor.setTransactionAttributeSource(transactionAttributeSource); +// if (txManager != null) { +// interceptor.setTransactionManager(txManager); +// } +// return interceptor; +// } +// +// @Bean +// @Role(BeanDefinition.ROLE_INFRASTRUCTURE) +// public TransactionAttributeSource transactionAttributeSource() { +// return new AnnotationTransactionAttributeSource(); +// } +// +// @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME) +// @Role(BeanDefinition.ROLE_INFRASTRUCTURE) +// public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor( +// TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) { +// +// BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor(); +// advisor.setTransactionAttributeSource(transactionAttributeSource); +// advisor.setAdvice(transactionInterceptor); +// // if (this.enableTx != null) { +// // advisor.setOrder(this.enableTx.getNumber("order")); +// // } +// return advisor; +// } +// +// } @Service @Component @@ -915,11 +918,11 @@ public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor( class PersonService { final CouchbaseOperations personOperations; - final CouchbaseCallbackTransactionManager manager; // final ReactiveCouchbaseTransactionManager manager; + final CouchbaseSimpleCallbackTransactionManager manager; // final ReactiveCouchbaseTransactionManager manager; final ReactiveCouchbaseOperations personOperationsRx; final ReactiveCouchbaseTransactionManager managerRx; - public PersonService(CouchbaseOperations ops, CouchbaseCallbackTransactionManager mgr, ReactiveCouchbaseOperations opsRx, + public PersonService(CouchbaseOperations ops, CouchbaseSimpleCallbackTransactionManager mgr, ReactiveCouchbaseOperations opsRx, ReactiveCouchbaseTransactionManager mgrRx) { personOperations = ops; manager = mgr; @@ -1024,7 +1027,7 @@ public Person declarativeSavePersonErrors(Person person) { return p; } - @Autowired CouchbaseCallbackTransactionManager callbackTm; + @Autowired CouchbaseSimpleCallbackTransactionManager callbackTm; /** * to execute while ThreadReplaceloop() is running should force a retry @@ -1036,11 +1039,11 @@ public Person declarativeSavePersonErrors(Person person) { public Person declarativeFindReplacePersonCallback(Person person, AtomicInteger tryCount) { assertInAnnotationTransaction(true); System.err.println("declarativeFindReplacePersonCallback try: " + tryCount.incrementAndGet()); - System.err.println("declarativeFindReplacePersonCallback cluster : " - + callbackTm.template().getCouchbaseClientFactory().getCluster().block()); - System.err.println("declarativeFindReplacePersonCallback resourceHolder : " - + org.springframework.transaction.support.TransactionSynchronizationManager - .getResource(callbackTm.template().getCouchbaseClientFactory().getCluster().block())); +// System.err.println("declarativeFindReplacePersonCallback cluster : " +// + callbackTm.template().getCouchbaseClientFactory().getCluster().block()); +// System.err.println("declarativeFindReplacePersonCallback resourceHolder : " +// + org.springframework.transaction.support.TransactionSynchronizationManager +// .getResource(callbackTm.template().getCouchbaseClientFactory().getCluster().block())); Person p = personOperations.findById(Person.class).one(person.getId().toString()); return personOperations.replaceById(Person.class).one(p); } diff --git a/src/test/java/org/springframework/data/couchbase/util/ClusterAwareIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/util/ClusterAwareIntegrationTests.java index a7dd9416e..5deab4ba9 100644 --- a/src/test/java/org/springframework/data/couchbase/util/ClusterAwareIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/util/ClusterAwareIntegrationTests.java @@ -31,6 +31,8 @@ import com.couchbase.client.core.service.Service; import com.couchbase.client.java.ClusterOptions; import com.couchbase.client.java.env.ClusterEnvironment; +import com.couchbase.client.java.transactions.config.TransactionsCleanupConfig; +import com.couchbase.client.java.transactions.config.TransactionsConfig; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -62,7 +64,10 @@ public abstract class ClusterAwareIntegrationTests { @BeforeAll static void setup(TestClusterConfig config) { testClusterConfig = config; - ClusterEnvironment env = ClusterEnvironment.builder().build(); + // todo gp disabling cleanupLostAttempts to simplify output during development + ClusterEnvironment env = ClusterEnvironment.builder() + .transactionsConfig(TransactionsConfig.cleanupConfig(TransactionsCleanupConfig.cleanupLostAttempts(false))) + .build(); String connectString = connectionString(); try (CouchbaseClientFactory couchbaseClientFactory = new SimpleCouchbaseClientFactory(connectString, authenticator(), bucketName(), null, env)) { From 98b695e7e628237fe1ff575e384d437454370a4f Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Tue, 3 May 2022 17:45:01 +0100 Subject: [PATCH 4/4] Adding WIP get-and-replace @Transactional support (Not yet working as CAS/version field in Person is not populated correctly.) --- .../SimpleCouchbaseClientFactory.java | 7 +- .../AbstractCouchbaseConfiguration.java | 7 +- .../data/couchbase/config/BeanNames.java | 2 +- .../data/couchbase/core/GenericSupport.java | 61 + .../ReactiveFindByIdOperationSupport.java | 59 +- .../ReactiveInsertByIdOperationSupport.java | 83 +- .../ReactiveReplaceByIdOperationSupport.java | 118 +- .../core/mapping/CouchbaseDocument.java | 3 + .../CouchbaseCallbackTransactionManager.java | 590 ++++---- ...hbaseSimpleCallbackTransactionManager.java | 13 +- ...basePersonTransactionIntegrationTests.java | 18 +- ...onTransactionReactiveIntegrationTests.java | 1218 ++++++++--------- 12 files changed, 1156 insertions(+), 1023 deletions(-) create mode 100644 src/main/java/org/springframework/data/couchbase/core/GenericSupport.java diff --git a/src/main/java/org/springframework/data/couchbase/SimpleCouchbaseClientFactory.java b/src/main/java/org/springframework/data/couchbase/SimpleCouchbaseClientFactory.java index 6c095735a..6c8243cc7 100644 --- a/src/main/java/org/springframework/data/couchbase/SimpleCouchbaseClientFactory.java +++ b/src/main/java/org/springframework/data/couchbase/SimpleCouchbaseClientFactory.java @@ -151,9 +151,10 @@ public ClientSession getSession(ClientSessionOptions options, ReactiveTransactio @Override public void close() { - if (cluster instanceof OwnedSupplier) { - cluster.get().disconnect(); - } + // todo gp +// if (cluster instanceof OwnedSupplier) { +// cluster.get().disconnect(); +// } } private static Duration now() { diff --git a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java index f7a1d63e5..efa98996e 100644 --- a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java +++ b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java @@ -46,7 +46,6 @@ import org.springframework.data.couchbase.core.mapping.Document; import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOperationsMapping; import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; -import org.springframework.data.couchbase.transaction.CouchbaseCallbackTransactionManager; import org.springframework.data.couchbase.transaction.CouchbaseSimpleCallbackTransactionManager; import org.springframework.data.couchbase.transaction.CouchbaseTransactionManager; import org.springframework.data.couchbase.transaction.ReactiveCouchbaseTransactionManager; @@ -355,6 +354,12 @@ CouchbaseSimpleCallbackTransactionManager transactionManager(CouchbaseClientFact return new CouchbaseSimpleCallbackTransactionManager(clientFactory); } +// @Bean(BeanNames.COUCHBASE_SIMPLE_CALLBACK_TRANSACTION_MANAGER) +// CouchbaseSimpleCallbackTransactionManager simpleCallbackTransactionManager(CouchbaseClientFactory clientFactory) { +// return new CouchbaseSimpleCallbackTransactionManager(clientFactory); +// } + + /** * Blocking Transaction Manager * diff --git a/src/main/java/org/springframework/data/couchbase/config/BeanNames.java b/src/main/java/org/springframework/data/couchbase/config/BeanNames.java index f462c0480..100c841e5 100644 --- a/src/main/java/org/springframework/data/couchbase/config/BeanNames.java +++ b/src/main/java/org/springframework/data/couchbase/config/BeanNames.java @@ -68,5 +68,5 @@ public class BeanNames { public static final String COUCHBASE_TRANSACTION_MANAGER = "couchbaseTransactionManager"; - public static final String COUCHBASE_CALLBACK_TRANSACTION_MANAGER = "couchbaseCallbackTransactionManager"; + public static final String COUCHBASE_SIMPLE_CALLBACK_TRANSACTION_MANAGER = "couchbaseSimpleCallbackTransactionManager"; } diff --git a/src/main/java/org/springframework/data/couchbase/core/GenericSupport.java b/src/main/java/org/springframework/data/couchbase/core/GenericSupport.java new file mode 100644 index 000000000..9899a99f6 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/GenericSupport.java @@ -0,0 +1,61 @@ +package org.springframework.data.couchbase.core; + +import com.couchbase.client.core.annotation.Stability; +import com.couchbase.client.java.Collection; +import com.couchbase.client.java.transactions.TransactionAttemptContext; +import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; +import org.springframework.lang.Nullable; +import reactor.core.publisher.Mono; + +import java.util.Optional; +import java.util.function.Function; + +// todo gp better name +@Stability.Internal +class GenericSupportHelper { + public final CouchbaseDocument converted; + public final Collection collection; + public final @Nullable TransactionAttemptContext ctx; + + public GenericSupportHelper(CouchbaseDocument doc, Collection collection, @Nullable TransactionAttemptContext ctx) { + this.converted = doc; + this.collection = collection; + this.ctx = ctx; + } +} + +// todo gp better name +@Stability.Internal +public class GenericSupport { + public static Mono one(ReactiveCouchbaseTemplate template, + String scopeName, + String collectionName, + ReactiveTemplateSupport support, + T object, + Function> nonTransactional, + Function> transactional) { + // todo gp how safe is this? I think we can switch threads potentially + Optional ctxr = Optional.ofNullable((TransactionAttemptContext) + org.springframework.transaction.support.TransactionSynchronizationManager.getResource(TransactionAttemptContext.class)); + + return template.getCouchbaseClientFactory().withScope(scopeName).getCollection(collectionName) + .flatMap(collection -> + support.encodeEntity(object) + .flatMap(converted -> { + GenericSupportHelper gsh = new GenericSupportHelper(converted, collection, ctxr.orElse(null)); + if (!ctxr.isPresent()) { + return nonTransactional.apply(gsh); + } else { + return transactional.apply(gsh); + } + })) + .onErrorMap(throwable -> { + if (throwable instanceof RuntimeException) { + return template.potentiallyConvertRuntimeException((RuntimeException) throwable); + } else { + return throwable; + } + }); + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java index 2e947d71d..36083b53d 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java @@ -17,6 +17,8 @@ import static com.couchbase.client.java.kv.GetAndTouchOptions.getAndTouchOptions; +import com.couchbase.client.java.transactions.TransactionAttemptContext; +import com.couchbase.client.java.transactions.TransactionGetResult; import org.springframework.data.couchbase.repository.support.TransactionResultHolder; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -25,6 +27,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -86,16 +89,43 @@ public Mono one(final String id) { CommonOptions gOptions = initGetOptions(); PseudoArgs pArgs = new PseudoArgs(template, scope, collection, gOptions, txCtx, domainType); LOG.trace("findById {}", pArgs); - ReactiveCollection rc = template.getCouchbaseClientFactory().withScope(pArgs.getScope()) - .getCollection(pArgs.getCollection()).block().reactive(); - Mono tmpl = template.doGetTemplate(); +// return GenericSupport.one(template, scope, collection, support, object, +// (GenericSupportHelper support) -> { +// if (pArgs.getOptions() instanceof GetAndTouchOptions) { +// return rc.getAndTouch(id, expiryToUse(), (GetAndTouchOptions) pArgs.getOptions()).flatMap( +// result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), domainType, pArgs.getScope(), pArgs.getCollection(), null)); +// } else { +// return rc.get(id, (GetOptions) pArgs.getOptions()).flatMap( +// result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), domainType, pArgs.getScope(), pArgs.getCollection(), null)); +// } +// }, +// (GenericSupportHelper support) -> { +// return s.getReactiveTransactionAttemptContext().get(rc, id) +// // todo gp no cas +// .flatMap(result -> support.decodeEntity(id, result.contentAsObject().toString(), 0, +// domainType, pArgs.getScope(), pArgs.getCollection(), new TransactionResultHolder(result), s)); +// } +// })).onErrorResume(throwable -> { +// if (throwable instanceof DocumentNotFoundException) { +// return Mono.empty(); +// } +// return Mono.error(throwable); +// }); + + Optional ctxr = Optional.ofNullable((TransactionAttemptContext) + org.springframework.transaction.support.TransactionSynchronizationManager.getResource(TransactionAttemptContext.class)); + + com.couchbase.client.java.Collection coll = template.getCouchbaseClientFactory().withScope(pArgs.getScope()) + .getCollection(pArgs.getCollection()).block(); + ReactiveCollection rc = coll.reactive(); + +// Mono tmpl = template.doGetTemplate(); //ReactiveTransactionAttemptContext ctx = CouchbaseTransactionalTemplate.getContextReactive(template); //ClientSession session = CouchbaseTransactionalTemplate.getSession(template); - Mono reactiveEntity = tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getSession(null) - .flatMap(s -> { - if ( s == null || s.getReactiveTransactionAttemptContext() == null ) { + Mono reactiveEntity = Mono.defer(() -> { + if (!ctxr.isPresent()) { if (pArgs.getOptions() instanceof GetAndTouchOptions) { return rc.getAndTouch(id, expiryToUse(), (GetAndTouchOptions) pArgs.getOptions()).flatMap( result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), domainType, pArgs.getScope(), pArgs.getCollection(), null)); @@ -104,12 +134,18 @@ public Mono one(final String id) { result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), domainType, pArgs.getScope(), pArgs.getCollection(), null)); } } else { - return s.getReactiveTransactionAttemptContext().get(rc, id) - // todo gp no cas - .flatMap(result -> support.decodeEntity(id, result.contentAsObject().toString(), 0, - domainType, pArgs.getScope(), pArgs.getCollection(), new TransactionResultHolder(result), s)); + return Mono.defer(() -> { + TransactionGetResult result = ctxr.get().get(coll, id); + // todo gp no cas + return support.decodeEntity(id, result.contentAsObject().toString(), 0, + domainType, pArgs.getScope(), pArgs.getCollection(), new TransactionResultHolder(result), null) + .doOnNext(out -> { + // todo gp is this safe? are we on the right thread? + // org.springframework.transaction.support.TransactionSynchronizationManager.bindResource(out, result); + }); + }); } - })); + }); return reactiveEntity.onErrorResume(throwable -> { if (throwable instanceof DocumentNotFoundException) { @@ -131,6 +167,7 @@ private TransactionGetOptions buildTranasactionOptions(ReplaceOptions buildOptio } */ + @Override public Flux all(final Collection ids) { return Flux.fromIterable(ids).flatMap(this::one); diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java index 63e9a9094..16c57b629 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java @@ -15,7 +15,6 @@ */ package org.springframework.data.couchbase.core; -import com.couchbase.client.java.transactions.TransactionAttemptContext; import com.couchbase.client.java.transactions.TransactionGetResult; import org.springframework.data.couchbase.repository.support.TransactionResultHolder; import reactor.core.publisher.Flux; @@ -23,7 +22,6 @@ import java.time.Duration; import java.util.Collection; -import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,9 +37,6 @@ import com.couchbase.client.java.kv.PersistTo; import com.couchbase.client.java.kv.ReplicateTo; -import static com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_COLLECTION; -import static com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_SCOPE; - public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOperation { private final ReactiveCouchbaseTemplate template; @@ -111,74 +106,24 @@ static class ReactiveInsertByIdSupport implements ReactiveInsertById { @Override public Mono one(T object) { - // ReactiveCouchbaseResourceHolder resourceHolder = (ReactiveCouchbaseResourceHolder) synchronizationManager - // .getResource(getRequiredDatabaseFactory()); - - // ((ReactiveCouchbaseResourceHolder) - // TransactionSynchronizationManager.forCurrentTransaction().flatMap((synchronizationManager) -> { - // return Mono.just(synchronizationManager.getResource( template.getCouchbaseClientFactory())); - // }).block()).getSession().getAttemptContextReactive() / - // if (TransactionSynchronizationManager.hasResource(template.getCouchbaseClientFactory())){ - // - // } - // the template should have the session(???) PseudoArgs pArgs = new PseudoArgs(template, scope, collection, options, txCtx, domainType); LOG.trace("insertById {}", pArgs); - Optional ctxr = Optional.ofNullable((TransactionAttemptContext) - org.springframework.transaction.support.TransactionSynchronizationManager.getResource(TransactionAttemptContext.class)); - - Mono reactiveEntity = support.encodeEntity(object) - .flatMap(converted -> { - if (!ctxr.isPresent()) { - return template.getCouchbaseClientFactory().withScope(pArgs.getScope()) - .getCollection(pArgs.getCollection()) - .flatMap(collection -> collection.reactive() - .insert(converted.getId(), converted.export(), buildOptions(pArgs.getOptions(), converted)) - .flatMap( - result -> support.applyResult(object, converted, converted.getId(), result.cas(), null))); - } else { - return template.doGetTemplate() - // todo gp this runnable probably not great - .flatMap(tp -> Mono.defer(() -> { -// ReactiveTransactionAttemptContext ctx = (ReactiveTransactionAttemptContext) ctxr.get(); - TransactionGetResult result = ctxr.get().insert( - tp.doGetDatabase().block().bucket(tp.getBucketName()) - .scope(pArgs.getScope() != null ? pArgs.getScope() : DEFAULT_SCOPE) - .collection(pArgs.getCollection() != null ? pArgs.getCollection() : DEFAULT_COLLECTION), - converted.getId(), converted.getContent()); - // todo gp don't have result.cas() anymore - needed? - return support.applyResult(object, converted, converted.getId(), 0L, new TransactionResultHolder(result), null); - })); - } + return GenericSupport.one(template, scope, collection, support, object, + (GenericSupportHelper support) -> { + return support.collection.reactive().insert(support.converted.getId(), support.converted.export(), buildOptions(pArgs.getOptions(), support.converted)) + .flatMap(result -> + this.support.applyResult(object, support.converted, support.converted.getId(), result.cas(), null)); + }, + (GenericSupportHelper support) -> { + return template.doGetTemplate() + // todo gp this runnable probably not great + .flatMap(tp -> Mono.defer(() -> { + TransactionGetResult result = support.ctx.insert(support.collection, support.converted.getId(), support.converted.getContent()); + // todo gp don't have result.cas() anymore - needed? + return this.support.applyResult(object, support.converted, support.converted.getId(), 0L, new TransactionResultHolder(result), null); + })); }); - // .flatMap(converted ->/* rc */tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getCluster().flatMap( cl -> - // cl.bucket("my_bucket").reactive() - // .defaultCollection() - // .insert(converted.getId(), converted.export(), buildOptions(pArgs.getOptions(), converted)) - // .flatMap(result -> support.applyResult(object, converted, converted.getId(), result.cas(), null))))); - /* - } else { - reactiveEntity = support.encodeEntity(object).flatMap(converted -> pArgs.getTxOp().getAttemptContextReactive() // transactional() - // needs - // to - // have - // initted - // acr - .insert(template.doGetDatabase().block().bucket("my_bucket").reactive().defaultCollection(), - converted.getId(), converted.getContent(), buildTxOptions(pArgs.getOptions(), converted)) - .flatMap(result -> support.applyResult(object, converted, converted.getId(), result.cas(), - pArgs.getTxOp().transactionResultHolder(result)))); - } - */ - - return reactiveEntity.onErrorMap(throwable -> { - if (throwable instanceof RuntimeException) { - return template.potentiallyConvertRuntimeException((RuntimeException) throwable); - } else { - return throwable; - } - }); } @Override diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java index 3ef25c78c..1afd1849f 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java @@ -15,9 +15,14 @@ */ package org.springframework.data.couchbase.core; +import com.couchbase.client.core.error.transaction.RetryTransactionException; +import com.couchbase.client.core.transaction.CoreTransactionGetResult; +import com.couchbase.client.java.transactions.TransactionAttemptContext; +import com.couchbase.client.java.transactions.TransactionGetResult; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.lang.reflect.Method; import java.time.Duration; import java.util.Collection; import java.util.Optional; @@ -117,37 +122,90 @@ public Mono one(T object) { public Mono one(T object) { PseudoArgs pArgs = new PseudoArgs<>(template, scope, collection, options, txCtx, domainType); LOG.trace("replaceById {}", pArgs); - Mono tmpl = template.doGetTemplate(); - Mono reactiveEntity; - - CouchbaseDocument converted = support.encodeEntity(object).block(); - reactiveEntity = tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getSession(null).flatMap(s -> { - if (s == null || s.getReactiveTransactionAttemptContext() == null) { - System.err.println("ReactiveReplaceById: not"); - Mono op = template.getCouchbaseClientFactory() - .withScope(pArgs.getScope()).getCollection(pArgs.getCollection()); - return op.flatMap(collection -> collection.reactive() - .replace(converted.getId(), converted.export(), - buildReplaceOptions(pArgs.getOptions(), object, converted)) - .flatMap(result -> support.applyResult(object, converted, converted.getId(), result.cas(), null))); - } else { - System.err.println("ReactiveReplaceById: transaction"); - return s.getReactiveTransactionAttemptContext() - .replace(s.transactionResultHolder(getTransactionHolder(object)).transactionGetResult(), - converted.getContent()) - // todo gp no CAS - .flatMap(result -> support.applyResult(object, converted, converted.getId(), 0L, - new TransactionResultHolder(result), s)); - } - })); - return reactiveEntity.onErrorMap(throwable -> { - if (throwable instanceof RuntimeException) { - return template.potentiallyConvertRuntimeException((RuntimeException) throwable); - } else { - return throwable; - } - }); + return GenericSupport.one(template, scope, collection, support, object, + (GenericSupportHelper support) -> { + CouchbaseDocument converted = support.converted; + + return support.collection.reactive() + .replace(converted.getId(), converted.export(), + buildReplaceOptions(pArgs.getOptions(), object, converted)) + .flatMap(result -> this.support.applyResult(object, converted, converted.getId(), result.cas(), null)); + }, + (GenericSupportHelper support) -> { + CouchbaseDocument converted = support.converted; + + // todo gp replace is a nightmare... + // Where to put and how to pass the TransactionGetResult + // - Idea: TransactionSynchronizationManager.bindResource + // - Idea: use @Version as an index into Map + // - As below, one idea is not to store it at all. + // Person could have been fetched outside of @Transactional block. Need to flat out prevent. Right?? + // - Maybe not. Could have the replaceById do a ctx.get(), and check the CAS matches the Person (will mandate @Version on Person). + // - Could always do that in fact. Then no need to hold onto TransactionGetResult anywhere - but slower too (could optimise later). + // - And if had get-less replaces, could pass in the CAS. + // - Note: if Person was fetched outside the transaction, the transaction will inevitably expire (continuous CAS mismatch). + // -- Will have to doc that the user generally wants to do the read inside the txn. + // -- Can we detect this scenario and reject at runtime? That would also probably need storing something in Person. + +// TransactionGetResult gr = (TransactionGetResult) org.springframework.transaction.support.TransactionSynchronizationManager.getResource(object); + TransactionGetResult gr = support.ctx.get(support.collection, converted.getId()); + + // todo gp if we need this of course needs to be exposed nicely + CoreTransactionGetResult internal; + try { + Method method = TransactionGetResult.class.getDeclaredMethod("internal"); + method.setAccessible(true); + internal = (CoreTransactionGetResult) method.invoke(gr); + } + catch (Throwable err) { + throw new RuntimeException(err); + } + + if (internal.cas() != support.converted.version) { + // todo gp really want to set internal state and raise a TransactionOperationFailed + throw new RetryTransactionException(); + } + + support.ctx.replace(gr, converted.getContent()); + // todo gp no CAS + return this.support.applyResult(object, converted, converted.getId(), 0L, null, null); + }); + +// Mono tmpl = template.doGetTemplate(); +// Mono reactiveEntity; +// +// Optional ctxr = Optional.ofNullable((TransactionAttemptContext) +// org.springframework.transaction.support.TransactionSynchronizationManager.getResource(TransactionAttemptContext.class)); +// +// CouchbaseDocument converted = support.encodeEntity(object).block(); +// reactiveEntity = tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getSession(null).flatMap(s -> { +// if (s == null || s.getReactiveTransactionAttemptContext() == null) { +// System.err.println("ReactiveReplaceById: not"); +// Mono op = template.getCouchbaseClientFactory() +// .withScope(pArgs.getScope()).getCollection(pArgs.getCollection()); +// return op.flatMap(collection -> collection.reactive() +// .replace(converted.getId(), converted.export(), +// buildReplaceOptions(pArgs.getOptions(), object, converted)) +// .flatMap(result -> support.applyResult(object, converted, converted.getId(), result.cas(), null))); +// } else { +// System.err.println("ReactiveReplaceById: transaction"); +// return s.getReactiveTransactionAttemptContext() +// .replace(s.transactionResultHolder(getTransactionHolder(object)).transactionGetResult(), +// converted.getContent()) +// // todo gp no CAS +// .flatMap(result -> support.applyResult(object, converted, converted.getId(), 0L, +// new TransactionResultHolder(result), s)); +// } +// })); +// +// return reactiveEntity.onErrorMap(throwable -> { +// if (throwable instanceof RuntimeException) { +// return template.potentiallyConvertRuntimeException((RuntimeException) throwable); +// } else { +// return throwable; +// } +// }); } private Integer getTransactionHolder(T object) { diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseDocument.java b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseDocument.java index ead8146ed..02566f767 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseDocument.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseDocument.java @@ -56,6 +56,9 @@ public class CouchbaseDocument implements CouchbaseStorable { */ private int expiration; + // todo gp + public long version; + /** * Creates a completely empty {@link CouchbaseDocument}. */ diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseCallbackTransactionManager.java b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseCallbackTransactionManager.java index 74d7f6fba..e1904055b 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseCallbackTransactionManager.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseCallbackTransactionManager.java @@ -1,295 +1,295 @@ -/* - * Copyright 2021 the original author or authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.couchbase.transaction; - -import com.couchbase.client.core.error.transaction.TransactionOperationFailedException; -import com.couchbase.client.java.transactions.ReactiveTransactionAttemptContext; -import com.couchbase.client.java.transactions.TransactionResult; -import reactor.core.publisher.Mono; - -import java.time.Duration; -import java.time.temporal.ChronoUnit; -import java.util.concurrent.atomic.AtomicReference; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.data.couchbase.CouchbaseClientFactory; -import org.springframework.data.couchbase.ReactiveCouchbaseClientFactory; -import org.springframework.data.couchbase.core.CouchbaseTemplate; -import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate; -import org.springframework.transaction.TransactionDefinition; -import org.springframework.transaction.TransactionException; -import org.springframework.transaction.reactive.TransactionContextManager; -import org.springframework.transaction.reactive.TransactionSynchronizationManager; -import org.springframework.transaction.support.AbstractPlatformTransactionManager; -import org.springframework.transaction.support.CallbackPreferringPlatformTransactionManager; -import org.springframework.transaction.support.DefaultTransactionStatus; -import org.springframework.transaction.support.ResourceTransactionManager; -import org.springframework.transaction.support.SmartTransactionObject; -import org.springframework.transaction.support.TransactionCallback; -import org.springframework.transaction.support.TransactionSynchronizationUtils; -import org.springframework.util.Assert; - -/** - * Blocking TransactionManager - * - * @author Michael Nitschinger - * @author Michael Reiche - */ - -public class CouchbaseCallbackTransactionManager extends AbstractPlatformTransactionManager - implements DisposableBean, ResourceTransactionManager, CallbackPreferringPlatformTransactionManager { - - private static final Logger LOGGER = LoggerFactory.getLogger(CouchbaseTransactionManager.class); - - private final CouchbaseTemplate template; - private final ReactiveCouchbaseTemplate reactiveTemplate; - private final ReactiveCouchbaseClientFactory reactiveCouchbaseClientFactory; - private final CouchbaseClientFactory couchbaseClientFactory; - - private ReactiveCouchbaseTransactionManager.ReactiveCouchbaseTransactionObject transaction; - - public CouchbaseCallbackTransactionManager(CouchbaseTemplate template, ReactiveCouchbaseTemplate reactiveTemplate) { - this.template = template; - this.reactiveTemplate = reactiveTemplate; - this.reactiveCouchbaseClientFactory = this.reactiveTemplate.getCouchbaseClientFactory(); - this.couchbaseClientFactory = this.template.getCouchbaseClientFactory(); - } - - public ReactiveCouchbaseTemplate template() { - return reactiveTemplate; - } - - private CouchbaseResourceHolder newResourceHolder(TransactionDefinition definition, ClientSessionOptions options, - ReactiveTransactionAttemptContext atr) { - - CouchbaseClientFactory databaseFactory = template.getCouchbaseClientFactory(); - - CouchbaseResourceHolder resourceHolder = new CouchbaseResourceHolder( - databaseFactory.getSession(options, atr), databaseFactory); - return resourceHolder; - } - - @Override - public T execute(TransactionDefinition definition, TransactionCallback callback) throws TransactionException { - final AtomicReference execResult = new AtomicReference<>(); - AtomicReference startTime = new AtomicReference<>(0L); - - Mono txnResult = couchbaseClientFactory.getCluster().reactive().transactions().run(ctx -> { - /* begin spring-data-couchbase transaction 1/2 */ - ClientSession clientSession = reactiveCouchbaseClientFactory // couchbaseClientFactory - .getSession(ClientSessionOptions.builder().causallyConsistent(true).build()) - .block(); - ReactiveCouchbaseResourceHolder reactiveResourceHolder = new ReactiveCouchbaseResourceHolder(clientSession, - reactiveCouchbaseClientFactory); - - CouchbaseResourceHolder resourceHolder = new CouchbaseResourceHolder(clientSession, - template.getCouchbaseClientFactory()); - - Mono sync = TransactionContextManager.currentContext() - .map(TransactionSynchronizationManager::new) - . flatMap(synchronizationManager -> { - System.err.println("CallbackTransactionManager: " + this); - System.err.println("bindResource: " + reactiveCouchbaseClientFactory.getCluster().block()); - // todo gp not sure why we bind, unbind, bind again? - synchronizationManager.bindResource(reactiveCouchbaseClientFactory.getCluster().block(), - reactiveResourceHolder); - org.springframework.transaction.support.TransactionSynchronizationManager - .unbindResourceIfPossible(reactiveCouchbaseClientFactory.getCluster().block()); - org.springframework.transaction.support.TransactionSynchronizationManager - .bindResource(reactiveCouchbaseClientFactory.getCluster().block(), resourceHolder); - ReactiveCouchbaseTransactionManager.ReactiveCouchbaseTransactionObject transaction = new ReactiveCouchbaseTransactionManager.ReactiveCouchbaseTransactionObject( - reactiveResourceHolder); - setTransaction(transaction); - - // todo gp experimenting with replacing the ClientSession, the ReactiveCouchbaseTransactionObject, - // the resource holders etc., with just storing the TransactionAttemptContext. - synchronizationManager.bindResource(ReactiveTransactionAttemptContext.class, ctx); - - /* end spring-data-couchbase transaction 1/2 */ - - // todo gp do we need TransactionSynchronizationManager.forCurrentTransaction()? as we already have synchronizationManager - Mono result = TransactionSynchronizationManager.forCurrentTransaction().flatMap((sm) -> { - // todo gp not sure why re-binding again? - sm.unbindResourceIfPossible(reactiveCouchbaseClientFactory.getCluster().block()); - sm.bindResource(reactiveCouchbaseClientFactory.getCluster().block(), - reactiveResourceHolder); - CouchbaseTransactionStatus status = new CouchbaseTransactionStatus(transaction, true, false, false, true, null, sm); - prepareSynchronization(status, new CouchbaseTransactionDefinition()); - // System.err.println("deferContextual.ctx : " + xxx); - //Mono cxView = Mono.deferContextual(cx -> { System.err.println("CallbackTransactionManager.cx: "+cx); return Mono.just(cx);}); - try { - // Since we are on a different thread now transparently, at least make sure - // that the original method invocation is synchronized. - synchronized (this) { - // todo gp this will execute the lambda, and so we likely don't want that to be inside a synchronized block - execResult.set(callback.doInTransaction(status)); - } - } catch (RuntimeException e) { - throw e; - } catch (Throwable e) { - throw new RuntimeException(e); - } - return Mono.empty(); - }).contextWrite(TransactionContextManager.getOrCreateContext()) // this doesn't create a context on the desired publisher - .contextWrite(TransactionContextManager.getOrCreateContextHolder()).then(); - - // todo gp this isn't part of the chain (no `result = result.onErrorResume...`) so isn't called - // and presumably isn't needed? -// result.onErrorResume(err -> { -// AttemptContextReactiveAccessor.getLogger(ctx).info(ctx.attemptId(), -// "caught exception '%s' in async, rethrowing", err); -// return Mono.error(ctx.TransactionOperationFailedException.convertToOperationFailedIfNeeded(err, ctx)); -// }).thenReturn(ctx); - - return result.then(Mono.just(synchronizationManager)); - }); - /* begin spring-data-couchbase transaction 2/2 */ // this doesn't create a context on the desired publisher - return sync.contextWrite(TransactionContextManager.getOrCreateContext()) - .contextWrite(TransactionContextManager.getOrCreateContextHolder()).then(); - /* end spring-data-couchbase transaction 2/2 */ - }).doOnSubscribe(v -> startTime.set(System.nanoTime())); - - txnResult.block(); - return execResult.get(); // transactions.reactive().executeTransaction(merged,overall,ob).doOnNext(v->overall.span().finish()).doOnError(err->overall.span().failWith(err));}); - - } - - private void setTransaction(ReactiveCouchbaseTransactionManager.ReactiveCouchbaseTransactionObject transaction) { - this.transaction = transaction; - } - - @Override - protected ReactiveCouchbaseTransactionManager.ReactiveCouchbaseTransactionObject doGetTransaction() - throws TransactionException { - /* - CouchbaseResourceHolder resourceHolder = (CouchbaseResourceHolder) TransactionSynchronizationManager - .getResource(template.getCouchbaseClientFactory()); - return new CouchbaseTransactionManager.CouchbaseTransactionObject(resourceHolder); - */ - return (ReactiveCouchbaseTransactionManager.ReactiveCouchbaseTransactionObject) transaction; - } - - @Override - protected boolean isExistingTransaction(Object transaction) throws TransactionException { - return extractTransaction(transaction).hasResourceHolder(); - } - - @Override - protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException { - LOGGER.debug("Beginning Couchbase Transaction with Definition {}", definition); - } - - @Override - protected void doCommit(DefaultTransactionStatus status) throws TransactionException { - LOGGER.debug("Committing Couchbase Transaction with status {}", status); - } - - @Override - protected void doRollback(DefaultTransactionStatus status) throws TransactionException { - LOGGER.warn("Rolling back Couchbase Transaction with status {}", status); - org.springframework.transaction.support.TransactionSynchronizationManager - .unbindResource(reactiveCouchbaseClientFactory); - } - - @Override - protected void doCleanupAfterCompletion(Object transaction) { - LOGGER.trace("Performing cleanup of Couchbase Transaction {}", transaction); - org.springframework.transaction.support.TransactionSynchronizationManager - .unbindResource(reactiveCouchbaseClientFactory); - return; - } - - @Override - public void destroy() { - } - - @Override - public Object getResourceFactory() { - return reactiveTemplate.getCouchbaseClientFactory(); - } - - private static CouchbaseTransactionObject extractTransaction(Object transaction) { - Assert.isInstanceOf(CouchbaseTransactionObject.class, transaction, - () -> String.format("Expected to find a %s but it turned out to be %s.", CouchbaseTransactionObject.class, - transaction.getClass())); - - return (CouchbaseTransactionObject) transaction; - } - /* - public class CouchbaseResourceHolder extends ResourceHolderSupport { - - private volatile ReactiveTransactionAttemptContext attemptContext; - //private volatile TransactionResultMap resultMap = new TransactionResultMap(template); - - public CouchbaseResourceHolder(ReactiveTransactionAttemptContext attemptContext) { - this.attemptContext = attemptContext; - } - - public ReactiveTransactionAttemptContext getAttemptContext() { - return attemptContext; - } - - public void setAttemptContext(ReactiveTransactionAttemptContext attemptContext) { - this.attemptContext = attemptContext; - } - - //public TransactionResultMap getTxResultMap() { - // return resultMap; - //} - - @Override - public String toString() { - return "CouchbaseResourceHolder{" + "attemptContext=" + attemptContext + "}"; - } - } - - */ - - protected static class CouchbaseTransactionObject implements SmartTransactionObject { - - private final CouchbaseResourceHolder resourceHolder; - - CouchbaseTransactionObject(CouchbaseResourceHolder resourceHolder) { - this.resourceHolder = resourceHolder; - } - - @Override - public boolean isRollbackOnly() { - return this.resourceHolder != null && this.resourceHolder.isRollbackOnly(); - } - - @Override - public void flush() { - TransactionSynchronizationUtils.triggerFlush(); - } - - public boolean hasResourceHolder() { - return resourceHolder != null; - } - - @Override - public String toString() { - return "CouchbaseTransactionObject{" + "resourceHolder=" + resourceHolder + '}'; - } - } - - private static Duration now() { - return Duration.of(System.nanoTime(), ChronoUnit.NANOS); - } - -} +///* +// * Copyright 2021 the original author or authors +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * https://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +//package org.springframework.data.couchbase.transaction; +// +//import com.couchbase.client.core.error.transaction.TransactionOperationFailedException; +//import com.couchbase.client.java.transactions.ReactiveTransactionAttemptContext; +//import com.couchbase.client.java.transactions.TransactionResult; +//import reactor.core.publisher.Mono; +// +//import java.time.Duration; +//import java.time.temporal.ChronoUnit; +//import java.util.concurrent.atomic.AtomicReference; +// +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +//import org.springframework.beans.factory.DisposableBean; +//import org.springframework.data.couchbase.CouchbaseClientFactory; +//import org.springframework.data.couchbase.ReactiveCouchbaseClientFactory; +//import org.springframework.data.couchbase.core.CouchbaseTemplate; +//import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate; +//import org.springframework.transaction.TransactionDefinition; +//import org.springframework.transaction.TransactionException; +//import org.springframework.transaction.reactive.TransactionContextManager; +//import org.springframework.transaction.reactive.TransactionSynchronizationManager; +//import org.springframework.transaction.support.AbstractPlatformTransactionManager; +//import org.springframework.transaction.support.CallbackPreferringPlatformTransactionManager; +//import org.springframework.transaction.support.DefaultTransactionStatus; +//import org.springframework.transaction.support.ResourceTransactionManager; +//import org.springframework.transaction.support.SmartTransactionObject; +//import org.springframework.transaction.support.TransactionCallback; +//import org.springframework.transaction.support.TransactionSynchronizationUtils; +//import org.springframework.util.Assert; +// +///** +// * Blocking TransactionManager +// * +// * @author Michael Nitschinger +// * @author Michael Reiche +// */ +// +//public class CouchbaseCallbackTransactionManager extends AbstractPlatformTransactionManager +// implements DisposableBean, ResourceTransactionManager, CallbackPreferringPlatformTransactionManager { +// +// private static final Logger LOGGER = LoggerFactory.getLogger(CouchbaseTransactionManager.class); +// +// private final CouchbaseTemplate template; +// private final ReactiveCouchbaseTemplate reactiveTemplate; +// private final ReactiveCouchbaseClientFactory reactiveCouchbaseClientFactory; +// private final CouchbaseClientFactory couchbaseClientFactory; +// +// private ReactiveCouchbaseTransactionManager.ReactiveCouchbaseTransactionObject transaction; +// +// public CouchbaseCallbackTransactionManager(CouchbaseTemplate template, ReactiveCouchbaseTemplate reactiveTemplate) { +// this.template = template; +// this.reactiveTemplate = reactiveTemplate; +// this.reactiveCouchbaseClientFactory = this.reactiveTemplate.getCouchbaseClientFactory(); +// this.couchbaseClientFactory = this.template.getCouchbaseClientFactory(); +// } +// +// public ReactiveCouchbaseTemplate template() { +// return reactiveTemplate; +// } +// +// private CouchbaseResourceHolder newResourceHolder(TransactionDefinition definition, ClientSessionOptions options, +// ReactiveTransactionAttemptContext atr) { +// +// CouchbaseClientFactory databaseFactory = template.getCouchbaseClientFactory(); +// +// CouchbaseResourceHolder resourceHolder = new CouchbaseResourceHolder( +// databaseFactory.getSession(options, atr), databaseFactory); +// return resourceHolder; +// } +// +// @Override +// public T execute(TransactionDefinition definition, TransactionCallback callback) throws TransactionException { +// final AtomicReference execResult = new AtomicReference<>(); +// AtomicReference startTime = new AtomicReference<>(0L); +// +// Mono txnResult = couchbaseClientFactory.getCluster().reactive().transactions().run(ctx -> { +// /* begin spring-data-couchbase transaction 1/2 */ +// ClientSession clientSession = reactiveCouchbaseClientFactory // couchbaseClientFactory +// .getSession(ClientSessionOptions.builder().causallyConsistent(true).build()) +// .block(); +// ReactiveCouchbaseResourceHolder reactiveResourceHolder = new ReactiveCouchbaseResourceHolder(clientSession, +// reactiveCouchbaseClientFactory); +// +// CouchbaseResourceHolder resourceHolder = new CouchbaseResourceHolder(clientSession, +// template.getCouchbaseClientFactory()); +// +// Mono sync = TransactionContextManager.currentContext() +// .map(TransactionSynchronizationManager::new) +// . flatMap(synchronizationManager -> { +// System.err.println("CallbackTransactionManager: " + this); +// System.err.println("bindResource: " + reactiveCouchbaseClientFactory.getCluster().block()); +// // todo gp not sure why we bind, unbind, bind again? +// synchronizationManager.bindResource(reactiveCouchbaseClientFactory.getCluster().block(), +// reactiveResourceHolder); +// org.springframework.transaction.support.TransactionSynchronizationManager +// .unbindResourceIfPossible(reactiveCouchbaseClientFactory.getCluster().block()); +// org.springframework.transaction.support.TransactionSynchronizationManager +// .bindResource(reactiveCouchbaseClientFactory.getCluster().block(), resourceHolder); +// ReactiveCouchbaseTransactionManager.ReactiveCouchbaseTransactionObject transaction = new ReactiveCouchbaseTransactionManager.ReactiveCouchbaseTransactionObject( +// reactiveResourceHolder); +// setTransaction(transaction); +// +// // todo gp experimenting with replacing the ClientSession, the ReactiveCouchbaseTransactionObject, +// // the resource holders etc., with just storing the TransactionAttemptContext. +// synchronizationManager.bindResource(ReactiveTransactionAttemptContext.class, ctx); +// +// /* end spring-data-couchbase transaction 1/2 */ +// +// // todo gp do we need TransactionSynchronizationManager.forCurrentTransaction()? as we already have synchronizationManager +// Mono result = TransactionSynchronizationManager.forCurrentTransaction().flatMap((sm) -> { +// // todo gp not sure why re-binding again? +// sm.unbindResourceIfPossible(reactiveCouchbaseClientFactory.getCluster().block()); +// sm.bindResource(reactiveCouchbaseClientFactory.getCluster().block(), +// reactiveResourceHolder); +// CouchbaseTransactionStatus status = new CouchbaseTransactionStatus(transaction, true, false, false, true, null, sm); +// prepareSynchronization(status, new CouchbaseTransactionDefinition()); +// // System.err.println("deferContextual.ctx : " + xxx); +// //Mono cxView = Mono.deferContextual(cx -> { System.err.println("CallbackTransactionManager.cx: "+cx); return Mono.just(cx);}); +// try { +// // Since we are on a different thread now transparently, at least make sure +// // that the original method invocation is synchronized. +// synchronized (this) { +// // todo gp this will execute the lambda, and so we likely don't want that to be inside a synchronized block +// execResult.set(callback.doInTransaction(status)); +// } +// } catch (RuntimeException e) { +// throw e; +// } catch (Throwable e) { +// throw new RuntimeException(e); +// } +// return Mono.empty(); +// }).contextWrite(TransactionContextManager.getOrCreateContext()) // this doesn't create a context on the desired publisher +// .contextWrite(TransactionContextManager.getOrCreateContextHolder()).then(); +// +// // todo gp this isn't part of the chain (no `result = result.onErrorResume...`) so isn't called +// // and presumably isn't needed? +//// result.onErrorResume(err -> { +//// AttemptContextReactiveAccessor.getLogger(ctx).info(ctx.attemptId(), +//// "caught exception '%s' in async, rethrowing", err); +//// return Mono.error(ctx.TransactionOperationFailedException.convertToOperationFailedIfNeeded(err, ctx)); +//// }).thenReturn(ctx); +// +// return result.then(Mono.just(synchronizationManager)); +// }); +// /* begin spring-data-couchbase transaction 2/2 */ // this doesn't create a context on the desired publisher +// return sync.contextWrite(TransactionContextManager.getOrCreateContext()) +// .contextWrite(TransactionContextManager.getOrCreateContextHolder()).then(); +// /* end spring-data-couchbase transaction 2/2 */ +// }).doOnSubscribe(v -> startTime.set(System.nanoTime())); +// +// txnResult.block(); +// return execResult.get(); // transactions.reactive().executeTransaction(merged,overall,ob).doOnNext(v->overall.span().finish()).doOnError(err->overall.span().failWith(err));}); +// +// } +// +// private void setTransaction(ReactiveCouchbaseTransactionManager.ReactiveCouchbaseTransactionObject transaction) { +// this.transaction = transaction; +// } +// +// @Override +// protected ReactiveCouchbaseTransactionManager.ReactiveCouchbaseTransactionObject doGetTransaction() +// throws TransactionException { +// /* +// CouchbaseResourceHolder resourceHolder = (CouchbaseResourceHolder) TransactionSynchronizationManager +// .getResource(template.getCouchbaseClientFactory()); +// return new CouchbaseTransactionManager.CouchbaseTransactionObject(resourceHolder); +// */ +// return (ReactiveCouchbaseTransactionManager.ReactiveCouchbaseTransactionObject) transaction; +// } +// +// @Override +// protected boolean isExistingTransaction(Object transaction) throws TransactionException { +// return extractTransaction(transaction).hasResourceHolder(); +// } +// +// @Override +// protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException { +// LOGGER.debug("Beginning Couchbase Transaction with Definition {}", definition); +// } +// +// @Override +// protected void doCommit(DefaultTransactionStatus status) throws TransactionException { +// LOGGER.debug("Committing Couchbase Transaction with status {}", status); +// } +// +// @Override +// protected void doRollback(DefaultTransactionStatus status) throws TransactionException { +// LOGGER.warn("Rolling back Couchbase Transaction with status {}", status); +// org.springframework.transaction.support.TransactionSynchronizationManager +// .unbindResource(reactiveCouchbaseClientFactory); +// } +// +// @Override +// protected void doCleanupAfterCompletion(Object transaction) { +// LOGGER.trace("Performing cleanup of Couchbase Transaction {}", transaction); +// org.springframework.transaction.support.TransactionSynchronizationManager +// .unbindResource(reactiveCouchbaseClientFactory); +// return; +// } +// +// @Override +// public void destroy() { +// } +// +// @Override +// public Object getResourceFactory() { +// return reactiveTemplate.getCouchbaseClientFactory(); +// } +// +// private static CouchbaseTransactionObject extractTransaction(Object transaction) { +// Assert.isInstanceOf(CouchbaseTransactionObject.class, transaction, +// () -> String.format("Expected to find a %s but it turned out to be %s.", CouchbaseTransactionObject.class, +// transaction.getClass())); +// +// return (CouchbaseTransactionObject) transaction; +// } +// /* +// public class CouchbaseResourceHolder extends ResourceHolderSupport { +// +// private volatile ReactiveTransactionAttemptContext attemptContext; +// //private volatile TransactionResultMap resultMap = new TransactionResultMap(template); +// +// public CouchbaseResourceHolder(ReactiveTransactionAttemptContext attemptContext) { +// this.attemptContext = attemptContext; +// } +// +// public ReactiveTransactionAttemptContext getAttemptContext() { +// return attemptContext; +// } +// +// public void setAttemptContext(ReactiveTransactionAttemptContext attemptContext) { +// this.attemptContext = attemptContext; +// } +// +// //public TransactionResultMap getTxResultMap() { +// // return resultMap; +// //} +// +// @Override +// public String toString() { +// return "CouchbaseResourceHolder{" + "attemptContext=" + attemptContext + "}"; +// } +// } +// +// */ +// +// protected static class CouchbaseTransactionObject implements SmartTransactionObject { +// +// private final CouchbaseResourceHolder resourceHolder; +// +// CouchbaseTransactionObject(CouchbaseResourceHolder resourceHolder) { +// this.resourceHolder = resourceHolder; +// } +// +// @Override +// public boolean isRollbackOnly() { +// return this.resourceHolder != null && this.resourceHolder.isRollbackOnly(); +// } +// +// @Override +// public void flush() { +// TransactionSynchronizationUtils.triggerFlush(); +// } +// +// public boolean hasResourceHolder() { +// return resourceHolder != null; +// } +// +// @Override +// public String toString() { +// return "CouchbaseTransactionObject{" + "resourceHolder=" + resourceHolder + '}'; +// } +// } +// +// private static Duration now() { +// return Duration.of(System.nanoTime(), ChronoUnit.NANOS); +// } +// +//} diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseSimpleCallbackTransactionManager.java b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseSimpleCallbackTransactionManager.java index 35048cb27..676ee2a74 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseSimpleCallbackTransactionManager.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseSimpleCallbackTransactionManager.java @@ -54,9 +54,20 @@ public T execute(TransactionDefinition definition, TransactionCallback ca // Setting ThreadLocal storage TransactionSynchronizationManager.setActualTransactionActive(true); TransactionSynchronizationManager.initSynchronization(); + // Oddly, TransactionSynchronizationManager.clear() does not clear resources + try { + TransactionSynchronizationManager.unbindResource(TransactionAttemptContext.class); + } + // todo gp must be a nicer way... + catch (IllegalStateException err) {} TransactionSynchronizationManager.bindResource(TransactionAttemptContext.class, ctx); - execResult.set(callback.doInTransaction(status)); + try { + execResult.set(callback.doInTransaction(status)); + } + finally { + TransactionSynchronizationManager.clear(); + } }); TransactionSynchronizationManager.clear(); diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionIntegrationTests.java index 05a70cc3e..1543211a8 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionIntegrationTests.java @@ -66,7 +66,6 @@ import org.springframework.data.couchbase.repository.config.EnableReactiveCouchbaseRepositories; import org.springframework.data.couchbase.transaction.ClientSession; import org.springframework.data.couchbase.transaction.ClientSessionOptions; -import org.springframework.data.couchbase.transaction.CouchbaseCallbackTransactionManager; import org.springframework.data.couchbase.transaction.CouchbaseTransactionDefinition; import org.springframework.data.couchbase.transaction.CouchbaseTransactionManager; import org.springframework.data.couchbase.transaction.ReactiveCouchbaseResourceHolder; @@ -136,7 +135,7 @@ public static void beforeAll() { @AfterAll public static void afterAll() { callSuperAfterAll(new Object() {}); - context.close(); + //context.close(); } @BeforeEach @@ -231,6 +230,18 @@ public void commitShouldPersistTxEntriesOfTxAnnotatedMethod() { assertEquals(1, count, "should have saved and found 1"); } + @Test + public void replaceInTxAnnotatedCallback() { + Person person = new Person(1, "Walter", "White"); + Person switchedPerson = new Person(1, "Dave", "Reynolds"); + cbTmpl.insertById(Person.class).one(person); + AtomicInteger tryCount = new AtomicInteger(0); + Person p = personService.declarativeFindReplacePersonCallback(person, tryCount); + Person pFound = rxCBTmpl.findById(Person.class).one(person.getId().toString()).block(); + assertEquals(switchedPerson.getFirstname(), pFound.getFirstname(), "should have been switched"); + } + + @Test public void commitShouldPersistTxEntriesOfTxAnnotatedMethodReactive() { Person p = new Person(null, "Walter", "White"); @@ -1035,7 +1046,7 @@ public Person declarativeSavePersonErrors(Person person) { * @param person * @return */ - @Transactional(transactionManager = BeanNames.COUCHBASE_CALLBACK_TRANSACTION_MANAGER) + @Transactional(transactionManager = BeanNames.COUCHBASE_TRANSACTION_MANAGER) public Person declarativeFindReplacePersonCallback(Person person, AtomicInteger tryCount) { assertInAnnotationTransaction(true); System.err.println("declarativeFindReplacePersonCallback try: " + tryCount.incrementAndGet()); @@ -1048,6 +1059,7 @@ public Person declarativeFindReplacePersonCallback(Person person, AtomicInteger return personOperations.replaceById(Person.class).one(p); } + /** * to execute while ThreadReplaceloop() is running should force a retry * diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionReactiveIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionReactiveIntegrationTests.java index 062139ea4..2c703f9b4 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionReactiveIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionReactiveIntegrationTests.java @@ -1,609 +1,609 @@ -/* - * Copyright 2012-2021 the original author or authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.data.couchbase.transactions; - -import com.couchbase.client.java.Cluster; -import lombok.Data; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.data.couchbase.config.BeanNames; -import org.springframework.data.couchbase.core.CouchbaseOperations; -import org.springframework.data.couchbase.transaction.CouchbaseCallbackTransactionManager; -import org.springframework.data.couchbase.transaction.CouchbaseTransactionManager; -import org.springframework.data.domain.Persistable; -import org.springframework.test.context.transaction.AfterTransaction; -import org.springframework.test.context.transaction.BeforeTransaction; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.support.GenericApplicationContext; -import org.springframework.dao.DataRetrievalFailureException; -import org.springframework.data.couchbase.CouchbaseClientFactory; -import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; -import org.springframework.data.couchbase.core.CouchbaseTemplate; -import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations; -import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate; -import org.springframework.data.couchbase.core.query.Query; -import org.springframework.data.couchbase.domain.Person; -import org.springframework.data.couchbase.domain.PersonRepository; -import org.springframework.data.couchbase.domain.ReactivePersonRepository; -import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories; -import org.springframework.data.couchbase.repository.config.EnableReactiveCouchbaseRepositories; -import org.springframework.data.couchbase.transaction.ReactiveCouchbaseTransactionManager; -import org.springframework.data.couchbase.util.Capabilities; -import org.springframework.data.couchbase.util.ClusterType; -import org.springframework.data.couchbase.util.IgnoreWhen; -import org.springframework.data.couchbase.util.JavaIntegrationTests; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.reactive.TransactionalOperator; -import org.springframework.transaction.support.DefaultTransactionDefinition; - -import com.couchbase.client.core.cnc.Event; -import com.couchbase.client.core.error.DocumentNotFoundException; -import com.couchbase.client.java.Collection; -import com.couchbase.client.java.ReactiveCollection; -import com.couchbase.client.java.kv.RemoveOptions; - -import static com.couchbase.client.java.query.QueryScanConsistency.REQUEST_PLUS; -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for com.couchbase.transactions without using the spring data transactions framework - * - * @author Michael Reiche - */ -@IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED) -@SpringJUnitConfig(CouchbasePersonTransactionReactiveIntegrationTests.Config.class) -//@Transactional(transactionManager = BeanNames.COUCHBASE_TRANSACTION_MANAGER) -public class CouchbasePersonTransactionReactiveIntegrationTests extends JavaIntegrationTests { - - @Autowired CouchbaseClientFactory couchbaseClientFactory; - @Autowired ReactiveCouchbaseTransactionManager reactiveCouchbaseTransactionManager; - @Autowired CouchbaseCallbackTransactionManager couchbaseTransactionManager; - @Autowired ReactivePersonRepository rxRepo; - @Autowired PersonRepository repo; - @Autowired ReactiveCouchbaseTemplate rxCBTmpl; - - @Autowired Cluster myCluster; - - /* DO NOT @Autowired */ PersonService personService; - - static GenericApplicationContext context; - @Autowired ReactiveCouchbaseTemplate operations; - - @BeforeAll - public static void beforeAll() { - callSuperBeforeAll(new Object() {}); - context = new AnnotationConfigApplicationContext(CouchbasePersonTransactionReactiveIntegrationTests.Config.class, - CouchbasePersonTransactionReactiveIntegrationTests.PersonService.class); - } - - @AfterAll - public static void afterAll() { - callSuperAfterAll(new Object() {}); - } - - @BeforeEach - public void beforeEachTest() { - personService = context.getBean(CouchbasePersonTransactionReactiveIntegrationTests.PersonService.class); // getting it via autowired results in no @Transactional - operations.removeByQuery(Person.class).withConsistency(REQUEST_PLUS).all().collectList().block(); - operations.removeByQuery(EventLog.class).withConsistency(REQUEST_PLUS).all().collectList().block(); - operations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).all().collectList().block(); - operations.findByQuery(EventLog.class).withConsistency(REQUEST_PLUS).all().collectList().block(); - } - - - @Test // DATAMONGO-2265 - public void shouldRollbackAfterException() { - personService.savePersonErrors(new Person(null, "Walter", "White")) // - .as(StepVerifier::create) // - .verifyError(RuntimeException.class); - // operations.findByQuery(Person.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).count().block(); - // sleepMs(5000); - operations.count(new Query(), Person.class) // - .as(StepVerifier::create) // - .expectNext(0L) // - .verifyComplete(); - } - - @Test // DATAMONGO-2265 - // @Rollback(false) - public void shouldRollbackAfterExceptionOfTxAnnotatedMethod() { - Person p = new Person(null, "Walter", "White"); - try { - personService.declarativeSavePersonErrors(p) // - .as(StepVerifier::create) // - .expectComplete(); - // .verifyError(RuntimeException.class); - } catch (RuntimeException e) { - if (e instanceof SimulateFailureException || (e.getMessage() != null && e.getMessage().contains("poof"))) { - System.err.println(e); - } else { - throw e; - } - } - - } - - @Test // DATAMONGO-2265 - public void commitShouldPersistTxEntries() { - - personService.savePerson(new Person(null, "Walter", "White")) // - .as(StepVerifier::create) // - .expectNextCount(1) // - .verifyComplete(); - - operations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count().block(); - - operations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count() // - .as(StepVerifier::create) // - .expectNext(1L) // - .verifyComplete(); - } - - @Test // DATAMONGO-2265 - public void commitShouldPersistTxEntriesOfTxAnnotatedMethod() { - - personService.declarativeSavePerson(new Person(null, "Walter", "White")).as(StepVerifier::create) // - .expectNextCount(1) // - .verifyComplete(); - - operations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count() // - .as(StepVerifier::create) // - .expectNext(1L) // - .verifyComplete(); - - } - - @Test // DATAMONGO-2265 - public void commitShouldPersistTxEntriesAcrossCollections() { - - personService.saveWithLogs(new Person(null, "Walter", "White")) // - .then() // - .as(StepVerifier::create) // - .verifyComplete(); - - operations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count() // - .as(StepVerifier::create) // - .expectNext(1L) // - .verifyComplete(); - - operations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count() // - .as(StepVerifier::create) // - .expectNext(4L) // - .verifyComplete(); - } - - @Test // DATAMONGO-2265 - public void rollbackShouldAbortAcrossCollections() { - - personService.saveWithErrorLogs(new Person(null, "Walter", "White")) // - .then() // - .as(StepVerifier::create) // - .verifyError(); - - operations.count(new Query(), Person.class) // - .as(StepVerifier::create) // - .expectNext(0L) // - .verifyComplete(); - - operations.count(new Query(), EventLog.class)// - .as(StepVerifier::create) // - .expectNext(0L) // - .verifyComplete(); - } - - @Test // DATAMONGO-2265 - public void countShouldWorkInsideTransaction() { - - personService.countDuringTx(new Person(null, "Walter", "White")) // - .as(StepVerifier::create) // - .expectNext(1L) // - .verifyComplete(); - } - - @Test // DATAMONGO-2265 - public void emitMultipleElementsDuringTransaction() { - - try { - personService.saveWithLogs(new Person(null, "Walter", "White")) // - .as(StepVerifier::create) // - .expectNextCount(4L) // - .verifyComplete(); - } catch (Exception e) { - System.err.println("Done"); - throw e; - } - } - - @Test // DATAMONGO-2265 - public void errorAfterTxShouldNotAffectPreviousStep() { - - Person p = new Person(1, "Walter", "White"); - remove(couchbaseTemplate, "_default", p.getId().toString()); - personService.savePerson(p) // - //.delayElement(Duration.ofMillis(100)) // - .then(Mono.error(new RuntimeException("my big bad evil error"))).as(StepVerifier::create) // - .expectError() - .verify(); - //.expectError() // - //.as(StepVerifier::create) - //.expectNext(p) - //.verifyComplete(); - - operations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count() // - .as(StepVerifier::create) // - .expectNext(1L) // - .verifyComplete(); - } - - // @RequiredArgsConstructor - static class PersonService { - - final ReactiveCouchbaseOperations personOperationsRx; - final ReactiveCouchbaseTransactionManager managerRx; - final CouchbaseOperations personOperations; - final CouchbaseCallbackTransactionManager manager; - - public PersonService(CouchbaseOperations ops, CouchbaseCallbackTransactionManager mgr, ReactiveCouchbaseOperations opsRx, - ReactiveCouchbaseTransactionManager mgrRx) { - personOperations = ops; - manager = mgr; - System.err.println("operations cluster : " + personOperations.getCouchbaseClientFactory().getCluster()); -// System.err.println("manager cluster : " + manager.getDatabaseFactory().getCluster()); - System.err.println("manager Manager : " + manager); - - personOperationsRx = opsRx; - managerRx = mgrRx; - System.out - .println("operationsRx cluster : " + personOperationsRx.getCouchbaseClientFactory().getCluster().block()); - System.out.println("managerRx cluster : " + mgrRx.getDatabaseFactory().getCluster().block()); - System.out.println("managerRx Manager : " + managerRx); - return; - } - - public Mono savePersonErrors(Person person) { - - TransactionalOperator transactionalOperator = TransactionalOperator.create(managerRx); - return personOperationsRx.insertById(Person.class).one(person) // - . flatMap(it -> Mono.error(new RuntimeException("poof!"))) // - .as(transactionalOperator::transactional); - } - - public Mono savePerson(Person person) { - - TransactionalOperator transactionalOperator = TransactionalOperator.create(managerRx, - new DefaultTransactionDefinition()); - - return personOperationsRx.insertById(Person.class).one(person) // - .flatMap(Mono::just) // - .as(transactionalOperator::transactional); - } - - public Mono countDuringTx(Person person) { - - TransactionalOperator transactionalOperator = TransactionalOperator.create(managerRx, - new DefaultTransactionDefinition()); - - return personOperationsRx.save(person) // - .then(personOperationsRx.count(new Query(), Person.class)) // - .as(transactionalOperator::transactional); - } - - public Flux saveWithLogs(Person person) { - - TransactionalOperator transactionalOperator = TransactionalOperator.create(managerRx, - new DefaultTransactionDefinition()); - - return Flux.merge(personOperationsRx.save(new EventLog(new ObjectId().toString(), "beforeConvert")), // - personOperationsRx.save(new EventLog(new ObjectId(), "afterConvert")), // - personOperationsRx.save(new EventLog(new ObjectId(), "beforeInsert")), // - personOperationsRx.save(person), // - personOperationsRx.save(new EventLog(new ObjectId(), "afterInsert"))) // - .thenMany(personOperationsRx.findByQuery(EventLog.class).all()) // - .as(transactionalOperator::transactional); - } - - public Flux saveWithErrorLogs(Person person) { - - TransactionalOperator transactionalOperator = TransactionalOperator.create(managerRx, - new DefaultTransactionDefinition()); - - return Flux.merge(personOperationsRx.save(new EventLog(new ObjectId(), "beforeConvert")), // - personOperationsRx.save(new EventLog(new ObjectId(), "afterConvert")), // - personOperationsRx.save(new EventLog(new ObjectId(), "beforeInsert")), // - personOperationsRx.save(person), // - personOperationsRx.save(new EventLog(new ObjectId(), "afterInsert"))) // - . flatMap(it -> Mono.error(new RuntimeException("poof!"))) // - .as(transactionalOperator::transactional); - } - - @Transactional - public Flux declarativeSavePerson(Person person) { - - TransactionalOperator transactionalOperator = TransactionalOperator.create(managerRx, - new DefaultTransactionDefinition()); - - return transactionalOperator.execute(reactiveTransaction -> personOperationsRx.save(person)); - } - - @Transactional(transactionManager = BeanNames.COUCHBASE_TRANSACTION_MANAGER) - public Flux declarativeSavePersonErrors(Person person) { - Person p = personOperations.insertById(Person.class).one(person); - // if(1==1)throw new RuntimeException("poof!"); - Person pp = personOperations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).all().get(0); - System.err.println("pp=" + pp); - SimulateFailureException.throwEx(); - return Flux.just(p); - } - } - - /* - @Test - public void deletePersonCBTransactionsRxTmpl() { - Person person = new Person(1, "Walter", "White"); - remove(cbTmpl, cName, person.getId().toString()); - rxCBTmpl.insertById(Person.class).inCollection(cName).one(person).block(); - - Mono result = transactions.reactive(((ctx) -> { // get the ctx - return rxCBTmpl.removeById(Person.class).inCollection(cName).transaction(ctx).one(person.getId().toString()) - .then(); - })); - result.block(); - Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); - assertNull(pFound, "Should not have found " + pFound); - } - - @Test - public void deletePersonCBTransactionsRxTmplFail() { - Person person = new Person(1, "Walter", "White"); - remove(cbTmpl, cName, person.getId().toString()); - cbTmpl.insertById(Person.class).inCollection(cName).one(person); - - Mono result = transactions.reactive(((ctx) -> { // get the ctx - return rxCBTmpl.removeById(Person.class).inCollection(cName).transaction(ctx).one(person.getId().toString()) - .then(rxCBTmpl.removeById(Person.class).inCollection(cName).transaction(ctx).one(person.getId().toString())) - .then(); - })); - assertThrows(TransactionFailedException.class, result::block); - Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); - assertEquals(pFound, person, "Should have found " + person); - } - - // RxRepo //////////////////////////////////////////////////////////////////////////////////////////// - - @Test - public void deletePersonCBTransactionsRxRepo() { - Person person = new Person(1, "Walter", "White"); - remove(cbTmpl, cName, person.getId().toString()); - rxRepo.withCollection(cName).save(person).block(); - - Mono result = transactions.reactive(((ctx) -> { // get the ctx - return rxRepo.withCollection(cName).withTransaction(ctx).deleteById(person.getId().toString()).then(); - })); - result.block(); - Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); - assertNull(pFound, "Should not have found " + pFound); - } - - @Test - public void deletePersonCBTransactionsRxRepoFail() { - Person person = new Person(1, "Walter", "White"); - remove(cbTmpl, cName, person.getId().toString()); - rxRepo.withCollection(cName).save(person).block(); - - Mono result = transactions.reactive(((ctx) -> { // get the ctx - return rxRepo.withCollection(cName).withTransaction(ctx).deleteById(person.getId().toString()) - .then(rxRepo.withCollection(cName).withTransaction(ctx).deleteById(person.getId().toString())).then(); - })); - assertThrows(TransactionFailedException.class, result::block); - Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); - assertEquals(pFound, person, "Should have found " + person); - } - - @Test - public void findPersonCBTransactions() { - Person person = new Person(1, "Walter", "White"); - remove(cbTmpl, cName, person.getId().toString()); - cbTmpl.insertById(Person.class).inCollection(cName).one(person); - List docs = new LinkedList<>(); - Query q = Query.query(QueryCriteria.where("meta().id").eq(person.getId())); - Mono result = transactions.reactive(((ctx) -> { // get the ctx - return rxCBTmpl.findByQuery(Person.class).inCollection(cName).matching(q).transaction(ctx).one().map(doc -> { - docs.add(doc); - return doc; - }).then(); - })); - result.block(); - assertFalse(docs.isEmpty(), "Should have found " + person); - for (Object o : docs) { - assertEquals(o, person, "Should have found " + person); - } - } - - @Test - // @Transactional // TODO @Transactional does nothing. Transaction is handled by transactionalOperator - // Failed to retrieve PlatformTransactionManager for @Transactional test: - public void insertPersonRbCBTransactions() { - Person person = new Person(1, "Walter", "White"); - remove(cbTmpl, cName, person.getId().toString()); - - Mono result = transactions.reactive((ctx) -> { // get the ctx - return rxCBTmpl.insertById(Person.class).inCollection(cName).transaction(ctx).one(person) - . flatMap(it -> Mono.error(new PoofException())).then(); - }); - - try { - result.block(); - } catch (TransactionFailedException e) { - e.printStackTrace(); - if (e.getCause() instanceof PoofException) { - Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); - assertNull(pFound, "Should not have found " + pFound); - return; - } else { - e.printStackTrace(); - } - } - throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); - } - - @Test - // @Transactional // TODO @Transactional does nothing. Transaction is handled by transactionalOperator - // Failed to retrieve PlatformTransactionManager for @Transactional test: - public void replacePersonRbCBTransactions() { - Person person = new Person(1, "Walter", "White"); - remove(cbTmpl, cName, person.getId().toString()); - cbTmpl.insertById(Person.class).inCollection(cName).one(person); - Mono result = transactions.reactive((ctx) -> { // get the ctx - return rxCBTmpl.findById(Person.class).inCollection(cName).transaction(ctx).one(person.getId().toString()) - .flatMap(pFound -> rxCBTmpl.replaceById(Person.class).inCollection(cName).transaction(ctx) - .one(pFound.withFirstName("Walt"))) - . flatMap(it -> Mono.error(new PoofException())).then(); - }); - - try { - result.block(); - } catch (TransactionFailedException e) { - if (e.getCause() instanceof PoofException) { - Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); - assertEquals(person, pFound, "Should have found " + person); - return; - } else { - e.printStackTrace(); - } - } - throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); - } - - @Test - public void findPersonSpringTransactions() { - Person person = new Person(1, "Walter", "White"); - remove(cbTmpl, cName, person.getId().toString()); - cbTmpl.insertById(Person.class).inCollection(cName).one(person); - List docs = new LinkedList<>(); - Query q = Query.query(QueryCriteria.where("meta().id").eq(person.getId())); - Mono result = transactions.reactive((ctx) -> { // get the ctx - return rxCBTmpl.findByQuery(Person.class).inCollection(cName).matching(q).transaction(ctx).one().map(doc -> { - docs.add(doc); - return doc; - }).then(); - }); - result.block(); - assertFalse(docs.isEmpty(), "Should have found " + person); - for (Object o : docs) { - assertEquals(o, person, "Should have found " + person); - } - } - */ - void remove(Collection col, String id) { - remove(col.reactive(), id); - } - - void remove(ReactiveCollection col, String id) { - try { - col.remove(id, RemoveOptions.removeOptions().timeout(Duration.ofSeconds(10))).block(); - } catch (DocumentNotFoundException nfe) { - System.out.println(id + " : " + "DocumentNotFound when deleting"); - } - } - - void remove(CouchbaseTemplate template, String collection, String id) { - remove(template.reactive(), collection, id); - } - - void remove(ReactiveCouchbaseTemplate template, String collection, String id) { - try { - template.removeById(Person.class).inCollection(collection).one(id).block(); - System.out.println("removed " + id); - } catch (DocumentNotFoundException | DataRetrievalFailureException nfe) { - System.out.println(id + " : " + "DocumentNotFound when deleting"); - } - } - - @Configuration - @EnableCouchbaseRepositories("org.springframework.data.couchbase") - @EnableReactiveCouchbaseRepositories("org.springframework.data.couchbase") - static class Config extends AbstractCouchbaseConfiguration { - - @Override - public String getConnectionString() { - return connectionString(); - } - - @Override - public String getUserName() { - return config().adminUsername(); - } - - @Override - public String getPassword() { - return config().adminPassword(); - } - - @Override - public String getBucketName() { - return bucketName(); - } - - @Bean - public Cluster couchbaseCluster() { - return Cluster.connect("10.144.220.101", "Administrator", "password"); - } - - /* - @Bean("personService") - PersonService getPersonService(CouchbaseOperations ops, CouchbaseTransactionManager mgr, - ReactiveCouchbaseOperations opsRx, ReactiveCouchbaseTransactionManager mgrRx) { - return new PersonService(ops, mgr, opsRx, mgrRx); - } - */ - - } - - @Data - // @AllArgsConstructor - static class EventLog { - public EventLog() {} - - public EventLog(ObjectId oid, String action) { - this.id = oid.toString(); - this.action = action; - } - - public EventLog(String id, String action) { - this.id = id; - this.action = action; - } - - String id; - String action; - } -} +///* +// * Copyright 2012-2021 the original author or authors +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * https://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +// +//package org.springframework.data.couchbase.transactions; +// +//import com.couchbase.client.java.Cluster; +//import lombok.Data; +//import org.springframework.context.annotation.AnnotationConfigApplicationContext; +//import org.springframework.context.annotation.Bean; +//import org.springframework.data.couchbase.config.BeanNames; +//import org.springframework.data.couchbase.core.CouchbaseOperations; +//import org.springframework.data.couchbase.transaction.CouchbaseSimpleCallbackTransactionManager; +//import org.springframework.data.couchbase.transaction.CouchbaseTransactionManager; +//import org.springframework.data.domain.Persistable; +//import org.springframework.test.context.transaction.AfterTransaction; +//import org.springframework.test.context.transaction.BeforeTransaction; +//import reactor.core.publisher.Flux; +//import reactor.core.publisher.Mono; +//import reactor.test.StepVerifier; +// +//import java.time.Duration; +//import java.util.ArrayList; +//import java.util.List; +//import java.util.UUID; +// +//import org.junit.jupiter.api.AfterAll; +//import org.junit.jupiter.api.BeforeAll; +//import org.junit.jupiter.api.BeforeEach; +//import org.junit.jupiter.api.Test; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.context.support.GenericApplicationContext; +//import org.springframework.dao.DataRetrievalFailureException; +//import org.springframework.data.couchbase.CouchbaseClientFactory; +//import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; +//import org.springframework.data.couchbase.core.CouchbaseTemplate; +//import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations; +//import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate; +//import org.springframework.data.couchbase.core.query.Query; +//import org.springframework.data.couchbase.domain.Person; +//import org.springframework.data.couchbase.domain.PersonRepository; +//import org.springframework.data.couchbase.domain.ReactivePersonRepository; +//import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories; +//import org.springframework.data.couchbase.repository.config.EnableReactiveCouchbaseRepositories; +//import org.springframework.data.couchbase.transaction.ReactiveCouchbaseTransactionManager; +//import org.springframework.data.couchbase.util.Capabilities; +//import org.springframework.data.couchbase.util.ClusterType; +//import org.springframework.data.couchbase.util.IgnoreWhen; +//import org.springframework.data.couchbase.util.JavaIntegrationTests; +//import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +//import org.springframework.transaction.annotation.Transactional; +//import org.springframework.transaction.reactive.TransactionalOperator; +//import org.springframework.transaction.support.DefaultTransactionDefinition; +// +//import com.couchbase.client.core.cnc.Event; +//import com.couchbase.client.core.error.DocumentNotFoundException; +//import com.couchbase.client.java.Collection; +//import com.couchbase.client.java.ReactiveCollection; +//import com.couchbase.client.java.kv.RemoveOptions; +// +//import static com.couchbase.client.java.query.QueryScanConsistency.REQUEST_PLUS; +//import static org.assertj.core.api.Assertions.assertThat; +// +///** +// * Tests for com.couchbase.transactions without using the spring data transactions framework +// * +// * @author Michael Reiche +// */ +//@IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED) +//@SpringJUnitConfig(CouchbasePersonTransactionReactiveIntegrationTests.Config.class) +////@Transactional(transactionManager = BeanNames.COUCHBASE_TRANSACTION_MANAGER) +//public class CouchbasePersonTransactionReactiveIntegrationTests extends JavaIntegrationTests { +// +// @Autowired CouchbaseClientFactory couchbaseClientFactory; +// @Autowired ReactiveCouchbaseTransactionManager reactiveCouchbaseTransactionManager; +// @Autowired CouchbaseSimpleCallbackTransactionManager couchbaseTransactionManager; +// @Autowired ReactivePersonRepository rxRepo; +// @Autowired PersonRepository repo; +// @Autowired ReactiveCouchbaseTemplate rxCBTmpl; +// +// @Autowired Cluster myCluster; +// +// /* DO NOT @Autowired */ PersonService personService; +// +// static GenericApplicationContext context; +// @Autowired ReactiveCouchbaseTemplate operations; +// +// @BeforeAll +// public static void beforeAll() { +// callSuperBeforeAll(new Object() {}); +// context = new AnnotationConfigApplicationContext(CouchbasePersonTransactionReactiveIntegrationTests.Config.class, +// CouchbasePersonTransactionReactiveIntegrationTests.PersonService.class); +// } +// +// @AfterAll +// public static void afterAll() { +// callSuperAfterAll(new Object() {}); +// } +// +// @BeforeEach +// public void beforeEachTest() { +// personService = context.getBean(CouchbasePersonTransactionReactiveIntegrationTests.PersonService.class); // getting it via autowired results in no @Transactional +// operations.removeByQuery(Person.class).withConsistency(REQUEST_PLUS).all().collectList().block(); +// operations.removeByQuery(EventLog.class).withConsistency(REQUEST_PLUS).all().collectList().block(); +// operations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).all().collectList().block(); +// operations.findByQuery(EventLog.class).withConsistency(REQUEST_PLUS).all().collectList().block(); +// } +// +// +// @Test // DATAMONGO-2265 +// public void shouldRollbackAfterException() { +// personService.savePersonErrors(new Person(null, "Walter", "White")) // +// .as(StepVerifier::create) // +// .verifyError(RuntimeException.class); +// // operations.findByQuery(Person.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).count().block(); +// // sleepMs(5000); +// operations.count(new Query(), Person.class) // +// .as(StepVerifier::create) // +// .expectNext(0L) // +// .verifyComplete(); +// } +// +// @Test // DATAMONGO-2265 +// // @Rollback(false) +// public void shouldRollbackAfterExceptionOfTxAnnotatedMethod() { +// Person p = new Person(null, "Walter", "White"); +// try { +// personService.declarativeSavePersonErrors(p) // +// .as(StepVerifier::create) // +// .expectComplete(); +// // .verifyError(RuntimeException.class); +// } catch (RuntimeException e) { +// if (e instanceof SimulateFailureException || (e.getMessage() != null && e.getMessage().contains("poof"))) { +// System.err.println(e); +// } else { +// throw e; +// } +// } +// +// } +// +// @Test // DATAMONGO-2265 +// public void commitShouldPersistTxEntries() { +// +// personService.savePerson(new Person(null, "Walter", "White")) // +// .as(StepVerifier::create) // +// .expectNextCount(1) // +// .verifyComplete(); +// +// operations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count().block(); +// +// operations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count() // +// .as(StepVerifier::create) // +// .expectNext(1L) // +// .verifyComplete(); +// } +// +// @Test // DATAMONGO-2265 +// public void commitShouldPersistTxEntriesOfTxAnnotatedMethod() { +// +// personService.declarativeSavePerson(new Person(null, "Walter", "White")).as(StepVerifier::create) // +// .expectNextCount(1) // +// .verifyComplete(); +// +// operations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count() // +// .as(StepVerifier::create) // +// .expectNext(1L) // +// .verifyComplete(); +// +// } +// +// @Test // DATAMONGO-2265 +// public void commitShouldPersistTxEntriesAcrossCollections() { +// +// personService.saveWithLogs(new Person(null, "Walter", "White")) // +// .then() // +// .as(StepVerifier::create) // +// .verifyComplete(); +// +// operations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count() // +// .as(StepVerifier::create) // +// .expectNext(1L) // +// .verifyComplete(); +// +// operations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count() // +// .as(StepVerifier::create) // +// .expectNext(4L) // +// .verifyComplete(); +// } +// +// @Test // DATAMONGO-2265 +// public void rollbackShouldAbortAcrossCollections() { +// +// personService.saveWithErrorLogs(new Person(null, "Walter", "White")) // +// .then() // +// .as(StepVerifier::create) // +// .verifyError(); +// +// operations.count(new Query(), Person.class) // +// .as(StepVerifier::create) // +// .expectNext(0L) // +// .verifyComplete(); +// +// operations.count(new Query(), EventLog.class)// +// .as(StepVerifier::create) // +// .expectNext(0L) // +// .verifyComplete(); +// } +// +// @Test // DATAMONGO-2265 +// public void countShouldWorkInsideTransaction() { +// +// personService.countDuringTx(new Person(null, "Walter", "White")) // +// .as(StepVerifier::create) // +// .expectNext(1L) // +// .verifyComplete(); +// } +// +// @Test // DATAMONGO-2265 +// public void emitMultipleElementsDuringTransaction() { +// +// try { +// personService.saveWithLogs(new Person(null, "Walter", "White")) // +// .as(StepVerifier::create) // +// .expectNextCount(4L) // +// .verifyComplete(); +// } catch (Exception e) { +// System.err.println("Done"); +// throw e; +// } +// } +// +// @Test // DATAMONGO-2265 +// public void errorAfterTxShouldNotAffectPreviousStep() { +// +// Person p = new Person(1, "Walter", "White"); +// remove(couchbaseTemplate, "_default", p.getId().toString()); +// personService.savePerson(p) // +// //.delayElement(Duration.ofMillis(100)) // +// .then(Mono.error(new RuntimeException("my big bad evil error"))).as(StepVerifier::create) // +// .expectError() +// .verify(); +// //.expectError() // +// //.as(StepVerifier::create) +// //.expectNext(p) +// //.verifyComplete(); +// +// operations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count() // +// .as(StepVerifier::create) // +// .expectNext(1L) // +// .verifyComplete(); +// } +// +// // @RequiredArgsConstructor +// static class PersonService { +// +// final ReactiveCouchbaseOperations personOperationsRx; +// final ReactiveCouchbaseTransactionManager managerRx; +// final CouchbaseOperations personOperations; +// final CouchbaseCallbackTransactionManager manager; +// +// public PersonService(CouchbaseOperations ops, CouchbaseCallbackTransactionManager mgr, ReactiveCouchbaseOperations opsRx, +// ReactiveCouchbaseTransactionManager mgrRx) { +// personOperations = ops; +// manager = mgr; +// System.err.println("operations cluster : " + personOperations.getCouchbaseClientFactory().getCluster()); +//// System.err.println("manager cluster : " + manager.getDatabaseFactory().getCluster()); +// System.err.println("manager Manager : " + manager); +// +// personOperationsRx = opsRx; +// managerRx = mgrRx; +// System.out +// .println("operationsRx cluster : " + personOperationsRx.getCouchbaseClientFactory().getCluster().block()); +// System.out.println("managerRx cluster : " + mgrRx.getDatabaseFactory().getCluster().block()); +// System.out.println("managerRx Manager : " + managerRx); +// return; +// } +// +// public Mono savePersonErrors(Person person) { +// +// TransactionalOperator transactionalOperator = TransactionalOperator.create(managerRx); +// return personOperationsRx.insertById(Person.class).one(person) // +// . flatMap(it -> Mono.error(new RuntimeException("poof!"))) // +// .as(transactionalOperator::transactional); +// } +// +// public Mono savePerson(Person person) { +// +// TransactionalOperator transactionalOperator = TransactionalOperator.create(managerRx, +// new DefaultTransactionDefinition()); +// +// return personOperationsRx.insertById(Person.class).one(person) // +// .flatMap(Mono::just) // +// .as(transactionalOperator::transactional); +// } +// +// public Mono countDuringTx(Person person) { +// +// TransactionalOperator transactionalOperator = TransactionalOperator.create(managerRx, +// new DefaultTransactionDefinition()); +// +// return personOperationsRx.save(person) // +// .then(personOperationsRx.count(new Query(), Person.class)) // +// .as(transactionalOperator::transactional); +// } +// +// public Flux saveWithLogs(Person person) { +// +// TransactionalOperator transactionalOperator = TransactionalOperator.create(managerRx, +// new DefaultTransactionDefinition()); +// +// return Flux.merge(personOperationsRx.save(new EventLog(new ObjectId().toString(), "beforeConvert")), // +// personOperationsRx.save(new EventLog(new ObjectId(), "afterConvert")), // +// personOperationsRx.save(new EventLog(new ObjectId(), "beforeInsert")), // +// personOperationsRx.save(person), // +// personOperationsRx.save(new EventLog(new ObjectId(), "afterInsert"))) // +// .thenMany(personOperationsRx.findByQuery(EventLog.class).all()) // +// .as(transactionalOperator::transactional); +// } +// +// public Flux saveWithErrorLogs(Person person) { +// +// TransactionalOperator transactionalOperator = TransactionalOperator.create(managerRx, +// new DefaultTransactionDefinition()); +// +// return Flux.merge(personOperationsRx.save(new EventLog(new ObjectId(), "beforeConvert")), // +// personOperationsRx.save(new EventLog(new ObjectId(), "afterConvert")), // +// personOperationsRx.save(new EventLog(new ObjectId(), "beforeInsert")), // +// personOperationsRx.save(person), // +// personOperationsRx.save(new EventLog(new ObjectId(), "afterInsert"))) // +// . flatMap(it -> Mono.error(new RuntimeException("poof!"))) // +// .as(transactionalOperator::transactional); +// } +// +// @Transactional +// public Flux declarativeSavePerson(Person person) { +// +// TransactionalOperator transactionalOperator = TransactionalOperator.create(managerRx, +// new DefaultTransactionDefinition()); +// +// return transactionalOperator.execute(reactiveTransaction -> personOperationsRx.save(person)); +// } +// +// @Transactional(transactionManager = BeanNames.COUCHBASE_TRANSACTION_MANAGER) +// public Flux declarativeSavePersonErrors(Person person) { +// Person p = personOperations.insertById(Person.class).one(person); +// // if(1==1)throw new RuntimeException("poof!"); +// Person pp = personOperations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).all().get(0); +// System.err.println("pp=" + pp); +// SimulateFailureException.throwEx(); +// return Flux.just(p); +// } +// } +// +// /* +// @Test +// public void deletePersonCBTransactionsRxTmpl() { +// Person person = new Person(1, "Walter", "White"); +// remove(cbTmpl, cName, person.getId().toString()); +// rxCBTmpl.insertById(Person.class).inCollection(cName).one(person).block(); +// +// Mono result = transactions.reactive(((ctx) -> { // get the ctx +// return rxCBTmpl.removeById(Person.class).inCollection(cName).transaction(ctx).one(person.getId().toString()) +// .then(); +// })); +// result.block(); +// Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); +// assertNull(pFound, "Should not have found " + pFound); +// } +// +// @Test +// public void deletePersonCBTransactionsRxTmplFail() { +// Person person = new Person(1, "Walter", "White"); +// remove(cbTmpl, cName, person.getId().toString()); +// cbTmpl.insertById(Person.class).inCollection(cName).one(person); +// +// Mono result = transactions.reactive(((ctx) -> { // get the ctx +// return rxCBTmpl.removeById(Person.class).inCollection(cName).transaction(ctx).one(person.getId().toString()) +// .then(rxCBTmpl.removeById(Person.class).inCollection(cName).transaction(ctx).one(person.getId().toString())) +// .then(); +// })); +// assertThrows(TransactionFailedException.class, result::block); +// Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); +// assertEquals(pFound, person, "Should have found " + person); +// } +// +// // RxRepo //////////////////////////////////////////////////////////////////////////////////////////// +// +// @Test +// public void deletePersonCBTransactionsRxRepo() { +// Person person = new Person(1, "Walter", "White"); +// remove(cbTmpl, cName, person.getId().toString()); +// rxRepo.withCollection(cName).save(person).block(); +// +// Mono result = transactions.reactive(((ctx) -> { // get the ctx +// return rxRepo.withCollection(cName).withTransaction(ctx).deleteById(person.getId().toString()).then(); +// })); +// result.block(); +// Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); +// assertNull(pFound, "Should not have found " + pFound); +// } +// +// @Test +// public void deletePersonCBTransactionsRxRepoFail() { +// Person person = new Person(1, "Walter", "White"); +// remove(cbTmpl, cName, person.getId().toString()); +// rxRepo.withCollection(cName).save(person).block(); +// +// Mono result = transactions.reactive(((ctx) -> { // get the ctx +// return rxRepo.withCollection(cName).withTransaction(ctx).deleteById(person.getId().toString()) +// .then(rxRepo.withCollection(cName).withTransaction(ctx).deleteById(person.getId().toString())).then(); +// })); +// assertThrows(TransactionFailedException.class, result::block); +// Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); +// assertEquals(pFound, person, "Should have found " + person); +// } +// +// @Test +// public void findPersonCBTransactions() { +// Person person = new Person(1, "Walter", "White"); +// remove(cbTmpl, cName, person.getId().toString()); +// cbTmpl.insertById(Person.class).inCollection(cName).one(person); +// List docs = new LinkedList<>(); +// Query q = Query.query(QueryCriteria.where("meta().id").eq(person.getId())); +// Mono result = transactions.reactive(((ctx) -> { // get the ctx +// return rxCBTmpl.findByQuery(Person.class).inCollection(cName).matching(q).transaction(ctx).one().map(doc -> { +// docs.add(doc); +// return doc; +// }).then(); +// })); +// result.block(); +// assertFalse(docs.isEmpty(), "Should have found " + person); +// for (Object o : docs) { +// assertEquals(o, person, "Should have found " + person); +// } +// } +// +// @Test +// // @Transactional // TODO @Transactional does nothing. Transaction is handled by transactionalOperator +// // Failed to retrieve PlatformTransactionManager for @Transactional test: +// public void insertPersonRbCBTransactions() { +// Person person = new Person(1, "Walter", "White"); +// remove(cbTmpl, cName, person.getId().toString()); +// +// Mono result = transactions.reactive((ctx) -> { // get the ctx +// return rxCBTmpl.insertById(Person.class).inCollection(cName).transaction(ctx).one(person) +// . flatMap(it -> Mono.error(new PoofException())).then(); +// }); +// +// try { +// result.block(); +// } catch (TransactionFailedException e) { +// e.printStackTrace(); +// if (e.getCause() instanceof PoofException) { +// Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); +// assertNull(pFound, "Should not have found " + pFound); +// return; +// } else { +// e.printStackTrace(); +// } +// } +// throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); +// } +// +// @Test +// // @Transactional // TODO @Transactional does nothing. Transaction is handled by transactionalOperator +// // Failed to retrieve PlatformTransactionManager for @Transactional test: +// public void replacePersonRbCBTransactions() { +// Person person = new Person(1, "Walter", "White"); +// remove(cbTmpl, cName, person.getId().toString()); +// cbTmpl.insertById(Person.class).inCollection(cName).one(person); +// Mono result = transactions.reactive((ctx) -> { // get the ctx +// return rxCBTmpl.findById(Person.class).inCollection(cName).transaction(ctx).one(person.getId().toString()) +// .flatMap(pFound -> rxCBTmpl.replaceById(Person.class).inCollection(cName).transaction(ctx) +// .one(pFound.withFirstName("Walt"))) +// . flatMap(it -> Mono.error(new PoofException())).then(); +// }); +// +// try { +// result.block(); +// } catch (TransactionFailedException e) { +// if (e.getCause() instanceof PoofException) { +// Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); +// assertEquals(person, pFound, "Should have found " + person); +// return; +// } else { +// e.printStackTrace(); +// } +// } +// throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); +// } +// +// @Test +// public void findPersonSpringTransactions() { +// Person person = new Person(1, "Walter", "White"); +// remove(cbTmpl, cName, person.getId().toString()); +// cbTmpl.insertById(Person.class).inCollection(cName).one(person); +// List docs = new LinkedList<>(); +// Query q = Query.query(QueryCriteria.where("meta().id").eq(person.getId())); +// Mono result = transactions.reactive((ctx) -> { // get the ctx +// return rxCBTmpl.findByQuery(Person.class).inCollection(cName).matching(q).transaction(ctx).one().map(doc -> { +// docs.add(doc); +// return doc; +// }).then(); +// }); +// result.block(); +// assertFalse(docs.isEmpty(), "Should have found " + person); +// for (Object o : docs) { +// assertEquals(o, person, "Should have found " + person); +// } +// } +// */ +// void remove(Collection col, String id) { +// remove(col.reactive(), id); +// } +// +// void remove(ReactiveCollection col, String id) { +// try { +// col.remove(id, RemoveOptions.removeOptions().timeout(Duration.ofSeconds(10))).block(); +// } catch (DocumentNotFoundException nfe) { +// System.out.println(id + " : " + "DocumentNotFound when deleting"); +// } +// } +// +// void remove(CouchbaseTemplate template, String collection, String id) { +// remove(template.reactive(), collection, id); +// } +// +// void remove(ReactiveCouchbaseTemplate template, String collection, String id) { +// try { +// template.removeById(Person.class).inCollection(collection).one(id).block(); +// System.out.println("removed " + id); +// } catch (DocumentNotFoundException | DataRetrievalFailureException nfe) { +// System.out.println(id + " : " + "DocumentNotFound when deleting"); +// } +// } +// +// @Configuration +// @EnableCouchbaseRepositories("org.springframework.data.couchbase") +// @EnableReactiveCouchbaseRepositories("org.springframework.data.couchbase") +// static class Config extends AbstractCouchbaseConfiguration { +// +// @Override +// public String getConnectionString() { +// return connectionString(); +// } +// +// @Override +// public String getUserName() { +// return config().adminUsername(); +// } +// +// @Override +// public String getPassword() { +// return config().adminPassword(); +// } +// +// @Override +// public String getBucketName() { +// return bucketName(); +// } +// +// @Bean +// public Cluster couchbaseCluster() { +// return Cluster.connect("10.144.220.101", "Administrator", "password"); +// } +// +// /* +// @Bean("personService") +// PersonService getPersonService(CouchbaseOperations ops, CouchbaseTransactionManager mgr, +// ReactiveCouchbaseOperations opsRx, ReactiveCouchbaseTransactionManager mgrRx) { +// return new PersonService(ops, mgr, opsRx, mgrRx); +// } +// */ +// +// } +// +// @Data +// // @AllArgsConstructor +// static class EventLog { +// public EventLog() {} +// +// public EventLog(ObjectId oid, String action) { +// this.id = oid.toString(); +// this.action = action; +// } +// +// public EventLog(String id, String action) { +// this.id = id; +// this.action = action; +// } +// +// String id; +// String action; +// } +//}