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

io.hekate.cluster.seed.jdbc.JdbcSeedNodeProvider Maven / Gradle / Ivy

There is a newer version: 4.1.3
Show newest version
/*
 * Copyright 2020 The Hekate Project
 *
 * The Hekate Project licenses this file to you 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 io.hekate.cluster.seed.jdbc;

import io.hekate.cluster.ClusterServiceFactory;
import io.hekate.cluster.seed.SeedNodeProvider;
import io.hekate.core.HekateException;
import io.hekate.core.internal.util.AddressUtils;
import io.hekate.core.internal.util.ArgAssert;
import io.hekate.core.internal.util.ConfigCheck;
import io.hekate.core.jmx.JmxSupport;
import io.hekate.core.report.ConfigReportSupport;
import io.hekate.core.report.ConfigReporter;
import io.hekate.util.format.ToString;
import io.hekate.util.format.ToStringIgnore;
import java.net.InetSocketAddress;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * JDBC-based implementation of {@link SeedNodeProvider} interface.
 *
 * 

* This provider uses a shared database table to store seed node addresses. When provider starts discovering other nodes it registers the * local node addresses within the database table (with configurable name and columns) and uses this table to search for remote node * addresses. *

* *

Please see the documentation of {@link JdbcSeedNodeProviderConfig} class for more details about available configuration options.

* * @see ClusterServiceFactory#setSeedNodeProvider(SeedNodeProvider) * @see SeedNodeProvider */ public class JdbcSeedNodeProvider implements SeedNodeProvider, JmxSupport, ConfigReportSupport { private static final Logger log = LoggerFactory.getLogger(JdbcSeedNodeProvider.class); private static final boolean DEBUG = log.isDebugEnabled(); private final DataSource ds; private final int queryTimeout; private final long cleanupInterval; @ToStringIgnore private final String selSql; @ToStringIgnore private final String delSql; @ToStringIgnore private final String insSql; /** * Constructs new instance. * * @param cfg Configuration. */ public JdbcSeedNodeProvider(JdbcSeedNodeProviderConfig cfg) { ArgAssert.notNull(cfg, "configuration"); ConfigCheck check = ConfigCheck.get(JdbcSeedNodeProviderConfig.class); check.notNull(cfg.getDataSource(), "datasource"); check.notEmpty(cfg.getTable(), "table"); check.notEmpty(cfg.getHostColumn(), "host column"); check.notEmpty(cfg.getPortColumn(), "port column"); check.notEmpty(cfg.getClusterColumn(), "cluster name column"); ds = cfg.getDataSource(); queryTimeout = cfg.getQueryTimeout(); cleanupInterval = cfg.getCleanupInterval(); String table = cfg.getTable().trim(); String host = cfg.getHostColumn().trim(); String port = cfg.getPortColumn().trim(); String cluster = cfg.getClusterColumn().trim(); selSql = "SELECT " + host + ", " + port + " FROM " + table + " WHERE " + cluster + " = ?"; delSql = "DELETE FROM " + table + " WHERE " + host + " = ? AND " + port + " = ? AND " + cluster + " = ?"; insSql = "INSERT INTO " + table + " (" + host + ", " + port + ", " + cluster + ") VALUES (?, ?, ?)"; } @Override public void report(ConfigReporter report) { report.section("jdbc", r -> { r.value("datasource", ds); r.value("insert-sql", insSql); r.value("cleanup-interval", cleanupInterval); r.value("query-timeout", queryTimeout); }); } @Override public List findSeedNodes(String cluster) throws HekateException { try ( Connection conn = ds.getConnection(); PreparedStatement st = conn.prepareStatement(selSql) ) { if (DEBUG) { log.debug("Executing SQL query [sql={}, cluster={}]", selSql, cluster); } if (queryTimeout > 0) { st.setQueryTimeout(queryTimeout); } st.setString(1, cluster); try (ResultSet rs = st.executeQuery()) { List result = new ArrayList<>(); while (rs.next()) { String host = rs.getString(1); int port = rs.getInt(2); InetSocketAddress address = new InetSocketAddress(host, port); result.add(address); } if (DEBUG) { log.debug("Loaded data from a database [result={}]", result); } return result; } } catch (SQLException e) { throw new HekateException("Failed to load seed nodes list from a database.", e); } } @Override public void startDiscovery(String cluster, InetSocketAddress node) throws HekateException { doRegister(cluster, node); } @Override public void stopDiscovery(String cluster, InetSocketAddress node) throws HekateException { doUnregister(cluster, node); } @Override public long cleanupInterval() { return cleanupInterval; } @Override public void registerRemote(String cluster, InetSocketAddress node) throws HekateException { doRegister(cluster, node); } @Override public void unregisterRemote(String cluster, InetSocketAddress node) throws HekateException { doUnregister(cluster, node); } @Override public void suspendDiscovery() throws HekateException { // No-op. } /** * Returns the data source of this provider. * * @return Data source. * * @see JdbcSeedNodeProviderConfig#setDataSource(DataSource) */ public DataSource dataSource() { return ds; } /** * Returns the JDBC query timeout in seconds. * * @return Timeout in seconds. * * @see JdbcSeedNodeProviderConfig#setQueryTimeout(int) */ public int queryTimeout() { return queryTimeout; } /** * Returns the SQL string for selecting records from the seed nodes table. * * @return SQL string. * * @see JdbcSeedNodeProviderConfig#setTable(String) */ public String selectSql() { return selSql; } /** * Returns the SQL string for inserting new records into the seed nodes table. * * @return SQL string. * * @see JdbcSeedNodeProviderConfig#setTable(String) */ public String deleteSql() { return delSql; } /** * Returns the SQL string for deleting records from the seed nodes table. * * @return SQL string. * * @see JdbcSeedNodeProviderConfig#setTable(String) */ public String insertSql() { return insSql; } @Override public JdbcSeedNodeProviderJmx jmx() { return new JdbcSeedNodeProviderJmx() { @Override public String getDataSourceInfo() { return ds.toString(); } @Override public int getQueryTimeout() { return queryTimeout; } @Override public long getCleanupInterval() { return cleanupInterval; } @Override public String getInsertSql() { return insSql; } @Override public String getSelectSql() { return selSql; } @Override public String getDeleteSql() { return delSql; } }; } private void doUnregister(String cluster, InetSocketAddress address) throws HekateException { try (Connection conn = ds.getConnection()) { conn.setAutoCommit(false); try (PreparedStatement delSt = conn.prepareStatement(delSql)) { if (queryTimeout > 0) { delSt.setQueryTimeout(queryTimeout); } String host = AddressUtils.host(address); int port = address.getPort(); if (DEBUG) { log.debug("Executing SQL query [sql={}, host={}, port={}, cluster={}]", delSql, host, port, cluster); } delSt.setString(1, host); delSt.setInt(2, port); delSt.setString(3, cluster); int updated = delSt.executeUpdate(); if (DEBUG) { log.debug("Done executing SQL query [updated-records={}]", updated); } conn.commit(); } catch (SQLException e) { rollback(conn); throw e; } } catch (SQLException e) { throw new HekateException("Failed to register seed node within a database " + "[cluster=" + cluster + ", node=" + address + ']', e); } } private void doRegister(String cluster, InetSocketAddress node) throws HekateException { try (Connection conn = ds.getConnection()) { conn.setAutoCommit(false); try ( PreparedStatement delSt = conn.prepareStatement(delSql); PreparedStatement insSt = conn.prepareStatement(insSql) ) { if (queryTimeout > 0) { delSt.setQueryTimeout(queryTimeout); insSt.setQueryTimeout(queryTimeout); } String host = node.getAddress().getHostAddress(); int port = node.getPort(); if (DEBUG) { log.debug("Executing SQL query [sql={}, host={}, port={}, cluster={}]", delSql, host, port, cluster); } delSt.setString(1, host); delSt.setInt(2, port); delSt.setString(3, cluster); delSt.executeUpdate(); if (DEBUG) { log.debug("Executing SQL query [sql={}, host={}, port={}, cluster={}]", insSql, host, port, cluster); } insSt.setString(1, host); insSt.setInt(2, port); insSt.setString(3, cluster); insSt.executeUpdate(); conn.commit(); } catch (SQLException e) { rollback(conn); throw e; } } catch (SQLException e) { throw new HekateException("Failed to register seed node [cluster=" + cluster + ", node=" + node + ']', e); } } private void rollback(Connection conn) { if (conn != null) { try { conn.rollback(); } catch (SQLException e) { // No-op. } } } @Override public String toString() { return ToString.format(this); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy