/**
   * Constructs a new LocatorInvocationHandler
   *
   * @param parent
   * @param proxy
   * @param invoker
   * @param locatorClass
   * @param locators
   * @param updateCopy
   * @param readMethodSet
   * @param writeMethodSet
   */
  protected LocatorInvocationHandler(
      P parent,
      SQLProxy<Z, D, P, SQLException> proxy,
      Invoker<Z, D, P, T, SQLException> invoker,
      Class<T> locatorClass,
      Map<D, T> locators,
      boolean updateCopy,
      Set<Method> readMethodSet,
      Set<Method> writeMethodSet) {
    super(parent, proxy, invoker, locatorClass, SQLException.class, locators);

    this.freeMethod = Methods.findMethod(locatorClass, "free");
    this.updateCopy = updateCopy;
    this.readMethodSet = readMethodSet;
    this.writeMethodSet = writeMethodSet;
  }
/**
 * @author Paul Ferraro
 * @param <D>
 * @param <P>
 */
public class ConnectionInvocationHandler<Z, D extends Database<Z>, P>
    extends ChildInvocationHandler<
        Z, D, P, SQLException, Connection, SQLException, ConnectionProxyFactory<Z, D, P>> {
  private static final Set<Method> driverReadMethodSet =
      Methods.findMethods(
          Connection.class,
          "createStruct",
          "getAutoCommit",
          "getCatalog",
          "getClientInfo",
          "getHoldability",
          "getNetworkTimeout",
          "getSchema",
          "getTypeMap",
          "getWarnings",
          "isClosed",
          "isCloseOnCompletion",
          "isReadOnly",
          "nativeSQL");
  private static final Set<Method> databaseReadMethodSet =
      Methods.findMethods(Connection.class, "getTransactionIsolation", "isValid");
  private static final Set<Method> driverWriterMethodSet =
      Methods.findMethods(
          Connection.class,
          "abort",
          "clearWarnings",
          "closeOnCompletion",
          "setClientInfo",
          "setHoldability",
          "setNetworkTimeout",
          "setSchema",
          "setTypeMap");
  private static final Set<Method> createStatementMethodSet =
      Methods.findMethods(Connection.class, "createStatement");
  private static final Set<Method> prepareStatementMethodSet =
      Methods.findMethods(Connection.class, "prepareStatement");
  private static final Set<Method> prepareCallMethodSet =
      Methods.findMethods(Connection.class, "prepareCall");
  private static final Set<Method> setSavepointMethodSet =
      Methods.findMethods(Connection.class, "setSavepoint");

  private static final Method setAutoCommitMethod =
      Methods.getMethod(Connection.class, "setAutoCommit", Boolean.TYPE);
  private static final Method commitMethod = Methods.getMethod(Connection.class, "commit");
  private static final Method rollbackMethod = Methods.getMethod(Connection.class, "rollback");
  private static final Method getMetaDataMethod =
      Methods.getMethod(Connection.class, "getMetaData");
  private static final Method releaseSavepointMethod =
      Methods.getMethod(Connection.class, "releaseSavepoint", Savepoint.class);
  private static final Method rollbackSavepointMethod =
      Methods.getMethod(Connection.class, "rollback", Savepoint.class);
  private static final Method closeMethod = Methods.getMethod(Connection.class, "close");
  private static final Method createArrayMethod =
      Methods.getMethod(Connection.class, "createArrayOf", String.class, Object[].class);
  private static final Method createBlobMethod = Methods.getMethod(Connection.class, "createBlob");
  private static final Method createClobMethod = Methods.getMethod(Connection.class, "createClob");
  private static final Method createNClobMethod =
      Methods.getMethod(Connection.class, "createNClob");
  private static final Method createSQLXMLMethod =
      Methods.getMethod(Connection.class, "createSQLXML");

  private static final Set<Method> endTransactionMethodSet =
      new HashSet<Method>(Arrays.asList(commitMethod, rollbackMethod, setAutoCommitMethod));
  private static final Set<Method> createLocatorMethodSet =
      new HashSet<Method>(
          Arrays.asList(createBlobMethod, createClobMethod, createNClobMethod, createSQLXMLMethod));

  private static final StaticRegistry<Method, Durability.Phase> phaseRegistry =
      new DurabilityPhaseRegistry(
          Arrays.asList(commitMethod, setAutoCommitMethod), Arrays.asList(rollbackMethod));

  /**
   * Constructs a new ConnectionInvocationHandler
   *
   * @param proxy
   * @param handler
   * @param invoker
   * @param connectionMap
   * @param transactionContext
   */
  public ConnectionInvocationHandler(ConnectionProxyFactory<Z, D, P> map) {
    super(Connection.class, map, null);
  }

  @Override
  protected ProxyFactoryFactory<Z, D, Connection, SQLException, ?, ? extends Exception>
      getProxyFactoryFactory(Connection connection, Method method, Object... parameters)
          throws SQLException {
    if (createStatementMethodSet.contains(method)) {
      return new StatementProxyFactoryFactory<Z, D>(this.getProxyFactory().getTransactionContext());
    }
    if (prepareStatementMethodSet.contains(method)) {
      String sql = (String) parameters[0];
      return new PreparedStatementProxyFactoryFactory<Z, D>(
          this.getProxyFactory().getTransactionContext(),
          this.getProxyFactory().extractLocks(sql),
          this.getProxyFactory().isSelectForUpdate(sql));
    }
    if (prepareCallMethodSet.contains(method)) {
      String sql = (String) parameters[0];
      return new CallableStatementProxyFactoryFactory<Z, D>(
          this.getProxyFactory().getTransactionContext(), this.getProxyFactory().extractLocks(sql));
    }

    if (setSavepointMethodSet.contains(method)) {
      return new SavepointProxyFactoryFactory<Z, D>();
    }

    if (method.equals(getMetaDataMethod)) {
      return new DatabaseMetaDataProxyFactoryFactory<Z, D>();
    }

    if (method.equals(createArrayMethod)) {
      return new ArrayProxyFactoryFactory<Z, D, Connection>(
          this.getProxyFactory().locatorsUpdateCopy());
    }
    if (method.equals(createBlobMethod)) {
      return new BlobProxyFactoryFactory<Z, D, Connection>(
          this.getProxyFactory().locatorsUpdateCopy());
    }
    if (method.equals(createClobMethod)) {
      return new ClobProxyFactoryFactory<Z, D, Connection, Clob>(
          Clob.class, this.getProxyFactory().locatorsUpdateCopy());
    }
    if (method.equals(createNClobMethod)) {
      return new ClobProxyFactoryFactory<Z, D, Connection, NClob>(
          NClob.class, this.getProxyFactory().locatorsUpdateCopy());
    }
    if (method.equals(createSQLXMLMethod)) {
      return new SQLXMLProxyFactoryFactory<Z, D, Connection>(
          this.getProxyFactory().locatorsUpdateCopy());
    }

    return super.getProxyFactoryFactory(connection, method, parameters);
  }

  /**
   * @throws SQLException
   * @see net.sf.hajdbc.sql.AbstractChildInvocationHandler#getInvocationStrategy(java.lang.Object,
   *     java.lang.reflect.Method, java.lang.Object[])
   */
  @Override
  protected InvocationStrategy getInvocationStrategy(
      Connection connection, Method method, Object... parameters) throws SQLException {
    if (driverReadMethodSet.contains(method)) {
      return InvocationStrategies.INVOKE_ON_ANY;
    }

    if (databaseReadMethodSet.contains(method) || method.equals(getMetaDataMethod)) {
      return InvocationStrategies.INVOKE_ON_NEXT;
    }

    if (driverWriterMethodSet.contains(method)
        || method.equals(closeMethod)
        || createStatementMethodSet.contains(method)) {
      return InvocationStrategies.INVOKE_ON_EXISTING;
    }

    if (prepareStatementMethodSet.contains(method)
        || prepareCallMethodSet.contains(method)
        || createLocatorMethodSet.contains(method)) {
      return InvocationStrategies.INVOKE_ON_ALL;
    }

    if (endTransactionMethodSet.contains(method)) {
      return this.getProxyFactory()
          .getTransactionContext()
          .end(InvocationStrategies.END_TRANSACTION_INVOKE_ON_ALL, phaseRegistry.get(method));
    }

    if (method.equals(rollbackSavepointMethod) || method.equals(releaseSavepointMethod)) {
      return InvocationStrategies.END_TRANSACTION_INVOKE_ON_ALL;
    }

    if (setSavepointMethodSet.contains(method)) {
      return InvocationStrategies.TRANSACTION_INVOKE_ON_ALL;
    }

    return super.getInvocationStrategy(connection, method, parameters);
  }

  /**
   * @see net.sf.hajdbc.sql.AbstractChildInvocationHandler#getInvoker(java.lang.Object,
   *     java.lang.reflect.Method, java.lang.Object[])
   */
  @Override
  protected <R> Invoker<Z, D, Connection, R, SQLException> getInvoker(
      Connection connection, Method method, Object... parameters) throws SQLException {
    if (method.equals(releaseSavepointMethod) || method.equals(rollbackSavepointMethod)) {
      return this.getInvoker(Savepoint.class, 0, connection, method, parameters);
    }

    if (prepareStatementMethodSet.contains(method) || prepareCallMethodSet.contains(method)) {
      parameters[0] = this.getProxyFactory().evaluate((String) parameters[0]);
    }

    Invoker<Z, D, Connection, R, SQLException> invoker =
        super.getInvoker(connection, method, parameters);

    if (endTransactionMethodSet.contains(method)) {
      return this.getProxyFactory().getTransactionContext().end(invoker, phaseRegistry.get(method));
    }

    return invoker;
  }

  @Override
  protected <R> void postInvoke(
      Invoker<Z, D, Connection, R, SQLException> invoker,
      Connection proxy,
      Method method,
      Object... parameters) {
    if (driverWriterMethodSet.contains(method) || method.equals(setAutoCommitMethod)) {
      this.getProxyFactory().record(invoker);
    } else if (method.equals(closeMethod)) {
      this.getProxyFactory().getTransactionContext().close();
      this.getProxyFactory().remove();
    } else if (method.equals(releaseSavepointMethod)) {
      SavepointInvocationHandler<Z, D> handler =
          (SavepointInvocationHandler<Z, D>) Proxy.getInvocationHandler(parameters[0]);
      this.getProxyFactory().removeChild(handler.getProxyFactory());
    }
  }
}
/**
 * @author Paul Ferraro
 * @param <D>
 * @param <P>
 */
@SuppressWarnings("nls")
public class ConnectionInvocationHandler<Z, D extends Database<Z>, P>
    extends ChildInvocationHandler<Z, D, P, Connection, SQLException> {
  private static final Set<Method> driverReadMethodSet =
      Methods.findMethods(
          Connection.class,
          "create(ArrayOf|Struct)",
          "getAutoCommit",
          "getCatalog",
          "getClientInfo",
          "getHoldability",
          "getTypeMap",
          "getWarnings",
          "isClosed",
          "isReadOnly",
          "nativeSQL");
  private static final Set<Method> databaseReadMethodSet =
      Methods.findMethods(Connection.class, "getTransactionIsolation", "isValid");
  private static final Set<Method> driverWriterMethodSet =
      Methods.findMethods(
          Connection.class, "clearWarnings", "setClientInfo", "setHoldability", "setTypeMap");
  private static final Set<Method> createStatementMethodSet =
      Methods.findMethods(Connection.class, "createStatement");
  private static final Set<Method> prepareStatementMethodSet =
      Methods.findMethods(Connection.class, "prepareStatement");
  private static final Set<Method> prepareCallMethodSet =
      Methods.findMethods(Connection.class, "prepareCall");
  private static final Set<Method> setSavepointMethodSet =
      Methods.findMethods(Connection.class, "setSavepoint");

  private static final Method setAutoCommitMethod =
      Methods.getMethod(Connection.class, "setAutoCommit", Boolean.TYPE);
  private static final Method commitMethod = Methods.getMethod(Connection.class, "commit");
  private static final Method rollbackMethod = Methods.getMethod(Connection.class, "rollback");
  private static final Method getMetaDataMethod =
      Methods.getMethod(Connection.class, "getMetaData");
  private static final Method releaseSavepointMethod =
      Methods.getMethod(Connection.class, "releaseSavepoint", Savepoint.class);
  private static final Method rollbackSavepointMethod =
      Methods.getMethod(Connection.class, "rollback", Savepoint.class);
  private static final Method closeMethod = Methods.getMethod(Connection.class, "close");
  private static final Method createBlobMethod = Methods.getMethod(Connection.class, "createBlob");
  private static final Method createClobMethod = Methods.getMethod(Connection.class, "createClob");
  private static final Method createNClobMethod =
      Methods.getMethod(Connection.class, "createNClob");
  private static final Method createSQLXMLMethod =
      Methods.getMethod(Connection.class, "createSQLXML");

  private static final Set<Method> endTransactionMethodSet =
      new HashSet<Method>(Arrays.asList(commitMethod, rollbackMethod, setAutoCommitMethod));
  private static final Set<Method> createLocatorMethodSet =
      new HashSet<Method>(
          Arrays.asList(createBlobMethod, createClobMethod, createNClobMethod, createSQLXMLMethod));

  private static final StaticRegistry<Method, Durability.Phase> phaseRegistry =
      new DurabilityPhaseRegistry(
          Arrays.asList(commitMethod, setAutoCommitMethod), Arrays.asList(rollbackMethod));

  private TransactionContext<Z, D> transactionContext;

  /**
   * Constructs a new ConnectionInvocationHandler
   *
   * @param proxy
   * @param handler
   * @param invoker
   * @param connectionMap
   * @param transactionContext
   */
  public ConnectionInvocationHandler(
      P proxy,
      SQLProxy<Z, D, P, SQLException> handler,
      Invoker<Z, D, P, Connection, SQLException> invoker,
      Map<D, Connection> connectionMap,
      TransactionContext<Z, D> transactionContext) {
    super(proxy, handler, invoker, Connection.class, SQLException.class, connectionMap);

    this.transactionContext = transactionContext;
  }

  /**
   * {@inheritDoc}
   *
   * @see net.sf.hajdbc.sql.AbstractInvocationHandler#getInvocationHandlerFactory(java.lang.Object,
   *     java.lang.reflect.Method, java.lang.Object[])
   */
  @Override
  protected InvocationHandlerFactory<Z, D, Connection, ?, SQLException> getInvocationHandlerFactory(
      Connection connection, Method method, Object[] parameters) {
    if (createStatementMethodSet.contains(method)) {
      return new StatementInvocationHandlerFactory<Z, D>(this.transactionContext);
    }

    if (prepareStatementMethodSet.contains(method)) {
      return new PreparedStatementInvocationHandlerFactory<Z, D>(
          this.transactionContext, (String) parameters[0]);
    }

    if (prepareCallMethodSet.contains(method)) {
      return new CallableStatementInvocationHandlerFactory<Z, D>(this.transactionContext);
    }

    if (setSavepointMethodSet.contains(method)) {
      return new SavepointInvocationHandlerFactory<Z, D>();
    }

    if (method.equals(getMetaDataMethod)) {
      return new DatabaseMetaDataInvocationHandlerFactory<Z, D>();
    }

    if (method.equals(createBlobMethod)) {
      return new BlobInvocationHandlerFactory<Z, D, Connection>(connection);
    }

    if (method.equals(createClobMethod)) {
      return new ClobInvocationHandlerFactory<Z, D, Connection>(connection);
    }

    if (method.equals(createNClobMethod)) {
      return new NClobInvocationHandlerFactory<Z, D, Connection>(connection);
    }

    if (method.equals(createSQLXMLMethod)) {
      return new SQLXMLInvocationHandlerFactory<Z, D, Connection>(connection);
    }

    return null;
  }

  /**
   * @throws SQLException
   * @see net.sf.hajdbc.sql.AbstractChildInvocationHandler#getInvocationStrategy(java.lang.Object,
   *     java.lang.reflect.Method, java.lang.Object[])
   */
  @Override
  protected InvocationStrategy getInvocationStrategy(
      Connection connection, Method method, Object[] parameters) throws SQLException {
    if (driverReadMethodSet.contains(method)) {
      return InvocationStrategyEnum.INVOKE_ON_ANY;
    }

    if (databaseReadMethodSet.contains(method) || method.equals(getMetaDataMethod)) {
      return InvocationStrategyEnum.INVOKE_ON_NEXT;
    }

    if (driverWriterMethodSet.contains(method)
        || method.equals(closeMethod)
        || createStatementMethodSet.contains(method)) {
      return InvocationStrategyEnum.INVOKE_ON_EXISTING;
    }

    if (prepareStatementMethodSet.contains(method)
        || prepareCallMethodSet.contains(method)
        || createLocatorMethodSet.contains(method)) {
      return InvocationStrategyEnum.INVOKE_ON_ALL;
    }

    if (endTransactionMethodSet.contains(method)) {
      return this.transactionContext.end(
          InvocationStrategyEnum.END_TRANSACTION_INVOKE_ON_ALL, phaseRegistry.get(method));
    }

    if (method.equals(rollbackSavepointMethod) || method.equals(releaseSavepointMethod)) {
      return InvocationStrategyEnum.END_TRANSACTION_INVOKE_ON_ALL;
    }

    if (setSavepointMethodSet.contains(method)) {
      return InvocationStrategyEnum.TRANSACTION_INVOKE_ON_ALL;
    }

    return super.getInvocationStrategy(connection, method, parameters);
  }

  /**
   * @see net.sf.hajdbc.sql.AbstractChildInvocationHandler#getInvoker(java.lang.Object,
   *     java.lang.reflect.Method, java.lang.Object[])
   */
  @Override
  protected <R> Invoker<Z, D, Connection, R, SQLException> getInvoker(
      Connection connection, Method method, Object[] parameters) throws SQLException {
    if (method.equals(releaseSavepointMethod)) {
      final SQLProxy<Z, D, Savepoint, SQLException> proxy =
          this.getInvocationHandler((Savepoint) parameters[0]);

      return new Invoker<Z, D, Connection, R, SQLException>() {
        @Override
        public R invoke(D database, Connection connection) throws SQLException {
          connection.releaseSavepoint(proxy.getObject(database));

          return null;
        }
      };
    }

    if (method.equals(rollbackSavepointMethod)) {
      final SQLProxy<Z, D, Savepoint, SQLException> proxy =
          this.getInvocationHandler((Savepoint) parameters[0]);

      return new Invoker<Z, D, Connection, R, SQLException>() {
        @Override
        public R invoke(D database, Connection connection) throws SQLException {
          connection.rollback(proxy.getObject(database));

          return null;
        }
      };
    }

    Invoker<Z, D, Connection, R, SQLException> invoker =
        super.getInvoker(connection, method, parameters);

    if (endTransactionMethodSet.contains(method)) {
      return this.transactionContext.end(invoker, phaseRegistry.get(method));
    }

    return invoker;
  }

  /** @see net.sf.hajdbc.sql.AbstractChildInvocationHandler#isSQLMethod(java.lang.reflect.Method) */
  @Override
  protected boolean isSQLMethod(Method method) {
    return prepareStatementMethodSet.contains(method);
  }

  @Override
  protected boolean isRecordable(Method method) {
    return driverWriterMethodSet.contains(method) || method.equals(setAutoCommitMethod);
  }

  /**
   * @see net.sf.hajdbc.sql.AbstractChildInvocationHandler#postInvoke(java.lang.Object,
   *     java.lang.reflect.Method, java.lang.Object[])
   */
  @Override
  protected void postInvoke(Connection object, Method method, Object[] parameters) {
    if (method.equals(closeMethod)) {
      this.transactionContext.close();

      this.getParentProxy().removeChild(this);
    } else if (method.equals(releaseSavepointMethod)) {
      @SuppressWarnings("unchecked")
      SQLProxy<Z, D, Savepoint, SQLException> proxy =
          (SQLProxy<Z, D, Savepoint, SQLException>) Proxy.getInvocationHandler(parameters[0]);

      this.removeChild(proxy);
    }
  }

  /**
   * @see net.sf.hajdbc.sql.AbstractChildInvocationHandler#close(java.lang.Object, java.lang.Object)
   */
  @Override
  protected void close(P parent, Connection connection) throws SQLException {
    connection.close();
  }
}
/**
 * @author Paul Ferraro
 * @param <D>
 * @param <S>
 */
@SuppressWarnings("nls")
public abstract class AbstractStatementInvocationHandler<
        Z,
        D extends Database<Z>,
        S extends Statement,
        F extends AbstractStatementProxyFactory<Z, D, S>>
    extends InputSinkRegistryInvocationHandler<Z, D, Connection, S, F> {
  private static final Set<Method> driverReadMethodSet =
      Methods.findMethods(
          Statement.class,
          "getFetchDirection",
          "getFetchSize",
          "getGeneratedKeys",
          "getMaxFieldSize",
          "getMaxRows",
          "getQueryTimeout",
          "getResultSetConcurrency",
          "getResultSetHoldability",
          "getResultSetType",
          "getUpdateCount",
          "getWarnings",
          "isClosed",
          "isPoolable");
  private static final Set<Method> driverWriteMethodSet =
      Methods.findMethods(
          Statement.class,
          "clearWarnings",
          "setCursorName",
          "setEscapeProcessing",
          "setFetchDirection",
          "setFetchSize",
          "setMaxFieldSize",
          "setMaxRows",
          "setPoolable",
          "setQueryTimeout");
  private static final Set<Method> executeMethodSet =
      Methods.findMethods(Statement.class, "execute(Update)?");

  private static final Method getConnectionMethod =
      Methods.getMethod(Statement.class, "getConnection");
  private static final Method executeQueryMethod =
      Methods.getMethod(Statement.class, "executeQuery", String.class);
  private static final Method clearBatchMethod = Methods.getMethod(Statement.class, "clearBatch");
  private static final Method executeBatchMethod =
      Methods.getMethod(Statement.class, "executeBatch");
  private static final Method getMoreResultsMethod =
      Methods.getMethod(Statement.class, "getMoreResults", Integer.TYPE);
  private static final Method getResultSetMethod =
      Methods.getMethod(Statement.class, "getResultSet");
  private static final Method addBatchMethod =
      Methods.getMethod(Statement.class, "addBatch", String.class);
  private static final Method closeMethod = Methods.getMethod(Statement.class, "close");

  public AbstractStatementInvocationHandler(Class<S> statementClass, F proxyFactory) {
    super(statementClass, proxyFactory, getConnectionMethod);
  }

  @Override
  protected ProxyFactoryFactory<Z, D, S, SQLException, ?, ? extends Exception>
      getProxyFactoryFactory(S object, Method method, Object... parameters) throws SQLException {
    if (method.equals(executeQueryMethod) || method.equals(getResultSetMethod)) {
      return new ResultSetProxyFactoryFactory<Z, D, S>(
          this.getProxyFactory().getTransactionContext(),
          this.getProxyFactory().getInputSinkRegistry());
    }

    return super.getProxyFactoryFactory(object, method, parameters);
  }

  @Override
  protected InvocationStrategy getInvocationStrategy(
      S statement, Method method, Object... parameters) throws SQLException {
    if (driverReadMethodSet.contains(method)) {
      return InvocationStrategies.INVOKE_ON_ANY;
    }

    if (driverWriteMethodSet.contains(method) || method.equals(closeMethod)) {
      return InvocationStrategies.INVOKE_ON_EXISTING;
    }

    if (executeMethodSet.contains(method)) {
      List<Lock> locks = this.getProxyFactory().extractLocks((String) parameters[0]);

      return this.getProxyFactory()
          .getTransactionContext()
          .start(
              new LockingInvocationStrategy(InvocationStrategies.TRANSACTION_INVOKE_ON_ALL, locks),
              this.getProxyFactory().getParentProxy());
    }

    if (method.equals(executeQueryMethod)) {
      String sql = (String) parameters[0];

      List<Lock> locks = this.getProxyFactory().extractLocks(sql);
      int concurrency = statement.getResultSetConcurrency();
      boolean selectForUpdate = this.getProxyFactory().isSelectForUpdate(sql);

      if (locks.isEmpty() && (concurrency == ResultSet.CONCUR_READ_ONLY) && !selectForUpdate) {
        boolean repeatableReadSelect =
            (statement.getConnection().getTransactionIsolation()
                >= Connection.TRANSACTION_REPEATABLE_READ);

        return repeatableReadSelect
            ? InvocationStrategies.INVOKE_ON_PRIMARY
            : InvocationStrategies.INVOKE_ON_NEXT;
      }

      InvocationStrategy strategy = InvocationStrategies.TRANSACTION_INVOKE_ON_ALL;
      if (!locks.isEmpty()) {
        strategy = new LockingInvocationStrategy(strategy, locks);
      }

      return selectForUpdate
          ? this.getProxyFactory()
              .getTransactionContext()
              .start(strategy, this.getProxyFactory().getParentProxy())
          : strategy;
    }

    if (method.equals(executeBatchMethod)) {
      return this.getProxyFactory()
          .getTransactionContext()
          .start(
              new LockingInvocationStrategy(
                  InvocationStrategies.TRANSACTION_INVOKE_ON_ALL,
                  this.getProxyFactory().getBatchLocks()),
              this.getProxyFactory().getParentProxy());
    }

    if (method.equals(getMoreResultsMethod)) {
      if (parameters[0].equals(Statement.KEEP_CURRENT_RESULT)) {
        return InvocationStrategies.INVOKE_ON_EXISTING;
      }
    }

    if (method.equals(getResultSetMethod)) {
      if (statement.getResultSetConcurrency() == ResultSet.CONCUR_READ_ONLY) {
        return InvocationStrategies.INVOKE_ON_EXISTING;
      }

      return InvocationStrategies.INVOKE_ON_ALL;
    }

    return super.getInvocationStrategy(statement, method, parameters);
  }

  @Override
  protected <R> Invoker<Z, D, S, R, SQLException> getInvoker(
      S proxy, Method method, Object... parameters) throws SQLException {
    if (method.equals(addBatchMethod)
        || method.equals(executeQueryMethod)
        || executeMethodSet.contains(method)) {
      parameters[0] = this.getProxyFactory().evaluate((String) parameters[0]);
    }

    return super.getInvoker(proxy, method, parameters);
  }

  @Override
  protected <R> void postInvoke(
      Invoker<Z, D, S, R, SQLException> invoker, S proxy, Method method, Object... parameters) {
    if (method.equals(addBatchMethod)) {
      this.getProxyFactory().addBatchSQL((String) parameters[0]);
    } else if (method.equals(clearBatchMethod) || method.equals(executeBatchMethod)) {
      this.getProxyFactory().clearBatch();
      this.logger.log(Level.TRACE, "Clearing recorded batch methods");
      this.getProxyFactory().clearBatchInvokers();
    } else if (method.equals(closeMethod)) {
      Resources.close(this.getProxyFactory().getInputSinkRegistry());
      this.getProxyFactory().remove();
    }

    if (this.isBatchMethod(method)) {
      this.logger.log(Level.TRACE, "Recording batch method: {0}", invoker);
      this.getProxyFactory().addBatchInvoker(invoker);
    } else if (driverWriteMethodSet.contains(method)) {
      this.getProxyFactory().record(invoker);
    }
  }

  protected boolean isBatchMethod(Method method) {
    return method.equals(addBatchMethod);
  }
}