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

com.hazelcast.dataconnection.impl.JdbcDataConnection Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2008-2024, Hazelcast, Inc. 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 com.hazelcast.dataconnection.impl;

import com.hazelcast.config.DataConnectionConfig;
import com.hazelcast.core.HazelcastException;
import com.hazelcast.dataconnection.DataConnection;
import com.hazelcast.dataconnection.DataConnectionBase;
import com.hazelcast.dataconnection.DataConnectionResource;
import com.hazelcast.dataconnection.impl.jdbcproperties.HikariTranslator;
import com.hazelcast.jet.impl.util.ConcurrentMemoizingSupplier;
import com.hazelcast.shaded.com.zaxxer.hikari.HikariConfig;
import com.hazelcast.shaded.com.zaxxer.hikari.HikariDataSource;

import javax.annotation.Nonnull;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;

import static com.hazelcast.dataconnection.impl.DatabaseDialect.resolveDialect;
import static com.hazelcast.dataconnection.impl.jdbcproperties.DataConnectionProperties.JDBC_URL;
import static com.hazelcast.dataconnection.impl.jdbcproperties.DriverManagerTranslator.translate;
import static java.util.Locale.ROOT;

/**
 * {@link DataConnection} implementation for JDBC.
 * 

* Implementation is based on {@link HikariDataSource}. {@link DataConnectionConfig#getProperties()} are passed directly * to {@link HikariConfig}. For available options see * HikariCP configuration * * @since 5.3 */ public class JdbcDataConnection extends DataConnectionBase { public static final String OBJECT_TYPE_TABLE = "Table"; private static final List H2_SYSTEM_SCHEMA_LIST = List.of("INFORMATION_SCHEMA"); private static final List MYSQL_SYSTEM_CATALOG_LIST = List.of("SYS"); private static final List MSSQL_SYSTEM_SCHEMA_LIST = List.of("sys", "INFORMATION_SCHEMA"); private static final List MSSQL_SYSTEM_TABLE_LIST = List.of("MSreplication_options", "spt_fallback_db", "spt_fallback_dev", "spt_fallback_usg", "spt_monitor", "spt_values"); private static final AtomicInteger DATA_SOURCE_COUNTER = new AtomicInteger(); private volatile ConcurrentMemoizingSupplier pooledDataSourceSup; private volatile Supplier singleUseConnectionSup; public JdbcDataConnection(DataConnectionConfig config) { super(config); if (config.isShared()) { this.pooledDataSourceSup = new ConcurrentMemoizingSupplier<>(this::createHikariDataSource); } else { this.singleUseConnectionSup = createSingleConnectionSup(); } } protected HikariDataSource createHikariDataSource() { try { validate(getConfig()); Properties properties = getConfig().getProperties(); HikariTranslator translator = new HikariTranslator(DATA_SOURCE_COUNTER, getName()); Properties translatedProperties = translator.translate(properties); HikariConfig dataSourceConfig = new HikariConfig(translatedProperties); return new HikariDataSource(dataSourceConfig); } catch (Exception e) { throw new HazelcastException("Could not create pool for data connection '" + getName() + "'", e); } } private Supplier createSingleConnectionSup() { validate(getConfig()); Properties properties = getConfig().getProperties(); Properties translatedProperties = translate(properties); return () -> { try { String jdbcUrl = properties.getProperty(JDBC_URL); return DriverManager.getConnection(jdbcUrl, translatedProperties); } catch (SQLException e) { throw new HazelcastException("Could not create a new connection: " + e, e); } }; } private void validate(DataConnectionConfig config) { Properties properties = config.getProperties(); if (properties.get(JDBC_URL) == null) { throw new HazelcastException(JDBC_URL + " property is not defined for data connection '" + getName() + "'"); } } @Nonnull @Override public Collection resourceTypes() { return Collections.singleton(OBJECT_TYPE_TABLE); } @Nonnull @Override public List listResources() { try { try (Connection connection = getConnection()) { DatabaseMetaData databaseMetaData = connection.getMetaData(); ResourceReader reader = new ResourceReader(); switch (resolveDialect(databaseMetaData)) { case H2: reader.withCatalog(connection.getCatalog()) .exclude( (catalog, schema, table) -> H2_SYSTEM_SCHEMA_LIST.contains(schema) ); break; case POSTGRESQL: reader.withCatalog(connection.getCatalog()); break; case MYSQL: reader.exclude( (catalog, schema, table) -> catalog != null && MYSQL_SYSTEM_CATALOG_LIST.contains(catalog.toUpperCase(ROOT)) ); break; case MICROSOFT_SQL_SERVER: reader .withCatalog(connection.getCatalog()) .exclude( (catalog, schema, table) -> MSSQL_SYSTEM_SCHEMA_LIST.contains(schema) || MSSQL_SYSTEM_TABLE_LIST.contains(table) ); break; default: // Nothing to do } return reader.listResources(connection); } } catch (Exception exception) { throw new HazelcastException("Could not read resources for DataConnection " + getName(), exception); } } private Connection pooledConnection() { retain(); try { return new ConnectionDelegate(pooledDataSourceSup.get().getConnection()) { @Override public void close() { try { super.close(); } catch (Exception e) { throw new HazelcastException("Could not close connection", e); } finally { release(); } } }; } catch (SQLException e) { throw new HazelcastException("Could not get Connection from pool", e); } } private Connection singleUseConnection() { // We don't call retain() for single-use connections. They are independent of the DataConnection, // so closing the DataConnection doesn't affect an active single-use connection. return singleUseConnectionSup.get(); } /** * Return a {@link Connection} based on this DataConnection configuration. *

* Depending on the {@link DataConnectionConfig#isShared()} config the Connection is * - shared=true -> a new Connection is created each time the method is called * - shared=false -> a Connection is obtained from a pool, returned back to * the pool, when it is closed *

* When finished, the caller must close the Connection to ensure the proper release of the underlying resources. * If the connection is for single use, it is closed immediately. * For pooled connections, the connection is returned back to the pool. */ public Connection getConnection() { return getConfig().isShared() ? pooledConnection() : singleUseConnection(); } @Override public void destroy() { ConcurrentMemoizingSupplier localPooledDataSourceSup = pooledDataSourceSup; if (localPooledDataSourceSup != null) { HikariDataSource dataSource = localPooledDataSourceSup.remembered(); pooledDataSourceSup = null; if (dataSource != null) { try { dataSource.close(); } catch (Exception e) { throw new HazelcastException("Could not close connection pool", e); } } } singleUseConnectionSup = null; } /** * For tests only */ HikariDataSource pooledDataSource() { return pooledDataSourceSup.get(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy