/*
 * Copyright 2002-2019 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.transaction.reactive;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import org.springframework.transaction.ReactiveTransaction;
import org.springframework.transaction.ReactiveTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.util.Assert;

/**
 * Operator class that simplifies programmatic transaction demarcation and
 * transaction exception handling.
 *
 * @author Mark Paluch
 * @author Juergen Hoeller
 * @since 5.2
 * @see #execute
 * @see ReactiveTransactionManager
 */
@SuppressWarnings("serial")
final class TransactionalOperatorImpl implements TransactionalOperator {

	private static final Log logger = LogFactory.getLog(TransactionalOperatorImpl.class);

	private final ReactiveTransactionManager transactionManager;

	private final TransactionDefinition transactionDefinition;


	/**
	 * Construct a new TransactionTemplate using the given transaction manager,
	 * taking its default settings from the given transaction definition.
	 * @param transactionManager the transaction management strategy to be used
	 * @param transactionDefinition the transaction definition to copy the
	 * default settings from. Local properties can still be set to change values.
	 */
	TransactionalOperatorImpl(ReactiveTransactionManager transactionManager, TransactionDefinition transactionDefinition) {
		Assert.notNull(transactionManager, "ReactiveTransactionManager must not be null");
		Assert.notNull(transactionManager, "TransactionDefinition must not be null");
		this.transactionManager = transactionManager;
		this.transactionDefinition = transactionDefinition;
	}


	/**
	 * Return the transaction management strategy to be used.
	 */
	public ReactiveTransactionManager getTransactionManager() {
		return this.transactionManager;
	}


	@Override
	public <T> Flux<T> execute(TransactionCallback<T> action) throws TransactionException {
		return TransactionContextManager.currentContext().flatMapMany(context -> {
			Mono<ReactiveTransaction> status = this.transactionManager.getReactiveTransaction(this.transactionDefinition);
			return status.flatMapMany(it -> {
				// This is an around advice: Invoke the next interceptor in the chain.
				// This will normally result in a target object being invoked.
				// Need re-wrapping of ReactiveTransaction until we get hold of the exception
				// through usingWhen.
				return Flux.usingWhen(Mono.just(it), action::doInTransaction,
						this.transactionManager::commit, s -> Mono.empty())
						.onErrorResume(ex -> rollbackOnException(it, ex).then(Mono.error(ex)));
			});
		})
		.subscriberContext(TransactionContextManager.getOrCreateContext())
		.subscriberContext(TransactionContextManager.getOrCreateContextHolder());
	}

	/**
	 * Perform a rollback, handling rollback exceptions properly.
	 * @param status object representing the transaction
	 * @param ex the thrown application exception or error
	 * @throws TransactionException in case of a rollback error
	 */
	private Mono<Void> rollbackOnException(ReactiveTransaction status, Throwable ex) throws TransactionException {
		logger.debug("Initiating transaction rollback on application exception", ex);
		return this.transactionManager.rollback(status).onErrorMap(ex2 -> {
					logger.error("Application exception overridden by rollback exception", ex);
					if (ex2 instanceof TransactionSystemException) {
						((TransactionSystemException) ex2).initApplicationException(ex);
					}
					return ex2;
				}
		);
	}


	@Override
	public boolean equals(Object other) {
		return (this == other || (super.equals(other) && (!(other instanceof TransactionalOperatorImpl) ||
				getTransactionManager() == ((TransactionalOperatorImpl) other).getTransactionManager())));
	}

	@Override
	public int hashCode() {
		return getTransactionManager().hashCode();
	}

}

@@ -2978,46 +2978,8 @@
 n);%0A
-%09%09%09return status.flatMapMany(it -%3E %7B%0A%09
 %09%09%09/
@@ -3051,17 +3051,16 @@
 ain.%0A%09%09%09
-%09
 // This 
@@ -3114,17 +3114,16 @@
 nvoked.%0A
-%09
 %09%09%09// Ne
@@ -3195,17 +3195,16 @@
 ception%0A
-%09
 %09%09%09// th
@@ -3223,23 +3223,47 @@
 hen.%0A%09%09%09
-%09
 return
+ status.flatMapMany(it -%3E
  Flux.us
@@ -3315,17 +3315,16 @@
 n,%0A%09%09%09%09%09
-%09
 this.tra
@@ -3367,17 +3367,16 @@
 mpty())%0A
-%09
 %09%09%09%09%09.on
@@ -3446,14 +3446,8 @@
 x)))
-;%0A%09%09%09%7D
 );%0A%09

Fix Checkstyle violation