/* * Copyright 2013-2014 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 * * 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 org.springframework.cloud.aws.jdbc.retry; import org.aopalliance.intercept.MethodInvocation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.retry.interceptor.RetryOperationsInterceptor; import org.springframework.retry.support.RetrySynchronizationManager; import org.springframework.transaction.support.TransactionSynchronizationManager; /** * Subclass of {@link RetryOperationsInterceptor} that checks that there is no transaction available while starting a * retryable operation. This class also ensures that there is only one outer retry operation in case of nested * retryable methods. * <p>This allows service to call other service that might have a retry interceptor configured.</p> * * @author Agim Emruli * @since 1.0 */ public class RdbmsRetryOperationsInterceptor extends RetryOperationsInterceptor { private static final Logger LOGGER = LoggerFactory.getLogger(RdbmsRetryOperationsInterceptor.class); /** * Checks that there is no retry operation open before delegating to the method {@link * RetryOperationsInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)} method. Execute the MethodInvocation * directly if there is already a {@link org.springframework.retry.RetryContext} available for the current thread * execution. * * @param invocation * - the method invocation that is the target of this interceptor * @return the result of the method invocation * @throws Throwable * - the exception thrown by the method invocations target or a {@link JdbcRetryException} if there is already a * transaction available. */ @Override public Object invoke(MethodInvocation invocation) throws Throwable { Object result; if (!isRetryContextOperationActive()) { assertNoTransactionActive(); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Starting a new Retry Interceptor for {}", (invocation != null ? invocation.getMethod() : null)); } result = super.invoke(invocation); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Finished a new Retry Interceptor for {}", (invocation != null ? invocation.getMethod() : null)); } } else { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Participating in existing retry operation"); } result = invocation.proceed(); } return result; } /** * Returns whenever there is already a proxy running inside this thread execution. To avoid multiple retries in the * case if this bean is called by another bean which already has a RetryOperationsInterceptor. * * @return <code>true</code> if there is a {@link org.springframework.retry.RetryContext} available inside the {@link * RetrySynchronizationManager} or <code>false</code> otherwise. */ protected boolean isRetryContextOperationActive() { return RetrySynchronizationManager.getContext() != null; } /** * Checks that there is no current transaction active. This should never happen as this interceptor must run actually * before a transaction is created, to ensure a new transaction is started while retrying. */ private static void assertNoTransactionActive() { if (TransactionSynchronizationManager.isActualTransactionActive()) { throw new JdbcRetryException("An active transaction was found. This is not allowed when starting a retryable operation."); } } }