
org.jgroups.protocols.JDBC_PING2 Maven / Gradle / Ivy
package org.jgroups.protocols;
import org.jgroups.Address;
import org.jgroups.annotations.ManagedOperation;
import org.jgroups.annotations.Property;
import org.jgroups.protocols.relay.SiteUUID;
import org.jgroups.stack.IpAddress;
import org.jgroups.util.NameCache;
import org.jgroups.util.Responses;
import org.jgroups.util.Util;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.sql.*;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import static java.sql.ResultSet.CONCUR_UPDATABLE;
import static java.sql.ResultSet.TYPE_FORWARD_ONLY;
/**
* New version of {@link JDBC_PING}. Has a new, better legible schema. plus some refactoring
*
* @author Bela Ban
* @since 5.4, 5.3.7
*/
public class JDBC_PING2 extends FILE_PING {
/* ----------------------------------------- Properties -------------------------------------------------- */
@Property(description="The JDBC connection URL", writable=false)
protected String connection_url;
@Property(description="The JDBC connection username", writable=false)
protected String connection_username;
@Property(description="The JDBC connection password", writable=false, exposeAsManagedAttribute=false)
protected String connection_password;
@Property(description="The JDBC connection driver name", writable=false)
protected String connection_driver;
@Property(description="If not null, this SQL statement will be performed at startup. Customize it to create the " +
"needed table. To allow for creation attempts, errors performing this statement will be logged "
+ "but not considered fatal. To avoid any creation, set this to null.")
protected String initialize_sql="CREATE TABLE jgroups (address varchar(200) NOT NULL, " +
"name varchar(200), " +
"cluster varchar(200) NOT NULL, " +
"ip varchar(200) NOT NULL, " +
"coord boolean, " +
"PRIMARY KEY (address) )";
@Property(description="Definition of a stored procedure which deletes an existing row and inserts a new one. Used " +
"only if non-null (as an optimization of calling delete, then insert (1 SQL statement instead of 2). Needs to " +
"accept address (varchar), name (varchar), cluster (varchar), ip (varchar) and coord (boolean")
protected String insert_sp;
@Property(description="Calls the insert_sp stored procedure. Not used if null.")
protected String call_insert_sp;
@Property(description="SQL used to insert a new row")
protected String insert_single_sql="INSERT INTO jgroups values (?, ?, ?, ?, ?)";
@Property(description="SQL used to delete a row")
protected String delete_single_sql="DELETE FROM jgroups WHERE address=?";
@Property(description="SQL to clear the table")
protected String clear_sql="DELETE from jgroups WHERE cluster=?";
@Property(description="SQL used to fetch the data of all nodes")
protected String select_all_pingdata_sql="SELECT address, name, ip, coord FROM jgroups WHERE cluster=?";
@Property(description="To use a DataSource registered in JNDI, specify the JNDI name here")
protected String datasource_jndi_name;
@Property(description="The fully qualified name of a class which implements a Function. " +
"If not null, this has precedence over datasource_jndi_name.")
protected String datasource_injecter_class;
/* --------------------------------------------- Fields ------------------------------------------------------ */
protected DataSource dataSource;
@Override
protected void createRootDir() {
; // do *not* create root file system (don't remove !)
}
public JDBC_PING2 setDataSource(DataSource ds) {this.dataSource=ds; return this;}
public DataSource getDataSource() {return dataSource;}
public String getConnectionUrl() {return connection_url;}
public JDBC_PING2 setConnectionUrl(String c) {this.connection_url=c; return this;}
public String getConnectionUsername() {return connection_username;}
public JDBC_PING2 setConnectionUsername(String c) {this.connection_username=c; return this;}
public String getConnectionPassword() {return connection_password;}
public JDBC_PING2 setConnectionPassword(String c) {this.connection_password=c; return this;}
public String getConnectionDriver() {return connection_driver;}
public JDBC_PING2 setConnectionDriver(String c) {this.connection_driver=c; return this;}
public String getInitializeSql() {return initialize_sql;}
public JDBC_PING2 setInitializeSql(String i) {this.initialize_sql=i; return this;}
public String getInsertSingleSql() {return insert_single_sql;}
public JDBC_PING2 setInsertSingleSql(String i) {this.insert_single_sql=i; return this;}
public String getInsertSp() {return insert_sp;}
public JDBC_PING2 setInsertSp(String sp) {insert_sp=sp; return this;}
public String getCallInsertSp() {return call_insert_sp;}
public JDBC_PING2 setCallInsertSp(String sp) {call_insert_sp=sp; return this;}
public String getDeleteSingleSql() {return delete_single_sql;}
public JDBC_PING2 setDeleteSingleSql(String d) {this.delete_single_sql=d; return this;}
public String getClearSql() {return clear_sql;}
public JDBC_PING2 setClearSql(String c) {this.clear_sql=c; return this;}
public String getSelectAllPingdataSql() {return select_all_pingdata_sql;}
public JDBC_PING2 setSelectAllPingdataSql(String s) {this.select_all_pingdata_sql=s; return this;}
public String getDatasourceJndiName() {return datasource_jndi_name;}
public JDBC_PING2 setDatasourceJndiName(String d) {this.datasource_jndi_name=d; return this;}
public String getDatasourceInjecterClass() {return datasource_injecter_class;}
public JDBC_PING2 setDatasourceInjecterClass(String d) {this.datasource_injecter_class=d; return this;}
@Override
public void init() throws Exception {
super.init();
// If dataSource is already set, skip loading driver or JNDI lookup
if(dataSource == null) {
if(datasource_injecter_class != null) {
dataSource=injectDataSource(datasource_injecter_class);
if(dataSource == null) {
String m=String.format("datasource_injecter_class %s created null datasource", datasource_injecter_class);
throw new IllegalArgumentException(m);
}
}
else {
if(datasource_jndi_name != null)
dataSource=getDataSourceFromJNDI(datasource_jndi_name.trim());
else
loadDriver();
}
}
createSchema();
createInsertStoredProcedure();
}
@ManagedOperation(description="Lists all rows in the database")
public String dump(String cluster) throws Exception {
List list=readFromDB(cluster);
return list.stream().map(pd -> String.format("%s", pd)).collect(Collectors.joining("\n"));
}
protected void write(List list, String clustername) {
for(PingData data: list) {
try {
writeToDB(data, clustername);
}
catch(SQLException e) {
log.error("%s: failed writing to DB: %s", local_addr, e);
}
}
writes++;
}
// It's possible that multiple threads in the same cluster node invoke this concurrently;
// Since delete and insert operations are not atomic
// (and there is no SQL standard way to do this without introducing a transaction)
// we need the synchronization or risk a duplicate insertion on same primary key.
// This synchronization should not be a performance problem as this is just a Discovery protocol.
// Many SQL dialects have some "insert or update" expression, but that would need
// additional configuration and testing on each database. See JGRP-1440
protected synchronized void writeToDB(PingData data, String clustername) throws SQLException {
try(Connection connection=getConnection()) {
if(call_insert_sp != null && insert_sp != null)
callInsertStoredProcedure(connection, data, clustername);
else {
delete(connection, clustername, data.getAddress());
insert(connection, data, clustername);
}
}
}
protected void remove(String clustername, Address addr) {
try {
delete(clustername, addr);
}
catch(SQLException e) {
log.error(String.format("%s: failed deleting %s from the table", local_addr, addr), e);
}
}
protected void removeAll(String clustername) {
try {
clearTable(clustername);
}
catch(Exception ex) {
log.error(String.format("%s: failed clearing the table for cluster %s", local_addr, clustername), ex);
}
}
protected void readAll(List members, String cluster, Responses rsps) {
try {
List list=readFromDB(cluster);
for(PingData data: list) {
Address addr=data.getAddress();
if(data == null || (members != null && !members.contains(addr)))
continue;
rsps.addResponse(data, false);
if(local_addr != null && !local_addr.equals(addr))
addDiscoveryResponseToCaches(addr, data.getLogicalName(), data.getPhysicalAddr());
}
}
catch(Exception e) {
log.error(String.format("%s: failed reading from the DB", local_addr), e);
}
}
protected List readFromDB(String cluster) throws Exception {
try(Connection conn=getConnection();
PreparedStatement ps=prepare(conn, select_all_pingdata_sql, TYPE_FORWARD_ONLY, CONCUR_UPDATABLE)) {
ps.setString(1, cluster);
if(log.isTraceEnabled())
log.trace("%s: SQL for reading: %s", local_addr, ps);
try(ResultSet resultSet=ps.executeQuery()) {
reads++;
List retval=new LinkedList<>();
while(resultSet.next()) {
String uuid=resultSet.getString(1);
String name=resultSet.getString(2);
String ip=resultSet.getString(3);
boolean coord=resultSet.getBoolean(4);
Address addr=Util.addressFromString(uuid);
IpAddress ip_addr=new IpAddress(ip);
PingData data=new PingData(addr, true, name, ip_addr).coord(coord);
retval.add(data);
}
return retval;
}
}
}
protected static PreparedStatement prepare(final Connection conn, final String sql, final int resultSetType,
final int resultSetConcurrency) throws SQLException {
try {
return conn.prepareStatement(sql, resultSetType, resultSetConcurrency);
}
catch(final SQLException x) {
try {
return conn.prepareStatement(sql);
}
catch(final SQLException x2) {
x.addSuppressed(x2);
throw x;
}
}
}
protected void createSchema() {
if(initialize_sql == null) {
log.debug("%s: table creation step skipped: initialize_sql attribute is missing", local_addr);
return;
}
try(Connection conn=getConnection(); PreparedStatement ps=conn.prepareStatement(initialize_sql)) {
log.trace("%s: SQL for initializing schema: %s", local_addr, ps);
ps.execute();
log.debug("%s: table created for JDBC_PING discovery protocol", local_addr);
}
catch(SQLException e) {
log.debug("%s: failed executing initialize_sql statement; not necessarily an error, we always attempt to " +
"create the schema. To suppress this message, set initialize_sql to null. Cause: %s",
local_addr, e.getMessage());
}
}
protected void createInsertStoredProcedure() throws SQLException {
if(insert_sp == null)
return;
try(Connection conn=getConnection()) {
try(PreparedStatement ps=conn.prepareStatement(insert_sp)) {
log.trace("%s: attempting to create stored procedure %s", local_addr, insert_sp);
ps.execute();
log.debug("%s: successfully created stored procedure %s", local_addr, insert_sp);
}
catch(SQLException ex) {
log.debug("%s: failed creating stored procedure %s: %s", local_addr, insert_sp, ex.getMessage());
}
}
}
protected void loadDriver() {
assertNonNull("connection_driver", connection_driver);
log.debug("%s: loading JDBC driver %s", local_addr, connection_driver);
try {
Util.loadClass(connection_driver, this.getClass().getClassLoader());
}
catch(ClassNotFoundException e) {
throw new IllegalArgumentException(String.format("JDBC driver could not be loaded: '%s'", connection_driver));
}
}
protected DataSource injectDataSource(String ds_class) throws Exception {
Class> cl=Util.loadClass(ds_class, Thread.currentThread().getContextClassLoader());
Object obj=cl.getConstructor().newInstance();
Function fun=(Function)obj;
return fun.apply(this);
}
protected Connection getConnection() throws SQLException {
return dataSource != null? dataSource.getConnection() :
DriverManager.getConnection(connection_url, connection_username, connection_password);
}
protected synchronized void insert(Connection connection, PingData data, String clustername) throws SQLException {
try(PreparedStatement ps=connection.prepareStatement(insert_single_sql)) {
Address address=data.getAddress();
String addr=Util.addressToString(address);
String name=address instanceof SiteUUID? ((SiteUUID)address).getName() : NameCache.get(address);
IpAddress ip_addr=(IpAddress)data.getPhysicalAddr();
String ip=ip_addr.toString();
ps.setString(1, addr);
ps.setString(2, name);
ps.setString(3, clustername);
ps.setString(4, ip);
ps.setBoolean(5, data.isCoord());
if(log.isTraceEnabled())
log.trace("%s: SQL for insertion: %s", local_addr, ps);
ps.executeUpdate();
log.debug("%s: inserted %s for cluster %s", local_addr, address, clustername);
}
}
protected synchronized void callInsertStoredProcedure(Connection connection, PingData data, String clustername) throws SQLException {
try(PreparedStatement ps=connection.prepareStatement(call_insert_sp)) {
Address address=data.getAddress();
String addr=Util.addressToString(address);
String name=address instanceof SiteUUID? ((SiteUUID)address).getName() : NameCache.get(address);
IpAddress ip_addr=(IpAddress)data.getPhysicalAddr();
String ip=ip_addr.toString();
ps.setString(1, addr);
ps.setString(2, name);
ps.setString(3, clustername);
ps.setString(4, ip);
ps.setBoolean(5, data.isCoord());
if(log.isTraceEnabled())
log.trace("%s: SQL for insertion: %s", local_addr, ps);
ps.executeUpdate();
log.debug("%s: inserted %s for cluster %s", local_addr, address, clustername);
}
}
protected synchronized void delete(Connection conn, String clustername, Address addressToDelete) throws SQLException {
try(PreparedStatement ps=conn.prepareStatement(delete_single_sql)) {
String addr=Util.addressToString(addressToDelete);
ps.setString(1, addr);
if(log.isTraceEnabled())
log.trace("%s: SQL for deletion: %s", local_addr, ps);
ps.executeUpdate();
log.debug("%s: removed %s for cluster %s from database", local_addr, addressToDelete, clustername);
}
}
protected void delete(String clustername, Address addressToDelete) throws SQLException {
try(Connection connection=getConnection()) {
delete(connection, clustername, addressToDelete);
}
}
protected void clearTable(String clustername) throws SQLException {
try(Connection conn=getConnection(); PreparedStatement ps=conn.prepareStatement(clear_sql)) {
// check presence of cluster parameter for backwards compatibility
if(clear_sql.indexOf('?') >= 0)
ps.setString(1, clustername);
else
log.debug("%s: please update your clear_sql to include the cluster parameter", local_addr);
ps.execute();
log.debug("%s: cleared table for cluster %s", local_addr, clustername);
}
}
protected DataSource getDataSourceFromJNDI(String name) {
final DataSource data_source;
InitialContext ctx=null;
try {
ctx=new InitialContext();
Object whatever=ctx.lookup(name);
if(whatever == null)
throw new IllegalArgumentException("JNDI name " + name + " is not bound");
if(!(whatever instanceof DataSource))
throw new IllegalArgumentException("JNDI name " + name + " was found but is not a DataSource");
data_source=(DataSource)whatever;
log.debug("%s: datasource found via JNDI lookup via name: %s", local_addr, name);
return data_source;
}
catch(NamingException e) {
throw new IllegalArgumentException("Could not lookup datasource " + name, e);
}
finally {
if(ctx != null) {
try {
ctx.close();
}
catch(NamingException e) {
log.warn("%s: failed to close naming context: %s", local_addr, e);
}
}
}
}
protected static void assertNonNull(String... strings) {
for(int i=0; i < strings.length; i+=2) {
String attr=strings[i], val=strings[i + 1];
if(val == null)
throw new IllegalArgumentException(String.format("%s must not be null", attr));
}
}
public static void main(String[] args) throws Exception {
String driver="org.hsqldb.jdbcDriver";
String user="SA";
String pwd="";
String conn="jdbc:hsqldb:hsql://localhost/";
String cluster="draw";
String select="SELECT address, name, cluster, ip, coord FROM JGROUPS WHERE cluster=?";
for(int i=0; i < args.length; i++) {
if(args[i].equals("-driver")) {
driver=args[++i];
continue;
}
if(args[i].equals("-conn")) {
conn=args[++i];
continue;
}
if(args[i].equals("-user")) {
user=args[++i];
continue;
}
if(args[i].equals("-pwd")) {
pwd=args[++i];
continue;
}
if(args[i].equals("-cluster")) {
cluster=args[++i];
continue;
}
if(args[i].equals("-select")) {
select=args[++i];
continue;
}
System.out.println("JDBC_PING2 [-driver driver] [-conn conn-url] [-user user] [-pwd password] " +
"[-cluster cluster-name] [-select select-stmt]");
return;
}
Class.forName(driver);
try(Connection c=DriverManager.getConnection(conn, user, pwd);
PreparedStatement ps=prepare(c, select, TYPE_FORWARD_ONLY, CONCUR_UPDATABLE)) {
ps.setString(1, cluster);
try(ResultSet resultSet=ps.executeQuery()) {
int index=1;
while(resultSet.next()) {
String uuid=resultSet.getString(1);
String name=resultSet.getString(2);
String cluster_name=resultSet.getString(3);
String ip=resultSet.getString(4);
boolean coord=resultSet.getBoolean(5);
System.out.printf("%d: %s, name=%s, ip=%s, %b (cluster=%s)\n",
index++, uuid, name, ip, coord? "coord" : "server", cluster_name);
}
}
}
catch(SQLException e) {
e.printStackTrace();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy