Package org.springframework.data.redis.core

Source Code of org.springframework.data.redis.core.RedisConnectionUtils$RedisTransactionSynchronizer

/*
* Copyright 2011-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.data.redis.core;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.transaction.support.ResourceHolder;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;

/**
* Helper class featuring {@link RedisConnection} handling, allowing for reuse of instances within
* 'transactions'/scopes.
*
* @author Costin Leau
* @author Christoph Strobl
* @author Thomas Darimont
*/
public abstract class RedisConnectionUtils {

  private static final Log log = LogFactory.getLog(RedisConnectionUtils.class);

  /**
   * Binds a new Redis connection (from the given factory) to the current thread, if none is already bound.
   *
   * @param factory connection factory
   * @return a new Redis connection without transaction support.
   */
  public static RedisConnection bindConnection(RedisConnectionFactory factory) {
    return bindConnection(factory, false);
  }

  /**
   * Binds a new Redis connection (from the given factory) to the current thread, if none is already bound and enables
   * transaction support if {@code enableTranactionSupport} is set to {@literal true}.
   *
   * @param factory connection factory
   * @param enableTranactionSupport
   * @return a new Redis connection with transaction support if requested.
   */
  public static RedisConnection bindConnection(RedisConnectionFactory factory, boolean enableTranactionSupport) {
    return doGetConnection(factory, true, true, enableTranactionSupport);
  }

  /**
   * Gets a Redis connection from the given factory. Is aware of and will return any existing corresponding connections
   * bound to the current thread, for example when using a transaction manager. Will always create a new connection
   * otherwise.
   *
   * @param factory connection factory for creating the connection
   * @return an active Redis connection without transaction management.
   */
  public static RedisConnection getConnection(RedisConnectionFactory factory) {
    return getConnection(factory, false);
  }

  /**
   * Gets a Redis connection from the given factory. Is aware of and will return any existing corresponding connections
   * bound to the current thread, for example when using a transaction manager. Will always create a new connection
   * otherwise.
   *
   * @param factory connection factory for creating the connection
   * @param enableTranactionSupport
   * @return an active Redis connection with transaction management if requested.
   */
  public static RedisConnection getConnection(RedisConnectionFactory factory, boolean enableTranactionSupport) {
    return doGetConnection(factory, true, false, enableTranactionSupport);
  }

  /**
   * Gets a Redis connection. Is aware of and will return any existing corresponding connections bound to the current
   * thread, for example when using a transaction manager. Will create a new Connection otherwise, if
   * {@code allowCreate} is <tt>true</tt>.
   *
   * @param factory connection factory for creating the connection
   * @param allowCreate whether a new (unbound) connection should be created when no connection can be found for the
   *          current thread
   * @param bind binds the connection to the thread, in case one was created
   * @param enableTransactionSupport
   * @return an active Redis connection
   */
  public static RedisConnection doGetConnection(RedisConnectionFactory factory, boolean allowCreate, boolean bind,
      boolean enableTransactionSupport) {

    Assert.notNull(factory, "No RedisConnectionFactory specified");

    RedisConnectionHolder connHolder = (RedisConnectionHolder) TransactionSynchronizationManager.getResource(factory);

    if (connHolder != null) {
      if (enableTransactionSupport) {
        potentiallyRegisterTransactionSynchronisation(connHolder, factory);
      }
      return connHolder.getConnection();
    }

    if (!allowCreate) {
      throw new IllegalArgumentException("No connection found and allowCreate = false");
    }

    if (log.isDebugEnabled()) {
      log.debug("Opening RedisConnection");
    }

    RedisConnection conn = factory.getConnection();

    if (bind) {

      RedisConnection connectionToBind = conn;
      if (enableTransactionSupport && isActualNonReadonlyTransactionActive()) {
        connectionToBind = createConnectionProxy(conn, factory);
      }

      connHolder = new RedisConnectionHolder(connectionToBind);

      TransactionSynchronizationManager.bindResource(factory, connHolder);
      if (enableTransactionSupport) {
        potentiallyRegisterTransactionSynchronisation(connHolder, factory);
      }

      return connHolder.getConnection();
    }

    return conn;
  }

  private static void potentiallyRegisterTransactionSynchronisation(RedisConnectionHolder connHolder,
      final RedisConnectionFactory factory) {

    if (isActualNonReadonlyTransactionActive()) {

      if (!connHolder.isTransactionSyncronisationActive()) {
        connHolder.setTransactionSyncronisationActive(true);
       
        RedisConnection conn = connHolder.getConnection();
        conn.multi();

        TransactionSynchronizationManager.registerSynchronization(new RedisTransactionSynchronizer(connHolder, conn));
      }
    }
  }

  private static boolean isActualNonReadonlyTransactionActive() {
    return TransactionSynchronizationManager.isActualTransactionActive()
        && !TransactionSynchronizationManager.isCurrentTransactionReadOnly();
  }

  private static RedisConnection createConnectionProxy(RedisConnection connection, RedisConnectionFactory factory) {

    ProxyFactory proxyFactory = new ProxyFactory(connection);
    proxyFactory.addAdvice(new ConnectionSplittingInterceptor(factory));

    return RedisConnection.class.cast(proxyFactory.getProxy());
  }

  /**
   * Closes the given connection, created via the given factory if not managed externally (i.e. not bound to the
   * thread).
   *
   * @param conn the Redis connection to close
   * @param factory the Redis factory that the connection was created with
   */
  public static void releaseConnection(RedisConnection conn, RedisConnectionFactory factory) {

    if (conn == null) {
      return;
    }

    RedisConnectionHolder connHolder = (RedisConnectionHolder) TransactionSynchronizationManager.getResource(factory);

    if (connHolder != null && connHolder.isTransactionSyncronisationActive()) {
      if (log.isDebugEnabled()) {
        log.debug("Redis Connection will be closed when transaction finished.");
      }
      return;
    }

    // Only release non-transactional/non-bound connections.
    if (!isConnectionTransactional(conn, factory)) {
      if (log.isDebugEnabled()) {
        log.debug("Closing Redis Connection");
      }
      conn.close();
    }
  }

  /**
   * Unbinds and closes the connection (if any) associated with the given factory.
   *
   * @param factory Redis factory
   */
  public static void unbindConnection(RedisConnectionFactory factory) {

    RedisConnectionHolder connHolder = (RedisConnectionHolder) TransactionSynchronizationManager
        .unbindResourceIfPossible(factory);

    if (connHolder != null) {
      if (connHolder.isTransactionSyncronisationActive()) {
        if (log.isDebugEnabled()) {
          log.debug("Redis Connection will be closed when outer transaction finished.");
        }
      } else {
        if (log.isDebugEnabled()) {
          log.debug("Closing bound connection.");
        }
        RedisConnection connection = connHolder.getConnection();
        connection.close();
      }
    }

  }

  /**
   * Return whether the given Redis connection is transactional, that is, bound to the current thread by Spring's
   * transaction facilities.
   *
   * @param conn Redis connection to check
   * @param connFactory Redis connection factory that the connection was created with
   * @return whether the connection is transactional or not
   */
  public static boolean isConnectionTransactional(RedisConnection conn, RedisConnectionFactory connFactory) {
    if (connFactory == null) {
      return false;
    }
    RedisConnectionHolder connHolder = (RedisConnectionHolder) TransactionSynchronizationManager
        .getResource(connFactory);

    return (connHolder != null && conn == connHolder.getConnection());
  }

  /**
   * A {@link TransactionSynchronizationAdapter} that makes sure that the associated RedisConnection is released after the transaction completes.
   *
   * @author Christoph Strobl
   * @author Thomas Darimont
   */
  private static class RedisTransactionSynchronizer extends TransactionSynchronizationAdapter {
   
    private final RedisConnectionHolder connHolder;
    private final RedisConnection connection;

    /**
     * Creates a new {@link RedisTransactionSynchronizer}.
     *
     * @param connHolder
     * @param connection
     */
    private RedisTransactionSynchronizer(RedisConnectionHolder connHolder, RedisConnection connection) {
     
      this.connHolder = connHolder;
      this.connection = connection;
    }

    @Override
    public void afterCompletion(int status) {

      try {
        switch (status) {

          case TransactionSynchronization.STATUS_COMMITTED:
            connection.exec();
            break;

          case TransactionSynchronization.STATUS_ROLLED_BACK:
          case TransactionSynchronization.STATUS_UNKNOWN:
          default:
            connection.discard();
        }
      } finally {

        if (log.isDebugEnabled()) {
          log.debug("Closing bound connection after transaction completed with " + status);
        }
       
        connHolder.setTransactionSyncronisationActive(false);
        connection.close();
      }
    }
  }

  /**
   * @author Christoph Strobl
   * @since 1.3
   */
  static class ConnectionSplittingInterceptor implements MethodInterceptor,
      org.springframework.cglib.proxy.MethodInterceptor {

    private final RedisConnectionFactory factory;

    public ConnectionSplittingInterceptor(RedisConnectionFactory factory) {
      this.factory = factory;
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

      RedisCommand commandToExecute = RedisCommand.failsafeCommandLookup(method.getName());

      if (isPotentiallyThreadBoundCommand(commandToExecute)) {

        if (log.isDebugEnabled()) {
          log.debug(String.format("Invoke '%s' on bound conneciton", method.getName()));
        }

        return invoke(method, obj, args);
      }

      if (log.isDebugEnabled()) {
        log.debug(String.format("Invoke '%s' on unbound conneciton", method.getName()));
      }

      RedisConnection connection = factory.getConnection();

      try {
        return invoke(method, connection, args);
      } finally {
        // properly close the unbound connection after executing command
        if (!connection.isClosed()) {
          connection.close();
        }
      }
    }

    private Object invoke(Method method, Object target, Object[] args) throws Throwable {

      try {
        return method.invoke(target, args);
      } catch (InvocationTargetException e) {
        throw e.getCause();
      }
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
      return intercept(invocation.getThis(), invocation.getMethod(), invocation.getArguments(), null);
    }

    private boolean isPotentiallyThreadBoundCommand(RedisCommand command) {
      return RedisCommand.UNKNOWN.equals(command) || !command.isReadonly();
    }
  }

  /**
   * @author Christoph Strobl
   */
  private static class RedisConnectionHolder implements ResourceHolder {

    private boolean unbound;
    private final RedisConnection conn;
    private boolean transactionSyncronisationActive;

    public RedisConnectionHolder(RedisConnection conn) {
      this.conn = conn;
    }

    public boolean isVoid() {
      return unbound;
    }

    public RedisConnection getConnection() {
      return conn;
    }

    public void reset() {
      // no-op
    }

    public void unbound() {
      this.unbound = true;
    }

    /**
     * @return
     * @since 1.3
     */
    public boolean isTransactionSyncronisationActive() {
      return transactionSyncronisationActive;
    }

    /**
     * @param transactionSyncronisationActive
     * @since 1.3
     */
    public void setTransactionSyncronisationActive(boolean transactionSyncronisationActive) {
      this.transactionSyncronisationActive = transactionSyncronisationActive;
    }
  }
}
TOP

Related Classes of org.springframework.data.redis.core.RedisConnectionUtils$RedisTransactionSynchronizer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.