All Downloads are FREE. Search and download functionalities are using the official Maven repository.

software.amazon.jdbc.plugin.DefaultConnectionPlugin Maven / Gradle / Ivy

The newest version!
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * 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 software.amazon.jdbc.plugin;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import software.amazon.jdbc.ConnectionPlugin;
import software.amazon.jdbc.ConnectionProvider;
import software.amazon.jdbc.ConnectionProviderManager;
import software.amazon.jdbc.HostListProviderService;
import software.amazon.jdbc.HostRole;
import software.amazon.jdbc.HostSpec;
import software.amazon.jdbc.JdbcCallable;
import software.amazon.jdbc.NodeChangeOptions;
import software.amazon.jdbc.OldConnectionSuggestedAction;
import software.amazon.jdbc.PluginManagerService;
import software.amazon.jdbc.PluginService;
import software.amazon.jdbc.hostavailability.HostAvailability;
import software.amazon.jdbc.util.Messages;
import software.amazon.jdbc.util.SqlMethodAnalyzer;
import software.amazon.jdbc.util.WrapperUtils;
import software.amazon.jdbc.util.telemetry.TelemetryContext;
import software.amazon.jdbc.util.telemetry.TelemetryFactory;
import software.amazon.jdbc.util.telemetry.TelemetryTraceLevel;

/**
 * This connection plugin will always be the last plugin in the connection plugin chain, and will
 * invoke the JDBC method passed down the chain.
 */
public final class DefaultConnectionPlugin implements ConnectionPlugin {

  private static final Logger LOGGER =  Logger.getLogger(DefaultConnectionPlugin.class.getName());
  private static final Set subscribedMethods = Collections.unmodifiableSet(new HashSet<>(
      Collections.singletonList("*")));
  private static final SqlMethodAnalyzer sqlMethodAnalyzer = new SqlMethodAnalyzer();

  private final @NonNull ConnectionProvider defaultConnProvider;
  private final @Nullable ConnectionProvider effectiveConnProvider;

  private final ConnectionProviderManager connProviderManager;
  private final PluginService pluginService;
  private final PluginManagerService pluginManagerService;

  public DefaultConnectionPlugin(
      final PluginService pluginService,
      final ConnectionProvider defaultConnProvider,
      final @Nullable ConnectionProvider effectiveConnProvider,
      final PluginManagerService pluginManagerService) {
    this(pluginService,
        defaultConnProvider,
        effectiveConnProvider,
        pluginManagerService,
        new ConnectionProviderManager(defaultConnProvider));
  }

  public DefaultConnectionPlugin(
      final PluginService pluginService,
      final ConnectionProvider defaultConnProvider,
      final @Nullable ConnectionProvider effectiveConnProvider,
      final PluginManagerService pluginManagerService,
      final ConnectionProviderManager connProviderManager) {
    if (pluginService == null) {
      throw new IllegalArgumentException("pluginService");
    }
    if (pluginManagerService == null) {
      throw new IllegalArgumentException("pluginManagerService");
    }
    if (defaultConnProvider == null) {
      throw new IllegalArgumentException("connectionProvider");
    }

    this.pluginService = pluginService;
    this.pluginManagerService = pluginManagerService;
    this.defaultConnProvider = defaultConnProvider;
    this.effectiveConnProvider = effectiveConnProvider;
    this.connProviderManager = connProviderManager;
  }

  @Override
  public Set getSubscribedMethods() {
    return subscribedMethods;
  }

  @Override
  public  T execute(
      final Class resultClass,
      final Class exceptionClass,
      final Object methodInvokeOn,
      final String methodName,
      final JdbcCallable jdbcMethodFunc,
      final Object[] jdbcMethodArgs)
      throws E {

    LOGGER.finest(
        () -> Messages.get("DefaultConnectionPlugin.executingMethod", new Object[] {methodName}));

    TelemetryFactory telemetryFactory = this.pluginService.getTelemetryFactory();
    TelemetryContext telemetryContext = telemetryFactory.openTelemetryContext(
        this.pluginService.getTargetName(), TelemetryTraceLevel.NESTED);

    T result;
    try {
      result = jdbcMethodFunc.call();
    } finally {
      telemetryContext.closeContext();
    }

    final Connection currentConn = this.pluginService.getCurrentConnection();
    final Connection boundConnection = WrapperUtils.getConnectionFromSqlObject(methodInvokeOn);
    if (boundConnection != null && boundConnection != currentConn) {
      // The method being invoked is using an old connection, so transaction/autocommit analysis should be skipped.
      // ConnectionPluginManager#execute blocks all methods invoked using old connections except for close/abort.
      return result;
    }

    if (sqlMethodAnalyzer.doesOpenTransaction(currentConn, methodName, jdbcMethodArgs)) {
      this.pluginManagerService.setInTransaction(true);
    } else if (
        sqlMethodAnalyzer.doesCloseTransaction(currentConn, methodName, jdbcMethodArgs)
            // According to the JDBC spec, transactions are committed if autocommit is switched from false to true.
            || sqlMethodAnalyzer.doesSwitchAutoCommitFalseTrue(currentConn, methodName,
            jdbcMethodArgs)) {
      this.pluginManagerService.setInTransaction(false);
    }

    if (sqlMethodAnalyzer.isStatementSettingAutoCommit(methodName, jdbcMethodArgs)) {
      final Boolean autocommit = sqlMethodAnalyzer.getAutoCommitValueFromSqlStatement(jdbcMethodArgs);
      if (autocommit != null) {
        try {
          currentConn.setAutoCommit(autocommit);
        } catch (final SQLException e) {
          // do nothing
        }
      }
    }

    return result;
  }

  @Override
  public Connection connect(
      final String driverProtocol,
      final HostSpec hostSpec,
      final Properties props,
      final boolean isInitialConnection,
      final JdbcCallable connectFunc)
      throws SQLException {

    ConnectionProvider connProvider = null;

    if (this.effectiveConnProvider != null) {
      if (this.effectiveConnProvider.acceptsUrl(driverProtocol, hostSpec, props)) {
        connProvider = this.effectiveConnProvider;
      }
    }

    if (connProvider == null) {
      connProvider =
          this.connProviderManager.getConnectionProvider(driverProtocol, hostSpec, props);
    }

    // It's guaranteed that this plugin is always the last in plugin chain so connectFunc can be
    // ignored.
    return connectInternal(driverProtocol, hostSpec, props, connProvider, isInitialConnection);
  }

  private Connection connectInternal(
      String driverProtocol,
      HostSpec hostSpec,
      Properties props,
      ConnectionProvider connProvider,
      final boolean isInitialConnection)
      throws SQLException {

    TelemetryFactory telemetryFactory = this.pluginService.getTelemetryFactory();
    TelemetryContext telemetryContext = telemetryFactory.openTelemetryContext(
        connProvider.getTargetName(), TelemetryTraceLevel.NESTED);

    Connection conn;
    try {
      conn = connProvider.connect(
          driverProtocol,
          this.pluginService.getDialect(),
          this.pluginService.getTargetDriverDialect(),
          hostSpec,
          props);
    } finally {
      telemetryContext.closeContext();
    }

    this.connProviderManager.initConnection(conn, driverProtocol, hostSpec, props);

    this.pluginService.setAvailability(hostSpec.asAliases(), HostAvailability.AVAILABLE);
    if (isInitialConnection) {
      this.pluginService.updateDialect(conn);
    }

    return conn;
  }

  @Override
  public Connection forceConnect(
      final String driverProtocol,
      final HostSpec hostSpec,
      final Properties props,
      final boolean isInitialConnection,
      final JdbcCallable forceConnectFunc)
      throws SQLException {

    // It's guaranteed that this plugin is always the last in plugin chain so forceConnectFunc can be
    // ignored.
    return connectInternal(driverProtocol, hostSpec, props, this.defaultConnProvider, isInitialConnection);
  }

  @Override
  public boolean acceptsStrategy(HostRole role, String strategy) {
    if (HostRole.UNKNOWN.equals(role)) {
      // Users must request either a writer or a reader role.
      return false;
    }

    if (this.effectiveConnProvider != null) {
      return this.effectiveConnProvider.acceptsStrategy(role, strategy);
    }
    return this.connProviderManager.acceptsStrategy(role, strategy);
  }

  @Override
  public HostSpec getHostSpecByStrategy(HostRole role, String strategy)
      throws SQLException {
    List hosts = this.pluginService.getHosts();

    return this.getHostSpecByStrategy(hosts, role, strategy);
  }

  @Override
  public HostSpec getHostSpecByStrategy(final List hosts, final HostRole role, final String strategy)
      throws SQLException {
    if (HostRole.UNKNOWN.equals(role)) {
      // Users must request either a writer or a reader role.
      throw new SQLException("DefaultConnectionPlugin.unknownRoleRequested");
    }

    if (hosts.isEmpty()) {
      throw new SQLException(Messages.get("DefaultConnectionPlugin.noHostsAvailable"));
    }

    if (this.effectiveConnProvider != null) {
      return this.effectiveConnProvider.getHostSpecByStrategy(hosts,
          role, strategy, this.pluginService.getProperties());
    }
    return this.connProviderManager.getHostSpecByStrategy(hosts, role, strategy, this.pluginService.getProperties());
  }

  @Override
  public void initHostProvider(
      final String driverProtocol,
      final String initialUrl,
      final Properties props,
      final HostListProviderService hostListProviderService,
      final JdbcCallable initHostProviderFunc)
      throws SQLException {

    // do nothing
    // It's guaranteed that this plugin is always the last in plugin chain so initHostProviderFunc
    // can be omitted.
  }

  @Override
  public OldConnectionSuggestedAction notifyConnectionChanged(
      final EnumSet changes) {
    return OldConnectionSuggestedAction.NO_OPINION;
  }

  @Override
  public void notifyNodeListChanged(final Map> changes) {
    // do nothing
  }

  List parseMultiStatementQueries(String query) {
    if (query == null || query.isEmpty()) {
      return new ArrayList<>();
    }

    query = query.replaceAll("\\s+", " ");

    // Check to see if string only has blank spaces.
    if (query.trim().isEmpty()) {
      return new ArrayList<>();
    }

    return Arrays.stream(query.split(";")).collect(Collectors.toList());
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy